| ... | @@ -365,6 +365,8 @@ pub const Manifest = struct { | ... | @@ -365,6 +365,8 @@ pub const Manifest = struct { |
| 365 | inode: u64, | 365 | inode: u64, |
| 366 | /// Nanoseconds. | 366 | /// Nanoseconds. |
| 367 | mtime: i64, | 367 | mtime: i64, |
| | 368 | /// To simplify the hashing logic, this value is computed from size, inode, and mtime |
| | 369 | /// in `hashFromMetadata` in the case that `Flags.metadata_only` is `true`. |
| 368 | digest: BinDigest, | 370 | digest: BinDigest, |
| 369 | /// Starting with this field and continuing into the path, excluding the null byte, | 371 | /// Starting with this field and continuing into the path, excluding the null byte, |
| 370 | /// is the string that is hashed for the manifest digest. | 372 | /// is the string that is hashed for the manifest digest. |
| ... | @@ -440,6 +442,14 @@ pub const Manifest = struct { | ... | @@ -440,6 +442,14 @@ pub const Manifest = struct { |
| 440 | } | 442 | } |
| 441 | }; | 443 | }; |
| 442 | | 444 | |
| | 445 | fn hashFromMetadata(file: *File) void { |
| | 446 | var hasher = hasher_init; |
| | 447 | hasher.update(mem.asBytes(&file.size)); |
| | 448 | hasher.update(mem.asBytes(&file.inode)); |
| | 449 | hasher.update(mem.asBytes(&file.mtime)); |
| | 450 | hasher.final(&file.digest); |
| | 451 | } |
| | 452 | |
| 443 | fn setStat(file: *File, m: *Manifest, stat: Stat) Io.Cancelable!void { | 453 | fn setStat(file: *File, m: *Manifest, stat: Stat) Io.Cancelable!void { |
| 444 | file.size = stat.size; | 454 | file.size = stat.size; |
| 445 | file.inode = stat.inode; | 455 | file.inode = stat.inode; |
| ... | @@ -660,16 +670,19 @@ pub const Manifest = struct { | ... | @@ -660,16 +670,19 @@ pub const Manifest = struct { |
| 660 | pub fn checkProgressless(man: *Manifest) Check.Error!Check.Status { | 670 | pub fn checkProgressless(man: *Manifest) Check.Error!Check.Status { |
| 661 | assert(man.manifest_file == null); | 671 | assert(man.manifest_file == null); |
| 662 | | 672 | |
| | 673 | // This is *not* hashing the contents of the input files. It is the |
| | 674 | // flags (including prefix) and path only. |
| 663 | for (man.files.keys()[0..man.input_paths.items.len]) |file_off| { | 675 | for (man.files.keys()[0..man.input_paths.items.len]) |file_off| { |
| 664 | man.digestHash(file_off, &man.hash.hasher); | 676 | man.hashFlagsAndPath(file_off, &man.hash.hasher); |
| 665 | } | 677 | } |
| 666 | | 678 | |
| 667 | man.diagnostic = .none; | 679 | man.diagnostic = .none; |
| 668 | | 680 | |
| 669 | var bin_digest: BinDigest = undefined; | 681 | var input_digest: BinDigest = undefined; |
| 670 | man.hash.hasher.final(&bin_digest); | 682 | man.hash.hasher.final(&input_digest); |
| 671 | const hex_digest = binToHex(bin_digest); | 683 | const input_hex_digest = binToHex(input_digest); |
| 672 | const manifest_file_path = &hex_digest; | 684 | |
| | 685 | const manifest_file_path = &input_hex_digest; |
| 673 | const io = man.cache.io; | 686 | const io = man.cache.io; |
| 674 | | 687 | |
| 675 | // We'll try to open the cache with an exclusive lock, but if that would block | 688 | // We'll try to open the cache with an exclusive lock, but if that would block |
| ... | @@ -744,22 +757,20 @@ pub const Manifest = struct { | ... | @@ -744,22 +757,20 @@ pub const Manifest = struct { |
| 744 | | 757 | |
| 745 | man.want_refresh_timestamp = true; | 758 | man.want_refresh_timestamp = true; |
| 746 | | 759 | |
| 747 | // We're going to construct a second hash. Its input will begin with the digest we've | 760 | // We're going to construct a second hash. Its input will begin with the digest we've already computed |
| 748 | // already computed (`bin_digest`), and then it'll have the digests of each input file, | 761 | // (`input_digest`), and then it'll have the digests of each input file, including discovered files (see |
| 749 | // including "post" files (see `addDiscoveredPath`). If this is a hit, we learn the set of "post" | 762 | // `addDiscoveredPath`). If this is a hit, we learn the set of discovered files from the manifest on disk. If |
| 750 | // files from the manifest on disk. If this is a miss, we'll learn those from future calls | 763 | // this is a miss, we'll learn those from future calls to `addDiscoveredPath` etc. As such, the state of |
| 751 | // to `addDiscoveredPath` etc. As such, the state of `man.hash.hasher` after this function | 764 | // `man.hash.hasher` after this function depends on whether this is a hit or a miss. |
| 752 | // depends on whether this is a hit or a miss. | | |
| 753 | // | 765 | // |
| 754 | // If we return `CacheStatus.hit`, then `man.hash.hasher` must already include | 766 | // If we return `CacheStatus.hit`, then `man.hash.hasher` must already include the digests of the discovered |
| 755 | // the digests of the "post" files, so the caller can call `final`. Otherwise, on a cache | 767 | // files, so the caller can call `final`. Otherwise, on a cache miss, `man.hash.hasher` will include the digests |
| 756 | // miss, `man.hash.hasher` will include the digests of all non-"post" files -- that is, | 768 | // of all non-discovered files -- that is, the ones we've already been told about. The rest will be discovered |
| 757 | // the ones we've already been told about. The rest will be discovered through calls to | 769 | // through calls to `addDiscoveredPath` etc, which will update the hasher. After all files are added, the user |
| 758 | // `addDiscoveredPath` etc, which will update the hasher. After all files are added, the user can | 770 | // can use `final`, and will at some point `writeManifest` the file list to disk. |
| 759 | // use `final`, and will at some point `writeManifest` the file list to disk. | | |
| 760 | | 771 | |
| 761 | man.hash.hasher = hasher_init; | 772 | man.hash.hasher = hasher_init; |
| 762 | man.hash.hasher.update(&bin_digest); | 773 | man.hash.hasher.update(&input_digest); |
| 763 | | 774 | |
| 764 | hit: { | 775 | hit: { |
| 765 | digests: { | 776 | digests: { |
| ... | @@ -772,7 +783,7 @@ pub const Manifest = struct { | ... | @@ -772,7 +783,7 @@ pub const Manifest = struct { |
| 772 | // Before trying again, we must reset `man.hash.hasher` and `man.files`. | 783 | // Before trying again, we must reset `man.hash.hasher` and `man.files`. |
| 773 | // This is basically just the first half of `unhit`. | 784 | // This is basically just the first half of `unhit`. |
| 774 | man.hash.hasher = hasher_init; | 785 | man.hash.hasher = hasher_init; |
| 775 | man.hash.hasher.update(&bin_digest); | 786 | man.hash.hasher.update(&input_digest); |
| 776 | man.shrinkFilesToInput(); | 787 | man.shrinkFilesToInput(); |
| 777 | switch (try man.checkLocked()) { | 788 | switch (try man.checkLocked()) { |
| 778 | .hit => break :hit, | 789 | .hit => break :hit, |
| ... | @@ -784,7 +795,7 @@ pub const Manifest = struct { | ... | @@ -784,7 +795,7 @@ pub const Manifest = struct { |
| 784 | // unless it returns an error. | 795 | // unless it returns an error. |
| 785 | man.manifest_dirty = true; | 796 | man.manifest_dirty = true; |
| 786 | // All input file digests are already populated by `checkLocked`, so we can call `unhit` directly. | 797 | // All input file digests are already populated by `checkLocked`, so we can call `unhit` directly. |
| 787 | unhit(man, &bin_digest); | 798 | unhit(man, &input_digest); |
| 788 | return .miss; | 799 | return .miss; |
| 789 | } | 800 | } |
| 790 | | 801 | |
| ... | @@ -1005,9 +1016,9 @@ pub const Manifest = struct { | ... | @@ -1005,9 +1016,9 @@ pub const Manifest = struct { |
| 1005 | const actual_is_directory = actual_stat.kind == .directory; | 1016 | const actual_is_directory = actual_stat.kind == .directory; |
| 1006 | if (actual_is_directory != file.flags.is_directory) return .miss; | 1017 | if (actual_is_directory != file.flags.is_directory) return .miss; |
| 1007 | | 1018 | |
| 1008 | if (try file.setStatChanged(m, .init(actual_stat))) return .miss; | 1019 | const changed = try file.setStatChanged(m, .init(actual_stat)); |
| 1009 | | 1020 | file.hashFromMetadata(); |
| 1010 | return .hit; | 1021 | return if (changed) .miss else .hit; |
| 1011 | } | 1022 | } |
| 1012 | | 1023 | |
| 1013 | if (file.flags.is_directory) { | 1024 | if (file.flags.is_directory) { |
| ... | @@ -1082,15 +1093,15 @@ pub const Manifest = struct { | ... | @@ -1082,15 +1093,15 @@ pub const Manifest = struct { |
| 1082 | return .hit; | 1093 | return .hit; |
| 1083 | } | 1094 | } |
| 1084 | | 1095 | |
| 1085 | /// Reset `man.hash.hasher` to the state it should be in after `hit` returns `Check.Status.miss`. | 1096 | /// Reset `man.hash.hasher` to the state it should be in after `check` returns `Check.Status.miss`. |
| 1086 | /// The hasher contains the original input digest, and all original input file digests (i.e. | 1097 | /// The hasher contains the original input digest, and all original input file digests (i.e. |
| 1087 | /// not including post files). | 1098 | /// not including discovered files). |
| 1088 | /// | 1099 | /// |
| 1089 | /// Assumes that `bin_digest` is populated for all input files. | 1100 | /// Assumes that `digest` is populated for all input files. |
| 1090 | pub fn unhit(man: *Manifest, bin_digest: *const BinDigest) void { | 1101 | pub fn unhit(man: *Manifest, input_digest: *const BinDigest) void { |
| 1091 | // Reset the hash. | 1102 | // Reset the hash. |
| 1092 | man.hash.hasher = hasher_init; | 1103 | man.hash.hasher = hasher_init; |
| 1093 | man.hash.hasher.update(bin_digest); | 1104 | man.hash.hasher.update(input_digest); |
| 1094 | man.shrinkFilesToInput(); | 1105 | man.shrinkFilesToInput(); |
| 1095 | const contents = man.contents.items; | 1106 | const contents = man.contents.items; |
| 1096 | for (man.files.keys()) |off| { | 1107 | for (man.files.keys()) |off| { |
| ... | @@ -1206,13 +1217,14 @@ pub const Manifest = struct { | ... | @@ -1206,13 +1217,14 @@ pub const Manifest = struct { |
| 1206 | if (options.stat) |stat| { | 1217 | if (options.stat) |stat| { |
| 1207 | try header.setStat(m, stat); | 1218 | try header.setStat(m, stat); |
| 1208 | if (header.flags.metadata_only) { | 1219 | if (header.flags.metadata_only) { |
| 1209 | return; | 1220 | header.hashFromMetadata(); |
| 1210 | } else if (options.contents) |contents| { | 1221 | } else if (options.contents) |contents| { |
| 1211 | var hasher = hasher_init; | 1222 | var hasher = hasher_init; |
| 1212 | hasher.update(contents); | 1223 | hasher.update(contents); |
| 1213 | hasher.final(&header.digest); | 1224 | hasher.final(&header.digest); |
| 1214 | return; | | |
| 1215 | } | 1225 | } |
| | 1226 | m.hash.hasher.update(&header.digest); |
| | 1227 | return; |
| 1216 | } | 1228 | } |
| 1217 | | 1229 | |
| 1218 | const need_stat = options.stat == null; | 1230 | const need_stat = options.stat == null; |
| ... | @@ -1243,6 +1255,8 @@ pub const Manifest = struct { | ... | @@ -1243,6 +1255,8 @@ pub const Manifest = struct { |
| 1243 | try populateFile(m, header, need_stat, handle, options.contents, metadata_only); | 1255 | try populateFile(m, header, need_stat, handle, options.contents, metadata_only); |
| 1244 | }, | 1256 | }, |
| 1245 | } | 1257 | } |
| | 1258 | |
| | 1259 | m.hash.hasher.update(&header.digest); |
| 1246 | } | 1260 | } |
| 1247 | | 1261 | |
| 1248 | fn populateFile( | 1262 | fn populateFile( |
| ... | @@ -1259,7 +1273,10 @@ pub const Manifest = struct { | ... | @@ -1259,7 +1273,10 @@ pub const Manifest = struct { |
| 1259 | const stat = try handle.stat(io); | 1273 | const stat = try handle.stat(io); |
| 1260 | try file.setStat(m, .init(stat)); | 1274 | try file.setStat(m, .init(stat)); |
| 1261 | } | 1275 | } |
| 1262 | if (metadata_only) return; | 1276 | if (metadata_only) { |
| | 1277 | file.hashFromMetadata(); |
| | 1278 | return; |
| | 1279 | } |
| 1263 | if (contents) |bytes| { | 1280 | if (contents) |bytes| { |
| 1264 | var hasher = hasher_init; | 1281 | var hasher = hasher_init; |
| 1265 | hasher.update(bytes); | 1282 | hasher.update(bytes); |
| ... | @@ -1285,7 +1302,10 @@ pub const Manifest = struct { | ... | @@ -1285,7 +1302,10 @@ pub const Manifest = struct { |
| 1285 | const stat = try handle.stat(io); | 1302 | const stat = try handle.stat(io); |
| 1286 | try file.setStat(m, .init(stat)); | 1303 | try file.setStat(m, .init(stat)); |
| 1287 | } | 1304 | } |
| 1288 | if (metadata_only) return; | 1305 | if (metadata_only) { |
| | 1306 | file.hashFromMetadata(); |
| | 1307 | return; |
| | 1308 | } |
| 1289 | if (contents) |bytes| { | 1309 | if (contents) |bytes| { |
| 1290 | var hasher = hasher_init; | 1310 | var hasher = hasher_init; |
| 1291 | hasher.update(bytes); | 1311 | hasher.update(bytes); |
| ... | @@ -1611,7 +1631,7 @@ pub const Manifest = struct { | ... | @@ -1611,7 +1631,7 @@ pub const Manifest = struct { |
| 1611 | hasher.final(bin_digest); | 1631 | hasher.final(bin_digest); |
| 1612 | } | 1632 | } |
| 1613 | | 1633 | |
| 1614 | fn digestHash(m: *const Manifest, off: File.Offset, hasher: *Hasher) void { | 1634 | fn hashFlagsAndPath(m: *const Manifest, off: File.Offset, hasher: *Hasher) void { |
| 1615 | const contents = m.contents.items; | 1635 | const contents = m.contents.items; |
| 1616 | const flags_off = @offsetOf(File, "flags"); | 1636 | const flags_off = @offsetOf(File, "flags"); |
| 1617 | comptime assert(@offsetOf(File, "path_start") - flags_off == 1); | 1637 | comptime assert(@offsetOf(File, "path_start") - flags_off == 1); |