1
1
use std:: {
2
2
fs,
3
3
io:: { self , Cursor , IsTerminal , Seek , Write } ,
4
+ mem,
4
5
path:: Path ,
5
6
} ;
6
7
7
- use color_eyre:: eyre:: { eyre, Report , WrapErr } ;
8
+ use color_eyre:: eyre:: { bail , eyre, Report , WrapErr } ;
8
9
9
10
use zip:: {
10
11
unstable:: path_to_string,
11
12
write:: { SimpleFileOptions , ZipWriter } ,
12
- CompressionMethod ,
13
+ CompressionMethod , ZIP64_BYTES_THR ,
13
14
} ;
14
15
15
16
use crate :: args:: * ;
@@ -61,10 +62,11 @@ fn enter_recursive_dir_entries(
61
62
) ?;
62
63
writer
63
64
. add_directory ( & base_dirname, options)
64
- . wrap_err_with ( || eyre ! ( "{base_dirname}" ) ) ?;
65
+ . wrap_err_with ( || eyre ! ( "error adding top-level directory entry {base_dirname}" ) ) ?;
65
66
66
67
let mut readdir_stack: Vec < ( fs:: ReadDir , String ) > = vec ! [ (
67
- fs:: read_dir( root) . wrap_err_with( || eyre!( "{}" , root. display( ) ) ) ?,
68
+ fs:: read_dir( root)
69
+ . wrap_err_with( || eyre!( "error reading directory contents for {}" , root. display( ) ) ) ?,
68
70
base_dirname,
69
71
) ] ;
70
72
while let Some ( ( mut readdir, top_component) ) = readdir_stack. pop ( ) {
@@ -82,29 +84,51 @@ fn enter_recursive_dir_entries(
82
84
83
85
let file_type = dir_entry
84
86
. file_type ( )
85
- . wrap_err_with ( || eyre ! ( "{}" , dir_entry . path ( ) . display ( ) ) ) ?;
87
+ . wrap_err_with ( || eyre ! ( "failed to read file type for dir entry {dir_entry:?}" ) ) ?;
86
88
if file_type. is_symlink ( ) {
87
89
let target: String = path_to_string (
88
90
fs:: read_link ( dir_entry. path ( ) )
89
- . wrap_err_with ( || eyre ! ( "{}" , dir_entry . path ( ) . display ( ) ) ) ?,
91
+ . wrap_err_with ( || eyre ! ( "failed to read symlink from {dir_entry:?}" ) ) ?,
90
92
)
91
93
. into ( ) ;
94
+ if target. len ( ) > ZIP64_BYTES_THR . try_into ( ) . unwrap ( ) {
95
+ bail ! (
96
+ "symlink target for {full_path} is over {ZIP64_BYTES_THR} bytes (was: {})" ,
97
+ target. len( )
98
+ ) ;
99
+ }
92
100
writeln ! (
93
101
err,
94
102
"writing recursive symlink entry with name {full_path:?} and target {target:?}"
95
103
) ?;
96
104
writer
97
105
. add_symlink ( & full_path, & target, options)
98
- . wrap_err_with ( || eyre ! ( "{full_path}->{target}" ) ) ?;
106
+ . wrap_err_with ( || eyre ! ( "error adding symlink from {full_path}->{target}" ) ) ?;
99
107
} else if file_type. is_file ( ) {
100
108
writeln ! ( err, "writing recursive file entry with name {full_path:?}" ) ?;
109
+ let mut f = fs:: File :: open ( dir_entry. path ( ) ) . wrap_err_with ( || {
110
+ eyre ! ( "error opening file for {full_path} from dir entry {dir_entry:?}" )
111
+ } ) ?;
112
+ /* Get the length of the file before reading it and set large_file if needed. */
113
+ let input_len: u64 = f
114
+ . metadata ( )
115
+ . wrap_err_with ( || eyre ! ( "error reading file metadata for {f:?}" ) ) ?
116
+ . len ( ) ;
117
+ let maybe_large_file_options = if input_len > ZIP64_BYTES_THR {
118
+ writeln ! (
119
+ err,
120
+ "temporarily ensuring .large_file(true) for current entry"
121
+ ) ?;
122
+ options. large_file ( true )
123
+ } else {
124
+ options
125
+ } ;
101
126
writer
102
- . start_file ( & full_path, options)
103
- . wrap_err_with ( || eyre ! ( "{full_path}" ) ) ?;
104
- let mut f = fs:: File :: open ( dir_entry. path ( ) )
105
- . wrap_err_with ( || eyre ! ( "{}" , dir_entry. path( ) . display( ) ) ) ?;
106
- io:: copy ( & mut f, writer)
107
- . wrap_err_with ( || eyre ! ( "{}" , dir_entry. path( ) . display( ) ) ) ?;
127
+ . start_file ( & full_path, maybe_large_file_options)
128
+ . wrap_err_with ( || eyre ! ( "error creating file entry for {full_path}" ) ) ?;
129
+ io:: copy ( & mut f, writer) . wrap_err_with ( || {
130
+ eyre ! ( "error copying content for {full_path} from file {f:?}" )
131
+ } ) ?;
108
132
} else {
109
133
assert ! ( file_type. is_dir( ) ) ;
110
134
writeln ! (
@@ -113,13 +137,14 @@ fn enter_recursive_dir_entries(
113
137
) ?;
114
138
writer
115
139
. add_directory ( & full_path, options)
116
- . wrap_err_with ( || eyre ! ( "{full_path}" ) ) ?;
140
+ . wrap_err_with ( || eyre ! ( "failed to create directory entry {full_path}" ) ) ?;
117
141
writeln ! (
118
142
err,
119
143
"adding subdirectories depth-first for recursive directory entry {entry_basename:?}"
120
144
) ?;
121
- let new_readdir = fs:: read_dir ( dir_entry. path ( ) )
122
- . wrap_err_with ( || eyre ! ( "{}" , dir_entry. path( ) . display( ) ) ) ?;
145
+ let new_readdir = fs:: read_dir ( dir_entry. path ( ) ) . wrap_err_with ( || {
146
+ eyre ! ( "failed to read recursive directory contents from {dir_entry:?}" )
147
+ } ) ?;
123
148
readdir_stack. push ( ( new_readdir, entry_basename) ) ;
124
149
}
125
150
}
@@ -142,7 +167,9 @@ pub fn execute_compress(
142
167
Some ( path) => {
143
168
writeln ! ( err, "writing compressed zip to output file path {path:?}" ) ?;
144
169
OutputHandle :: File (
145
- fs:: File :: create ( & path) . wrap_err_with ( || eyre ! ( "{}" , path. display( ) ) ) ?,
170
+ fs:: File :: create ( & path) . wrap_err_with ( || {
171
+ eyre ! ( "failed to create output file at {}" , path. display( ) )
172
+ } ) ?,
146
173
)
147
174
}
148
175
None => {
@@ -213,7 +240,7 @@ pub fn execute_compress(
213
240
} ) ;
214
241
writer
215
242
. add_directory ( & dirname, options)
216
- . wrap_err_with ( || eyre ! ( "{dirname}" ) ) ?;
243
+ . wrap_err_with ( || eyre ! ( "failed to create dir entry {dirname}" ) ) ?;
217
244
}
218
245
CompressionArg :: Symlink => {
219
246
writeln ! ( err, "setting symlink flag for next entry" ) ?;
@@ -227,6 +254,17 @@ pub fn execute_compress(
227
254
let name = last_name. take ( ) . unwrap_or_else ( || {
228
255
Compress :: exit_arg_invalid ( "no name provided for immediate data {data:?}" )
229
256
} ) ;
257
+ /* It's highly unlikely any OS allows process args of this length, so even though
258
+ * we're using rust's env::args_os() and it would be very impressive for an attacker
259
+ * to get CLI args to overflow, it seems likely to be inefficient in any case, and
260
+ * very unlikely to be useful, so exit with a clear error. */
261
+ if data. len ( ) > ZIP64_BYTES_THR . try_into ( ) . unwrap ( ) {
262
+ bail ! (
263
+ "length of immediate data argument is {}; use a file for inputs over {} bytes" ,
264
+ data. len( ) ,
265
+ ZIP64_BYTES_THR
266
+ ) ;
267
+ } ;
230
268
if symlink_flag {
231
269
/* This is a symlink entry. */
232
270
let target = data. into_string ( ) . unwrap_or_else ( |target| {
@@ -241,7 +279,9 @@ pub fn execute_compress(
241
279
/* TODO: .add_symlink() should support OsString targets! */
242
280
writer
243
281
. add_symlink ( & name, & target, options)
244
- . wrap_err_with ( || eyre ! ( "{name}->{target}" ) ) ?;
282
+ . wrap_err_with ( || {
283
+ eyre ! ( "failed to created symlink entry {name}->{target}" )
284
+ } ) ?;
245
285
symlink_flag = false ;
246
286
} else {
247
287
/* This is a file entry. */
@@ -252,10 +292,13 @@ pub fn execute_compress(
252
292
let data = data. into_encoded_bytes ( ) ;
253
293
writer
254
294
. start_file ( & name, options)
255
- . wrap_err_with ( || eyre ! ( "{name}" ) ) ?;
256
- writer
257
- . write_all ( data. as_ref ( ) )
258
- . wrap_err_with ( || eyre ! ( "{name}" ) ) ?;
295
+ . wrap_err_with ( || eyre ! ( "failed to create file entry {name}" ) ) ?;
296
+ writer. write_all ( data. as_ref ( ) ) . wrap_err_with ( || {
297
+ eyre ! (
298
+ "failed writing immediate data of length {} to file entry {name}" ,
299
+ data. len( )
300
+ )
301
+ } ) ?;
259
302
}
260
303
}
261
304
CompressionArg :: FilePath ( path) => {
@@ -264,27 +307,56 @@ pub fn execute_compress(
264
307
. unwrap_or_else ( || path_to_string ( & path) . into ( ) ) ;
265
308
if symlink_flag {
266
309
/* This is a symlink entry. */
267
- let target: String = path_to_string (
268
- fs:: read_link ( & path) . wrap_err_with ( || eyre ! ( "{}" , path. display( ) ) ) ?,
269
- )
270
- . into ( ) ;
310
+ let target: String =
311
+ path_to_string ( fs:: read_link ( & path) . wrap_err_with ( || {
312
+ eyre ! ( "failed to read symlink from path {}" , path. display( ) )
313
+ } ) ?)
314
+ . into ( ) ;
315
+ /* Similarly to immediate data arguments, we're simply not going to support
316
+ * symlinks over this length, which should be impossible anyway. */
317
+ if target. len ( ) > ZIP64_BYTES_THR . try_into ( ) . unwrap ( ) {
318
+ bail ! (
319
+ "symlink target for {name} is over {ZIP64_BYTES_THR} bytes (was: {})" ,
320
+ target. len( )
321
+ ) ;
322
+ }
271
323
writeln ! ( err, "writing symlink entry from path {path:?} with name {name:?} and target {target:?}" ) ?;
272
324
writer
273
325
. add_symlink ( & name, & target, options)
274
- . wrap_err_with ( || eyre ! ( "{name}->{target}" ) ) ?;
326
+ . wrap_err_with ( || {
327
+ eyre ! ( "failed to create symlink entry for {name}->{target}" )
328
+ } ) ?;
275
329
symlink_flag = false ;
276
330
} else {
277
331
/* This is a file entry. */
278
332
writeln ! (
279
333
err,
280
334
"writing file entry from path {path:?} with name {name:?}"
281
335
) ?;
336
+ let mut f = fs:: File :: open ( & path) . wrap_err_with ( || {
337
+ eyre ! ( "error opening file for {name} at {}" , path. display( ) )
338
+ } ) ?;
339
+ /* Get the length of the file before reading it and set large_file if needed. */
340
+ let input_len: u64 = f
341
+ . metadata ( )
342
+ . wrap_err_with ( || eyre ! ( "error reading file metadata for {f:?}" ) ) ?
343
+ . len ( ) ;
344
+ writeln ! ( err, "entry is {input_len} bytes long" ) ?;
345
+ let maybe_large_file_options = if input_len > ZIP64_BYTES_THR {
346
+ writeln ! (
347
+ err,
348
+ "temporarily ensuring .large_file(true) for current entry"
349
+ ) ?;
350
+ options. large_file ( true )
351
+ } else {
352
+ options
353
+ } ;
282
354
writer
283
- . start_file ( & name, options )
284
- . wrap_err_with ( || eyre ! ( "{name}" ) ) ?;
285
- let mut f =
286
- fs :: File :: open ( & path ) . wrap_err_with ( || eyre ! ( "{}" , path . display ( ) ) ) ? ;
287
- io :: copy ( & mut f , & mut writer ) . wrap_err_with ( || eyre ! ( "{}" , path . display ( ) ) ) ?;
355
+ . start_file ( & name, maybe_large_file_options )
356
+ . wrap_err_with ( || eyre ! ( "error creating file entry for {name}" ) ) ?;
357
+ io :: copy ( & mut f, & mut writer ) . wrap_err_with ( || {
358
+ eyre ! ( "error copying content for {name} from file {f:?}" )
359
+ } ) ?;
288
360
}
289
361
}
290
362
CompressionArg :: RecursiveDirPath ( r) => {
@@ -296,7 +368,7 @@ pub fn execute_compress(
296
368
"writing recursive dir entries for path {r:?} with name {last_name:?}"
297
369
) ?;
298
370
enter_recursive_dir_entries ( err, last_name. take ( ) , & r, & mut writer, options)
299
- . wrap_err_with ( || eyre ! ( "{}" , r. display( ) ) ) ?;
371
+ . wrap_err_with ( || eyre ! ( "failed to read recursive dir {}" , r. display( ) ) ) ?;
300
372
}
301
373
}
302
374
}
@@ -310,47 +382,84 @@ pub fn execute_compress(
310
382
}
311
383
for pos_arg in positional_paths. into_iter ( ) {
312
384
let file_type = fs:: symlink_metadata ( & pos_arg)
313
- . wrap_err_with ( || eyre ! ( "{}" , pos_arg. display( ) ) ) ?
385
+ . wrap_err_with ( || eyre ! ( "failed to read metadata from path {}" , pos_arg. display( ) ) ) ?
314
386
. file_type ( ) ;
315
387
if file_type. is_symlink ( ) {
316
- let target =
317
- fs:: read_link ( & pos_arg) . wrap_err_with ( || eyre ! ( "{}" , pos_arg. display( ) ) ) ?;
388
+ let target = fs:: read_link ( & pos_arg) . wrap_err_with ( || {
389
+ eyre ! ( "failed to read symlink content from {}" , pos_arg. display( ) )
390
+ } ) ?;
318
391
writeln ! (
319
392
err,
320
393
"writing positional symlink entry with path {pos_arg:?} and target {target:?}"
321
394
) ?;
322
395
writer
323
396
. add_symlink_from_path ( & pos_arg, & target, options)
324
- . wrap_err_with ( || eyre ! ( "{}->{}" , pos_arg. display( ) , target. display( ) ) ) ?;
397
+ . wrap_err_with ( || {
398
+ eyre ! (
399
+ "failed to create symlink entry for {}->{}" ,
400
+ pos_arg. display( ) ,
401
+ target. display( )
402
+ )
403
+ } ) ?;
325
404
} else if file_type. is_file ( ) {
326
405
writeln ! ( err, "writing positional file entry with path {pos_arg:?}" ) ?;
406
+ let mut f = fs:: File :: open ( & pos_arg)
407
+ . wrap_err_with ( || eyre ! ( "failed to open file at {}" , pos_arg. display( ) ) ) ?;
408
+ /* Get the length of the file before reading it and set large_file if needed. */
409
+ let input_len: u64 = f
410
+ . metadata ( )
411
+ . wrap_err_with ( || eyre ! ( "error reading file metadata for {f:?}" ) ) ?
412
+ . len ( ) ;
413
+ let maybe_large_file_options = if input_len > ZIP64_BYTES_THR {
414
+ writeln ! (
415
+ err,
416
+ "temporarily ensuring .large_file(true) for current entry"
417
+ ) ?;
418
+ options. large_file ( true )
419
+ } else {
420
+ options
421
+ } ;
327
422
writer
328
- . start_file_from_path ( & pos_arg, options)
329
- . wrap_err_with ( || eyre ! ( "{}" , pos_arg. display( ) ) ) ?;
330
- let mut f =
331
- fs:: File :: open ( & pos_arg) . wrap_err_with ( || eyre ! ( "{}" , pos_arg. display( ) ) ) ?;
332
- io:: copy ( & mut f, & mut writer) . wrap_err_with ( || eyre ! ( "{}" , pos_arg. display( ) ) ) ?;
423
+ . start_file_from_path ( & pos_arg, maybe_large_file_options)
424
+ . wrap_err_with ( || eyre ! ( "failed to create file entry {}" , pos_arg. display( ) ) ) ?;
425
+ io:: copy ( & mut f, & mut writer)
426
+ . wrap_err_with ( || eyre ! ( "failed to copy file contents from {f:?}" ) ) ?;
333
427
} else {
334
428
assert ! ( file_type. is_dir( ) ) ;
335
429
writeln ! (
336
430
err,
337
431
"writing positional recursive dir entry for {pos_arg:?}"
338
432
) ?;
339
- enter_recursive_dir_entries ( err, None , & pos_arg, & mut writer, options)
340
- . wrap_err_with ( || eyre ! ( "{}" , pos_arg. display( ) ) ) ?;
433
+ enter_recursive_dir_entries ( err, None , & pos_arg, & mut writer, options) . wrap_err_with (
434
+ || {
435
+ eyre ! (
436
+ "failed to insert recursive directory entries from {}" ,
437
+ pos_arg. display( )
438
+ )
439
+ } ,
440
+ ) ?;
341
441
}
342
442
}
343
443
344
444
let handle = writer
345
445
. finish ( )
346
446
. wrap_err ( "failed to write zip to output handle" ) ?;
347
447
match handle {
348
- OutputHandle :: File ( _) => ( ) ,
448
+ OutputHandle :: File ( f) => {
449
+ let archive_len: u64 = f
450
+ . metadata ( )
451
+ . wrap_err_with ( || eyre ! ( "failed reading metadata from file {f:?}" ) ) ?
452
+ . len ( ) ;
453
+ writeln ! ( err, "file archive {f:?} was {archive_len} bytes" ) ?;
454
+ mem:: drop ( f) ; /* Superfluous explicit drop. */
455
+ }
349
456
OutputHandle :: InMem ( mut cursor) => {
457
+ let archive_len: u64 = cursor. position ( ) ;
458
+ writeln ! ( err, "in-memory archive was {archive_len} bytes" ) ?;
350
459
cursor. rewind ( ) . wrap_err ( "failed to rewind cursor" ) ?;
351
460
let mut stdout = io:: stdout ( ) . lock ( ) ;
352
461
io:: copy ( & mut cursor, & mut stdout)
353
- . wrap_err ( "failed to copy contents of cursor to stdout" ) ?;
462
+ . wrap_err ( "failed to copy {archive_len} byte archive to stdout" ) ?;
354
463
}
355
464
}
356
465
0 commit comments