authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-12-27 10:40:24-08:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-12-27 10:40:24-08:00
loga7c9d11b289b2140430f2666968bddb02a6f1a1f
treeb5b682039c0704b9beaf3f17737ab3d6962afb1a
parente55e6b5528bb2f01de242fcf32b172e244e98e74

std.Io: make file access time optional

Some filesystems, such as ZFS, do not report atime. It's pretty useless in general, so make it an optional field in File.Stat. Also take the opportunity to make setting timestamps API more flexible and match the APIs widely available, which have UTIME_OMIT and UTIME_NOW constants that can be independently set for both fields. This is needed to handle smoothly the case when atime is null.

6 files changed, 159 insertions(+), 211 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+86-186
...@@ -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,
...@@ -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,8 +6046,8 @@ fn fileSetTimestamps(...@@ -6084,8 +6046,8 @@ 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 const atime_ft = windows.nanoSecondsToFileTime(options.access_time);
6088 const mtime_ft = windows.nanoSecondsToFileTime(last_modified);6050 const mtime_ft = windows.nanoSecondsToFileTime(options.modify_time);
60896051
6090 // https://github.com/ziglang/zig/issues/18406052 // https://github.com/ziglang/zig/issues/1840
6091 const rc = windows.kernel32.SetFileTime(file.handle, null, &atime_ft, &mtime_ft);6053 const rc = windows.kernel32.SetFileTime(file.handle, null, &atime_ft, &mtime_ft);
...@@ -6097,123 +6059,53 @@ fn fileSetTimestamps(...@@ -6097,123 +6059,53 @@ fn fileSetTimestamps(
6097 return;6059 return;
6098 }6060 }
60996061
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) {6062 if (native_os == .wasi and !builtin.link_libc) {
6106 const atim = times[0].toTimestamp();6063 const atim = timestampToPosix(options.access_time.nanoseconds).toTimestamp();
6107 const mtim = times[1].toTimestamp();6064 const mtim = timestampToPosix(options.modify_time.nanoseconds).toTimestamp();
6108 try current_thread.beginSyscall();6065 try current_thread.beginSyscall();
6109 while (true) {6066 while (true) switch (std.os.wasi.fd_filestat_set_times(file.handle, atim, mtim, .{
6110 switch (std.os.wasi.fd_filestat_set_times(file.handle, atim, mtim, .{6067 .ATIM = true,
6111 .ATIM = true,6068 .MTIM = true,
6112 .MTIM = true,6069 })) {
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 }
6134
6135 try current_thread.beginSyscall();
6136 while (true) {
6137 switch (posix.errno(posix.system.futimens(file.handle, &times))) {
6138 .SUCCESS => return current_thread.endSyscall(),6070 .SUCCESS => return current_thread.endSyscall(),
6139 .INTR => {6071 .INTR => {
6140 try current_thread.checkCancel();6072 try current_thread.checkCancel();
6141 continue;6073 continue;
6142 },6074 },
6143 else => |e| {6075 .BADF => |err| return current_thread.endSyscallErrnoBug(err), // File descriptor use-after-free.
6144 current_thread.endSyscall();6076 .FAULT => |err| return current_thread.endSyscallErrnoBug(err),
6145 switch (e) {6077 .INVAL => |err| return current_thread.endSyscallErrnoBug(err),
6146 .ACCES => return error.AccessDenied,6078 .ACCES => return current_thread.endSyscallErrnoBug(error.AccessDenied),
6147 .PERM => return error.PermissionDenied,6079 .PERM => return current_thread.endSyscallErrnoBug(error.PermissionDenied),
6148 .BADF => |err| return errnoBug(err), // always a race condition6080 .ROFS => return current_thread.endSyscallErrnoBug(error.ReadOnlyFileSystem),
6149 .FAULT => |err| return errnoBug(err),6081 else => |err| return current_thread.endSyscallUnexpectedErrno(err),
6150 .INVAL => |err| return errnoBug(err),6082 };
6151 .ROFS => return error.ReadOnlyFileSystem,
6152 else => |err| return posix.unexpectedErrno(err),
6153 }
6154 },
6155 }
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 }6083 }
61666084
6167 if (native_os == .wasi and !builtin.link_libc) {6085 var times_buffer: [2]posix.timespec = undefined;
6168 try current_thread.beginSyscall();6086 const times = if (options.modify_timestamp == .now and options.access_timestamp == .now) null else p: {
6169 while (true) {6087 times_buffer = .{
6170 switch (std.os.wasi.fd_filestat_set_times(file.handle, 0, 0, .{6088 setTimestampToPosix(options.access_timestamp),
6171 .ATIM_NOW = true,6089 setTimestampToPosix(options.modify_timestamp),
6172 .MTIM_NOW = true,6090 };
6173 })) {6091 break :p &times_buffer;
6174 .SUCCESS => return current_thread.endSyscall(),6092 };
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 }
6193 }
61946093
6195 try current_thread.beginSyscall();6094 try current_thread.beginSyscall();
6196 while (true) {6095 while (true) switch (posix.errno(posix.system.futimens(file.handle, times))) {
6197 switch (posix.errno(posix.system.futimens(file.handle, null))) {6096 .SUCCESS => return current_thread.endSyscall(),
6198 .SUCCESS => return current_thread.endSyscall(),6097 .INTR => {
6199 .INTR => {6098 try current_thread.checkCancel();
6200 try current_thread.checkCancel();6099 continue;
6201 continue;6100 },
6202 },6101 .BADF => |err| return current_thread.endSyscallErrnoBug(err), // always a race condition
6203 else => |e| {6102 .FAULT => |err| return current_thread.endSyscallErrnoBug(err),
6204 current_thread.endSyscall();6103 .INVAL => |err| return current_thread.endSyscallErrnoBug(err),
6205 switch (e) {6104 .ACCES => return current_thread.endSyscallError(error.AccessDenied),
6206 .ACCES => return error.AccessDenied,6105 .PERM => return current_thread.endSyscallError(error.PermissionDenied),
6207 .PERM => return error.PermissionDenied,6106 .ROFS => return current_thread.endSyscallError(error.ReadOnlyFileSystem),
6208 .BADF => |err| return errnoBug(err), // always a race condition6107 else => |err| return current_thread.endSyscallUnexpectedErrno(err),
6209 .FAULT => |err| return errnoBug(err),6108 };
6210 .INVAL => |err| return errnoBug(err),
6211 .ROFS => return error.ReadOnlyFileSystem,
6212 else => |err| return posix.unexpectedErrno(err),
6213 }
6214 },
6215 }
6216 }
6217}6109}
62186110
6219const windows_lock_range_off: windows.LARGE_INTEGER = 0;6111const windows_lock_range_off: windows.LARGE_INTEGER = 0;
...@@ -11283,6 +11175,14 @@ fn timestampToPosix(nanoseconds: i96) posix.timespec {...@@ -11283,6 +11175,14 @@ fn timestampToPosix(nanoseconds: i96) posix.timespec {
11283 };11175 };
11284}11176}
1128511177
11178fn setTimestampToPosix(set_ts: File.SetTimestamp) posix.timespec {
11179 return switch (set_ts) {
11180 .unchanged => .OMIT,
11181 .now => .NOW,
11182 .new => |t| timestampToPosix(t.nanoseconds),
11183 };
11184}
11185
11286fn pathToPosix(file_path: []const u8, buffer: *[posix.PATH_MAX]u8) Dir.PathNameError![:0]u8 {11186fn 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;11187 if (std.mem.containsAtLeastScalar2(u8, file_path, 0, 1)) return error.BadPathName;
11288 // >= rather than > to make room for the null byte11188 // >= 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/os/linux.zig+12
...@@ -8423,6 +8423,18 @@ pub const kernel_timespec = extern struct {...@@ -8423,6 +8423,18 @@ pub const kernel_timespec = extern struct {
8423pub const timespec = if (native_arch == .hexagon or native_arch == .riscv32) kernel_timespec else extern struct {8423pub const timespec = if (native_arch == .hexagon or native_arch == .riscv32) kernel_timespec else extern struct {
8424 sec: isize,8424 sec: isize,
8425 nsec: isize,8425 nsec: isize,
8426
8427 /// For use with `utimensat` and `futimens`.
8428 pub const NOW: timespec = .{
8429 .sec = 0,
8430 .nsec = 0x3fffffff,
8431 };
8432
8433 /// For use with `utimensat` and `futimens`.
8434 pub const OMIT: timespec = .{
8435 .sec = 0,
8436 .nsec = 0x3ffffffe,
8437 };
8426};8438};
84278439
8428pub const XDP = struct {8440pub const XDP = struct {