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 {
681681 dirSetFileOwner: *const fn (?*anyopaque, Dir, []const u8, ?File.Uid, ?File.Gid, Dir.SetFileOwnerOptions) Dir.SetFileOwnerError!void,
682682 dirSetPermissions: *const fn (?*anyopaque, Dir, Dir.Permissions) Dir.SetPermissionsError!void,
683683 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,
685 dirSetTimestampsNow: *const fn (?*anyopaque, Dir, []const u8, Dir.SetTimestampsOptions) Dir.SetTimestampsError!void,
684 dirSetTimestamps: *const fn (?*anyopaque, Dir, []const u8, Dir.SetTimestampsOptions) Dir.SetTimestampsError!void,
686685 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
688687 fileStat: *const fn (?*anyopaque, File) File.StatError!File.Stat,
......@@ -705,8 +704,7 @@ pub const VTable = struct {
705704 fileSetLength: *const fn (?*anyopaque, File, u64) File.SetLengthError!void,
706705 fileSetOwner: *const fn (?*anyopaque, File, ?File.Uid, ?File.Gid) File.SetOwnerError!void,
707706 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,
709 fileSetTimestampsNow: *const fn (?*anyopaque, File) File.SetTimestampsError!void,
707 fileSetTimestamps: *const fn (?*anyopaque, File, File.SetTimestampsOptions) File.SetTimestampsError!void,
710708 fileLock: *const fn (?*anyopaque, File, File.Lock) File.LockError!void,
711709 fileTryLock: *const fn (?*anyopaque, File, File.Lock) File.LockError!bool,
712710 fileUnlock: *const fn (?*anyopaque, File) void,
lib/std/Io/Dir.zig+22-6
......@@ -615,7 +615,10 @@ pub fn updateFile(
615615 error.WriteFailed => return atomic_file.file_writer.err.?,
616616 };
617617 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 });
619622 try atomic_file.renameIntoPlace();
620623 return .stale;
621624}
......@@ -1825,6 +1828,8 @@ pub const SetTimestampsError = File.SetTimestampsError || PathNameError;
18251828
18261829pub const SetTimestampsOptions = struct {
18271830 follow_symlinks: bool = true,
1831 access_timestamp: File.SetTimestamp = .unchanged,
1832 modify_timestamp: File.SetTimestamp = .unchanged,
18281833};
18291834
18301835/// The granularity that ultimately is stored depends on the combination of
......@@ -1834,18 +1839,29 @@ pub fn setTimestamps(
18341839 dir: Dir,
18351840 io: Io,
18361841 sub_path: []const u8,
1837 last_accessed: Io.Timestamp,
1838 last_modified: Io.Timestamp,
18391842 options: SetTimestampsOptions,
18401843) 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);
18421845}
18431846
1847pub const SetTimestampsNowOptions = struct {
1848 follow_symlinks: bool = true,
1849};
1850
18441851/// Sets the accessed and modification timestamps of the provided path to the
18451852/// current wall clock time.
18461853///
18471854/// The granularity that ultimately is stored depends on the combination of
18481855/// operating system and file system.
1849pub fn setTimestampsNow(dir: Dir, io: Io, sub_path: []const u8, options: SetTimestampsOptions) SetTimestampsError!void {
1850 return io.vtable.fileSetTimestampsNow(io.userdata, dir, sub_path, options);
1856pub fn setTimestampsNow(
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 });
18511867}
lib/std/Io/File.zig+31-9
......@@ -53,7 +53,12 @@ pub const Stat = struct {
5353 permissions: Permissions,
5454 kind: Kind,
5555 /// 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,
5762 /// Last modification time in nanoseconds, relative to UTC 1970-01-01.
5863 mtime: Io.Timestamp,
5964 /// Last status/metadata change time in nanoseconds, relative to UTC 1970-01-01.
......@@ -478,16 +483,30 @@ pub const SetTimestampsError = error{
478483 ReadOnlyFileSystem,
479484} || 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
481505/// The granularity that ultimately is stored depends on the combination of
482506/// operating system and file system. When a value as provided that exceeds
483507/// this range, the value is clamped to the maximum.
484pub fn setTimestamps(
485 file: File,
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);
508pub fn setTimestamps(file: File, io: Io, options: SetTimestampsOptions) SetTimestampsError!void {
509 return io.vtable.fileSetTimestamps(io.userdata, file, options);
491510}
492511
493512/// Sets the accessed and modification timestamps of `file` to the current wall
......@@ -496,7 +515,10 @@ pub fn setTimestamps(
496515/// The granularity that ultimately is stored depends on the combination of
497516/// operating system and file system.
498517pub 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 });
500522}
501523
502524/// 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 {
235235 ) orelse return;
236236 }
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
238256 fn currentSignalId() SignaleeId {
239257 return if (std.Thread.use_pthreads) std.c.pthread_self() else std.Thread.getCurrentId();
240258 }
......@@ -811,7 +829,6 @@ pub fn io(t: *Threaded) Io {
811829 .dirSetPermissions = dirSetPermissions,
812830 .dirSetFilePermissions = dirSetFilePermissions,
813831 .dirSetTimestamps = dirSetTimestamps,
814 .dirSetTimestampsNow = dirSetTimestampsNow,
815832 .dirHardLink = dirHardLink,
816833
817834 .fileStat = fileStat,
......@@ -833,7 +850,6 @@ pub fn io(t: *Threaded) Io {
833850 .fileSetOwner = fileSetOwner,
834851 .fileSetPermissions = fileSetPermissions,
835852 .fileSetTimestamps = fileSetTimestamps,
836 .fileSetTimestampsNow = fileSetTimestampsNow,
837853 .fileLock = fileLock,
838854 .fileTryLock = fileTryLock,
839855 .fileUnlock = fileUnlock,
......@@ -947,7 +963,6 @@ pub fn ioBasic(t: *Threaded) Io {
947963 .dirSetPermissions = dirSetPermissions,
948964 .dirSetFilePermissions = dirSetFilePermissions,
949965 .dirSetTimestamps = dirSetTimestamps,
950 .dirSetTimestampsNow = dirSetTimestampsNow,
951966 .dirHardLink = dirHardLink,
952967
953968 .fileStat = fileStat,
......@@ -969,7 +984,6 @@ pub fn ioBasic(t: *Threaded) Io {
969984 .fileSetOwner = fileSetOwner,
970985 .fileSetPermissions = fileSetPermissions,
971986 .fileSetTimestamps = fileSetTimestamps,
972 .fileSetTimestampsNow = fileSetTimestampsNow,
973987 .fileLock = fileLock,
974988 .fileTryLock = fileTryLock,
975989 .fileUnlock = fileUnlock,
......@@ -1920,7 +1934,7 @@ fn dirStatFileLinux(
19201934 try current_thread.beginSyscall();
19211935 while (true) {
19221936 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))) {
19241938 .SUCCESS => {
19251939 current_thread.endSyscall();
19261940 return statFromLinux(&statx);
......@@ -2155,7 +2169,7 @@ fn fileStatLinux(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {
21552169 try current_thread.beginSyscall();
21562170 while (true) {
21572171 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))) {
21592173 .SUCCESS => {
21602174 current_thread.endSyscall();
21612175 return statFromLinux(&statx);
......@@ -5977,8 +5991,6 @@ fn dirSetTimestamps(
59775991 userdata: ?*anyopaque,
59785992 dir: Dir,
59795993 sub_path: []const u8,
5980 last_accessed: Io.Timestamp,
5981 last_modified: Io.Timestamp,
59825994 options: Dir.SetTimestampsOptions,
59835995) Dir.SetTimestampsError!void {
59845996 const t: *Threaded = @ptrCast(@alignCast(userdata));
......@@ -5992,9 +6004,13 @@ fn dirSetTimestamps(
59926004 @panic("TODO implement dirSetTimestamps wasi");
59936005 }
59946006
5995 const times: [2]posix.timespec = .{
5996 timestampToPosix(last_accessed.nanoseconds),
5997 timestampToPosix(last_modified.nanoseconds),
6007 var times_buffer: [2]posix.timespec = undefined;
6008 const times = if (options.modify_timestamp == .now and options.access_timestamp == .now) null else p: {
6009 times_buffer = .{
6010 setTimestampToPosix(options.access_timestamp),
6011 setTimestampToPosix(options.modify_timestamp),
6012 };
6013 break :p &times_buffer;
59986014 };
59996015
60006016 const flags: u32 = if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0;
......@@ -6003,80 +6019,26 @@ fn dirSetTimestamps(
60036019 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
60046020
60056021 try current_thread.beginSyscall();
6006 while (true) {
6007 switch (posix.errno(posix.system.utimensat(dir.handle, sub_path_posix, &times, flags))) {
6008 .SUCCESS => return current_thread.endSyscall(),
6009 .INTR => {
6010 try current_thread.checkCancel();
6011 continue;
6012 },
6013 else => |e| {
6014 current_thread.endSyscall();
6015 switch (e) {
6016 .ACCES => return error.AccessDenied,
6017 .PERM => return error.PermissionDenied,
6018 .BADF => |err| return errnoBug(err), // always a race condition
6019 .FAULT => |err| return errnoBug(err),
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 }
6022 while (true) switch (posix.errno(posix.system.utimensat(dir.handle, sub_path_posix, times, flags))) {
6023 .SUCCESS => return current_thread.endSyscall(),
6024 .INTR => {
6025 try current_thread.checkCancel();
6026 continue;
6027 },
6028 .BADF => |err| return current_thread.endSyscallErrnoBug(err), // always a race condition
6029 .FAULT => |err| return current_thread.endSyscallErrnoBug(err),
6030 .INVAL => |err| return current_thread.endSyscallErrnoBug(err),
6031 .ACCES => return current_thread.endSyscallError(error.AccessDenied),
6032 .PERM => return current_thread.endSyscallError(error.PermissionDenied),
6033 .ROFS => return current_thread.endSyscallError(error.ReadOnlyFileSystem),
6034 else => |err| return current_thread.endSyscallUnexpectedErrno(err),
6035 };
60736036}
60746037
60756038fn fileSetTimestamps(
60766039 userdata: ?*anyopaque,
60776040 file: File,
6078 last_accessed: Io.Timestamp,
6079 last_modified: Io.Timestamp,
6041 options: File.SetTimestampsOptions,
60806042) File.SetTimestampsError!void {
60816043 const t: *Threaded = @ptrCast(@alignCast(userdata));
60826044 const current_thread = Thread.getCurrent(t);
......@@ -6084,11 +6046,34 @@ fn fileSetTimestamps(
60846046 if (is_windows) {
60856047 try current_thread.checkCancel();
60866048
6087 const atime_ft = windows.nanoSecondsToFileTime(last_accessed);
6088 const mtime_ft = windows.nanoSecondsToFileTime(last_modified);
6049 var access_time_buffer: windows.FILETIME = undefined;
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
60906075 // 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);
60926077 if (rc == 0) {
60936078 switch (windows.GetLastError()) {
60946079 else => |err| return windows.unexpectedError(err),
......@@ -6097,123 +6082,70 @@ fn fileSetTimestamps(
60976082 return;
60986083 }
60996084
6100 const times: [2]posix.timespec = .{
6101 timestampToPosix(last_accessed.nanoseconds),
6102 timestampToPosix(last_modified.nanoseconds),
6103 };
6104
61056085 if (native_os == .wasi and !builtin.link_libc) {
6106 const atim = times[0].toTimestamp();
6107 const mtim = times[1].toTimestamp();
6108 try current_thread.beginSyscall();
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 }
6086 var atime: std.os.wasi.timestamp_t = 0;
6087 var mtime: std.os.wasi.timestamp_t = 0;
6088 var flags: std.os.wasi.fstflags_t = .{};
61346089
6135 try current_thread.beginSyscall();
6136 while (true) {
6137 switch (posix.errno(posix.system.futimens(file.handle, &times))) {
6138 .SUCCESS => return current_thread.endSyscall(),
6139 .INTR => {
6140 try current_thread.checkCancel();
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 }
6090 switch (options.access_timestamp) {
6091 .unchanged => {},
6092 .now => flags.ATIM_NOW = true,
6093 .new => |ts| {
6094 atime = timestampToPosix(ts.nanoseconds).toTimestamp();
6095 flags.ATIM = true;
61546096 },
61556097 }
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) {
6168 try current_thread.beginSyscall();
6169 while (true) {
6170 switch (std.os.wasi.fd_filestat_set_times(file.handle, 0, 0, .{
6171 .ATIM_NOW = true,
6172 .MTIM_NOW = true,
6173 })) {
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 }
6099 switch (options.modify_timestamp) {
6100 .unchanged => {},
6101 .now => flags.MTIM_NOW = true,
6102 .new => |ts| {
6103 mtime = timestampToPosix(ts.nanoseconds).toTimestamp();
6104 flags.MTIM = true;
6105 },
61926106 }
6193 }
61946107
6195 try current_thread.beginSyscall();
6196 while (true) {
6197 switch (posix.errno(posix.system.futimens(file.handle, null))) {
6108 try current_thread.beginSyscall();
6109 while (true) switch (std.os.wasi.fd_filestat_set_times(file.handle, atime, mtime, flags)) {
61986110 .SUCCESS => return current_thread.endSyscall(),
61996111 .INTR => {
62006112 try current_thread.checkCancel();
62016113 continue;
62026114 },
6203 else => |e| {
6204 current_thread.endSyscall();
6205 switch (e) {
6206 .ACCES => return error.AccessDenied,
6207 .PERM => return error.PermissionDenied,
6208 .BADF => |err| return errnoBug(err), // always a race condition
6209 .FAULT => |err| return errnoBug(err),
6210 .INVAL => |err| return errnoBug(err),
6211 .ROFS => return error.ReadOnlyFileSystem,
6212 else => |err| return posix.unexpectedErrno(err),
6213 }
6214 },
6215 }
6115 .BADF => |err| return current_thread.endSyscallErrnoBug(err), // File descriptor use-after-free.
6116 .FAULT => |err| return current_thread.endSyscallErrnoBug(err),
6117 .INVAL => |err| return current_thread.endSyscallErrnoBug(err),
6118 .ACCES => return current_thread.endSyscallError(error.AccessDenied),
6119 .PERM => return current_thread.endSyscallError(error.PermissionDenied),
6120 .ROFS => return current_thread.endSyscallError(error.ReadOnlyFileSystem),
6121 else => |err| return current_thread.endSyscallUnexpectedErrno(err),
6122 };
62166123 }
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 };
62176149}
62186150
62196151const windows_lock_range_off: windows.LARGE_INTEGER = 0;
......@@ -11169,7 +11101,7 @@ fn clockToWasi(clock: Io.Clock) std.os.wasi.clockid_t {
1116911101 };
1117011102}
1117111103
11172const linux_statx_mask: std.os.linux.STATX = .{
11104const linux_statx_request: std.os.linux.STATX = .{
1117311105 .TYPE = true,
1117411106 .MODE = true,
1117511107 .ATIME = true,
......@@ -11180,14 +11112,22 @@ const linux_statx_mask: std.os.linux.STATX = .{
1118011112 .NLINK = true,
1118111113};
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
1118311126fn statFromLinux(stx: *const std.os.linux.Statx) Io.UnexpectedError!File.Stat {
1118411127 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);
1118611129 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;
1119111131 return .{
1119211132 .inode = stx.ino,
1119311133 .nlink = stx.nlink,
......@@ -11203,9 +11143,11 @@ fn statFromLinux(stx: *const std.os.linux.Statx) Io.UnexpectedError!File.Stat {
1120311143 std.os.linux.S.IFSOCK => .unix_domain_socket,
1120411144 else => .unknown,
1120511145 },
11206 .atime = .{ .nanoseconds = @intCast(@as(i128, atime.sec) * std.time.ns_per_s + atime.nsec) },
11207 .mtime = .{ .nanoseconds = @intCast(@as(i128, mtime.sec) * std.time.ns_per_s + mtime.nsec) },
11208 .ctime = .{ .nanoseconds = @intCast(@as(i128, ctime.sec) * std.time.ns_per_s + ctime.nsec) },
11146 .atime = if (!stx.mask.ATIME) null else .{
11147 .nanoseconds = @intCast(@as(i128, stx.atime.sec) * std.time.ns_per_s + stx.atime.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) },
1120911151 };
1121011152}
1121111153
......@@ -11283,6 +11225,14 @@ fn timestampToPosix(nanoseconds: i96) posix.timespec {
1128311225 };
1128411226}
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
1128611236fn pathToPosix(file_path: []const u8, buffer: *[posix.PATH_MAX]u8) Dir.PathNameError![:0]u8 {
1128711237 if (std.mem.containsAtLeastScalar2(u8, file_path, 0, 1)) return error.BadPathName;
1128811238 // >= rather than > to make room for the null byte
lib/std/Io/test.zig+6-6
......@@ -174,14 +174,14 @@ test "setTimestamps" {
174174 defer file.close(io);
175175
176176 const stat_old = try file.stat(io);
177
177178 // Set atime and mtime to 5s before
178 try file.setTimestamps(
179 io,
180 stat_old.atime.subDuration(.fromSeconds(5)),
181 stat_old.mtime.subDuration(.fromSeconds(5)),
182 );
179 try file.setTimestamps(io, .{
180 .access_timestamp = if (stat_old.atime) |atime| .{ .new = atime.subDuration(.fromSeconds(5)) } else .unchanged,
181 .modify_timestamp = .{ .new = stat_old.mtime.subDuration(.fromSeconds(5)) },
182 });
183183 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);
185185 try expect(stat_new.mtime.nanoseconds < stat_old.mtime.nanoseconds);
186186}
187187
lib/std/c.zig+36
......@@ -114,6 +114,18 @@ pub const timespec = switch (native_os) {
114114 return @as(wasi.timestamp_t, @intCast(ts.sec * 1_000_000_000)) +
115115 @as(wasi.timestamp_t, @intCast(ts.nsec));
116116 }
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 };
117129 },
118130 // https://github.com/SerenityOS/serenity/blob/0a78056453578c18e0a04a0b45ebfb1c96d59005/Kernel/API/POSIX/time.h#L17-L20
119131 .windows, .serenity => extern struct {
......@@ -123,10 +135,34 @@ pub const timespec = switch (native_os) {
123135 .dragonfly, .freebsd, .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => extern struct {
124136 sec: isize,
125137 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 };
126150 },
127151 .netbsd, .illumos => extern struct {
128152 sec: i64,
129153 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 };
130166 },
131167 .openbsd, .haiku => extern struct {
132168 sec: time_t,
lib/std/os/linux.zig+24
......@@ -8417,12 +8417,36 @@ pub const timezone = extern struct {
84178417pub const kernel_timespec = extern struct {
84188418 sec: i64,
84198419 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 };
84208432};
84218433
84228434// https://github.com/ziglang/zig/issues/4726#issuecomment-2190337877
84238435pub const timespec = if (native_arch == .hexagon or native_arch == .riscv32) kernel_timespec else extern struct {
84248436 sec: isize,
84258437 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 };
84268450};
84278451
84288452pub const XDP = struct {