authorgravatar for igor.anic@gmail.comIgor Anić <igor.anic@gmail.com> 2024-02-24 23:37:55+01:00
committergravatar for igor.anic@gmail.comIgor Anić <igor.anic@gmail.com> 2024-02-24 23:37:55+01:00
logb84301c8e5362be1692c7812bf86e64c2ee5a850
tree59c1172ceced4868906c73c4cc2086af7e596a05
parent6fddc9cd3d8002a44cf65e05d32c449bde6ca60c

std.tar don't overwrite existing file

Fail with error if file already exists. File is not silently overwritten but an error is raised. Fixes: #18089

3 files changed, 59 insertions(+), 2 deletions(-)

lib/std/tar.zig+2-2
...@@ -544,12 +544,12 @@ pub fn pipeToFileSystem(dir: std.fs.Dir, reader: anytype, options: Options) !voi...@@ -544,12 +544,12 @@ pub fn pipeToFileSystem(dir: std.fs.Dir, reader: anytype, options: Options) !voi
544 const file_name = stripComponents(file.name, options.strip_components);544 const file_name = stripComponents(file.name, options.strip_components);
545 if (file_name.len == 0) return error.BadFileName;545 if (file_name.len == 0) return error.BadFileName;
546546
547 const fs_file = dir.createFile(file_name, .{}) catch |err| switch (err) {547 const fs_file = dir.createFile(file_name, .{ .exclusive = true }) catch |err| switch (err) {
548 error.FileNotFound => again: {548 error.FileNotFound => again: {
549 const code = code: {549 const code = code: {
550 if (std.fs.path.dirname(file_name)) |dir_name| {550 if (std.fs.path.dirname(file_name)) |dir_name| {
551 dir.makePath(dir_name) catch |code| break :code code;551 dir.makePath(dir_name) catch |code| break :code code;
552 break :again dir.createFile(file_name, .{}) catch |code| {552 break :again dir.createFile(file_name, .{ .exclusive = true }) catch |code| {
553 break :code code;553 break :code code;
554 };554 };
555 }555 }
lib/std/tar/test.zig+57
...@@ -369,3 +369,60 @@ const Md5Writer = struct {...@@ -369,3 +369,60 @@ const Md5Writer = struct {
369 return std.fmt.bytesToHex(s, .lower);369 return std.fmt.bytesToHex(s, .lower);
370 }370 }
371};371};
372
373test "tar should not overwrite existing file" {
374 // Starting from this folder structure:
375 // $ tree root
376 // root
377 // ├── a
378 // │   └── b
379 // │   └── c
380 // │   └── file.txt
381 // └── d
382 // └── b
383 // └── c
384 // └── file.txt
385 //
386 // Packed with command:
387 // $ cd root; tar cf overwrite_file.tar *
388 // Resulting tar has following structure:
389 // $ tar tvf overwrite_file.tar
390 // size path
391 // 0 a/
392 // 0 a/b/
393 // 0 a/b/c/
394 // 2 a/b/c/file.txt
395 // 0 d/
396 // 0 d/b/
397 // 0 d/b/c/
398 // 2 d/b/c/file.txt
399 //
400 // Note that there is no root folder in archive.
401 //
402 // With strip_components = 1 resulting unpacked folder was:
403 // root
404 // └── b
405 // └── c
406 // └── file.txt
407 //
408 // a/b/c/file.txt is overwritten with d/b/c/file.txt !!!
409 // This ensures that file is not overwritten.
410 //
411 const data = @embedFile("testdata/overwrite_file.tar");
412 var fsb = std.io.fixedBufferStream(data);
413
414 // Unpack with strip_components = 1 should fail
415 var root = std.testing.tmpDir(.{});
416 defer root.cleanup();
417 try testing.expectError(
418 error.PathAlreadyExists,
419 tar.pipeToFileSystem(root.dir, fsb.reader(), .{ .mode_mode = .ignore, .strip_components = 1 }),
420 );
421
422 // Unpack with strip_components = 0 should pass
423 fsb.reset();
424 var root2 = std.testing.tmpDir(.{});
425 defer root2.cleanup();
426 try tar.pipeToFileSystem(root2.dir, fsb.reader(), .{ .mode_mode = .ignore, .strip_components = 0 });
427}
428
lib/std/tar/testdata/overwrite_file.tar created
Binary files /dev/null and b/lib/std/tar/testdata/overwrite_file.tar differ