authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-12-27 23:35:51+01:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-12-27 23:35:51+01:00
log6984992153f0656b04c39279f9684fd5a06e952d
tree9cc749840315f7304a64a43e0f1f4691ef0eff31
parent2e73288e6302cc405b8fbb0c4c0667d089775c55
parent212968c574ada30dd593dddf6d5c0cfd962459e0

Merge pull request 'std.Io.File.Stat: make access time optional' (#30607) from atime into master

Reviewed-on: https://codeberg.org/ziglang/zig/pulls/30607

7 files changed, 266 insertions(+), 220 deletions(-)

lib/std/Io.zig+2-4
...@@ -681,8 +681,7 @@ pub const VTable = struct {...@@ -681,8 +681,7 @@ pub const VTable = struct {
681 dirSetFileOwner: *const fn (?*anyopaque, Dir, []const u8, ?File.Uid, ?File.Gid, Dir.SetFileOwnerOptions) Dir.SetFileOwnerError!void,681 dirSetFileOwner: *const fn (?*anyopaque, Dir, []const u8, ?File.Uid, ?File.Gid, Dir.SetFileOwnerOptions) Dir.SetFileOwnerError!void,
682 dirSetPermissions: *const fn (?*anyopaque, Dir, Dir.Permissions) Dir.SetPermissionsError!void,682 dirSetPermissions: *const fn (?*anyopaque, Dir, Dir.Permissions) Dir.SetPermissionsError!void,
683 dirSetFilePermissions: *const fn (?*anyopaque, Dir, []const u8, File.Permissions, Dir.SetFilePermissionsOptions) Dir.SetFilePermissionsError!void,683 dirSetFilePermissions: *const fn (?*anyopaque, Dir, []const u8, File.Permissions, Dir.SetFilePermissionsOptions) Dir.SetFilePermissionsError!void,
684 dirSetTimestamps: *const fn (?*anyopaque, Dir, []const u8, last_accessed: Timestamp, last_modified: Timestamp, Dir.SetTimestampsOptions) Dir.SetTimestampsError!void,684 dirSetTimestamps: *const fn (?*anyopaque, Dir, []const u8, Dir.SetTimestampsOptions) Dir.SetTimestampsError!void,
685 dirSetTimestampsNow: *const fn (?*anyopaque, Dir, []const u8, Dir.SetTimestampsOptions) Dir.SetTimestampsError!void,
686 dirHardLink: *const fn (?*anyopaque, old_dir: Dir, old_sub_path: []const u8, new_dir: Dir, new_sub_path: []const u8, Dir.HardLinkOptions) Dir.HardLinkError!void,685 dirHardLink: *const fn (?*anyopaque, old_dir: Dir, old_sub_path: []const u8, new_dir: Dir, new_sub_path: []const u8, Dir.HardLinkOptions) Dir.HardLinkError!void,
687686
688 fileStat: *const fn (?*anyopaque, File) File.StatError!File.Stat,687 fileStat: *const fn (?*anyopaque, File) File.StatError!File.Stat,
...@@ -705,8 +704,7 @@ pub const VTable = struct {...@@ -705,8 +704,7 @@ pub const VTable = struct {
705 fileSetLength: *const fn (?*anyopaque, File, u64) File.SetLengthError!void,704 fileSetLength: *const fn (?*anyopaque, File, u64) File.SetLengthError!void,
706 fileSetOwner: *const fn (?*anyopaque, File, ?File.Uid, ?File.Gid) File.SetOwnerError!void,705 fileSetOwner: *const fn (?*anyopaque, File, ?File.Uid, ?File.Gid) File.SetOwnerError!void,
707 fileSetPermissions: *const fn (?*anyopaque, File, File.Permissions) File.SetPermissionsError!void,706 fileSetPermissions: *const fn (?*anyopaque, File, File.Permissions) File.SetPermissionsError!void,
708 fileSetTimestamps: *const fn (?*anyopaque, File, last_accessed: Timestamp, last_modified: Timestamp) File.SetTimestampsError!void,707 fileSetTimestamps: *const fn (?*anyopaque, File, File.SetTimestampsOptions) File.SetTimestampsError!void,
709 fileSetTimestampsNow: *const fn (?*anyopaque, File) File.SetTimestampsError!void,
710 fileLock: *const fn (?*anyopaque, File, File.Lock) File.LockError!void,708 fileLock: *const fn (?*anyopaque, File, File.Lock) File.LockError!void,
711 fileTryLock: *const fn (?*anyopaque, File, File.Lock) File.LockError!bool,709 fileTryLock: *const fn (?*anyopaque, File, File.Lock) File.LockError!bool,
712 fileUnlock: *const fn (?*anyopaque, File) void,710 fileUnlock: *const fn (?*anyopaque, File) void,
lib/std/Io/Dir.zig+22-6
...@@ -615,7 +615,10 @@ pub fn updateFile(...@@ -615,7 +615,10 @@ pub fn updateFile(
615 error.WriteFailed => return atomic_file.file_writer.err.?,615 error.WriteFailed => return atomic_file.file_writer.err.?,
616 };616 };
617 try atomic_file.flush();617 try atomic_file.flush();
618 try atomic_file.file_writer.file.setTimestamps(io, src_stat.atime, src_stat.mtime);618 try atomic_file.file_writer.file.setTimestamps(io, .{
619 .access_timestamp = .init(src_stat.atime),
620 .modify_timestamp = .init(src_stat.mtime),
621 });
619 try atomic_file.renameIntoPlace();622 try atomic_file.renameIntoPlace();
620 return .stale;623 return .stale;
621}624}
...@@ -1825,6 +1828,8 @@ pub const SetTimestampsError = File.SetTimestampsError || PathNameError;...@@ -1825,6 +1828,8 @@ pub const SetTimestampsError = File.SetTimestampsError || PathNameError;
18251828
1826pub const SetTimestampsOptions = struct {1829pub const SetTimestampsOptions = struct {
1827 follow_symlinks: bool = true,1830 follow_symlinks: bool = true,
1831 access_timestamp: File.SetTimestamp = .unchanged,
1832 modify_timestamp: File.SetTimestamp = .unchanged,
1828};1833};
18291834
1830/// The granularity that ultimately is stored depends on the combination of1835/// The granularity that ultimately is stored depends on the combination of
...@@ -1834,18 +1839,29 @@ pub fn setTimestamps(...@@ -1834,18 +1839,29 @@ pub fn setTimestamps(
1834 dir: Dir,1839 dir: Dir,
1835 io: Io,1840 io: Io,
1836 sub_path: []const u8,1841 sub_path: []const u8,
1837 last_accessed: Io.Timestamp,
1838 last_modified: Io.Timestamp,
1839 options: SetTimestampsOptions,1842 options: SetTimestampsOptions,
1840) SetTimestampsError!void {1843) SetTimestampsError!void {
1841 return io.vtable.dirSetTimestamps(io.userdata, dir, sub_path, last_accessed, last_modified, options);1844 return io.vtable.dirSetTimestamps(io.userdata, dir, sub_path, options);
1842}1845}
18431846
1847pub const SetTimestampsNowOptions = struct {
1848 follow_symlinks: bool = true,
1849};
1850
1844/// Sets the accessed and modification timestamps of the provided path to the1851/// Sets the accessed and modification timestamps of the provided path to the
1845/// current wall clock time.1852/// current wall clock time.
1846///1853///
1847/// The granularity that ultimately is stored depends on the combination of1854/// The granularity that ultimately is stored depends on the combination of
1848/// operating system and file system.1855/// operating system and file system.
1849pub fn setTimestampsNow(dir: Dir, io: Io, sub_path: []const u8, options: SetTimestampsOptions) SetTimestampsError!void {1856pub fn setTimestampsNow(
1850 return io.vtable.fileSetTimestampsNow(io.userdata, dir, sub_path, options);1857 dir: Dir,
1858 io: Io,
1859 sub_path: []const u8,
1860 options: SetTimestampsNowOptions,
1861) SetTimestampsError!void {
1862 return io.vtable.fileSetTimestamps(io.userdata, dir, sub_path, .{
1863 .follow_symlinks = options.follow_symlinks,
1864 .access_timestamp = .now,
1865 .modify_timestamp = .now,
1866 });
1851}1867}
lib/std/Io/File.zig+31-9
...@@ -53,7 +53,12 @@ pub const Stat = struct {...@@ -53,7 +53,12 @@ pub const Stat = struct {
53 permissions: Permissions,53 permissions: Permissions,
54 kind: Kind,54 kind: Kind,
55 /// Last access time in nanoseconds, relative to UTC 1970-01-01.55 /// Last access time in nanoseconds, relative to UTC 1970-01-01.
56 atime: Io.Timestamp,56 ///
57 /// Filesystems generally find this value problematic to keep updated since
58 /// it turns read-only file system accesses into file system mutations.
59 /// Some systems report stale values, and some systems explicitly refuse to
60 /// report this value. The latter case is handled by `null`.
61 atime: ?Io.Timestamp,
57 /// Last modification time in nanoseconds, relative to UTC 1970-01-01.62 /// Last modification time in nanoseconds, relative to UTC 1970-01-01.
58 mtime: Io.Timestamp,63 mtime: Io.Timestamp,
59 /// Last status/metadata change time in nanoseconds, relative to UTC 1970-01-01.64 /// Last status/metadata change time in nanoseconds, relative to UTC 1970-01-01.
...@@ -478,16 +483,30 @@ pub const SetTimestampsError = error{...@@ -478,16 +483,30 @@ pub const SetTimestampsError = error{
478 ReadOnlyFileSystem,483 ReadOnlyFileSystem,
479} || Io.Cancelable || Io.UnexpectedError;484} || Io.Cancelable || Io.UnexpectedError;
480485
486pub const SetTimestampsOptions = struct {
487 access_timestamp: SetTimestamp = .unchanged,
488 modify_timestamp: SetTimestamp = .unchanged,
489};
490
491pub const SetTimestamp = union(enum) {
492 /// Leave the existing timestamp unmodified.
493 unchanged,
494 /// Set to current time using `Io.Clock.real`.
495 now,
496 /// Set to provided timestamp using `Io.Clock.real`.
497 new: Io.Timestamp,
498
499 /// Convenience for interacting with `Stat`, in which `null` indicates `unchanged`.
500 pub fn init(optional: ?Io.Timestamp) SetTimestamp {
501 return if (optional) |t| .{ .new = t } else .unchanged;
502 }
503};
504
481/// The granularity that ultimately is stored depends on the combination of505/// The granularity that ultimately is stored depends on the combination of
482/// operating system and file system. When a value as provided that exceeds506/// operating system and file system. When a value as provided that exceeds
483/// this range, the value is clamped to the maximum.507/// this range, the value is clamped to the maximum.
484pub fn setTimestamps(508pub fn setTimestamps(file: File, io: Io, options: SetTimestampsOptions) SetTimestampsError!void {
485 file: File,509 return io.vtable.fileSetTimestamps(io.userdata, file, options);
486 io: Io,
487 last_accessed: Io.Timestamp,
488 last_modified: Io.Timestamp,
489) SetTimestampsError!void {
490 return io.vtable.fileSetTimestamps(io.userdata, file, last_accessed, last_modified);
491}510}
492511
493/// Sets the accessed and modification timestamps of `file` to the current wall512/// Sets the accessed and modification timestamps of `file` to the current wall
...@@ -496,7 +515,10 @@ pub fn setTimestamps(...@@ -496,7 +515,10 @@ pub fn setTimestamps(
496/// The granularity that ultimately is stored depends on the combination of515/// The granularity that ultimately is stored depends on the combination of
497/// operating system and file system.516/// operating system and file system.
498pub fn setTimestampsNow(file: File, io: Io) SetTimestampsError!void {517pub fn setTimestampsNow(file: File, io: Io) SetTimestampsError!void {
499 return io.vtable.fileSetTimestampsNow(io.userdata, file);518 return io.vtable.fileSetTimestamps(io.userdata, file, .{
519 .access_timestamp = .now,
520 .modify_timestamp = .now,
521 });
500}522}
501523
502/// Returns 0 on stream end or if `buffer` has no space available for data.524/// Returns 0 on stream end or if `buffer` has no space available for data.
lib/std/Io/Threaded.zig+145-195
...@@ -235,6 +235,24 @@ const Thread = struct {...@@ -235,6 +235,24 @@ const Thread = struct {
235 ) orelse return;235 ) orelse return;
236 }236 }
237237
238 fn endSyscallErrnoBug(thread: *Thread, err: posix.E) Io.UnexpectedError {
239 @branchHint(.cold);
240 thread.endSyscall();
241 return errnoBug(err);
242 }
243
244 fn endSyscallUnexpectedErrno(thread: *Thread, err: posix.E) Io.UnexpectedError {
245 @branchHint(.cold);
246 thread.endSyscall();
247 return posix.unexpectedErrno(err);
248 }
249
250 /// inline to make error return traces slightly shallower.
251 inline fn endSyscallError(thread: *Thread, err: anytype) @TypeOf(err) {
252 thread.endSyscall();
253 return err;
254 }
255
238 fn currentSignalId() SignaleeId {256 fn currentSignalId() SignaleeId {
239 return if (std.Thread.use_pthreads) std.c.pthread_self() else std.Thread.getCurrentId();257 return if (std.Thread.use_pthreads) std.c.pthread_self() else std.Thread.getCurrentId();
240 }258 }
...@@ -811,7 +829,6 @@ pub fn io(t: *Threaded) Io {...@@ -811,7 +829,6 @@ pub fn io(t: *Threaded) Io {
811 .dirSetPermissions = dirSetPermissions,829 .dirSetPermissions = dirSetPermissions,
812 .dirSetFilePermissions = dirSetFilePermissions,830 .dirSetFilePermissions = dirSetFilePermissions,
813 .dirSetTimestamps = dirSetTimestamps,831 .dirSetTimestamps = dirSetTimestamps,
814 .dirSetTimestampsNow = dirSetTimestampsNow,
815 .dirHardLink = dirHardLink,832 .dirHardLink = dirHardLink,
816833
817 .fileStat = fileStat,834 .fileStat = fileStat,
...@@ -833,7 +850,6 @@ pub fn io(t: *Threaded) Io {...@@ -833,7 +850,6 @@ pub fn io(t: *Threaded) Io {
833 .fileSetOwner = fileSetOwner,850 .fileSetOwner = fileSetOwner,
834 .fileSetPermissions = fileSetPermissions,851 .fileSetPermissions = fileSetPermissions,
835 .fileSetTimestamps = fileSetTimestamps,852 .fileSetTimestamps = fileSetTimestamps,
836 .fileSetTimestampsNow = fileSetTimestampsNow,
837 .fileLock = fileLock,853 .fileLock = fileLock,
838 .fileTryLock = fileTryLock,854 .fileTryLock = fileTryLock,
839 .fileUnlock = fileUnlock,855 .fileUnlock = fileUnlock,
...@@ -947,7 +963,6 @@ pub fn ioBasic(t: *Threaded) Io {...@@ -947,7 +963,6 @@ pub fn ioBasic(t: *Threaded) Io {
947 .dirSetPermissions = dirSetPermissions,963 .dirSetPermissions = dirSetPermissions,
948 .dirSetFilePermissions = dirSetFilePermissions,964 .dirSetFilePermissions = dirSetFilePermissions,
949 .dirSetTimestamps = dirSetTimestamps,965 .dirSetTimestamps = dirSetTimestamps,
950 .dirSetTimestampsNow = dirSetTimestampsNow,
951 .dirHardLink = dirHardLink,966 .dirHardLink = dirHardLink,
952967
953 .fileStat = fileStat,968 .fileStat = fileStat,
...@@ -969,7 +984,6 @@ pub fn ioBasic(t: *Threaded) Io {...@@ -969,7 +984,6 @@ pub fn ioBasic(t: *Threaded) Io {
969 .fileSetOwner = fileSetOwner,984 .fileSetOwner = fileSetOwner,
970 .fileSetPermissions = fileSetPermissions,985 .fileSetPermissions = fileSetPermissions,
971 .fileSetTimestamps = fileSetTimestamps,986 .fileSetTimestamps = fileSetTimestamps,
972 .fileSetTimestampsNow = fileSetTimestampsNow,
973 .fileLock = fileLock,987 .fileLock = fileLock,
974 .fileTryLock = fileTryLock,988 .fileTryLock = fileTryLock,
975 .fileUnlock = fileUnlock,989 .fileUnlock = fileUnlock,
...@@ -1920,7 +1934,7 @@ fn dirStatFileLinux(...@@ -1920,7 +1934,7 @@ fn dirStatFileLinux(
1920 try current_thread.beginSyscall();1934 try current_thread.beginSyscall();
1921 while (true) {1935 while (true) {
1922 var statx = std.mem.zeroes(linux.Statx);1936 var statx = std.mem.zeroes(linux.Statx);
1923 switch (sys.errno(sys.statx(dir.handle, sub_path_posix, flags, linux_statx_mask, &statx))) {1937 switch (sys.errno(sys.statx(dir.handle, sub_path_posix, flags, linux_statx_request, &statx))) {
1924 .SUCCESS => {1938 .SUCCESS => {
1925 current_thread.endSyscall();1939 current_thread.endSyscall();
1926 return statFromLinux(&statx);1940 return statFromLinux(&statx);
...@@ -2155,7 +2169,7 @@ fn fileStatLinux(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {...@@ -2155,7 +2169,7 @@ fn fileStatLinux(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {
2155 try current_thread.beginSyscall();2169 try current_thread.beginSyscall();
2156 while (true) {2170 while (true) {
2157 var statx = std.mem.zeroes(linux.Statx);2171 var statx = std.mem.zeroes(linux.Statx);
2158 switch (sys.errno(sys.statx(file.handle, "", linux.AT.EMPTY_PATH, linux_statx_mask, &statx))) {2172 switch (sys.errno(sys.statx(file.handle, "", linux.AT.EMPTY_PATH, linux_statx_request, &statx))) {
2159 .SUCCESS => {2173 .SUCCESS => {
2160 current_thread.endSyscall();2174 current_thread.endSyscall();
2161 return statFromLinux(&statx);2175 return statFromLinux(&statx);
...@@ -5977,8 +5991,6 @@ fn dirSetTimestamps(...@@ -5977,8 +5991,6 @@ fn dirSetTimestamps(
5977 userdata: ?*anyopaque,5991 userdata: ?*anyopaque,
5978 dir: Dir,5992 dir: Dir,
5979 sub_path: []const u8,5993 sub_path: []const u8,
5980 last_accessed: Io.Timestamp,
5981 last_modified: Io.Timestamp,
5982 options: Dir.SetTimestampsOptions,5994 options: Dir.SetTimestampsOptions,
5983) Dir.SetTimestampsError!void {5995) Dir.SetTimestampsError!void {
5984 const t: *Threaded = @ptrCast(@alignCast(userdata));5996 const t: *Threaded = @ptrCast(@alignCast(userdata));
...@@ -5992,9 +6004,13 @@ fn dirSetTimestamps(...@@ -5992,9 +6004,13 @@ fn dirSetTimestamps(
5992 @panic("TODO implement dirSetTimestamps wasi");6004 @panic("TODO implement dirSetTimestamps wasi");
5993 }6005 }
59946006
5995 const times: [2]posix.timespec = .{6007 var times_buffer: [2]posix.timespec = undefined;
5996 timestampToPosix(last_accessed.nanoseconds),6008 const times = if (options.modify_timestamp == .now and options.access_timestamp == .now) null else p: {
5997 timestampToPosix(last_modified.nanoseconds),6009 times_buffer = .{
6010 setTimestampToPosix(options.access_timestamp),
6011 setTimestampToPosix(options.modify_timestamp),
6012 };
6013 break :p &times_buffer;
5998 };6014 };
59996015
6000 const flags: u32 = if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0;6016 const flags: u32 = if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0;
...@@ -6003,80 +6019,26 @@ fn dirSetTimestamps(...@@ -6003,80 +6019,26 @@ fn dirSetTimestamps(
6003 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);6019 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
60046020
6005 try current_thread.beginSyscall();6021 try current_thread.beginSyscall();
6006 while (true) {6022 while (true) switch (posix.errno(posix.system.utimensat(dir.handle, sub_path_posix, times, flags))) {
6007 switch (posix.errno(posix.system.utimensat(dir.handle, sub_path_posix, &times, flags))) {6023 .SUCCESS => return current_thread.endSyscall(),
6008 .SUCCESS => return current_thread.endSyscall(),6024 .INTR => {
6009 .INTR => {6025 try current_thread.checkCancel();
6010 try current_thread.checkCancel();6026 continue;
6011 continue;6027 },
6012 },6028 .BADF => |err| return current_thread.endSyscallErrnoBug(err), // always a race condition
6013 else => |e| {6029 .FAULT => |err| return current_thread.endSyscallErrnoBug(err),
6014 current_thread.endSyscall();6030 .INVAL => |err| return current_thread.endSyscallErrnoBug(err),
6015 switch (e) {6031 .ACCES => return current_thread.endSyscallError(error.AccessDenied),
6016 .ACCES => return error.AccessDenied,6032 .PERM => return current_thread.endSyscallError(error.PermissionDenied),
6017 .PERM => return error.PermissionDenied,6033 .ROFS => return current_thread.endSyscallError(error.ReadOnlyFileSystem),
6018 .BADF => |err| return errnoBug(err), // always a race condition6034 else => |err| return current_thread.endSyscallUnexpectedErrno(err),
6019 .FAULT => |err| return errnoBug(err),6035 };
6020 .INVAL => |err| return errnoBug(err),
6021 .ROFS => return error.ReadOnlyFileSystem,
6022 else => |err| return posix.unexpectedErrno(err),
6023 }
6024 },
6025 }
6026 }
6027}
6028
6029fn dirSetTimestampsNow(
6030 userdata: ?*anyopaque,
6031 dir: Dir,
6032 sub_path: []const u8,
6033 options: Dir.SetTimestampsOptions,
6034) Dir.SetTimestampsError!void {
6035 const t: *Threaded = @ptrCast(@alignCast(userdata));
6036 const current_thread = Thread.getCurrent(t);
6037
6038 if (is_windows) {
6039 @panic("TODO implement dirSetTimestampsNow windows");
6040 }
6041
6042 if (native_os == .wasi and !builtin.link_libc) {
6043 @panic("TODO implement dirSetTimestampsNow wasi");
6044 }
6045
6046 const flags: u32 = if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0;
6047
6048 var path_buffer: [posix.PATH_MAX]u8 = undefined;
6049 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
6050
6051 try current_thread.beginSyscall();
6052 while (true) {
6053 switch (posix.errno(posix.system.utimensat(dir.handle, sub_path_posix, null, flags))) {
6054 .SUCCESS => return current_thread.endSyscall(),
6055 .INTR => {
6056 try current_thread.checkCancel();
6057 continue;
6058 },
6059 else => |e| {
6060 current_thread.endSyscall();
6061 switch (e) {
6062 .ACCES => return error.AccessDenied,
6063 .PERM => return error.PermissionDenied,
6064 .BADF => |err| return errnoBug(err), // always a race condition
6065 .FAULT => |err| return errnoBug(err),
6066 .INVAL => |err| return errnoBug(err),
6067 .ROFS => return error.ReadOnlyFileSystem,
6068 else => |err| return posix.unexpectedErrno(err),
6069 }
6070 },
6071 }
6072 }
6073}6036}
60746037
6075fn fileSetTimestamps(6038fn fileSetTimestamps(
6076 userdata: ?*anyopaque,6039 userdata: ?*anyopaque,
6077 file: File,6040 file: File,
6078 last_accessed: Io.Timestamp,6041 options: File.SetTimestampsOptions,
6079 last_modified: Io.Timestamp,
6080) File.SetTimestampsError!void {6042) File.SetTimestampsError!void {
6081 const t: *Threaded = @ptrCast(@alignCast(userdata));6043 const t: *Threaded = @ptrCast(@alignCast(userdata));
6082 const current_thread = Thread.getCurrent(t);6044 const current_thread = Thread.getCurrent(t);
...@@ -6084,11 +6046,34 @@ fn fileSetTimestamps(...@@ -6084,11 +6046,34 @@ fn fileSetTimestamps(
6084 if (is_windows) {6046 if (is_windows) {
6085 try current_thread.checkCancel();6047 try current_thread.checkCancel();
60866048
6087 const atime_ft = windows.nanoSecondsToFileTime(last_accessed);6049 var access_time_buffer: windows.FILETIME = undefined;
6088 const mtime_ft = windows.nanoSecondsToFileTime(last_modified);6050 var modify_time_buffer: windows.FILETIME = undefined;
6051 var system_time_buffer: windows.LARGE_INTEGER = undefined;
6052
6053 if (options.access_timestamp == .now or options.modify_timestamp == .now) {
6054 system_time_buffer = windows.ntdll.RtlGetSystemTimePrecise();
6055 }
6056
6057 const access_ptr = switch (options.access_timestamp) {
6058 .unchanged => null,
6059 .now => @panic("TODO do SystemTimeToFileTime logic here"),
6060 .new => |ts| p: {
6061 access_time_buffer = windows.nanoSecondsToFileTime(ts);
6062 break :p &access_time_buffer;
6063 },
6064 };
6065
6066 const modify_ptr = switch (options.modify_timestamp) {
6067 .unchanged => null,
6068 .now => @panic("TODO do SystemTimeToFileTime logic here"),
6069 .new => |ts| p: {
6070 modify_time_buffer = windows.nanoSecondsToFileTime(ts);
6071 break :p &modify_time_buffer;
6072 },
6073 };
60896074
6090 // https://github.com/ziglang/zig/issues/18406075 // https://github.com/ziglang/zig/issues/1840
6091 const rc = windows.kernel32.SetFileTime(file.handle, null, &atime_ft, &mtime_ft);6076 const rc = windows.kernel32.SetFileTime(file.handle, null, access_ptr, modify_ptr);
6092 if (rc == 0) {6077 if (rc == 0) {
6093 switch (windows.GetLastError()) {6078 switch (windows.GetLastError()) {
6094 else => |err| return windows.unexpectedError(err),6079 else => |err| return windows.unexpectedError(err),
...@@ -6097,123 +6082,70 @@ fn fileSetTimestamps(...@@ -6097,123 +6082,70 @@ fn fileSetTimestamps(
6097 return;6082 return;
6098 }6083 }
60996084
6100 const times: [2]posix.timespec = .{
6101 timestampToPosix(last_accessed.nanoseconds),
6102 timestampToPosix(last_modified.nanoseconds),
6103 };
6104
6105 if (native_os == .wasi and !builtin.link_libc) {6085 if (native_os == .wasi and !builtin.link_libc) {
6106 const atim = times[0].toTimestamp();6086 var atime: std.os.wasi.timestamp_t = 0;
6107 const mtim = times[1].toTimestamp();6087 var mtime: std.os.wasi.timestamp_t = 0;
6108 try current_thread.beginSyscall();6088 var flags: std.os.wasi.fstflags_t = .{};
6109 while (true) {
6110 switch (std.os.wasi.fd_filestat_set_times(file.handle, atim, mtim, .{
6111 .ATIM = true,
6112 .MTIM = true,
6113 })) {
6114 .SUCCESS => return current_thread.endSyscall(),
6115 .INTR => {
6116 try current_thread.checkCancel();
6117 continue;
6118 },
6119 else => |e| {
6120 current_thread.endSyscall();
6121 switch (e) {
6122 .ACCES => return error.AccessDenied,
6123 .PERM => return error.PermissionDenied,
6124 .BADF => |err| return errnoBug(err), // File descriptor use-after-free.
6125 .FAULT => |err| return errnoBug(err),
6126 .INVAL => |err| return errnoBug(err),
6127 .ROFS => return error.ReadOnlyFileSystem,
6128 else => |err| return posix.unexpectedErrno(err),
6129 }
6130 },
6131 }
6132 }
6133 }
61346089
6135 try current_thread.beginSyscall();6090 switch (options.access_timestamp) {
6136 while (true) {6091 .unchanged => {},
6137 switch (posix.errno(posix.system.futimens(file.handle, &times))) {6092 .now => flags.ATIM_NOW = true,
6138 .SUCCESS => return current_thread.endSyscall(),6093 .new => |ts| {
6139 .INTR => {6094 atime = timestampToPosix(ts.nanoseconds).toTimestamp();
6140 try current_thread.checkCancel();6095 flags.ATIM = true;
6141 continue;
6142 },
6143 else => |e| {
6144 current_thread.endSyscall();
6145 switch (e) {
6146 .ACCES => return error.AccessDenied,
6147 .PERM => return error.PermissionDenied,
6148 .BADF => |err| return errnoBug(err), // always a race condition
6149 .FAULT => |err| return errnoBug(err),
6150 .INVAL => |err| return errnoBug(err),
6151 .ROFS => return error.ReadOnlyFileSystem,
6152 else => |err| return posix.unexpectedErrno(err),
6153 }
6154 },6096 },
6155 }6097 }
6156 }
6157}
6158
6159fn fileSetTimestampsNow(userdata: ?*anyopaque, file: File) File.SetTimestampsError!void {
6160 const t: *Threaded = @ptrCast(@alignCast(userdata));
6161 const current_thread = Thread.getCurrent(t);
6162
6163 if (is_windows) {
6164 @panic("TODO implement fileSetTimestampsNow windows");
6165 }
61666098
6167 if (native_os == .wasi and !builtin.link_libc) {6099 switch (options.modify_timestamp) {
6168 try current_thread.beginSyscall();6100 .unchanged => {},
6169 while (true) {6101 .now => flags.MTIM_NOW = true,
6170 switch (std.os.wasi.fd_filestat_set_times(file.handle, 0, 0, .{6102 .new => |ts| {
6171 .ATIM_NOW = true,6103 mtime = timestampToPosix(ts.nanoseconds).toTimestamp();
6172 .MTIM_NOW = true,6104 flags.MTIM = true;
6173 })) {6105 },
6174 .SUCCESS => return current_thread.endSyscall(),
6175 .INTR => {
6176 try current_thread.checkCancel();
6177 continue;
6178 },
6179 else => |e| {
6180 current_thread.endSyscall();
6181 switch (e) {
6182 .ACCES => return error.AccessDenied,
6183 .PERM => return error.PermissionDenied,
6184 .BADF => |err| return errnoBug(err), // always a race condition
6185 .FAULT => |err| return errnoBug(err),
6186 .INVAL => |err| return errnoBug(err),
6187 .ROFS => return error.ReadOnlyFileSystem,
6188 else => |err| return posix.unexpectedErrno(err),
6189 }
6190 },
6191 }
6192 }6106 }
6193 }
61946107
6195 try current_thread.beginSyscall();6108 try current_thread.beginSyscall();
6196 while (true) {6109 while (true) switch (std.os.wasi.fd_filestat_set_times(file.handle, atime, mtime, flags)) {
6197 switch (posix.errno(posix.system.futimens(file.handle, null))) {
6198 .SUCCESS => return current_thread.endSyscall(),6110 .SUCCESS => return current_thread.endSyscall(),
6199 .INTR => {6111 .INTR => {
6200 try current_thread.checkCancel();6112 try current_thread.checkCancel();
6201 continue;6113 continue;
6202 },6114 },
6203 else => |e| {6115 .BADF => |err| return current_thread.endSyscallErrnoBug(err), // File descriptor use-after-free.
6204 current_thread.endSyscall();6116 .FAULT => |err| return current_thread.endSyscallErrnoBug(err),
6205 switch (e) {6117 .INVAL => |err| return current_thread.endSyscallErrnoBug(err),
6206 .ACCES => return error.AccessDenied,6118 .ACCES => return current_thread.endSyscallError(error.AccessDenied),
6207 .PERM => return error.PermissionDenied,6119 .PERM => return current_thread.endSyscallError(error.PermissionDenied),
6208 .BADF => |err| return errnoBug(err), // always a race condition6120 .ROFS => return current_thread.endSyscallError(error.ReadOnlyFileSystem),
6209 .FAULT => |err| return errnoBug(err),6121 else => |err| return current_thread.endSyscallUnexpectedErrno(err),
6210 .INVAL => |err| return errnoBug(err),6122 };
6211 .ROFS => return error.ReadOnlyFileSystem,
6212 else => |err| return posix.unexpectedErrno(err),
6213 }
6214 },
6215 }
6216 }6123 }
6124
6125 var times_buffer: [2]posix.timespec = undefined;
6126 const times = if (options.modify_timestamp == .now and options.access_timestamp == .now) null else p: {
6127 times_buffer = .{
6128 setTimestampToPosix(options.access_timestamp),
6129 setTimestampToPosix(options.modify_timestamp),
6130 };
6131 break :p &times_buffer;
6132 };
6133
6134 try current_thread.beginSyscall();
6135 while (true) switch (posix.errno(posix.system.futimens(file.handle, times))) {
6136 .SUCCESS => return current_thread.endSyscall(),
6137 .INTR => {
6138 try current_thread.checkCancel();
6139 continue;
6140 },
6141 .BADF => |err| return current_thread.endSyscallErrnoBug(err), // always a race condition
6142 .FAULT => |err| return current_thread.endSyscallErrnoBug(err),
6143 .INVAL => |err| return current_thread.endSyscallErrnoBug(err),
6144 .ACCES => return current_thread.endSyscallError(error.AccessDenied),
6145 .PERM => return current_thread.endSyscallError(error.PermissionDenied),
6146 .ROFS => return current_thread.endSyscallError(error.ReadOnlyFileSystem),
6147 else => |err| return current_thread.endSyscallUnexpectedErrno(err),
6148 };
6217}6149}
62186150
6219const windows_lock_range_off: windows.LARGE_INTEGER = 0;6151const windows_lock_range_off: windows.LARGE_INTEGER = 0;
...@@ -11169,7 +11101,7 @@ fn clockToWasi(clock: Io.Clock) std.os.wasi.clockid_t {...@@ -11169,7 +11101,7 @@ fn clockToWasi(clock: Io.Clock) std.os.wasi.clockid_t {
11169 };11101 };
11170}11102}
1117111103
11172const linux_statx_mask: std.os.linux.STATX = .{11104const linux_statx_request: std.os.linux.STATX = .{
11173 .TYPE = true,11105 .TYPE = true,
11174 .MODE = true,11106 .MODE = true,
11175 .ATIME = true,11107 .ATIME = true,
...@@ -11180,14 +11112,22 @@ const linux_statx_mask: std.os.linux.STATX = .{...@@ -11180,14 +11112,22 @@ const linux_statx_mask: std.os.linux.STATX = .{
11180 .NLINK = true,11112 .NLINK = true,
11181};11113};
1118211114
11115const linux_statx_check: std.os.linux.STATX = .{
11116 .TYPE = true,
11117 .MODE = true,
11118 .ATIME = false,
11119 .MTIME = true,
11120 .CTIME = true,
11121 .INO = true,
11122 .SIZE = true,
11123 .NLINK = true,
11124};
11125
11183fn statFromLinux(stx: *const std.os.linux.Statx) Io.UnexpectedError!File.Stat {11126fn statFromLinux(stx: *const std.os.linux.Statx) Io.UnexpectedError!File.Stat {
11184 const actual_mask_int: u32 = @bitCast(stx.mask);11127 const actual_mask_int: u32 = @bitCast(stx.mask);
11185 const wanted_mask_int: u32 = @bitCast(linux_statx_mask);11128 const wanted_mask_int: u32 = @bitCast(linux_statx_check);
11186 if ((actual_mask_int | wanted_mask_int) != actual_mask_int) return error.Unexpected;11129 if ((actual_mask_int | wanted_mask_int) != actual_mask_int) return error.Unexpected;
1118711130
11188 const atime = stx.atime;
11189 const mtime = stx.mtime;
11190 const ctime = stx.ctime;
11191 return .{11131 return .{
11192 .inode = stx.ino,11132 .inode = stx.ino,
11193 .nlink = stx.nlink,11133 .nlink = stx.nlink,
...@@ -11203,9 +11143,11 @@ fn statFromLinux(stx: *const std.os.linux.Statx) Io.UnexpectedError!File.Stat {...@@ -11203,9 +11143,11 @@ fn statFromLinux(stx: *const std.os.linux.Statx) Io.UnexpectedError!File.Stat {
11203 std.os.linux.S.IFSOCK => .unix_domain_socket,11143 std.os.linux.S.IFSOCK => .unix_domain_socket,
11204 else => .unknown,11144 else => .unknown,
11205 },11145 },
11206 .atime = .{ .nanoseconds = @intCast(@as(i128, atime.sec) * std.time.ns_per_s + atime.nsec) },11146 .atime = if (!stx.mask.ATIME) null else .{
11207 .mtime = .{ .nanoseconds = @intCast(@as(i128, mtime.sec) * std.time.ns_per_s + mtime.nsec) },11147 .nanoseconds = @intCast(@as(i128, stx.atime.sec) * std.time.ns_per_s + stx.atime.nsec),
11208 .ctime = .{ .nanoseconds = @intCast(@as(i128, ctime.sec) * std.time.ns_per_s + ctime.nsec) },11148 },
11149 .mtime = .{ .nanoseconds = @intCast(@as(i128, stx.mtime.sec) * std.time.ns_per_s + stx.mtime.nsec) },
11150 .ctime = .{ .nanoseconds = @intCast(@as(i128, stx.ctime.sec) * std.time.ns_per_s + stx.ctime.nsec) },
11209 };11151 };
11210}11152}
1121111153
...@@ -11283,6 +11225,14 @@ fn timestampToPosix(nanoseconds: i96) posix.timespec {...@@ -11283,6 +11225,14 @@ fn timestampToPosix(nanoseconds: i96) posix.timespec {
11283 };11225 };
11284}11226}
1128511227
11228fn setTimestampToPosix(set_ts: File.SetTimestamp) posix.timespec {
11229 return switch (set_ts) {
11230 .unchanged => .OMIT,
11231 .now => .NOW,
11232 .new => |t| timestampToPosix(t.nanoseconds),
11233 };
11234}
11235
11286fn pathToPosix(file_path: []const u8, buffer: *[posix.PATH_MAX]u8) Dir.PathNameError![:0]u8 {11236fn pathToPosix(file_path: []const u8, buffer: *[posix.PATH_MAX]u8) Dir.PathNameError![:0]u8 {
11287 if (std.mem.containsAtLeastScalar2(u8, file_path, 0, 1)) return error.BadPathName;11237 if (std.mem.containsAtLeastScalar2(u8, file_path, 0, 1)) return error.BadPathName;
11288 // >= rather than > to make room for the null byte11238 // >= rather than > to make room for the null byte
lib/std/Io/test.zig+6-6
...@@ -174,14 +174,14 @@ test "setTimestamps" {...@@ -174,14 +174,14 @@ test "setTimestamps" {
174 defer file.close(io);174 defer file.close(io);
175175
176 const stat_old = try file.stat(io);176 const stat_old = try file.stat(io);
177
177 // Set atime and mtime to 5s before178 // Set atime and mtime to 5s before
178 try file.setTimestamps(179 try file.setTimestamps(io, .{
179 io,180 .access_timestamp = if (stat_old.atime) |atime| .{ .new = atime.subDuration(.fromSeconds(5)) } else .unchanged,
180 stat_old.atime.subDuration(.fromSeconds(5)),181 .modify_timestamp = .{ .new = stat_old.mtime.subDuration(.fromSeconds(5)) },
181 stat_old.mtime.subDuration(.fromSeconds(5)),182 });
182 );
183 const stat_new = try file.stat(io);183 const stat_new = try file.stat(io);
184 try expect(stat_new.atime.nanoseconds < stat_old.atime.nanoseconds);184 if (stat_old.atime) |old_atime| try expect(stat_new.atime.?.nanoseconds < old_atime.nanoseconds);
185 try expect(stat_new.mtime.nanoseconds < stat_old.mtime.nanoseconds);185 try expect(stat_new.mtime.nanoseconds < stat_old.mtime.nanoseconds);
186}186}
187187
lib/std/c.zig+36
...@@ -114,6 +114,18 @@ pub const timespec = switch (native_os) {...@@ -114,6 +114,18 @@ pub const timespec = switch (native_os) {
114 return @as(wasi.timestamp_t, @intCast(ts.sec * 1_000_000_000)) +114 return @as(wasi.timestamp_t, @intCast(ts.sec * 1_000_000_000)) +
115 @as(wasi.timestamp_t, @intCast(ts.nsec));115 @as(wasi.timestamp_t, @intCast(ts.nsec));
116 }116 }
117
118 /// For use with `utimensat` and `futimens`.
119 pub const NOW: timespec = .{
120 .sec = 0,
121 .nsec = 0x3fffffff,
122 };
123
124 /// For use with `utimensat` and `futimens`.
125 pub const OMIT: timespec = .{
126 .sec = 0,
127 .nsec = 0x3ffffffe,
128 };
117 },129 },
118 // https://github.com/SerenityOS/serenity/blob/0a78056453578c18e0a04a0b45ebfb1c96d59005/Kernel/API/POSIX/time.h#L17-L20130 // https://github.com/SerenityOS/serenity/blob/0a78056453578c18e0a04a0b45ebfb1c96d59005/Kernel/API/POSIX/time.h#L17-L20
119 .windows, .serenity => extern struct {131 .windows, .serenity => extern struct {
...@@ -123,10 +135,34 @@ pub const timespec = switch (native_os) {...@@ -123,10 +135,34 @@ pub const timespec = switch (native_os) {
123 .dragonfly, .freebsd, .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => extern struct {135 .dragonfly, .freebsd, .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => extern struct {
124 sec: isize,136 sec: isize,
125 nsec: isize,137 nsec: isize,
138
139 /// For use with `utimensat` and `futimens`.
140 pub const NOW: timespec = .{
141 .sec = 0,
142 .nsec = -1,
143 };
144
145 /// For use with `utimensat` and `futimens`.
146 pub const OMIT: timespec = .{
147 .sec = 0,
148 .nsec = -2,
149 };
126 },150 },
127 .netbsd, .illumos => extern struct {151 .netbsd, .illumos => extern struct {
128 sec: i64,152 sec: i64,
129 nsec: isize,153 nsec: isize,
154
155 /// For use with `utimensat` and `futimens`.
156 pub const NOW: timespec = .{
157 .sec = 0,
158 .nsec = 0x3fffffff,
159 };
160
161 /// For use with `utimensat` and `futimens`.
162 pub const OMIT: timespec = .{
163 .sec = 0,
164 .nsec = 0x3ffffffe,
165 };
130 },166 },
131 .openbsd, .haiku => extern struct {167 .openbsd, .haiku => extern struct {
132 sec: time_t,168 sec: time_t,
lib/std/os/linux.zig+24
...@@ -8417,12 +8417,36 @@ pub const timezone = extern struct {...@@ -8417,12 +8417,36 @@ pub const timezone = extern struct {
8417pub const kernel_timespec = extern struct {8417pub const kernel_timespec = extern struct {
8418 sec: i64,8418 sec: i64,
8419 nsec: i64,8419 nsec: i64,
8420
8421 /// For use with `utimensat` and `futimens`.
8422 pub const NOW: timespec = .{
8423 .sec = 0,
8424 .nsec = 0x3fffffff,
8425 };
8426
8427 /// For use with `utimensat` and `futimens`.
8428 pub const OMIT: timespec = .{
8429 .sec = 0,
8430 .nsec = 0x3ffffffe,
8431 };
8420};8432};
84218433
8422// https://github.com/ziglang/zig/issues/4726#issuecomment-21903378778434// https://github.com/ziglang/zig/issues/4726#issuecomment-2190337877
8423pub const timespec = if (native_arch == .hexagon or native_arch == .riscv32) kernel_timespec else extern struct {8435pub const timespec = if (native_arch == .hexagon or native_arch == .riscv32) kernel_timespec else extern struct {
8424 sec: isize,8436 sec: isize,
8425 nsec: isize,8437 nsec: isize,
8438
8439 /// For use with `utimensat` and `futimens`.
8440 pub const NOW: timespec = .{
8441 .sec = 0,
8442 .nsec = 0x3fffffff,
8443 };
8444
8445 /// For use with `utimensat` and `futimens`.
8446 pub const OMIT: timespec = .{
8447 .sec = 0,
8448 .nsec = 0x3ffffffe,
8449 };
8426};8450};
84278451
8428pub const XDP = struct {8452pub const XDP = struct {