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 {
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+86-186
......@@ -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,
......@@ -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,8 +6046,8 @@ 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 const atime_ft = windows.nanoSecondsToFileTime(options.access_time);
6050 const mtime_ft = windows.nanoSecondsToFileTime(options.modify_time);
60896051
60906052 // https://github.com/ziglang/zig/issues/1840
60916053 const rc = windows.kernel32.SetFileTime(file.handle, null, &atime_ft, &mtime_ft);
......@@ -6097,123 +6059,53 @@ fn fileSetTimestamps(
60976059 return;
60986060 }
60996061
6100 const times: [2]posix.timespec = .{
6101 timestampToPosix(last_accessed.nanoseconds),
6102 timestampToPosix(last_modified.nanoseconds),
6103 };
6104
61056062 if (native_os == .wasi and !builtin.link_libc) {
6106 const atim = times[0].toTimestamp();
6107 const mtim = times[1].toTimestamp();
6063 const atim = timestampToPosix(options.access_time.nanoseconds).toTimestamp();
6064 const mtim = timestampToPosix(options.modify_time.nanoseconds).toTimestamp();
61086065 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 }
6134
6135 try current_thread.beginSyscall();
6136 while (true) {
6137 switch (posix.errno(posix.system.futimens(file.handle, &times))) {
6066 while (true) switch (std.os.wasi.fd_filestat_set_times(file.handle, atim, mtim, .{
6067 .ATIM = true,
6068 .MTIM = true,
6069 })) {
61386070 .SUCCESS => return current_thread.endSyscall(),
61396071 .INTR => {
61406072 try current_thread.checkCancel();
61416073 continue;
61426074 },
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 },
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");
6075 .BADF => |err| return current_thread.endSyscallErrnoBug(err), // File descriptor use-after-free.
6076 .FAULT => |err| return current_thread.endSyscallErrnoBug(err),
6077 .INVAL => |err| return current_thread.endSyscallErrnoBug(err),
6078 .ACCES => return current_thread.endSyscallErrnoBug(error.AccessDenied),
6079 .PERM => return current_thread.endSyscallErrnoBug(error.PermissionDenied),
6080 .ROFS => return current_thread.endSyscallErrnoBug(error.ReadOnlyFileSystem),
6081 else => |err| return current_thread.endSyscallUnexpectedErrno(err),
6082 };
61656083 }
61666084
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 }
6192 }
6193 }
6085 var times_buffer: [2]posix.timespec = undefined;
6086 const times = if (options.modify_timestamp == .now and options.access_timestamp == .now) null else p: {
6087 times_buffer = .{
6088 setTimestampToPosix(options.access_timestamp),
6089 setTimestampToPosix(options.modify_timestamp),
6090 };
6091 break :p &times_buffer;
6092 };
61946093
61956094 try current_thread.beginSyscall();
6196 while (true) {
6197 switch (posix.errno(posix.system.futimens(file.handle, null))) {
6198 .SUCCESS => return current_thread.endSyscall(),
6199 .INTR => {
6200 try current_thread.checkCancel();
6201 continue;
6202 },
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 }
6216 }
6095 while (true) switch (posix.errno(posix.system.futimens(file.handle, times))) {
6096 .SUCCESS => return current_thread.endSyscall(),
6097 .INTR => {
6098 try current_thread.checkCancel();
6099 continue;
6100 },
6101 .BADF => |err| return current_thread.endSyscallErrnoBug(err), // always a race condition
6102 .FAULT => |err| return current_thread.endSyscallErrnoBug(err),
6103 .INVAL => |err| return current_thread.endSyscallErrnoBug(err),
6104 .ACCES => return current_thread.endSyscallError(error.AccessDenied),
6105 .PERM => return current_thread.endSyscallError(error.PermissionDenied),
6106 .ROFS => return current_thread.endSyscallError(error.ReadOnlyFileSystem),
6107 else => |err| return current_thread.endSyscallUnexpectedErrno(err),
6108 };
62176109}
62186110
62196111const windows_lock_range_off: windows.LARGE_INTEGER = 0;
......@@ -11283,6 +11175,14 @@ fn timestampToPosix(nanoseconds: i96) posix.timespec {
1128311175 };
1128411176}
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
1128611186fn pathToPosix(file_path: []const u8, buffer: *[posix.PATH_MAX]u8) Dir.PathNameError![:0]u8 {
1128711187 if (std.mem.containsAtLeastScalar2(u8, file_path, 0, 1)) return error.BadPathName;
1128811188 // >= 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/os/linux.zig+12
......@@ -8423,6 +8423,18 @@ pub const kernel_timespec = extern struct {
84238423pub const timespec = if (native_arch == .hexagon or native_arch == .riscv32) kernel_timespec else extern struct {
84248424 sec: isize,
84258425 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 };
84268438};
84278439
84288440pub const XDP = struct {