| author | |
| committer | |
| log | 81aa74e7e1acc07872fafcd399f047a4652e3ec6 |
| tree | 7e5916fb58ad248284cfd35725201ef805b08858 |
| parent | d51aa9748f9e4e3616328a207a8047ff37d81f8b |
| parent | 65e5c46d6122120b2837c8c74c43801f78aaf60f |
| signature |
std.tar don't overwrite files on unpack5 files changed, 151 insertions(+), 38 deletions(-)
lib/std/tar.zig+36-38| ... | @@ -550,31 +550,15 @@ pub fn pipeToFileSystem(dir: std.fs.Dir, reader: anytype, options: Options) !voi | ... | @@ -550,31 +550,15 @@ pub fn pipeToFileSystem(dir: std.fs.Dir, reader: anytype, options: Options) !voi |
| 550 | const file_name = stripComponents(file.name, options.strip_components); | 550 | const file_name = stripComponents(file.name, options.strip_components); |
| 551 | if (file_name.len == 0) return error.BadFileName; | 551 | if (file_name.len == 0) return error.BadFileName; |
| 552 | 552 | ||
| 553 | const fs_file = dir.createFile(file_name, .{}) catch |err| switch (err) { | 553 | if (createDirAndFile(dir, file_name)) |fs_file| { |
| 554 | error.FileNotFound => again: { | 554 | defer fs_file.close(); |
| 555 | const code = code: { | 555 | try file.write(fs_file); |
| 556 | if (std.fs.path.dirname(file_name)) |dir_name| { | 556 | } else |err| { |
| 557 | dir.makePath(dir_name) catch |code| break :code code; | 557 | const d = options.diagnostics orelse return err; |
| 558 | break :again dir.createFile(file_name, .{}) catch |code| { | 558 | try d.errors.append(d.allocator, .{ .unable_to_create_file = .{ |
| 559 | break :code code; | 559 | .code = err, |
| 560 | }; | 560 | .file_name = try d.allocator.dupe(u8, file_name), |
| 561 | } | 561 | } }); |
| 562 | break :code err; | ||
| 563 | }; | ||
| 564 | const d = options.diagnostics orelse return error.UnableToCreateFile; | ||
| 565 | try d.errors.append(d.allocator, .{ .unable_to_create_file = .{ | ||
| 566 | .code = code, | ||
| 567 | .file_name = try d.allocator.dupe(u8, file_name), | ||
| 568 | } }); | ||
| 569 | break :again null; | ||
| 570 | }, | ||
| 571 | else => |e| return e, | ||
| 572 | }; | ||
| 573 | defer if (fs_file) |f| f.close(); | ||
| 574 | |||
| 575 | if (fs_file) |f| { | ||
| 576 | try file.write(f); | ||
| 577 | } else { | ||
| 578 | try file.skip(); | 562 | try file.skip(); |
| 579 | } | 563 | } |
| 580 | }, | 564 | }, |
| ... | @@ -585,21 +569,10 @@ pub fn pipeToFileSystem(dir: std.fs.Dir, reader: anytype, options: Options) !voi | ... | @@ -585,21 +569,10 @@ pub fn pipeToFileSystem(dir: std.fs.Dir, reader: anytype, options: Options) !voi |
| 585 | // The data inside the symbolic link. | 569 | // The data inside the symbolic link. |
| 586 | const link_name = file.link_name; | 570 | const link_name = file.link_name; |
| 587 | 571 | ||
| 588 | dir.symLink(link_name, file_name, .{}) catch |err| again: { | 572 | createDirAndSymlink(dir, link_name, file_name) catch |err| { |
| 589 | const code = code: { | ||
| 590 | if (err == error.FileNotFound) { | ||
| 591 | if (std.fs.path.dirname(file_name)) |dir_name| { | ||
| 592 | dir.makePath(dir_name) catch |code| break :code code; | ||
| 593 | break :again dir.symLink(link_name, file_name, .{}) catch |code| { | ||
| 594 | break :code code; | ||
| 595 | }; | ||
| 596 | } | ||
| 597 | } | ||
| 598 | break :code err; | ||
| 599 | }; | ||
| 600 | const d = options.diagnostics orelse return error.UnableToCreateSymLink; | 573 | const d = options.diagnostics orelse return error.UnableToCreateSymLink; |
| 601 | try d.errors.append(d.allocator, .{ .unable_to_create_sym_link = .{ | 574 | try d.errors.append(d.allocator, .{ .unable_to_create_sym_link = .{ |
| 602 | .code = code, | 575 | .code = err, |
| 603 | .file_name = try d.allocator.dupe(u8, file_name), | 576 | .file_name = try d.allocator.dupe(u8, file_name), |
| 604 | .link_name = try d.allocator.dupe(u8, link_name), | 577 | .link_name = try d.allocator.dupe(u8, link_name), |
| 605 | } }); | 578 | } }); |
| ... | @@ -610,6 +583,31 @@ pub fn pipeToFileSystem(dir: std.fs.Dir, reader: anytype, options: Options) !voi | ... | @@ -610,6 +583,31 @@ pub fn pipeToFileSystem(dir: std.fs.Dir, reader: anytype, options: Options) !voi |
| 610 | } | 583 | } |
| 611 | } | 584 | } |
| 612 | 585 | ||
| 586 | fn createDirAndFile(dir: std.fs.Dir, file_name: []const u8) !std.fs.File { | ||
| 587 | const fs_file = dir.createFile(file_name, .{ .exclusive = true }) catch |err| { | ||
| 588 | if (err == error.FileNotFound) { | ||
| 589 | if (std.fs.path.dirname(file_name)) |dir_name| { | ||
| 590 | try dir.makePath(dir_name); | ||
| 591 | return try dir.createFile(file_name, .{ .exclusive = true }); | ||
| 592 | } | ||
| 593 | } | ||
| 594 | return err; | ||
| 595 | }; | ||
| 596 | return fs_file; | ||
| 597 | } | ||
| 598 | |||
| 599 | fn createDirAndSymlink(dir: std.fs.Dir, link_name: []const u8, file_name: []const u8) !void { | ||
| 600 | dir.symLink(link_name, file_name, .{}) catch |err| { | ||
| 601 | if (err == error.FileNotFound) { | ||
| 602 | if (std.fs.path.dirname(file_name)) |dir_name| { | ||
| 603 | try dir.makePath(dir_name); | ||
| 604 | try dir.symLink(link_name, file_name, .{}); | ||
| 605 | } | ||
| 606 | } | ||
| 607 | return err; | ||
| 608 | }; | ||
| 609 | } | ||
| 610 | |||
| 613 | fn stripComponents(path: []const u8, count: u32) []const u8 { | 611 | fn stripComponents(path: []const u8, count: u32) []const u8 { |
| 614 | var i: usize = 0; | 612 | var i: usize = 0; |
| 615 | var c = count; | 613 | var c = count; |
lib/std/tar/test.zig+115| ... | @@ -373,3 +373,118 @@ const Md5Writer = struct { | ... | @@ -373,3 +373,118 @@ const Md5Writer = struct { |
| 373 | return std.fmt.bytesToHex(s, .lower); | 373 | return std.fmt.bytesToHex(s, .lower); |
| 374 | } | 374 | } |
| 375 | }; | 375 | }; |
| 376 | |||
| 377 | test "tar should not overwrite existing file" { | ||
| 378 | // Starting from this folder structure: | ||
| 379 | // $ tree root | ||
| 380 | // root | ||
| 381 | // ├── a | ||
| 382 | // │   └── b | ||
| 383 | // │   └── c | ||
| 384 | // │   └── file.txt | ||
| 385 | // └── d | ||
| 386 | // └── b | ||
| 387 | // └── c | ||
| 388 | // └── file.txt | ||
| 389 | // | ||
| 390 | // Packed with command: | ||
| 391 | // $ cd root; tar cf overwrite_file.tar * | ||
| 392 | // Resulting tar has following structure: | ||
| 393 | // $ tar tvf overwrite_file.tar | ||
| 394 | // size path | ||
| 395 | // 0 a/ | ||
| 396 | // 0 a/b/ | ||
| 397 | // 0 a/b/c/ | ||
| 398 | // 2 a/b/c/file.txt | ||
| 399 | // 0 d/ | ||
| 400 | // 0 d/b/ | ||
| 401 | // 0 d/b/c/ | ||
| 402 | // 2 d/b/c/file.txt | ||
| 403 | // | ||
| 404 | // Note that there is no root folder in archive. | ||
| 405 | // | ||
| 406 | // With strip_components = 1 resulting unpacked folder was: | ||
| 407 | // root | ||
| 408 | // └── b | ||
| 409 | // └── c | ||
| 410 | // └── file.txt | ||
| 411 | // | ||
| 412 | // a/b/c/file.txt is overwritten with d/b/c/file.txt !!! | ||
| 413 | // This ensures that file is not overwritten. | ||
| 414 | // | ||
| 415 | const data = @embedFile("testdata/overwrite_file.tar"); | ||
| 416 | var fsb = std.io.fixedBufferStream(data); | ||
| 417 | |||
| 418 | // Unpack with strip_components = 1 should fail | ||
| 419 | var root = std.testing.tmpDir(.{}); | ||
| 420 | defer root.cleanup(); | ||
| 421 | try testing.expectError( | ||
| 422 | error.PathAlreadyExists, | ||
| 423 | tar.pipeToFileSystem(root.dir, fsb.reader(), .{ .mode_mode = .ignore, .strip_components = 1 }), | ||
| 424 | ); | ||
| 425 | |||
| 426 | // Unpack with strip_components = 0 should pass | ||
| 427 | fsb.reset(); | ||
| 428 | var root2 = std.testing.tmpDir(.{}); | ||
| 429 | defer root2.cleanup(); | ||
| 430 | try tar.pipeToFileSystem(root2.dir, fsb.reader(), .{ .mode_mode = .ignore, .strip_components = 0 }); | ||
| 431 | } | ||
| 432 | |||
| 433 | test "tar case sensitivity" { | ||
| 434 | // Mimicking issue #18089, this tar contains, same file name in two case | ||
| 435 | // sensitive name version. Should fail on case insensitive file systems. | ||
| 436 | // | ||
| 437 | // $ tar tvf 18089.tar | ||
| 438 | // 18089/ | ||
| 439 | // 18089/alacritty/ | ||
| 440 | // 18089/alacritty/darkermatrix.yml | ||
| 441 | // 18089/alacritty/Darkermatrix.yml | ||
| 442 | // | ||
| 443 | const data = @embedFile("testdata/18089.tar"); | ||
| 444 | var fsb = std.io.fixedBufferStream(data); | ||
| 445 | |||
| 446 | var root = std.testing.tmpDir(.{}); | ||
| 447 | defer root.cleanup(); | ||
| 448 | |||
| 449 | tar.pipeToFileSystem(root.dir, fsb.reader(), .{ .mode_mode = .ignore, .strip_components = 1 }) catch |err| { | ||
| 450 | // on case insensitive fs we fail on overwrite existing file | ||
| 451 | try testing.expectEqual(error.PathAlreadyExists, err); | ||
| 452 | return; | ||
| 453 | }; | ||
| 454 | |||
| 455 | // on case sensitive os both files are created | ||
| 456 | try testing.expect((try root.dir.statFile("alacritty/darkermatrix.yml")).kind == .file); | ||
| 457 | try testing.expect((try root.dir.statFile("alacritty/Darkermatrix.yml")).kind == .file); | ||
| 458 | } | ||
| 459 | |||
| 460 | test "tar pipeToFileSystem" { | ||
| 461 | // $ tar tvf | ||
| 462 | // pipe_to_file_system_test/ | ||
| 463 | // pipe_to_file_system_test/b/ | ||
| 464 | // pipe_to_file_system_test/b/symlink -> ../a/file | ||
| 465 | // pipe_to_file_system_test/a/ | ||
| 466 | // pipe_to_file_system_test/a/file | ||
| 467 | // pipe_to_file_system_test/empty/ | ||
| 468 | const data = @embedFile("testdata/pipe_to_file_system_test.tar"); | ||
| 469 | var fsb = std.io.fixedBufferStream(data); | ||
| 470 | |||
| 471 | var root = std.testing.tmpDir(.{ .no_follow = true }); | ||
| 472 | defer root.cleanup(); | ||
| 473 | |||
| 474 | tar.pipeToFileSystem(root.dir, fsb.reader(), .{ | ||
| 475 | .mode_mode = .ignore, | ||
| 476 | .strip_components = 1, | ||
| 477 | .exclude_empty_directories = true, | ||
| 478 | }) catch |err| { | ||
| 479 | // Skip on platform which don't support symlinks | ||
| 480 | if (err == error.UnableToCreateSymLink) return error.SkipZigTest; | ||
| 481 | return err; | ||
| 482 | }; | ||
| 483 | |||
| 484 | try testing.expectError(error.FileNotFound, root.dir.statFile("empty")); | ||
| 485 | try testing.expect((try root.dir.statFile("a/file")).kind == .file); | ||
| 486 | // TODO is there better way to test symlink | ||
| 487 | try testing.expect((try root.dir.statFile("b/symlink")).kind == .file); // statFile follows symlink | ||
| 488 | var buf: [8]u8 = undefined; | ||
| 489 | _ = try root.dir.readLink("b/symlink", &buf); | ||
| 490 | } |
lib/std/tar/testdata/18089.tar created| Binary files /dev/null and b/lib/std/tar/testdata/18089.tar differ | |||
lib/std/tar/testdata/overwrite_file.tar created| Binary files /dev/null and b/lib/std/tar/testdata/overwrite_file.tar differ | |||
lib/std/tar/testdata/pipe_to_file_system_test.tar created| Binary files /dev/null and b/lib/std/tar/testdata/pipe_to_file_system_test.tar differ | |||