| ... | @@ -56,7 +56,7 @@ pub fn prefixes(cache: *const Cache) []const Directory { | ... | @@ -56,7 +56,7 @@ pub fn prefixes(cache: *const Cache) []const Directory { |
| 56 | return cache.prefixes_buffer[0..cache.prefixes_len]; | 56 | return cache.prefixes_buffer[0..cache.prefixes_len]; |
| 57 | } | 57 | } |
| 58 | | 58 | |
| 59 | const PrefixIndex = u6; | 59 | const PrefixIndex = u3; |
| 60 | | 60 | |
| 61 | const PrefixedPath = struct { | 61 | const PrefixedPath = struct { |
| 62 | prefix: PrefixIndex, | 62 | prefix: PrefixIndex, |
| ... | @@ -453,7 +453,9 @@ pub const Manifest = struct { | ... | @@ -453,7 +453,9 @@ pub const Manifest = struct { |
| 453 | pub const Flags = packed struct(u8) { | 453 | pub const Flags = packed struct(u8) { |
| 454 | is_directory: bool, | 454 | is_directory: bool, |
| 455 | metadata_only: bool, | 455 | metadata_only: bool, |
| | 456 | exists: bool, |
| 456 | prefix: PrefixIndex, | 457 | prefix: PrefixIndex, |
| | 458 | _: u2 = 0, |
| 457 | }; | 459 | }; |
| 458 | | 460 | |
| 459 | /// Prefixes path names in encoded directory contents. Starts numbering | 461 | /// Prefixes path names in encoded directory contents. Starts numbering |
| ... | @@ -576,6 +578,7 @@ pub const Manifest = struct { | ... | @@ -576,6 +578,7 @@ pub const Manifest = struct { |
| 576 | | 578 | |
| 577 | /// Returns true if the stat was changed. Updates the `file` with the new stat value. | 579 | /// Returns true if the stat was changed. Updates the `file` with the new stat value. |
| 578 | fn setStatChanged(file: *File, m: *Manifest, s: Stat) Io.Cancelable!bool { | 580 | fn setStatChanged(file: *File, m: *Manifest, s: Stat) Io.Cancelable!bool { |
| | 581 | assert(file.flags.exists); |
| 579 | if (s.size == file.size and | 582 | if (s.size == file.size and |
| 580 | s.mtime.nanoseconds == file.mtime and | 583 | s.mtime.nanoseconds == file.mtime and |
| 581 | s.inode == file.inode) | 584 | s.inode == file.inode) |
| ... | @@ -693,12 +696,12 @@ pub const Manifest = struct { | ... | @@ -693,12 +696,12 @@ pub const Manifest = struct { |
| 693 | }; | 696 | }; |
| 694 | | 697 | |
| 695 | /// Add a file or directory path as a dependency of process being cached. | 698 | /// Add a file or directory path as a dependency of process being cached. |
| 696 | /// When `hit` is called, the contents will be checked to ensure | 699 | /// When `check` is called, the contents will be checked to ensure |
| 697 | /// that it matches the contents from previous times. | 700 | /// that it matches the contents from previous times. |
| 698 | /// | 701 | /// |
| 699 | /// The contents of the input file may be requested and subsequently | 702 | /// The contents of the input file may be requested and subsequently |
| 700 | /// obtained via methods of the returned `InputPath.Index` after calling | 703 | /// obtained via methods of the returned `InputPath.Index` after calling |
| 701 | /// `hit`. | 704 | /// `check`. |
| 702 | /// | 705 | /// |
| 703 | /// Contents of a directory are considered to be the sorted list of file | 706 | /// Contents of a directory are considered to be the sorted list of file |
| 704 | /// names of direct entries, separated by null byte. Each file name is | 707 | /// names of direct entries, separated by null byte. Each file name is |
| ... | @@ -726,6 +729,7 @@ pub const Manifest = struct { | ... | @@ -726,6 +729,7 @@ pub const Manifest = struct { |
| 726 | .prefix = new_prefix, | 729 | .prefix = new_prefix, |
| 727 | .is_directory = is_directory, | 730 | .is_directory = is_directory, |
| 728 | .metadata_only = options.metadata_only, | 731 | .metadata_only = options.metadata_only, |
| | 732 | .exists = true, // modified later if necessary |
| 729 | }; | 733 | }; |
| 730 | | 734 | |
| 731 | const gop = m.files.getOrPutAssumeCapacityContext(new_file_offset, .{ .contents = m.contents.items }); | 735 | const gop = m.files.getOrPutAssumeCapacityContext(new_file_offset, .{ .contents = m.contents.items }); |
| ... | @@ -841,6 +845,7 @@ pub const Manifest = struct { | ... | @@ -841,6 +845,7 @@ pub const Manifest = struct { |
| 841 | hit, | 845 | hit, |
| 842 | incomplete_manifest, | 846 | incomplete_manifest, |
| 843 | invalid_manifest, | 847 | invalid_manifest, |
| | 848 | path_created: File.Offset, |
| 844 | path_deleted: File.Offset, | 849 | path_deleted: File.Offset, |
| 845 | directory_status_changed: File.Offset, | 850 | directory_status_changed: File.Offset, |
| 846 | metadata_changed: File.Offset, | 851 | metadata_changed: File.Offset, |
| ... | @@ -853,7 +858,7 @@ pub const Manifest = struct { | ... | @@ -853,7 +858,7 @@ pub const Manifest = struct { |
| 853 | pub fn format(this: @This(), w: *Io.Writer) Io.Writer.Error!void { | 858 | pub fn format(this: @This(), w: *Io.Writer) Io.Writer.Error!void { |
| 854 | switch (this.check_result) { | 859 | switch (this.check_result) { |
| 855 | .hit, .incomplete_manifest, .invalid_manifest => return w.writeAll(@tagName(this.check_result)), | 860 | .hit, .incomplete_manifest, .invalid_manifest => return w.writeAll(@tagName(this.check_result)), |
| 856 | .path_deleted, .directory_status_changed, .metadata_changed, .contents_changed => |off| { | 861 | .path_created, .path_deleted, .directory_status_changed, .metadata_changed, .contents_changed => |off| { |
| 857 | const path = off.path(this.manifest.contents.items); | 862 | const path = off.path(this.manifest.contents.items); |
| 858 | return w.print("{t} {s}", .{ this.check_result, path }); | 863 | return w.print("{t} {s}", .{ this.check_result, path }); |
| 859 | }, | 864 | }, |
| ... | @@ -1192,38 +1197,36 @@ pub const Manifest = struct { | ... | @@ -1192,38 +1197,36 @@ pub const Manifest = struct { |
| 1192 | }; | 1197 | }; |
| 1193 | if (!input_path.have_stat and stat_path_ok) { | 1198 | if (!input_path.have_stat and stat_path_ok) { |
| 1194 | // Since this is an input file, FileNotFound counts as a failure, not a cache miss. | 1199 | // Since this is an input file, FileNotFound counts as a failure, not a cache miss. |
| 1195 | const actual_stat = switch (input_path.getHandle(input_file)) { | 1200 | const stat_result = switch (input_path.getHandle(input_file)) { |
| 1196 | .none => parent_dir.statFile(io, file_path, .{}) catch |err| switch (err) { | 1201 | .none => parent_dir.statFile(io, file_path, .{}), |
| 1197 | error.Canceled => |e| return e, | 1202 | .file => |opened_file| opened_file.stat(io), |
| 1198 | else => |e| return fail(diag, .{ .file_stat = .{ | 1203 | .dir => |opened_dir| opened_dir.stat(io), |
| 1199 | .file_offset = file_off, | | |
| 1200 | .err = e, | | |
| 1201 | } }), | | |
| 1202 | }, | | |
| 1203 | .file => |opened_file| opened_file.stat(io) catch |err| switch (err) { | | |
| 1204 | error.Canceled => |e| return e, | | |
| 1205 | else => |e| return fail(diag, .{ .file_stat = .{ | | |
| 1206 | .file_offset = file_off, | | |
| 1207 | .err = e, | | |
| 1208 | } }), | | |
| 1209 | }, | | |
| 1210 | .dir => |opened_dir| opened_dir.stat(io) catch |err| switch (err) { | | |
| 1211 | error.Canceled => |e| return e, | | |
| 1212 | else => |e| return fail(diag, .{ .file_stat = .{ | | |
| 1213 | .file_offset = file_off, | | |
| 1214 | .err = e, | | |
| 1215 | } }), | | |
| 1216 | }, | | |
| 1217 | }; | 1204 | }; |
| 1218 | const actual_is_directory = actual_stat.kind == .directory; | 1205 | if (stat_result) |actual_stat| { |
| 1219 | if (actual_is_directory != input_file.flags.is_directory) { | 1206 | const actual_is_directory = actual_stat.kind == .directory; |
| 1220 | // Since this is an input file, this is a failure, not a cache miss. | 1207 | if (actual_is_directory != input_file.flags.is_directory) { |
| 1221 | return fail(diag, .{ .file_stat = .{ | 1208 | // Since this is an input file, this is a failure, not a cache miss. |
| | 1209 | return fail(diag, .{ .file_stat = .{ |
| | 1210 | .file_offset = file_off, |
| | 1211 | .err = if (actual_is_directory) error.IsDir else error.NotDir, |
| | 1212 | } }); |
| | 1213 | } |
| | 1214 | try input_file.setStat(m, .init(actual_stat)); |
| | 1215 | } else |err| switch (err) { |
| | 1216 | error.Canceled => |e| return e, |
| | 1217 | |
| | 1218 | error.FileNotFound => |e| if (input_file.flags.metadata_only) { |
| | 1219 | input_file.flags.exists = false; |
| | 1220 | } else return fail(diag, .{ .file_stat = .{ |
| 1222 | .file_offset = file_off, | 1221 | .file_offset = file_off, |
| 1223 | .err = if (actual_is_directory) error.IsDir else error.NotDir, | 1222 | .err = e, |
| 1224 | } }); | 1223 | } }), |
| | 1224 | |
| | 1225 | else => |e| return fail(diag, .{ .file_stat = .{ |
| | 1226 | .file_offset = file_off, |
| | 1227 | .err = e, |
| | 1228 | } }), |
| 1225 | } | 1229 | } |
| 1226 | try input_file.setStat(m, .init(actual_stat)); | | |
| 1227 | } | 1230 | } |
| 1228 | | 1231 | |
| 1229 | if (input_file.flags.metadata_only) { | 1232 | if (input_file.flags.metadata_only) { |
| ... | @@ -1369,6 +1372,9 @@ pub const Manifest = struct { | ... | @@ -1369,6 +1372,9 @@ pub const Manifest = struct { |
| 1369 | // space to compute the digest in case stat changed, in order to compare with `disk_file.digest`. | 1372 | // space to compute the digest in case stat changed, in order to compare with `disk_file.digest`. |
| 1370 | | 1373 | |
| 1371 | if (input_path.have_stat) { | 1374 | if (input_path.have_stat) { |
| | 1375 | if (!disk_file.flags.exists) { |
| | 1376 | return .{ .path_created = file_off }; |
| | 1377 | } |
| 1372 | const changed = try disk_file.setStatChanged(m, input_file.stat()); | 1378 | const changed = try disk_file.setStatChanged(m, input_file.stat()); |
| 1373 | if (!changed) return .hit; | 1379 | if (!changed) return .hit; |
| 1374 | } | 1380 | } |
| ... | @@ -1376,29 +1382,28 @@ pub const Manifest = struct { | ... | @@ -1376,29 +1382,28 @@ pub const Manifest = struct { |
| 1376 | if (disk_file.flags.metadata_only) { | 1382 | if (disk_file.flags.metadata_only) { |
| 1377 | if (!input_path.have_stat) { | 1383 | if (!input_path.have_stat) { |
| 1378 | // Since this is an input file, FileNotFound counts as a failure, not a cache miss. | 1384 | // Since this is an input file, FileNotFound counts as a failure, not a cache miss. |
| 1379 | const actual_stat = switch (input_path.getHandle(input_file)) { | 1385 | const stat_result = switch (input_path.getHandle(input_file)) { |
| 1380 | .none => parent_dir.statFile(io, file_path, .{}) catch |err| switch (err) { | 1386 | .none => parent_dir.statFile(io, file_path, .{}), |
| 1381 | error.Canceled => |e| return e, | 1387 | .file => |opened_file| opened_file.stat(io), |
| 1382 | else => |e| return fail(diag, .{ .file_stat = .{ | 1388 | .dir => |opened_dir| opened_dir.stat(io), |
| 1383 | .file_offset = file_off, | 1389 | }; |
| 1384 | .err = e, | 1390 | const actual_stat = stat_result catch |err| switch (err) { |
| 1385 | } }), | 1391 | error.Canceled => |e| return e, |
| 1386 | }, | 1392 | error.FileNotFound => |e| switch (disk_file.flags.exists) { |
| 1387 | .file => |opened_file| opened_file.stat(io) catch |err| switch (err) { | 1393 | false => return .hit, |
| 1388 | error.Canceled => |e| return e, | 1394 | true => return fail(diag, .{ .file_stat = .{ |
| 1389 | else => |e| return fail(diag, .{ .file_stat = .{ | | |
| 1390 | .file_offset = file_off, | | |
| 1391 | .err = e, | | |
| 1392 | } }), | | |
| 1393 | }, | | |
| 1394 | .dir => |opened_dir| opened_dir.stat(io) catch |err| switch (err) { | | |
| 1395 | error.Canceled => |e| return e, | | |
| 1396 | else => |e| return fail(diag, .{ .file_stat = .{ | | |
| 1397 | .file_offset = file_off, | 1395 | .file_offset = file_off, |
| 1398 | .err = e, | 1396 | .err = e, |
| 1399 | } }), | 1397 | } }), |
| 1400 | }, | 1398 | }, |
| | 1399 | else => |e| return fail(diag, .{ .file_stat = .{ |
| | 1400 | .file_offset = file_off, |
| | 1401 | .err = e, |
| | 1402 | } }), |
| 1401 | }; | 1403 | }; |
| | 1404 | if (!disk_file.flags.exists) { |
| | 1405 | return .{ .path_created = file_off }; |
| | 1406 | } |
| 1402 | // In the other case (have_stat=true), this check is redundant since we already asserted the flags are | 1407 | // In the other case (have_stat=true), this check is redundant since we already asserted the flags are |
| 1403 | // identical, which contains the prefix. | 1408 | // identical, which contains the prefix. |
| 1404 | const actual_is_directory = actual_stat.kind == .directory; | 1409 | const actual_is_directory = actual_stat.kind == .directory; |
| ... | @@ -1559,7 +1564,10 @@ pub const Manifest = struct { | ... | @@ -1559,7 +1564,10 @@ pub const Manifest = struct { |
| 1559 | | 1564 | |
| 1560 | if (file.flags.metadata_only) { | 1565 | if (file.flags.metadata_only) { |
| 1561 | const actual_stat = parent_dir.statFile(io, file_path, .{}) catch |err| switch (err) { | 1566 | const actual_stat = parent_dir.statFile(io, file_path, .{}) catch |err| switch (err) { |
| 1562 | error.FileNotFound => return .{ .path_deleted = file_off }, | 1567 | error.FileNotFound => switch (file.flags.exists) { |
| | 1568 | false => return .hit, |
| | 1569 | true => return .{ .path_deleted = file_off }, |
| | 1570 | }, |
| 1563 | error.Canceled => |e| return e, | 1571 | error.Canceled => |e| return e, |
| 1564 | else => |e| return fail(diag, .{ .file_stat = .{ | 1572 | else => |e| return fail(diag, .{ .file_stat = .{ |
| 1565 | .file_offset = file_off, | 1573 | .file_offset = file_off, |
| ... | @@ -1567,6 +1575,10 @@ pub const Manifest = struct { | ... | @@ -1567,6 +1575,10 @@ pub const Manifest = struct { |
| 1567 | } }), | 1575 | } }), |
| 1568 | }; | 1576 | }; |
| 1569 | | 1577 | |
| | 1578 | if (!file.flags.exists) { |
| | 1579 | return .{ .path_created = file_off }; |
| | 1580 | } |
| | 1581 | |
| 1570 | const actual_is_directory = actual_stat.kind == .directory; | 1582 | const actual_is_directory = actual_stat.kind == .directory; |
| 1571 | if (actual_is_directory != file.flags.is_directory) return .{ .directory_status_changed = file_off }; | 1583 | if (actual_is_directory != file.flags.is_directory) return .{ .directory_status_changed = file_off }; |
| 1572 | | 1584 | |
| ... | @@ -1576,6 +1588,8 @@ pub const Manifest = struct { | ... | @@ -1576,6 +1588,8 @@ pub const Manifest = struct { |
| 1576 | return .hit; | 1588 | return .hit; |
| 1577 | } | 1589 | } |
| 1578 | | 1590 | |
| | 1591 | assert(file.flags.exists); // file not existing can only be tracked with metadata_only |
| | 1592 | |
| 1579 | if (file.flags.is_directory) { | 1593 | if (file.flags.is_directory) { |
| 1580 | const opened_dir = parent_dir.openDir(io, file_path, .{ | 1594 | const opened_dir = parent_dir.openDir(io, file_path, .{ |
| 1581 | .iterate = true, | 1595 | .iterate = true, |
| ... | @@ -1774,6 +1788,7 @@ pub const Manifest = struct { | ... | @@ -1774,6 +1788,7 @@ pub const Manifest = struct { |
| 1774 | .prefix = new_prefix, | 1788 | .prefix = new_prefix, |
| 1775 | .is_directory = is_directory, | 1789 | .is_directory = is_directory, |
| 1776 | .metadata_only = options.metadata_only, | 1790 | .metadata_only = options.metadata_only, |
| | 1791 | .exists = true, // modified later if necessary |
| 1777 | }; | 1792 | }; |
| 1778 | | 1793 | |
| 1779 | const gop = m.files.getOrPutAssumeCapacityContext(new_file_offset, .{ .contents = m.contents.items }); | 1794 | const gop = m.files.getOrPutAssumeCapacityContext(new_file_offset, .{ .contents = m.contents.items }); |
| ... | @@ -1817,18 +1832,25 @@ pub const Manifest = struct { | ... | @@ -1817,18 +1832,25 @@ pub const Manifest = struct { |
| 1817 | } else { | 1832 | } else { |
| 1818 | const dir = cache.prefixes()[prefix].handle; | 1833 | const dir = cache.prefixes()[prefix].handle; |
| 1819 | const sub_path = file_offset.path(m.contents.items); | 1834 | const sub_path = file_offset.path(m.contents.items); |
| 1820 | const handle = dir.openDir(io, sub_path, .{ | 1835 | if (dir.openDir(io, sub_path, .{ |
| 1821 | .access_sub_paths = false, | 1836 | .access_sub_paths = false, |
| 1822 | .iterate = true, | 1837 | .iterate = true, |
| 1823 | }) catch |err| switch (err) { | 1838 | })) |handle| { |
| | 1839 | defer handle.close(io); |
| | 1840 | try populateDirectory(m, header, need_stat, handle, options.contents, metadata_only, options.diagnostic); |
| | 1841 | } else |err| switch (err) { |
| 1824 | error.Canceled => |e| return e, | 1842 | error.Canceled => |e| return e, |
| | 1843 | error.FileNotFound => |e| if (options.metadata_only) { |
| | 1844 | header.flags.exists = false; |
| | 1845 | } else { |
| | 1846 | if (options.diagnostic) |d| d.* = .{ .open_dir = e }; |
| | 1847 | return error.FileSystemFailure; |
| | 1848 | }, |
| 1825 | else => |e| { | 1849 | else => |e| { |
| 1826 | if (options.diagnostic) |d| d.* = .{ .open_dir = e }; | 1850 | if (options.diagnostic) |d| d.* = .{ .open_dir = e }; |
| 1827 | return error.FileSystemFailure; | 1851 | return error.FileSystemFailure; |
| 1828 | }, | 1852 | }, |
| 1829 | }; | 1853 | } |
| 1830 | defer handle.close(io); | | |
| 1831 | try populateDirectory(m, header, need_stat, handle, options.contents, metadata_only, options.diagnostic); | | |
| 1832 | }, | 1854 | }, |
| 1833 | | 1855 | |
| 1834 | .file => |opt_handle| if (opt_handle) |handle| { | 1856 | .file => |opt_handle| if (opt_handle) |handle| { |
| ... | @@ -1836,15 +1858,22 @@ pub const Manifest = struct { | ... | @@ -1836,15 +1858,22 @@ pub const Manifest = struct { |
| 1836 | } else { | 1858 | } else { |
| 1837 | const dir = cache.prefixes()[prefix].handle; | 1859 | const dir = cache.prefixes()[prefix].handle; |
| 1838 | const sub_path = file_offset.path(m.contents.items); | 1860 | const sub_path = file_offset.path(m.contents.items); |
| 1839 | const handle = dir.openFile(io, sub_path, .{ .mode = .read_only }) catch |err| switch (err) { | 1861 | if (dir.openFile(io, sub_path, .{ .mode = .read_only })) |handle| { |
| | 1862 | defer handle.close(io); |
| | 1863 | try populateFile(m, header, need_stat, handle, options.contents, metadata_only, options.diagnostic); |
| | 1864 | } else |err| switch (err) { |
| 1840 | error.Canceled => |e| return e, | 1865 | error.Canceled => |e| return e, |
| | 1866 | error.FileNotFound => |e| if (options.metadata_only) { |
| | 1867 | header.flags.exists = false; |
| | 1868 | } else { |
| | 1869 | if (options.diagnostic) |d| d.* = .{ .open_file = e }; |
| | 1870 | return error.FileSystemFailure; |
| | 1871 | }, |
| 1841 | else => |e| { | 1872 | else => |e| { |
| 1842 | if (options.diagnostic) |d| d.* = .{ .open_file = e }; | 1873 | if (options.diagnostic) |d| d.* = .{ .open_file = e }; |
| 1843 | return error.FileSystemFailure; | 1874 | return error.FileSystemFailure; |
| 1844 | }, | 1875 | }, |
| 1845 | }; | 1876 | } |
| 1846 | defer handle.close(io); | | |
| 1847 | try populateFile(m, header, need_stat, handle, options.contents, metadata_only, options.diagnostic); | | |
| 1848 | }, | 1877 | }, |
| 1849 | } | 1878 | } |
| 1850 | } | 1879 | } |