| ... | @@ -493,6 +493,11 @@ fn fetchAndUnpack( | ... | @@ -493,6 +493,11 @@ fn fetchAndUnpack( |
| 493 | // apply those rules directly to the filesystem right here. This ensures that files | 493 | // apply those rules directly to the filesystem right here. This ensures that files |
| 494 | // not protected by the hash are not present on the file system. | 494 | // not protected by the hash are not present on the file system. |
| 495 | | 495 | |
| | 496 | // TODO: raise an error for files that have illegal paths on some operating systems. |
| | 497 | // For example, on Linux a path with a backslash should raise an error here. |
| | 498 | // Of course, if the ignore rules above omit the file from the package, then everything |
| | 499 | // is fine and no error should be raised. |
| | 500 | |
| 496 | break :a try computePackageHash(thread_pool, .{ .dir = tmp_directory.handle }); | 501 | break :a try computePackageHash(thread_pool, .{ .dir = tmp_directory.handle }); |
| 497 | }; | 502 | }; |
| 498 | | 503 | |
| ... | @@ -546,7 +551,8 @@ fn unpackTarball( | ... | @@ -546,7 +551,8 @@ fn unpackTarball( |
| 546 | } | 551 | } |
| 547 | | 552 | |
| 548 | const HashedFile = struct { | 553 | const HashedFile = struct { |
| 549 | path: []const u8, | 554 | fs_path: []const u8, |
| | 555 | normalized_path: []const u8, |
| 550 | hash: [Manifest.Hash.digest_length]u8, | 556 | hash: [Manifest.Hash.digest_length]u8, |
| 551 | failure: Error!void, | 557 | failure: Error!void, |
| 552 | | 558 | |
| ... | @@ -554,7 +560,7 @@ const HashedFile = struct { | ... | @@ -554,7 +560,7 @@ const HashedFile = struct { |
| 554 | | 560 | |
| 555 | fn lessThan(context: void, lhs: *const HashedFile, rhs: *const HashedFile) bool { | 561 | fn lessThan(context: void, lhs: *const HashedFile, rhs: *const HashedFile) bool { |
| 556 | _ = context; | 562 | _ = context; |
| 557 | return mem.lessThan(u8, lhs.path, rhs.path); | 563 | return mem.lessThan(u8, lhs.normalized_path, rhs.normalized_path); |
| 558 | } | 564 | } |
| 559 | }; | 565 | }; |
| 560 | | 566 | |
| ... | @@ -590,8 +596,10 @@ fn computePackageHash( | ... | @@ -590,8 +596,10 @@ fn computePackageHash( |
| 590 | else => return error.IllegalFileTypeInPackage, | 596 | else => return error.IllegalFileTypeInPackage, |
| 591 | } | 597 | } |
| 592 | const hashed_file = try arena.create(HashedFile); | 598 | const hashed_file = try arena.create(HashedFile); |
| | 599 | const fs_path = try arena.dupe(u8, entry.path); |
| 593 | hashed_file.* = .{ | 600 | hashed_file.* = .{ |
| 594 | .path = try arena.dupe(u8, entry.path), | 601 | .fs_path = fs_path, |
| | 602 | .normalized_path = try normalizePath(arena, fs_path), |
| 595 | .hash = undefined, // to be populated by the worker | 603 | .hash = undefined, // to be populated by the worker |
| 596 | .failure = undefined, // to be populated by the worker | 604 | .failure = undefined, // to be populated by the worker |
| 597 | }; | 605 | }; |
| ... | @@ -609,7 +617,7 @@ fn computePackageHash( | ... | @@ -609,7 +617,7 @@ fn computePackageHash( |
| 609 | for (all_files.items) |hashed_file| { | 617 | for (all_files.items) |hashed_file| { |
| 610 | hashed_file.failure catch |err| { | 618 | hashed_file.failure catch |err| { |
| 611 | any_failures = true; | 619 | any_failures = true; |
| 612 | std.log.err("unable to hash '{s}': {s}", .{ hashed_file.path, @errorName(err) }); | 620 | std.log.err("unable to hash '{s}': {s}", .{ hashed_file.fs_path, @errorName(err) }); |
| 613 | }; | 621 | }; |
| 614 | hasher.update(&hashed_file.hash); | 622 | hasher.update(&hashed_file.hash); |
| 615 | } | 623 | } |
| ... | @@ -617,6 +625,24 @@ fn computePackageHash( | ... | @@ -617,6 +625,24 @@ fn computePackageHash( |
| 617 | return hasher.finalResult(); | 625 | return hasher.finalResult(); |
| 618 | } | 626 | } |
| 619 | | 627 | |
| | 628 | /// Make a file system path identical independently of operating system path inconsistencies. |
| | 629 | /// This converts backslashes into forward slashes. |
| | 630 | fn normalizePath(arena: Allocator, fs_path: []const u8) ![]const u8 { |
| | 631 | const canonical_sep = '/'; |
| | 632 | |
| | 633 | if (fs.path.sep == canonical_sep) |
| | 634 | return fs_path; |
| | 635 | |
| | 636 | const normalized = try arena.dupe(u8, fs_path); |
| | 637 | for (normalized) |*byte| { |
| | 638 | switch (byte.*) { |
| | 639 | fs.path.sep => byte.* = canonical_sep, |
| | 640 | else => continue, |
| | 641 | } |
| | 642 | } |
| | 643 | return normalized; |
| | 644 | } |
| | 645 | |
| 620 | fn workerHashFile(dir: fs.Dir, hashed_file: *HashedFile, wg: *WaitGroup) void { | 646 | fn workerHashFile(dir: fs.Dir, hashed_file: *HashedFile, wg: *WaitGroup) void { |
| 621 | defer wg.finish(); | 647 | defer wg.finish(); |
| 622 | hashed_file.failure = hashFileFallible(dir, hashed_file); | 648 | hashed_file.failure = hashFileFallible(dir, hashed_file); |
| ... | @@ -624,10 +650,10 @@ fn workerHashFile(dir: fs.Dir, hashed_file: *HashedFile, wg: *WaitGroup) void { | ... | @@ -624,10 +650,10 @@ fn workerHashFile(dir: fs.Dir, hashed_file: *HashedFile, wg: *WaitGroup) void { |
| 624 | | 650 | |
| 625 | fn hashFileFallible(dir: fs.Dir, hashed_file: *HashedFile) HashedFile.Error!void { | 651 | fn hashFileFallible(dir: fs.Dir, hashed_file: *HashedFile) HashedFile.Error!void { |
| 626 | var buf: [8000]u8 = undefined; | 652 | var buf: [8000]u8 = undefined; |
| 627 | var file = try dir.openFile(hashed_file.path, .{}); | 653 | var file = try dir.openFile(hashed_file.fs_path, .{}); |
| 628 | defer file.close(); | 654 | defer file.close(); |
| 629 | var hasher = Manifest.Hash.init(.{}); | 655 | var hasher = Manifest.Hash.init(.{}); |
| 630 | hasher.update(hashed_file.path); | 656 | hasher.update(hashed_file.normalized_path); |
| 631 | hasher.update(&.{ 0, @boolToInt(try isExecutable(file)) }); | 657 | hasher.update(&.{ 0, @boolToInt(try isExecutable(file)) }); |
| 632 | while (true) { | 658 | while (true) { |
| 633 | const bytes_read = try file.read(&buf); | 659 | const bytes_read = try file.read(&buf); |