Skip to content

Commit c35edf1

Browse files
committed
Using tar with .NET
1 parent 92e8b7a commit c35edf1

File tree

2 files changed

+169
-0
lines changed

2 files changed

+169
-0
lines changed

UsingTarWithDotnet/Program.cs

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
using System.Formats.Tar;
2+
using System.Globalization;
3+
using System.IO.Compression;
4+
5+
if (args.Length < 1)
6+
{
7+
return PrintUsage();
8+
}
9+
10+
if (args[0] == "--compress" && args.Length == 3)
11+
{
12+
var destination = args[1];
13+
var sourceDir = args[2];
14+
CreateTar(destination, sourceDir);
15+
}
16+
else if (args[0] == "--extract" && args.Length == 3)
17+
{
18+
var sourceTar = args[1];
19+
var destinationDir = args[2];
20+
ExtractTar(sourceTar, destinationDir);
21+
}
22+
else if (args[0] == "--single" && args.Length == 4)
23+
{
24+
var sourceTar = args[1];
25+
var pathInTar = args[2];
26+
var output = args[3];
27+
ExtractSingle(sourceTar, pathInTar, output);
28+
}
29+
else if (args[0] == "--list" && args.Length == 2)
30+
{
31+
var sourceTar = args[1];
32+
List(sourceTar);
33+
}
34+
else
35+
{
36+
return PrintUsage();
37+
}
38+
39+
return 0;
40+
41+
void CreateTar(string dest, string src)
42+
{
43+
Console.WriteLine($"Creating Tar file: '{dest}' from path '{src}");
44+
45+
// Throws if the destination file exists
46+
using FileStream fs = new(dest, FileMode.CreateNew, FileAccess.Write);
47+
using GZipStream gz = new(fs, CompressionMode.Compress, leaveOpen: true);
48+
49+
TarFile.CreateFromDirectory(src, gz, includeBaseDirectory: false);
50+
51+
Console.WriteLine("Done");
52+
}
53+
54+
void ExtractTar(string sourceTar, string dest)
55+
{
56+
Console.WriteLine($"Expanding Tar file: '{sourceTar}' to directory '{dest}");
57+
58+
// Throws if the file does not exist
59+
using FileStream fs = new(sourceTar, FileMode.Open, FileAccess.Read);
60+
using GZipStream gz = new(fs, CompressionMode.Decompress, leaveOpen: true);
61+
62+
TarFile.ExtractToDirectory(gz, dest, overwriteFiles: false);
63+
64+
Console.WriteLine("Done");
65+
}
66+
67+
void ExtractSingle(string sourceTar, string pathInTar, string destination)
68+
{
69+
Console.WriteLine($"Looking for '{pathInTar}' in file: '{sourceTar}'");
70+
71+
// read the tar and extract a single file
72+
using FileStream fs = new(sourceTar, FileMode.Open, FileAccess.Read);
73+
using GZipStream gz = new(fs, CompressionMode.Decompress, leaveOpen: true);
74+
using var reader = new TarReader(gz, leaveOpen: true);
75+
76+
while (reader.GetNextEntry() is { } entry)
77+
{
78+
if (entry.Name == pathInTar)
79+
{
80+
Console.WriteLine($"Found '{pathInTar}', extracting to '{destination}");
81+
entry.ExtractToFile(destination, overwrite: false);
82+
return;
83+
}
84+
}
85+
86+
Console.WriteLine("Could not extract path - not found");
87+
}
88+
89+
void List(string sourceTar)
90+
{
91+
Console.WriteLine($"Reading '{sourceTar}'");
92+
93+
// read the tar and loop through the entries
94+
using FileStream fs = new(sourceTar, FileMode.Open, FileAccess.Read);
95+
using GZipStream gz = new(fs, CompressionMode.Decompress, leaveOpen: true);
96+
using var reader = new TarReader(gz, leaveOpen: true);
97+
98+
while (reader.GetNextEntry() is { } entry)
99+
{
100+
// Get the file descriptor
101+
char type = entry.EntryType switch
102+
{
103+
TarEntryType.Directory => 'd',
104+
TarEntryType.HardLink => 'h',
105+
TarEntryType.SymbolicLink => 'l',
106+
_ => '-',
107+
};
108+
109+
// Construct the permissions e.g. rwxr-xr-x
110+
// Moved to a separate function just because it's a bit verbose
111+
string permissions = GetPermissions(entry);
112+
113+
// Display the owner info. 0 is special (root) but .NET doesn't
114+
// expose the mappings for these IDs natively, so ignoring for now
115+
string ownerUser = entry.Uid == 0 ? "root" : entry.Uid.ToString(CultureInfo.InvariantCulture);
116+
string ownerGroup = entry.Gid == 0 ? "root" : entry.Gid.ToString(CultureInfo.InvariantCulture);
117+
118+
// The length of the data and the modification date in bytes
119+
long size = entry.Length;
120+
DateTimeOffset date = entry.ModificationTime.UtcDateTime;
121+
122+
// Match the display format used by tar -tv
123+
string path = entry.EntryType switch
124+
{
125+
TarEntryType.HardLink => $"{entry.Name} link to {entry.LinkName}",
126+
TarEntryType.SymbolicLink => $"{entry.Name} -> {entry.LinkName}",
127+
_ => entry.Name,
128+
};
129+
130+
// Write the entry!
131+
Console.WriteLine($"{type}{permissions} {ownerUser}/{ownerGroup} {size,9} {date:yyyy-MM-dd HH:mm} {path}");
132+
}
133+
134+
// Construct the permissions
135+
static string GetPermissions(TarEntry entry)
136+
{
137+
var userRead = entry.Mode.HasFlag(UnixFileMode.UserRead) ? 'r' : '-';
138+
var userWrite = entry.Mode.HasFlag(UnixFileMode.UserWrite) ? 'w' : '-';
139+
var userExecute = entry.Mode.HasFlag(UnixFileMode.UserExecute) ? 'x' : '-';
140+
var groupRead = entry.Mode.HasFlag(UnixFileMode.GroupRead) ? 'r' : '-';
141+
var groupWrite = entry.Mode.HasFlag(UnixFileMode.GroupWrite) ? 'w' : '-';
142+
var groupExecute = entry.Mode.HasFlag(UnixFileMode.GroupExecute) ? 'x' : '-';
143+
var otherRead = entry.Mode.HasFlag(UnixFileMode.OtherRead) ? 'r' : '-';
144+
var otherWrite = entry.Mode.HasFlag(UnixFileMode.OtherWrite) ? 'w' : '-';
145+
var otherExecute = entry.Mode.HasFlag(UnixFileMode.OtherExecute) ? 'x' : '-';
146+
147+
return $"{userRead}{userWrite}{userExecute}{groupRead}{groupWrite}{groupExecute}{otherRead}{otherWrite}{otherExecute}";
148+
}
149+
}
150+
151+
int PrintUsage()
152+
{
153+
Console.WriteLine("USAGE: dotnetar --create <DESTINATIONTAR> <SOURCE>");
154+
Console.WriteLine("USAGE: dotnetar --extract <SOURCETAR> <DESTINATION>");
155+
Console.WriteLine("USAGE: dotnetar --single <SOURCETAR> <PATH_IN_TAR> <OUTPUT_PATH>");
156+
Console.WriteLine("USAGE: dotnetar --list <SOURCETAR>");
157+
return 1;
158+
}

UsingTarWithDotnet/dotnetar.csproj

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFrameworks>net7.0;net8.0</TargetFrameworks>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
9+
</PropertyGroup>
10+
11+
</Project>

0 commit comments

Comments
 (0)