| ... | ... | @@ -365,6 +365,8 @@ pub const Manifest = struct { |
| 365 | 365 | inode: u64, |
| 366 | 366 | /// Nanoseconds. |
| 367 | 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 | 370 | digest: BinDigest, |
| 369 | 371 | /// Starting with this field and continuing into the path, excluding the null byte, |
| 370 | 372 | /// is the string that is hashed for the manifest digest. |
| ... | ... | @@ -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 | 453 | fn setStat(file: *File, m: *Manifest, stat: Stat) Io.Cancelable!void { |
| 444 | 454 | file.size = stat.size; |
| 445 | 455 | file.inode = stat.inode; |
| ... | ... | @@ -660,16 +670,19 @@ pub const Manifest = struct { |
| 660 | 670 | pub fn checkProgressless(man: *Manifest) Check.Error!Check.Status { |
| 661 | 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 | 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 | 679 | man.diagnostic = .none; |
| 668 | 680 | |
| 669 | | var bin_digest: BinDigest = undefined; |
| 670 | | man.hash.hasher.final(&bin_digest); |
| 671 | | const hex_digest = binToHex(bin_digest); |
| 672 | | const manifest_file_path = &hex_digest; |
| 681 | var input_digest: BinDigest = undefined; |
| 682 | man.hash.hasher.final(&input_digest); |
| 683 | const input_hex_digest = binToHex(input_digest); |
| 684 | |
| 685 | const manifest_file_path = &input_hex_digest; |
| 673 | 686 | const io = man.cache.io; |
| 674 | 687 | |
| 675 | 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 | 757 | |
| 745 | 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 |
| 748 | | // already computed (`bin_digest`), and then it'll have the digests of each input file, |
| 749 | | // including "post" files (see `addDiscoveredPath`). If this is a hit, we learn the set of "post" |
| 750 | | // files from the manifest on disk. If this is a miss, we'll learn those from future calls |
| 751 | | // to `addDiscoveredPath` etc. As such, the state of `man.hash.hasher` after this function |
| 752 | | // depends on whether this is a hit or a miss. |
| 760 | // We're going to construct a second hash. Its input will begin with the digest we've already computed |
| 761 | // (`input_digest`), and then it'll have the digests of each input file, including discovered files (see |
| 762 | // `addDiscoveredPath`). If this is a hit, we learn the set of discovered files from the manifest on disk. If |
| 763 | // this is a miss, we'll learn those from future calls to `addDiscoveredPath` etc. As such, the state of |
| 764 | // `man.hash.hasher` after this function 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 |
| 755 | | // the digests of the "post" files, so the caller can call `final`. Otherwise, on a cache |
| 756 | | // miss, `man.hash.hasher` will include the digests of all non-"post" files -- that is, |
| 757 | | // the ones we've already been told about. The rest will be discovered through calls to |
| 758 | | // `addDiscoveredPath` etc, which will update the hasher. After all files are added, the user can |
| 759 | | // use `final`, and will at some point `writeManifest` the file list to disk. |
| 766 | // If we return `CacheStatus.hit`, then `man.hash.hasher` must already include the digests of the discovered |
| 767 | // files, so the caller can call `final`. Otherwise, on a cache miss, `man.hash.hasher` will include the digests |
| 768 | // of all non-discovered files -- that is, the ones we've already been told about. The rest will be discovered |
| 769 | // through calls to `addDiscoveredPath` etc, which will update the hasher. After all files are added, the user |
| 770 | // can use `final`, and will at some point `writeManifest` the file list to disk. |
| 760 | 771 | |
| 761 | 772 | man.hash.hasher = hasher_init; |
| 762 | | man.hash.hasher.update(&bin_digest); |
| 773 | man.hash.hasher.update(&input_digest); |
| 763 | 774 | |
| 764 | 775 | hit: { |
| 765 | 776 | digests: { |
| ... | ... | @@ -772,7 +783,7 @@ pub const Manifest = struct { |
| 772 | 783 | // Before trying again, we must reset `man.hash.hasher` and `man.files`. |
| 773 | 784 | // This is basically just the first half of `unhit`. |
| 774 | 785 | man.hash.hasher = hasher_init; |
| 775 | | man.hash.hasher.update(&bin_digest); |
| 786 | man.hash.hasher.update(&input_digest); |
| 776 | 787 | man.shrinkFilesToInput(); |
| 777 | 788 | switch (try man.checkLocked()) { |
| 778 | 789 | .hit => break :hit, |
| ... | ... | @@ -784,7 +795,7 @@ pub const Manifest = struct { |
| 784 | 795 | // unless it returns an error. |
| 785 | 796 | man.manifest_dirty = true; |
| 786 | 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 | 799 | return .miss; |
| 789 | 800 | } |
| 790 | 801 | |
| ... | ... | @@ -1005,9 +1016,9 @@ pub const Manifest = struct { |
| 1005 | 1016 | const actual_is_directory = actual_stat.kind == .directory; |
| 1006 | 1017 | if (actual_is_directory != file.flags.is_directory) return .miss; |
| 1007 | 1018 | |
| 1008 | | if (try file.setStatChanged(m, .init(actual_stat))) return .miss; |
| 1009 | | |
| 1010 | | return .hit; |
| 1019 | const changed = try file.setStatChanged(m, .init(actual_stat)); |
| 1020 | file.hashFromMetadata(); |
| 1021 | return if (changed) .miss else .hit; |
| 1011 | 1022 | } |
| 1012 | 1023 | |
| 1013 | 1024 | if (file.flags.is_directory) { |
| ... | ... | @@ -1082,15 +1093,15 @@ pub const Manifest = struct { |
| 1082 | 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 | 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. |
| 1090 | | pub fn unhit(man: *Manifest, bin_digest: *const BinDigest) void { |
| 1100 | /// Assumes that `digest` is populated for all input files. |
| 1101 | pub fn unhit(man: *Manifest, input_digest: *const BinDigest) void { |
| 1091 | 1102 | // Reset the hash. |
| 1092 | 1103 | man.hash.hasher = hasher_init; |
| 1093 | | man.hash.hasher.update(bin_digest); |
| 1104 | man.hash.hasher.update(input_digest); |
| 1094 | 1105 | man.shrinkFilesToInput(); |
| 1095 | 1106 | const contents = man.contents.items; |
| 1096 | 1107 | for (man.files.keys()) |off| { |
| ... | ... | @@ -1206,13 +1217,14 @@ pub const Manifest = struct { |
| 1206 | 1217 | if (options.stat) |stat| { |
| 1207 | 1218 | try header.setStat(m, stat); |
| 1208 | 1219 | if (header.flags.metadata_only) { |
| 1209 | | return; |
| 1220 | header.hashFromMetadata(); |
| 1210 | 1221 | } else if (options.contents) |contents| { |
| 1211 | 1222 | var hasher = hasher_init; |
| 1212 | 1223 | hasher.update(contents); |
| 1213 | 1224 | hasher.final(&header.digest); |
| 1214 | | return; |
| 1215 | 1225 | } |
| 1226 | m.hash.hasher.update(&header.digest); |
| 1227 | return; |
| 1216 | 1228 | } |
| 1217 | 1229 | |
| 1218 | 1230 | const need_stat = options.stat == null; |
| ... | ... | @@ -1243,6 +1255,8 @@ pub const Manifest = struct { |
| 1243 | 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 | 1262 | fn populateFile( |
| ... | ... | @@ -1259,7 +1273,10 @@ pub const Manifest = struct { |
| 1259 | 1273 | const stat = try handle.stat(io); |
| 1260 | 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 | 1280 | if (contents) |bytes| { |
| 1264 | 1281 | var hasher = hasher_init; |
| 1265 | 1282 | hasher.update(bytes); |
| ... | ... | @@ -1285,7 +1302,10 @@ pub const Manifest = struct { |
| 1285 | 1302 | const stat = try handle.stat(io); |
| 1286 | 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 | 1309 | if (contents) |bytes| { |
| 1290 | 1310 | var hasher = hasher_init; |
| 1291 | 1311 | hasher.update(bytes); |
| ... | ... | @@ -1611,7 +1631,7 @@ pub const Manifest = struct { |
| 1611 | 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 | 1635 | const contents = m.contents.items; |
| 1616 | 1636 | const flags_off = @offsetOf(File, "flags"); |
| 1617 | 1637 | comptime assert(@offsetOf(File, "path_start") - flags_off == 1); |