@@ -6,19 +6,18 @@ const testing = std.testing;
6
6
/// Use setRoot to nest all following entries under single root. If file don't
7
7
/// fit into posix header (name+prefix: 100+155 bytes) gnu extented header will
8
8
/// be used for long names. Options enables setting file premission mode and
9
- /// mtime. Default is to use current time for mtime and
10
- /// `default_mode`.file/dir/sym_link for mode.
9
+ /// mtime. Default is to use current time for mtime and 0o664 for file mode.
11
10
pub fn writer (underlying_writer : anytype ) Writer (@TypeOf (underlying_writer )) {
12
11
return .{ .underlying_writer = underlying_writer };
13
12
}
14
13
15
14
pub fn Writer (comptime WriterType : type ) type {
16
15
return struct {
17
16
const block_size = @sizeOf (Header );
17
+ const empty_block : [block_size ]u8 = [_ ]u8 {0 } ** block_size ;
18
18
19
- /// Options for writing file/dir/link. If left empty
20
- /// `default_mode.file`/`default_mode.dir`/`default_mode.sym_link` is
21
- /// used for mode and current time for mtime.
19
+ /// Options for writing file/dir/link. If left empty 0o664 is used for
20
+ /// file mode and current time for mtime.
22
21
pub const Options = struct {
23
22
/// File system permission mode.
24
23
mode : u32 = 0 ,
@@ -31,16 +30,15 @@ pub fn Writer(comptime WriterType: type) type {
31
30
prefix : []const u8 = "" ,
32
31
mtime_now : u64 = 0 ,
33
32
34
- /// Sets prefix for all other add * method paths.
33
+ /// Sets prefix for all other write * method paths.
35
34
pub fn setRoot (self : * Self , root : []const u8 ) ! void {
36
35
if (root .len > 0 )
37
36
try self .writeDir (root , .{});
38
37
39
38
self .prefix = root ;
40
39
}
41
40
42
- /// Writes directory. If options are omitted `default_mode.dir` is
43
- /// used for mode and current time for `mtime`.
41
+ /// Writes directory.
44
42
pub fn writeDir (self : * Self , sub_path : []const u8 , opt : Options ) ! void {
45
43
try self .writeHeader (.directory , sub_path , "" , 0 , opt );
46
44
}
@@ -49,14 +47,19 @@ pub fn Writer(comptime WriterType: type) type {
49
47
pub fn writeFile (self : * Self , sub_path : []const u8 , file : std.fs.File ) ! void {
50
48
const stat = try file .stat ();
51
49
const mtime : u64 = @intCast (@divFloor (stat .mtime , std .time .ns_per_s ));
52
- try self .writeHeader (.regular , sub_path , "" , stat .size , .{ .mtime = mtime });
50
+
51
+ var header = Header {};
52
+ try self .setPath (& header , sub_path );
53
+ try header .setSize (stat .size );
54
+ try header .setMtime (mtime );
55
+ try header .write (self .underlying_writer );
56
+
53
57
try self .underlying_writer .writeFile (file );
54
- try self .writePadding (@intCast ( stat .size ) );
58
+ try self .writePadding (stat .size );
55
59
}
56
60
57
61
/// Writes file reading file content from `reader`. Number of bytes in
58
- /// reader must be equal to `size`. If options are omitted `default_mode.file` is
59
- /// used for mode and current time for `mtime`.
62
+ /// reader must be equal to `size`.
60
63
pub fn writeFileStream (self : * Self , sub_path : []const u8 , size : usize , reader : anytype , opt : Options ) ! void {
61
64
try self .writeHeader (.regular , sub_path , "" , @intCast (size ), opt );
62
65
@@ -68,22 +71,19 @@ pub fn Writer(comptime WriterType: type) type {
68
71
}
69
72
70
73
/// Writes file using bytes buffer `content` for size and file content.
71
- /// If options are omitted `default_mode.file` is used for mode and
72
- /// current time for `mtime`.
73
74
pub fn writeFileBytes (self : * Self , sub_path : []const u8 , content : []const u8 , opt : Options ) ! void {
74
75
try self .writeHeader (.regular , sub_path , "" , @intCast (content .len ), opt );
75
76
try self .underlying_writer .writeAll (content );
76
77
try self .writePadding (content .len );
77
78
}
78
79
79
- /// Writes symlink. If options are omitted `default_mode.sym_link` is
80
- /// used for mode and current time for `mtime`.
80
+ /// Writes symlink.
81
81
pub fn writeLink (self : * Self , sub_path : []const u8 , link_name : []const u8 , opt : Options ) ! void {
82
82
try self .writeHeader (.symbolic_link , sub_path , link_name , 0 , opt );
83
83
}
84
84
85
85
/// Writes fs.Dir.WalkerEntry. Uses `mtime` from file system entry and
86
- /// default from `default_mode` for entry mode .
86
+ /// default for entry mode .
87
87
pub fn writeEntry (self : * Self , entry : std.fs.Dir.Walker.WalkerEntry ) ! void {
88
88
switch (entry .kind ) {
89
89
.directory = > {
@@ -115,9 +115,10 @@ pub fn Writer(comptime WriterType: type) type {
115
115
) ! void {
116
116
var header = Header .init (typeflag );
117
117
try self .setPath (& header , sub_path );
118
- try self .setMtime (& header , opt .mtime );
119
118
try header .setSize (size );
120
- try header .setMode (opt .mode );
119
+ try header .setMtime (if (opt .mtime != 0 ) opt .mtime else self .mtimeNow ());
120
+ if (opt .mode != 0 )
121
+ try header .setMode (opt .mode );
121
122
if (typeflag == .symbolic_link )
122
123
header .setLinkname (link_name ) catch | err | switch (err ) {
123
124
error .NameTooLong = > try self .writeExtendedHeader (.gnu_long_link , &.{link_name }),
@@ -126,17 +127,10 @@ pub fn Writer(comptime WriterType: type) type {
126
127
try header .write (self .underlying_writer );
127
128
}
128
129
129
- fn setMtime (self : * Self , header : * Header , mtime : u64 ) ! void {
130
- const mt = blk : {
131
- if (mtime == 0 ) {
132
- // use time now
133
- if (self .mtime_now == 0 )
134
- self .mtime_now = @intCast (std .time .timestamp ());
135
- break :blk self .mtime_now ;
136
- }
137
- break :blk mtime ;
138
- };
139
- try header .setMtime (mt );
130
+ fn mtimeNow (self : * Self ) u64 {
131
+ if (self .mtime_now == 0 )
132
+ self .mtime_now = @intCast (std .time .timestamp ());
133
+ return self .mtime_now ;
140
134
}
141
135
142
136
fn entryMtime (entry : std.fs.Dir.Walker.WalkerEntry ) ! u64 {
@@ -174,28 +168,22 @@ pub fn Writer(comptime WriterType: type) type {
174
168
try self .writePadding (len );
175
169
}
176
170
177
- fn writePadding (self : * Self , bytes : usize ) ! void {
178
- const remainder = bytes % block_size ;
179
- if (remainder == 0 ) return ;
180
- const padding = block_size - remainder ;
181
- try self .underlying_writer .writeByteNTimes (0 , padding );
171
+ fn writePadding (self : * Self , bytes : u64 ) ! void {
172
+ const pos : usize = @intCast (bytes % block_size );
173
+ if (pos == 0 ) return ;
174
+ try self .underlying_writer .writeAll (empty_block [pos .. ]);
182
175
}
183
176
184
177
/// Tar should finish with two zero blocks, but 'reasonable system must
185
178
/// not assume that such a block exists when reading an archive' (from
186
179
/// reference). In practice it is safe to skip this finish.
187
180
pub fn finish (self : * Self ) ! void {
188
- try self .underlying_writer .writeByteNTimes (0 , block_size * 2 );
181
+ try self .underlying_writer .writeAll (& empty_block );
182
+ try self .underlying_writer .writeAll (& empty_block );
189
183
}
190
184
};
191
185
}
192
186
193
- const default_mode = struct {
194
- const file = 0o664 ;
195
- const dir = 0o775 ;
196
- const sym_link = 0o777 ;
197
- };
198
-
199
187
/// A struct that is exactly 512 bytes and matches tar file format. This is
200
188
/// intended to be used for outputting tar files; for parsing there is
201
189
/// `std.tar.Header`.
@@ -208,24 +196,24 @@ const Header = extern struct {
208
196
// strings. All other fields are zero-filled octal numbers in ASCII. Each
209
197
// numeric field of width w contains w minus 1 digits, and a null.
210
198
// Reference: https://www.gnu.org/software/tar/manual/html_node/Standard.html
211
- // POSIX header: byte offset
212
- name : [100 ]u8 , // 0
213
- mode : [7 :0 ]u8 , // 100
214
- uid : [7 :0 ]u8 , // 108
215
- gid : [7 :0 ]u8 , // 116
216
- size : [11 :0 ]u8 , // 124
217
- mtime : [11 :0 ]u8 , // 136
218
- checksum : [7 :0 ]u8 , // 148
219
- typeflag : FileType , // 156
220
- linkname : [100 ]u8 , // 157
221
- magic : [6 ]u8 , // 257
222
- version : [2 ]u8 , // 263
223
- uname : [32 ]u8 , // 265
224
- gname : [32 ]u8 , // 297
225
- devmajor : [7 :0 ]u8 , // 329
226
- devminor : [7 :0 ]u8 , // 337
227
- prefix : [155 ]u8 , // 345
228
- pad : [12 ]u8 , // 500
199
+ // POSIX header: byte offset
200
+ name : [100 ]u8 = [ _ ] u8 { 0 } ** 100 , // 0
201
+ mode : [7 :0 ]u8 = default_mode . file , // 100
202
+ uid : [7 :0 ]u8 = [ _ : 0 ] u8 { 0 } ** 7 , // unused 108
203
+ gid : [7 :0 ]u8 = [ _ : 0 ] u8 { 0 } ** 7 , // unused 116
204
+ size : [11 :0 ]u8 = [ _ : 0 ] u8 { '0' } ** 11 , // 124
205
+ mtime : [11 :0 ]u8 = [ _ : 0 ] u8 { '0' } ** 11 , // 136
206
+ checksum : [7 :0 ]u8 = [ _ : 0 ] u8 { ' ' } ** 7 , // 148
207
+ typeflag : FileType = .regular , // 156
208
+ linkname : [100 ]u8 = [ _ ] u8 { 0 } ** 100 , // 157
209
+ magic : [6 ]u8 = [ _ ] u8 { 'u' , 's' , 't' , 'a' , 'r' , 0 }, // 257
210
+ version : [2 ]u8 = [ _ ] u8 { '0' , '0' }, // 263
211
+ uname : [32 ]u8 = [ _ ] u8 { 0 } ** 32 , // unused 265
212
+ gname : [32 ]u8 = [ _ ] u8 { 0 } ** 32 , // unused 297
213
+ devmajor : [7 :0 ]u8 = [ _ : 0 ] u8 { 0 } ** 7 , // unused 329
214
+ devminor : [7 :0 ]u8 = [ _ : 0 ] u8 { 0 } ** 7 , // unused 337
215
+ prefix : [155 ]u8 = [ _ ] u8 { 0 } ** 155 , // 345
216
+ pad : [12 ]u8 = [ _ ] u8 { 0 } ** 12 , // unused 500
229
217
230
218
pub const FileType = enum (u8 ) {
231
219
regular = '0' ,
@@ -235,47 +223,56 @@ const Header = extern struct {
235
223
gnu_long_link = 'K' ,
236
224
};
237
225
226
+ const default_mode = struct {
227
+ const file = [_ :0 ]u8 { '0' , '0' , '0' , '0' , '6' , '6' , '4' }; // 0o664
228
+ const dir = [_ :0 ]u8 { '0' , '0' , '0' , '0' , '7' , '7' , '5' }; // 0o775
229
+ const sym_link = [_ :0 ]u8 { '0' , '0' , '0' , '0' , '7' , '7' , '7' }; // 0o777
230
+ const other = [_ :0 ]u8 { '0' , '0' , '0' , '0' , '0' , '0' , '0' }; // 0o000
231
+ };
232
+
238
233
pub fn init (typeflag : FileType ) Header {
239
- var header = std .mem .zeroes (Header );
240
- header .magic = [_ ]u8 { 'u' , 's' , 't' , 'a' , 'r' , 0 };
241
- header .version = [_ ]u8 { '0' , '0' };
242
- header .typeflag = typeflag ;
243
- return header ;
234
+ return .{
235
+ .typeflag = typeflag ,
236
+ .mode = switch (typeflag ) {
237
+ .directory = > default_mode .dir ,
238
+ .symbolic_link = > default_mode .sym_link ,
239
+ .regular = > default_mode .file ,
240
+ else = > default_mode .other ,
241
+ },
242
+ };
244
243
}
245
244
246
245
pub fn setSize (self : * Header , size : u64 ) ! void {
247
- _ = try std .fmt .bufPrint (& self .size , "{o:0>11}" , .{size });
246
+ try octal (& self .size , size );
247
+ }
248
+
249
+ fn octal (buf : []u8 , value : u64 ) ! void {
250
+ var remainder : u64 = value ;
251
+ var pos : usize = buf .len ;
252
+ while (remainder > 0 and pos > 0 ) {
253
+ pos -= 1 ;
254
+ const c : u8 = @as (u8 , @intCast (remainder % 8 )) + '0' ;
255
+ buf [pos ] = c ;
256
+ remainder /= 8 ;
257
+ if (pos == 0 and remainder > 0 ) return error .OctalOverflow ;
258
+ }
248
259
}
249
260
250
261
pub fn setMode (self : * Header , mode : u32 ) ! void {
251
- const m : u32 = if (mode == 0 )
252
- switch (self .typeflag ) {
253
- .directory = > default_mode .dir ,
254
- .symbolic_link = > default_mode .sym_link ,
255
- else = > default_mode .file ,
256
- }
257
- else
258
- mode ;
259
- _ = try std .fmt .bufPrint (& self .mode , "{o:0>7}" , .{m });
262
+ try octal (& self .mode , mode );
260
263
}
261
264
262
265
// Integer number of seconds since January 1, 1970, 00:00 Coordinated Universal Time.
263
266
// mtime == 0 will use current time
264
267
pub fn setMtime (self : * Header , mtime : u64 ) ! void {
265
- _ = try std . fmt . bufPrint (& self .mtime , "{o:0>11}" , .{ mtime } );
268
+ try octal (& self .mtime , mtime );
266
269
}
267
270
268
271
pub fn updateChecksum (self : * Header ) ! void {
269
- const offset = @offsetOf (Header , "checksum" );
270
- var checksum : usize = 0 ;
271
- for (std .mem .asBytes (self ), 0.. ) | val , i | {
272
- checksum += if (i >= offset and i < offset + @sizeOf (@TypeOf (self .checksum )))
273
- ' '
274
- else
275
- val ;
276
- }
277
-
278
- _ = try std .fmt .bufPrint (& self .checksum , "{o:0>7}" , .{checksum });
272
+ var checksum : usize = ' ' ; // other 7 self.checksum bytes are initialized to ' '
273
+ for (std .mem .asBytes (self )) | val |
274
+ checksum += val ;
275
+ try octal (& self .checksum , checksum );
279
276
}
280
277
281
278
pub fn write (self : * Header , output_writer : anytype ) ! void {
@@ -495,5 +492,6 @@ test "write files" {
495
492
try actual .writeAll (content .writer ());
496
493
try testing .expectEqualSlices (u8 , expected .content , content .items );
497
494
}
495
+ try wrt .finish ();
498
496
}
499
497
}
0 commit comments