@@ -13,11 +13,6 @@ pub fn writer(underlying_writer: anytype, prefix: []const u8) !Writer(@TypeOf(un
13
13
14
14
pub fn Writer (comptime WriterType : type ) type {
15
15
return struct {
16
- const default_mode = struct {
17
- const file = 0o664 ;
18
- const dir = 0o775 ;
19
- const sym_link = 0o777 ;
20
- };
21
16
const block_size = @sizeOf (Header );
22
17
const zero : [@sizeOf (Header )]u8 = .{0 } ** @sizeOf (Header );
23
18
pub const Options = struct {
@@ -28,7 +23,9 @@ pub fn Writer(comptime WriterType: type) type {
28
23
29
24
underlying_writer : WriterType ,
30
25
prefix : []const u8 = "" ,
26
+
31
27
block_buffer : [block_size ]u8 = undefined ,
28
+ mtime_now : u64 = 0 ,
32
29
33
30
/// Tar will be written to the `underlying_writer`. All paths will be
34
31
/// prefixed with `perfix` if it is not empty string.
@@ -37,33 +34,31 @@ pub fn Writer(comptime WriterType: type) type {
37
34
.underlying_writer = underlying_writer ,
38
35
};
39
36
if (prefix .len > 0 ) {
40
- try self .directory (prefix , .{});
37
+ try self .addDir (prefix , .{});
41
38
self .prefix = prefix ;
42
39
}
43
40
return self ;
44
41
}
45
42
46
43
/// Writes directory. If options are omitted `default_mode.dir` is used
47
44
/// for mode and current time for mtime.
48
- pub fn directory (self : * Self , sub_path : []const u8 , opt : Options ) ! void {
49
- var header = Header .init ();
50
- header .typeflag = .directory ;
45
+ pub fn addDir (self : * Self , sub_path : []const u8 , opt : Options ) ! void {
46
+ var header = Header .init (.directory );
51
47
try self .setPath (& header , sub_path );
52
- try header . setMode ( if ( opt .mode == 0 ) default_mode . dir else opt . mode );
53
- try header .setMtime (opt .mtime );
48
+ try self . setMtime ( & header , opt .mtime );
49
+ try header .setMode (opt .mode );
54
50
try header .write (self .underlying_writer );
55
51
}
56
52
57
53
/// Writes file. File content is read from `reader`. Number of bytes in
58
54
/// reader must be equal to `size`. If options are omitted
59
55
/// `default_mode.file` is used for mode and current time for mtime.
60
- pub fn file (self : * Self , sub_path : []const u8 , size : usize , reader : anytype , opt : Options ) ! void {
61
- var header = Header .init ();
62
- header .typeflag = .regular ;
56
+ pub fn addFile (self : * Self , sub_path : []const u8 , size : usize , reader : anytype , opt : Options ) ! void {
57
+ var header = Header .init (.regular );
63
58
try self .setPath (& header , sub_path );
59
+ try self .setMtime (& header , opt .mtime );
64
60
try header .setSize (size );
65
- try header .setMode (if (opt .mode == 0 ) default_mode .file else opt .mode );
66
- try header .setMtime (opt .mtime );
61
+ try header .setMode (opt .mode );
67
62
try header .write (self .underlying_writer );
68
63
69
64
var written : usize = 0 ;
@@ -77,6 +72,58 @@ pub fn Writer(comptime WriterType: type) type {
77
72
}
78
73
}
79
74
75
+ fn setMtime (self : * Self , header : * Header , mtime : u64 ) ! void {
76
+ const mt = blk : {
77
+ if (mtime == 0 ) {
78
+ // use time now
79
+ if (self .mtime_now == 0 )
80
+ self .mtime_now = @intCast (std .time .timestamp ());
81
+ break :blk self .mtime_now ;
82
+ }
83
+ break :blk mtime ;
84
+ };
85
+ try header .setMtime (mt );
86
+ }
87
+
88
+ /// Writes symlink. If options are omitted `default_mode.sym_link` is
89
+ /// used for mode and current time for mtime.
90
+ pub fn addLink (self : * Self , sub_path : []const u8 , link_name : []const u8 , opt : Options ) ! void {
91
+ var header = Header .init (.symbolic_link );
92
+ try self .setPath (& header , sub_path );
93
+ try self .setMtime (& header , opt .mtime );
94
+ try header .setMode (opt .mode );
95
+ header .setLinkname (link_name ) catch | err | switch (err ) {
96
+ error .NameTooLong = > try self .writeExtendedHeader (.gnu_long_link , &.{link_name }),
97
+ else = > return err ,
98
+ };
99
+ try header .write (self .underlying_writer );
100
+ }
101
+
102
+ /// Writes fs.Dir.WalkerEntry. Uses mtime from file system entry and
103
+ /// defaults from `default_mode` for mode.
104
+ pub fn addEntry (self : * Self , entry : std.fs.Dir.Walker.WalkerEntry ) ! void {
105
+ const stat = try entry .dir .statFile (entry .basename );
106
+ const mtime : u64 = @intCast (@divFloor (stat .mtime , std .time .ns_per_s ));
107
+ switch (entry .kind ) {
108
+ .directory = > {
109
+ try self .addDir (entry .path , .{ .mtime = mtime });
110
+ },
111
+ .file = > {
112
+ var file = try entry .dir .openFile (entry .basename , .{});
113
+ defer file .close ();
114
+ try self .addFile (entry .path , stat .size , file .reader (), .{ .mtime = mtime });
115
+ },
116
+ .sym_link = > {
117
+ var link_name_buffer : [std .fs .MAX_PATH_BYTES ]u8 = undefined ;
118
+ const link_name = try entry .dir .readLink (entry .basename , & link_name_buffer );
119
+ try self .addLink (entry .path , link_name , .{ .mtime = mtime });
120
+ },
121
+ else = > {
122
+ return error .UnsupportedWalkerEntryKind ;
123
+ },
124
+ }
125
+ }
126
+
80
127
/// Writes path in posix header, if don't fit (in name+prefix; 100+155
81
128
/// bytes) writes it in gnu extended header.
82
129
fn setPath (self : * Self , header : * Header , sub_path : []const u8 ) ! void {
@@ -93,70 +140,27 @@ pub fn Writer(comptime WriterType: type) type {
93
140
};
94
141
}
95
142
96
- fn writeExtendedHeader (self : * Self , kind : Header.FileType , buffers : []const []const u8 ) ! void {
143
+ fn writeExtendedHeader (self : * Self , typeflag : Header.FileType , buffers : []const []const u8 ) ! void {
97
144
var len : usize = 0 ;
98
145
for (buffers ) | buf |
99
146
len += buf .len ;
100
147
101
- var header = Header .init ();
102
- header .typeflag = kind ;
148
+ var header = Header .init (typeflag );
103
149
try header .setSize (len );
104
150
try header .write (self .underlying_writer );
105
151
for (buffers ) | buf |
106
152
try self .underlying_writer .writeAll (buf );
107
- try self .addPadding (len );
153
+ try self .writePadding (len );
108
154
}
109
155
110
- fn addPadding (self : * Self , bytes : usize ) ! void {
156
+ fn writePadding (self : * Self , bytes : usize ) ! void {
111
157
const remainder = bytes % block_size ;
112
158
if (remainder == 0 ) return ;
113
159
const padding_bytes = block_size - remainder ;
114
160
@memset (self .block_buffer [0.. padding_bytes ], 0 );
115
161
try self .underlying_writer .writeAll (self .block_buffer [0.. padding_bytes ]);
116
162
}
117
163
118
- /// Writes symlink. If options are omitted `default_mode.sym_link` is
119
- /// used for mode and current time for mtime.
120
- pub fn symLink (self : * Self , sub_path : []const u8 , link_name : []const u8 , opt : Options ) ! void {
121
- var header = Header .init ();
122
- header .typeflag = .symbolic_link ;
123
- try self .setPath (& header , sub_path );
124
- header .setLinkname (link_name ) catch | err | switch (err ) {
125
- error .NameTooLong = > try self .writeExtendedHeader (.gnu_long_link , &.{link_name }),
126
- else = > return err ,
127
- };
128
- try header .setMode (if (opt .mode == 0 ) default_mode .sym_link else opt .mode );
129
- try header .setMtime (opt .mtime );
130
- try header .write (self .underlying_writer );
131
- }
132
-
133
- /// Writes fs.Dir.WalkerEntry. Uses mtime from file system entry and
134
- /// defaults from `default_mode` for mode.
135
- pub fn walkerEntry (self : * Self , entry : std.fs.Dir.Walker.WalkerEntry ) ! void {
136
- const stat = try entry .dir .statFile (entry .basename );
137
- const mtime : u64 = @intCast (@divFloor (stat .mtime , std .time .ns_per_s ));
138
- switch (entry .kind ) {
139
- .directory = > {
140
- try self .directory (entry .path , .{ .mtime = mtime });
141
- },
142
- .file = > {
143
- var f = try entry .dir .openFile (entry .basename , .{});
144
- defer f .close ();
145
- try self .file (entry .path , stat .size , f .reader (), .{ .mtime = mtime });
146
- },
147
- .sym_link = > {
148
- var f = try entry .dir .openFile (entry .basename , .{});
149
- defer f .close ();
150
- var link_name_buffer : [std .fs .MAX_PATH_BYTES ]u8 = undefined ;
151
- const link_name = try entry .dir .readLink (entry .basename , & link_name_buffer );
152
- try self .symLink (entry .path , link_name , .{ .mtime = mtime });
153
- },
154
- else = > {
155
- return error .UnsupportedWalkerEntryKind ;
156
- },
157
- }
158
- }
159
-
160
164
/// Tar should finish with two zero blocks, but 'reasonable system must
161
165
/// not assume that such a block exists when reading an archive' (from
162
166
/// reference). In practice it is safe to skip this finish.
@@ -167,6 +171,12 @@ pub fn Writer(comptime WriterType: type) type {
167
171
};
168
172
}
169
173
174
+ const default_mode = struct {
175
+ const file = 0o664 ;
176
+ const dir = 0o775 ;
177
+ const sym_link = 0o777 ;
178
+ };
179
+
170
180
/// A struct that is exactly 512 bytes and matches tar file format. This is
171
181
/// intended to be used for outputting tar files; for parsing there is
172
182
/// `std.tar.Header`.
@@ -206,28 +216,34 @@ const Header = extern struct {
206
216
gnu_long_link = 'K' ,
207
217
};
208
218
209
- pub fn init () Header {
210
- var ret = std .mem .zeroes (Header );
211
- ret .magic = [_ ]u8 { 'u' , 's' , 't' , 'a' , 'r' , 0 };
212
- ret .version = [_ ]u8 { '0' , '0' };
213
- return ret ;
219
+ pub fn init (typeflag : FileType ) Header {
220
+ var header = std .mem .zeroes (Header );
221
+ header .magic = [_ ]u8 { 'u' , 's' , 't' , 'a' , 'r' , 0 };
222
+ header .version = [_ ]u8 { '0' , '0' };
223
+ header .typeflag = typeflag ;
224
+ return header ;
214
225
}
215
226
216
227
pub fn setSize (self : * Header , size : u64 ) ! void {
217
228
_ = try std .fmt .bufPrint (& self .size , "{o:0>11}" , .{size });
218
229
}
219
230
220
231
pub fn setMode (self : * Header , mode : u32 ) ! void {
221
- _ = try std .fmt .bufPrint (& self .mode , "{o:0>7}" , .{mode });
232
+ const m : u32 = if (mode == 0 )
233
+ switch (self .typeflag ) {
234
+ .directory = > default_mode .dir ,
235
+ .symbolic_link = > default_mode .sym_link ,
236
+ else = > default_mode .file ,
237
+ }
238
+ else
239
+ mode ;
240
+ _ = try std .fmt .bufPrint (& self .mode , "{o:0>7}" , .{m });
222
241
}
223
242
224
243
// Integer number of seconds since January 1, 1970, 00:00 Coordinated Universal Time.
244
+ // mtime == 0 will use current time
225
245
pub fn setMtime (self : * Header , mtime : u64 ) ! void {
226
- _ = try std .fmt .bufPrint (
227
- & self .mtime ,
228
- "{o:0>11}" ,
229
- .{if (mtime == 0 ) @as (u64 , @intCast (std .time .timestamp ())) else mtime },
230
- );
246
+ _ = try std .fmt .bufPrint (& self .mtime , "{o:0>11}" , .{mtime });
231
247
}
232
248
233
249
pub fn updateChecksum (self : * Header ) ! void {
@@ -339,7 +355,7 @@ const Header = extern struct {
339
355
};
340
356
341
357
for (cases ) | case | {
342
- var header = Header .init ();
358
+ var header = Header .init (.regular );
343
359
try header .setPath (case .in [0 ], case .in [1 ]);
344
360
try testing .expectEqualStrings (case .out [0 ], str (& header .prefix ));
345
361
try testing .expectEqualStrings (case .out [1 ], str (& header .name ));
@@ -358,7 +374,7 @@ const Header = extern struct {
358
374
};
359
375
360
376
for (error_cases ) | case | {
361
- var header = Header .init ();
377
+ var header = Header .init (.regular );
362
378
try testing .expectError (
363
379
error .NameTooLong ,
364
380
header .setPath (case .in [0 ], case .in [1 ]),
@@ -404,7 +420,7 @@ test "write files" {
404
420
var wrt = try writer (output .writer (), prefix );
405
421
for (files ) | file | {
406
422
var content = std .io .fixedBufferStream (file .content );
407
- try wrt .file (file .path , file .content .len , content .reader (), .{});
423
+ try wrt .addFile (file .path , file .content .len , content .reader (), .{});
408
424
}
409
425
410
426
var input = std .io .fixedBufferStream (output .items );
@@ -441,7 +457,7 @@ test "write files" {
441
457
var wrt = try writer (output .writer (), "" );
442
458
for (files ) | file | {
443
459
var content = std .io .fixedBufferStream (file .content );
444
- try wrt .file (file .path , file .content .len , content .reader (), .{});
460
+ try wrt .addFile (file .path , file .content .len , content .reader (), .{});
445
461
}
446
462
447
463
var input = std .io .fixedBufferStream (output .items );
@@ -487,20 +503,20 @@ pub fn main() !void {
487
503
var wrt = try writer (cmp .writer (), in_dir_name );
488
504
489
505
const excluded = [_ ][]const u8 {
490
- "zig-cache/ " ,
491
- "zig-out/ " ,
506
+ "zig-cache" ,
507
+ "zig-out" ,
492
508
".git/" ,
493
509
"build/" ,
494
510
"build2/" ,
495
511
};
496
512
497
513
var walker = try in_dir .walk (gpa );
498
514
defer walker .deinit ();
499
- while (try walker .next ()) | entry | {
515
+ outer : while (try walker .next ()) | entry | {
500
516
for (excluded ) | ex |
501
- if (std .mem .indexOf (u8 , entry .path , ex )) | _ | continue ;
517
+ if (std .mem .indexOf (u8 , entry .path , ex )) | _ | continue : outer ;
502
518
503
- try wrt .walkerEntry (entry );
519
+ try wrt .addEntry (entry );
504
520
}
505
521
try cmp .finish ();
506
522
}
@@ -594,7 +610,7 @@ test "sourcesTar" {
594
610
},
595
611
else = > continue ,
596
612
}
597
- try w .walkerEntry (entry );
613
+ try w .addEntry (entry );
598
614
}
599
615
600
616
{
@@ -603,7 +619,7 @@ test "sourcesTar" {
603
619
// this source file corresponds to the user's host system.
604
620
const builtin_zig = @embedFile ("builtin" );
605
621
var stm = std .io .fixedBufferStream (builtin_zig );
606
- try w .file ("builtin.zig" , builtin_zig .len , stm .reader (), .{});
622
+ try w .addFile ("builtin.zig" , builtin_zig .len , stm .reader (), .{});
607
623
}
608
624
}
609
625
}
0 commit comments