diff --git a/lib/std/Io.zig b/lib/std/Io.zig index 162fedca5ff73168a789d4df044c4197c62fbac5..7c3aa98e168fdbe8e1ea612dee5693ba7044b31f 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -681,8 +681,7 @@ pub const VTable = struct { dirSetFileOwner: *const fn (?*anyopaque, Dir, []const u8, ?File.Uid, ?File.Gid, Dir.SetFileOwnerOptions) Dir.SetFileOwnerError!void, dirSetPermissions: *const fn (?*anyopaque, Dir, Dir.Permissions) Dir.SetPermissionsError!void, dirSetFilePermissions: *const fn (?*anyopaque, Dir, []const u8, File.Permissions, Dir.SetFilePermissionsOptions) Dir.SetFilePermissionsError!void, - dirSetTimestamps: *const fn (?*anyopaque, Dir, []const u8, last_accessed: Timestamp, last_modified: Timestamp, Dir.SetTimestampsOptions) Dir.SetTimestampsError!void, - dirSetTimestampsNow: *const fn (?*anyopaque, Dir, []const u8, Dir.SetTimestampsOptions) Dir.SetTimestampsError!void, + dirSetTimestamps: *const fn (?*anyopaque, Dir, []const u8, Dir.SetTimestampsOptions) Dir.SetTimestampsError!void, 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, fileStat: *const fn (?*anyopaque, File) File.StatError!File.Stat, @@ -705,8 +704,7 @@ pub const VTable = struct { fileSetLength: *const fn (?*anyopaque, File, u64) File.SetLengthError!void, fileSetOwner: *const fn (?*anyopaque, File, ?File.Uid, ?File.Gid) File.SetOwnerError!void, fileSetPermissions: *const fn (?*anyopaque, File, File.Permissions) File.SetPermissionsError!void, - fileSetTimestamps: *const fn (?*anyopaque, File, last_accessed: Timestamp, last_modified: Timestamp) File.SetTimestampsError!void, - fileSetTimestampsNow: *const fn (?*anyopaque, File) File.SetTimestampsError!void, + fileSetTimestamps: *const fn (?*anyopaque, File, File.SetTimestampsOptions) File.SetTimestampsError!void, fileLock: *const fn (?*anyopaque, File, File.Lock) File.LockError!void, fileTryLock: *const fn (?*anyopaque, File, File.Lock) File.LockError!bool, fileUnlock: *const fn (?*anyopaque, File) void, diff --git a/lib/std/Io/Dir.zig b/lib/std/Io/Dir.zig index e7b18066160adf95cbace687eb00843eb9175718..577e114c40cffb62ec505f56b7d38c96c7827611 100644 --- a/lib/std/Io/Dir.zig +++ b/lib/std/Io/Dir.zig @@ -615,7 +615,10 @@ pub fn updateFile( error.WriteFailed => return atomic_file.file_writer.err.?, }; try atomic_file.flush(); - try atomic_file.file_writer.file.setTimestamps(io, src_stat.atime, src_stat.mtime); + try atomic_file.file_writer.file.setTimestamps(io, .{ + .access_timestamp = .init(src_stat.atime), + .modify_timestamp = .init(src_stat.mtime), + }); try atomic_file.renameIntoPlace(); return .stale; } @@ -1825,6 +1828,8 @@ pub const SetTimestampsError = File.SetTimestampsError || PathNameError; pub const SetTimestampsOptions = struct { follow_symlinks: bool = true, + access_timestamp: File.SetTimestamp = .unchanged, + modify_timestamp: File.SetTimestamp = .unchanged, }; /// The granularity that ultimately is stored depends on the combination of @@ -1834,18 +1839,29 @@ pub fn setTimestamps( dir: Dir, io: Io, sub_path: []const u8, - last_accessed: Io.Timestamp, - last_modified: Io.Timestamp, options: SetTimestampsOptions, ) SetTimestampsError!void { - return io.vtable.dirSetTimestamps(io.userdata, dir, sub_path, last_accessed, last_modified, options); + return io.vtable.dirSetTimestamps(io.userdata, dir, sub_path, options); } +pub const SetTimestampsNowOptions = struct { + follow_symlinks: bool = true, +}; + /// Sets the accessed and modification timestamps of the provided path to the /// current wall clock time. /// /// The granularity that ultimately is stored depends on the combination of /// operating system and file system. -pub fn setTimestampsNow(dir: Dir, io: Io, sub_path: []const u8, options: SetTimestampsOptions) SetTimestampsError!void { - return io.vtable.fileSetTimestampsNow(io.userdata, dir, sub_path, options); +pub fn setTimestampsNow( + dir: Dir, + io: Io, + sub_path: []const u8, + options: SetTimestampsNowOptions, +) SetTimestampsError!void { + return io.vtable.fileSetTimestamps(io.userdata, dir, sub_path, .{ + .follow_symlinks = options.follow_symlinks, + .access_timestamp = .now, + .modify_timestamp = .now, + }); } diff --git a/lib/std/Io/File.zig b/lib/std/Io/File.zig index ee30103af0bee7e04d048da8da6cf66f9aa0f9a8..bbe550a9cc515afd5675040234a4bc43bc36dab2 100644 --- a/lib/std/Io/File.zig +++ b/lib/std/Io/File.zig @@ -53,7 +53,12 @@ pub const Stat = struct { permissions: Permissions, kind: Kind, /// Last access time in nanoseconds, relative to UTC 1970-01-01. - atime: Io.Timestamp, + /// + /// Filesystems generally find this value problematic to keep updated since + /// it turns read-only file system accesses into file system mutations. + /// Some systems report stale values, and some systems explicitly refuse to + /// report this value. The latter case is handled by `null`. + atime: ?Io.Timestamp, /// Last modification time in nanoseconds, relative to UTC 1970-01-01. mtime: Io.Timestamp, /// Last status/metadata change time in nanoseconds, relative to UTC 1970-01-01. @@ -478,16 +483,30 @@ pub const SetTimestampsError = error{ ReadOnlyFileSystem, } || Io.Cancelable || Io.UnexpectedError; +pub const SetTimestampsOptions = struct { + access_timestamp: SetTimestamp = .unchanged, + modify_timestamp: SetTimestamp = .unchanged, +}; + +pub const SetTimestamp = union(enum) { + /// Leave the existing timestamp unmodified. + unchanged, + /// Set to current time using `Io.Clock.real`. + now, + /// Set to provided timestamp using `Io.Clock.real`. + new: Io.Timestamp, + + /// Convenience for interacting with `Stat`, in which `null` indicates `unchanged`. + pub fn init(optional: ?Io.Timestamp) SetTimestamp { + return if (optional) |t| .{ .new = t } else .unchanged; + } +}; + /// The granularity that ultimately is stored depends on the combination of /// operating system and file system. When a value as provided that exceeds /// this range, the value is clamped to the maximum. -pub fn setTimestamps( - file: File, - io: Io, - last_accessed: Io.Timestamp, - last_modified: Io.Timestamp, -) SetTimestampsError!void { - return io.vtable.fileSetTimestamps(io.userdata, file, last_accessed, last_modified); +pub fn setTimestamps(file: File, io: Io, options: SetTimestampsOptions) SetTimestampsError!void { + return io.vtable.fileSetTimestamps(io.userdata, file, options); } /// Sets the accessed and modification timestamps of `file` to the current wall @@ -496,7 +515,10 @@ pub fn setTimestamps( /// The granularity that ultimately is stored depends on the combination of /// operating system and file system. pub fn setTimestampsNow(file: File, io: Io) SetTimestampsError!void { - return io.vtable.fileSetTimestampsNow(io.userdata, file); + return io.vtable.fileSetTimestamps(io.userdata, file, .{ + .access_timestamp = .now, + .modify_timestamp = .now, + }); } /// Returns 0 on stream end or if `buffer` has no space available for data. diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index d6d44aa775289a9dbee95f7edaac8c5e2181d30a..fdd4e6c34a0550cf2ea7f421c0699bd6a81a106f 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -235,6 +235,24 @@ const Thread = struct { ) orelse return; } + fn endSyscallErrnoBug(thread: *Thread, err: posix.E) Io.UnexpectedError { + @branchHint(.cold); + thread.endSyscall(); + return errnoBug(err); + } + + fn endSyscallUnexpectedErrno(thread: *Thread, err: posix.E) Io.UnexpectedError { + @branchHint(.cold); + thread.endSyscall(); + return posix.unexpectedErrno(err); + } + + /// inline to make error return traces slightly shallower. + inline fn endSyscallError(thread: *Thread, err: anytype) @TypeOf(err) { + thread.endSyscall(); + return err; + } + fn currentSignalId() SignaleeId { return if (std.Thread.use_pthreads) std.c.pthread_self() else std.Thread.getCurrentId(); } @@ -811,7 +829,6 @@ pub fn io(t: *Threaded) Io { .dirSetPermissions = dirSetPermissions, .dirSetFilePermissions = dirSetFilePermissions, .dirSetTimestamps = dirSetTimestamps, - .dirSetTimestampsNow = dirSetTimestampsNow, .dirHardLink = dirHardLink, .fileStat = fileStat, @@ -833,7 +850,6 @@ pub fn io(t: *Threaded) Io { .fileSetOwner = fileSetOwner, .fileSetPermissions = fileSetPermissions, .fileSetTimestamps = fileSetTimestamps, - .fileSetTimestampsNow = fileSetTimestampsNow, .fileLock = fileLock, .fileTryLock = fileTryLock, .fileUnlock = fileUnlock, @@ -947,7 +963,6 @@ pub fn ioBasic(t: *Threaded) Io { .dirSetPermissions = dirSetPermissions, .dirSetFilePermissions = dirSetFilePermissions, .dirSetTimestamps = dirSetTimestamps, - .dirSetTimestampsNow = dirSetTimestampsNow, .dirHardLink = dirHardLink, .fileStat = fileStat, @@ -969,7 +984,6 @@ pub fn ioBasic(t: *Threaded) Io { .fileSetOwner = fileSetOwner, .fileSetPermissions = fileSetPermissions, .fileSetTimestamps = fileSetTimestamps, - .fileSetTimestampsNow = fileSetTimestampsNow, .fileLock = fileLock, .fileTryLock = fileTryLock, .fileUnlock = fileUnlock, @@ -1920,7 +1934,7 @@ fn dirStatFileLinux( try current_thread.beginSyscall(); while (true) { var statx = std.mem.zeroes(linux.Statx); - switch (sys.errno(sys.statx(dir.handle, sub_path_posix, flags, linux_statx_mask, &statx))) { + switch (sys.errno(sys.statx(dir.handle, sub_path_posix, flags, linux_statx_request, &statx))) { .SUCCESS => { current_thread.endSyscall(); return statFromLinux(&statx); @@ -2155,7 +2169,7 @@ fn fileStatLinux(userdata: ?*anyopaque, file: File) File.StatError!File.Stat { try current_thread.beginSyscall(); while (true) { var statx = std.mem.zeroes(linux.Statx); - switch (sys.errno(sys.statx(file.handle, "", linux.AT.EMPTY_PATH, linux_statx_mask, &statx))) { + switch (sys.errno(sys.statx(file.handle, "", linux.AT.EMPTY_PATH, linux_statx_request, &statx))) { .SUCCESS => { current_thread.endSyscall(); return statFromLinux(&statx); @@ -5977,8 +5991,6 @@ fn dirSetTimestamps( userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, - last_accessed: Io.Timestamp, - last_modified: Io.Timestamp, options: Dir.SetTimestampsOptions, ) Dir.SetTimestampsError!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); @@ -5992,9 +6004,13 @@ fn dirSetTimestamps( @panic("TODO implement dirSetTimestamps wasi"); } - const times: [2]posix.timespec = .{ - timestampToPosix(last_accessed.nanoseconds), - timestampToPosix(last_modified.nanoseconds), + var times_buffer: [2]posix.timespec = undefined; + const times = if (options.modify_timestamp == .now and options.access_timestamp == .now) null else p: { + times_buffer = .{ + setTimestampToPosix(options.access_timestamp), + setTimestampToPosix(options.modify_timestamp), + }; + break :p ×_buffer; }; const flags: u32 = if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0; @@ -6003,80 +6019,26 @@ fn dirSetTimestamps( const sub_path_posix = try pathToPosix(sub_path, &path_buffer); try current_thread.beginSyscall(); - while (true) { - switch (posix.errno(posix.system.utimensat(dir.handle, sub_path_posix, ×, flags))) { - .SUCCESS => return current_thread.endSyscall(), - .INTR => { - try current_thread.checkCancel(); - continue; - }, - else => |e| { - current_thread.endSyscall(); - switch (e) { - .ACCES => return error.AccessDenied, - .PERM => return error.PermissionDenied, - .BADF => |err| return errnoBug(err), // always a race condition - .FAULT => |err| return errnoBug(err), - .INVAL => |err| return errnoBug(err), - .ROFS => return error.ReadOnlyFileSystem, - else => |err| return posix.unexpectedErrno(err), - } - }, - } - } -} - -fn dirSetTimestampsNow( - userdata: ?*anyopaque, - dir: Dir, - sub_path: []const u8, - options: Dir.SetTimestampsOptions, -) Dir.SetTimestampsError!void { - const t: *Threaded = @ptrCast(@alignCast(userdata)); - const current_thread = Thread.getCurrent(t); - - if (is_windows) { - @panic("TODO implement dirSetTimestampsNow windows"); - } - - if (native_os == .wasi and !builtin.link_libc) { - @panic("TODO implement dirSetTimestampsNow wasi"); - } - - const flags: u32 = if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0; - - var path_buffer: [posix.PATH_MAX]u8 = undefined; - const sub_path_posix = try pathToPosix(sub_path, &path_buffer); - - try current_thread.beginSyscall(); - while (true) { - switch (posix.errno(posix.system.utimensat(dir.handle, sub_path_posix, null, flags))) { - .SUCCESS => return current_thread.endSyscall(), - .INTR => { - try current_thread.checkCancel(); - continue; - }, - else => |e| { - current_thread.endSyscall(); - switch (e) { - .ACCES => return error.AccessDenied, - .PERM => return error.PermissionDenied, - .BADF => |err| return errnoBug(err), // always a race condition - .FAULT => |err| return errnoBug(err), - .INVAL => |err| return errnoBug(err), - .ROFS => return error.ReadOnlyFileSystem, - else => |err| return posix.unexpectedErrno(err), - } - }, - } - } + while (true) switch (posix.errno(posix.system.utimensat(dir.handle, sub_path_posix, times, flags))) { + .SUCCESS => return current_thread.endSyscall(), + .INTR => { + try current_thread.checkCancel(); + continue; + }, + .BADF => |err| return current_thread.endSyscallErrnoBug(err), // always a race condition + .FAULT => |err| return current_thread.endSyscallErrnoBug(err), + .INVAL => |err| return current_thread.endSyscallErrnoBug(err), + .ACCES => return current_thread.endSyscallError(error.AccessDenied), + .PERM => return current_thread.endSyscallError(error.PermissionDenied), + .ROFS => return current_thread.endSyscallError(error.ReadOnlyFileSystem), + else => |err| return current_thread.endSyscallUnexpectedErrno(err), + }; } fn fileSetTimestamps( userdata: ?*anyopaque, file: File, - last_accessed: Io.Timestamp, - last_modified: Io.Timestamp, + options: File.SetTimestampsOptions, ) File.SetTimestampsError!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); const current_thread = Thread.getCurrent(t); @@ -6084,11 +6046,34 @@ fn fileSetTimestamps( if (is_windows) { try current_thread.checkCancel(); - const atime_ft = windows.nanoSecondsToFileTime(last_accessed); - const mtime_ft = windows.nanoSecondsToFileTime(last_modified); + var access_time_buffer: windows.FILETIME = undefined; + var modify_time_buffer: windows.FILETIME = undefined; + var system_time_buffer: windows.LARGE_INTEGER = undefined; + + if (options.access_timestamp == .now or options.modify_timestamp == .now) { + system_time_buffer = windows.ntdll.RtlGetSystemTimePrecise(); + } + + const access_ptr = switch (options.access_timestamp) { + .unchanged => null, + .now => @panic("TODO do SystemTimeToFileTime logic here"), + .new => |ts| p: { + access_time_buffer = windows.nanoSecondsToFileTime(ts); + break :p &access_time_buffer; + }, + }; + + const modify_ptr = switch (options.modify_timestamp) { + .unchanged => null, + .now => @panic("TODO do SystemTimeToFileTime logic here"), + .new => |ts| p: { + modify_time_buffer = windows.nanoSecondsToFileTime(ts); + break :p &modify_time_buffer; + }, + }; // https://github.com/ziglang/zig/issues/1840 - const rc = windows.kernel32.SetFileTime(file.handle, null, &atime_ft, &mtime_ft); + const rc = windows.kernel32.SetFileTime(file.handle, null, access_ptr, modify_ptr); if (rc == 0) { switch (windows.GetLastError()) { else => |err| return windows.unexpectedError(err), @@ -6097,123 +6082,70 @@ fn fileSetTimestamps( return; } - const times: [2]posix.timespec = .{ - timestampToPosix(last_accessed.nanoseconds), - timestampToPosix(last_modified.nanoseconds), - }; - if (native_os == .wasi and !builtin.link_libc) { - const atim = times[0].toTimestamp(); - const mtim = times[1].toTimestamp(); - try current_thread.beginSyscall(); - while (true) { - switch (std.os.wasi.fd_filestat_set_times(file.handle, atim, mtim, .{ - .ATIM = true, - .MTIM = true, - })) { - .SUCCESS => return current_thread.endSyscall(), - .INTR => { - try current_thread.checkCancel(); - continue; - }, - else => |e| { - current_thread.endSyscall(); - switch (e) { - .ACCES => return error.AccessDenied, - .PERM => return error.PermissionDenied, - .BADF => |err| return errnoBug(err), // File descriptor use-after-free. - .FAULT => |err| return errnoBug(err), - .INVAL => |err| return errnoBug(err), - .ROFS => return error.ReadOnlyFileSystem, - else => |err| return posix.unexpectedErrno(err), - } - }, - } - } - } + var atime: std.os.wasi.timestamp_t = 0; + var mtime: std.os.wasi.timestamp_t = 0; + var flags: std.os.wasi.fstflags_t = .{}; - try current_thread.beginSyscall(); - while (true) { - switch (posix.errno(posix.system.futimens(file.handle, ×))) { - .SUCCESS => return current_thread.endSyscall(), - .INTR => { - try current_thread.checkCancel(); - continue; - }, - else => |e| { - current_thread.endSyscall(); - switch (e) { - .ACCES => return error.AccessDenied, - .PERM => return error.PermissionDenied, - .BADF => |err| return errnoBug(err), // always a race condition - .FAULT => |err| return errnoBug(err), - .INVAL => |err| return errnoBug(err), - .ROFS => return error.ReadOnlyFileSystem, - else => |err| return posix.unexpectedErrno(err), - } + switch (options.access_timestamp) { + .unchanged => {}, + .now => flags.ATIM_NOW = true, + .new => |ts| { + atime = timestampToPosix(ts.nanoseconds).toTimestamp(); + flags.ATIM = true; }, } - } -} - -fn fileSetTimestampsNow(userdata: ?*anyopaque, file: File) File.SetTimestampsError!void { - const t: *Threaded = @ptrCast(@alignCast(userdata)); - const current_thread = Thread.getCurrent(t); - - if (is_windows) { - @panic("TODO implement fileSetTimestampsNow windows"); - } - if (native_os == .wasi and !builtin.link_libc) { - try current_thread.beginSyscall(); - while (true) { - switch (std.os.wasi.fd_filestat_set_times(file.handle, 0, 0, .{ - .ATIM_NOW = true, - .MTIM_NOW = true, - })) { - .SUCCESS => return current_thread.endSyscall(), - .INTR => { - try current_thread.checkCancel(); - continue; - }, - else => |e| { - current_thread.endSyscall(); - switch (e) { - .ACCES => return error.AccessDenied, - .PERM => return error.PermissionDenied, - .BADF => |err| return errnoBug(err), // always a race condition - .FAULT => |err| return errnoBug(err), - .INVAL => |err| return errnoBug(err), - .ROFS => return error.ReadOnlyFileSystem, - else => |err| return posix.unexpectedErrno(err), - } - }, - } + switch (options.modify_timestamp) { + .unchanged => {}, + .now => flags.MTIM_NOW = true, + .new => |ts| { + mtime = timestampToPosix(ts.nanoseconds).toTimestamp(); + flags.MTIM = true; + }, } - } - try current_thread.beginSyscall(); - while (true) { - switch (posix.errno(posix.system.futimens(file.handle, null))) { + try current_thread.beginSyscall(); + while (true) switch (std.os.wasi.fd_filestat_set_times(file.handle, atime, mtime, flags)) { .SUCCESS => return current_thread.endSyscall(), .INTR => { try current_thread.checkCancel(); continue; }, - else => |e| { - current_thread.endSyscall(); - switch (e) { - .ACCES => return error.AccessDenied, - .PERM => return error.PermissionDenied, - .BADF => |err| return errnoBug(err), // always a race condition - .FAULT => |err| return errnoBug(err), - .INVAL => |err| return errnoBug(err), - .ROFS => return error.ReadOnlyFileSystem, - else => |err| return posix.unexpectedErrno(err), - } - }, - } + .BADF => |err| return current_thread.endSyscallErrnoBug(err), // File descriptor use-after-free. + .FAULT => |err| return current_thread.endSyscallErrnoBug(err), + .INVAL => |err| return current_thread.endSyscallErrnoBug(err), + .ACCES => return current_thread.endSyscallError(error.AccessDenied), + .PERM => return current_thread.endSyscallError(error.PermissionDenied), + .ROFS => return current_thread.endSyscallError(error.ReadOnlyFileSystem), + else => |err| return current_thread.endSyscallUnexpectedErrno(err), + }; } + + var times_buffer: [2]posix.timespec = undefined; + const times = if (options.modify_timestamp == .now and options.access_timestamp == .now) null else p: { + times_buffer = .{ + setTimestampToPosix(options.access_timestamp), + setTimestampToPosix(options.modify_timestamp), + }; + break :p ×_buffer; + }; + + try current_thread.beginSyscall(); + while (true) switch (posix.errno(posix.system.futimens(file.handle, times))) { + .SUCCESS => return current_thread.endSyscall(), + .INTR => { + try current_thread.checkCancel(); + continue; + }, + .BADF => |err| return current_thread.endSyscallErrnoBug(err), // always a race condition + .FAULT => |err| return current_thread.endSyscallErrnoBug(err), + .INVAL => |err| return current_thread.endSyscallErrnoBug(err), + .ACCES => return current_thread.endSyscallError(error.AccessDenied), + .PERM => return current_thread.endSyscallError(error.PermissionDenied), + .ROFS => return current_thread.endSyscallError(error.ReadOnlyFileSystem), + else => |err| return current_thread.endSyscallUnexpectedErrno(err), + }; } const windows_lock_range_off: windows.LARGE_INTEGER = 0; @@ -11169,7 +11101,7 @@ fn clockToWasi(clock: Io.Clock) std.os.wasi.clockid_t { }; } -const linux_statx_mask: std.os.linux.STATX = .{ +const linux_statx_request: std.os.linux.STATX = .{ .TYPE = true, .MODE = true, .ATIME = true, @@ -11180,14 +11112,22 @@ const linux_statx_mask: std.os.linux.STATX = .{ .NLINK = true, }; +const linux_statx_check: std.os.linux.STATX = .{ + .TYPE = true, + .MODE = true, + .ATIME = false, + .MTIME = true, + .CTIME = true, + .INO = true, + .SIZE = true, + .NLINK = true, +}; + fn statFromLinux(stx: *const std.os.linux.Statx) Io.UnexpectedError!File.Stat { const actual_mask_int: u32 = @bitCast(stx.mask); - const wanted_mask_int: u32 = @bitCast(linux_statx_mask); + const wanted_mask_int: u32 = @bitCast(linux_statx_check); if ((actual_mask_int | wanted_mask_int) != actual_mask_int) return error.Unexpected; - const atime = stx.atime; - const mtime = stx.mtime; - const ctime = stx.ctime; return .{ .inode = stx.ino, .nlink = stx.nlink, @@ -11203,9 +11143,11 @@ fn statFromLinux(stx: *const std.os.linux.Statx) Io.UnexpectedError!File.Stat { std.os.linux.S.IFSOCK => .unix_domain_socket, else => .unknown, }, - .atime = .{ .nanoseconds = @intCast(@as(i128, atime.sec) * std.time.ns_per_s + atime.nsec) }, - .mtime = .{ .nanoseconds = @intCast(@as(i128, mtime.sec) * std.time.ns_per_s + mtime.nsec) }, - .ctime = .{ .nanoseconds = @intCast(@as(i128, ctime.sec) * std.time.ns_per_s + ctime.nsec) }, + .atime = if (!stx.mask.ATIME) null else .{ + .nanoseconds = @intCast(@as(i128, stx.atime.sec) * std.time.ns_per_s + stx.atime.nsec), + }, + .mtime = .{ .nanoseconds = @intCast(@as(i128, stx.mtime.sec) * std.time.ns_per_s + stx.mtime.nsec) }, + .ctime = .{ .nanoseconds = @intCast(@as(i128, stx.ctime.sec) * std.time.ns_per_s + stx.ctime.nsec) }, }; } @@ -11283,6 +11225,14 @@ fn timestampToPosix(nanoseconds: i96) posix.timespec { }; } +fn setTimestampToPosix(set_ts: File.SetTimestamp) posix.timespec { + return switch (set_ts) { + .unchanged => .OMIT, + .now => .NOW, + .new => |t| timestampToPosix(t.nanoseconds), + }; +} + fn pathToPosix(file_path: []const u8, buffer: *[posix.PATH_MAX]u8) Dir.PathNameError![:0]u8 { if (std.mem.containsAtLeastScalar2(u8, file_path, 0, 1)) return error.BadPathName; // >= rather than > to make room for the null byte diff --git a/lib/std/Io/test.zig b/lib/std/Io/test.zig index 796800f000ad78ad915783219a861d2374161c1e..a317c822cb67c99728cd98742b8b005e90fd15df 100644 --- a/lib/std/Io/test.zig +++ b/lib/std/Io/test.zig @@ -174,14 +174,14 @@ test "setTimestamps" { defer file.close(io); const stat_old = try file.stat(io); + // Set atime and mtime to 5s before - try file.setTimestamps( - io, - stat_old.atime.subDuration(.fromSeconds(5)), - stat_old.mtime.subDuration(.fromSeconds(5)), - ); + try file.setTimestamps(io, .{ + .access_timestamp = if (stat_old.atime) |atime| .{ .new = atime.subDuration(.fromSeconds(5)) } else .unchanged, + .modify_timestamp = .{ .new = stat_old.mtime.subDuration(.fromSeconds(5)) }, + }); const stat_new = try file.stat(io); - try expect(stat_new.atime.nanoseconds < stat_old.atime.nanoseconds); + if (stat_old.atime) |old_atime| try expect(stat_new.atime.?.nanoseconds < old_atime.nanoseconds); try expect(stat_new.mtime.nanoseconds < stat_old.mtime.nanoseconds); } diff --git a/lib/std/c.zig b/lib/std/c.zig index fdd544cba944e5f1276286e0a120e38330cfce24..2a7579f2606b06e0a8cf16ea324a73d2ee7743f7 100644 --- a/lib/std/c.zig +++ b/lib/std/c.zig @@ -114,6 +114,18 @@ pub const timespec = switch (native_os) { return @as(wasi.timestamp_t, @intCast(ts.sec * 1_000_000_000)) + @as(wasi.timestamp_t, @intCast(ts.nsec)); } + + /// For use with `utimensat` and `futimens`. + pub const NOW: timespec = .{ + .sec = 0, + .nsec = 0x3fffffff, + }; + + /// For use with `utimensat` and `futimens`. + pub const OMIT: timespec = .{ + .sec = 0, + .nsec = 0x3ffffffe, + }; }, // https://github.com/SerenityOS/serenity/blob/0a78056453578c18e0a04a0b45ebfb1c96d59005/Kernel/API/POSIX/time.h#L17-L20 .windows, .serenity => extern struct { @@ -123,10 +135,34 @@ pub const timespec = switch (native_os) { .dragonfly, .freebsd, .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => extern struct { sec: isize, nsec: isize, + + /// For use with `utimensat` and `futimens`. + pub const NOW: timespec = .{ + .sec = 0, + .nsec = -1, + }; + + /// For use with `utimensat` and `futimens`. + pub const OMIT: timespec = .{ + .sec = 0, + .nsec = -2, + }; }, .netbsd, .illumos => extern struct { sec: i64, nsec: isize, + + /// For use with `utimensat` and `futimens`. + pub const NOW: timespec = .{ + .sec = 0, + .nsec = 0x3fffffff, + }; + + /// For use with `utimensat` and `futimens`. + pub const OMIT: timespec = .{ + .sec = 0, + .nsec = 0x3ffffffe, + }; }, .openbsd, .haiku => extern struct { sec: time_t, diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig index 96b4c8ee6f259d6617c4caee74fb8acbaba77f4f..174ef64d95f8f2e79c27aebba1055ac4457e74a0 100644 --- a/lib/std/os/linux.zig +++ b/lib/std/os/linux.zig @@ -8417,12 +8417,36 @@ pub const timezone = extern struct { pub const kernel_timespec = extern struct { sec: i64, nsec: i64, + + /// For use with `utimensat` and `futimens`. + pub const NOW: timespec = .{ + .sec = 0, + .nsec = 0x3fffffff, + }; + + /// For use with `utimensat` and `futimens`. + pub const OMIT: timespec = .{ + .sec = 0, + .nsec = 0x3ffffffe, + }; }; // https://github.com/ziglang/zig/issues/4726#issuecomment-2190337877 pub const timespec = if (native_arch == .hexagon or native_arch == .riscv32) kernel_timespec else extern struct { sec: isize, nsec: isize, + + /// For use with `utimensat` and `futimens`. + pub const NOW: timespec = .{ + .sec = 0, + .nsec = 0x3fffffff, + }; + + /// For use with `utimensat` and `futimens`. + pub const OMIT: timespec = .{ + .sec = 0, + .nsec = 0x3ffffffe, + }; }; pub const XDP = struct {