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+ }
0 commit comments