diff options
| author | Veikka Tuominen <git@vexu.eu> | 2022-02-14 12:33:49 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2022-02-14 12:33:49 +0200 |
| commit | 90f2a8d9c5885cdb302757244a5bb2971fdbabe0 (patch) | |
| tree | 336baece47cb908d26611d8d9048d1ecf5e2e7ef /lib | |
| parent | 8ed792b640ef3601dfb773d670915d74fbbbad13 (diff) | |
| parent | 11b4cc589c85b6330824ac9a64bfb0829cc1adb0 (diff) | |
| download | zig-90f2a8d9c5885cdb302757244a5bb2971fdbabe0.tar.gz zig-90f2a8d9c5885cdb302757244a5bb2971fdbabe0.zip | |
Merge pull request #10486 from ominitay/metadata
std: Implement cross-platform metadata API
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/std/c/darwin.zig | 31 | ||||
| -rw-r--r-- | lib/std/c/freebsd.zig | 4 | ||||
| -rw-r--r-- | lib/std/c/haiku.zig | 2 | ||||
| -rw-r--r-- | lib/std/c/netbsd.zig | 4 | ||||
| -rw-r--r-- | lib/std/c/openbsd.zig | 4 | ||||
| -rw-r--r-- | lib/std/c/wasi.zig | 24 | ||||
| -rw-r--r-- | lib/std/fs.zig | 25 | ||||
| -rw-r--r-- | lib/std/fs/file.zig | 479 | ||||
| -rw-r--r-- | lib/std/fs/test.zig | 76 | ||||
| -rw-r--r-- | lib/std/os/wasi.zig | 2 |
10 files changed, 611 insertions, 40 deletions
diff --git a/lib/std/c/darwin.zig b/lib/std/c/darwin.zig index 091f77e937..bd5d3aeead 100644 --- a/lib/std/c/darwin.zig +++ b/lib/std/c/darwin.zig @@ -372,14 +372,10 @@ pub const Stat = extern struct { uid: uid_t, gid: gid_t, rdev: i32, - atimesec: isize, - atimensec: isize, - mtimesec: isize, - mtimensec: isize, - ctimesec: isize, - ctimensec: isize, - birthtimesec: isize, - birthtimensec: isize, + atimespec: timespec, + mtimespec: timespec, + ctimespec: timespec, + birthtimespec: timespec, size: off_t, blocks: i64, blksize: i32, @@ -389,24 +385,19 @@ pub const Stat = extern struct { qspare: [2]i64, pub fn atime(self: @This()) timespec { - return timespec{ - .tv_sec = self.atimesec, - .tv_nsec = self.atimensec, - }; + return self.atimespec; } pub fn mtime(self: @This()) timespec { - return timespec{ - .tv_sec = self.mtimesec, - .tv_nsec = self.mtimensec, - }; + return self.mtimespec; } pub fn ctime(self: @This()) timespec { - return timespec{ - .tv_sec = self.ctimesec, - .tv_nsec = self.ctimensec, - }; + return self.ctimespec; + } + + pub fn birthtime(self: @This()) timespec { + return self.birthtimespec; } }; diff --git a/lib/std/c/freebsd.zig b/lib/std/c/freebsd.zig index 7b74c237ce..d5b6e4972d 100644 --- a/lib/std/c/freebsd.zig +++ b/lib/std/c/freebsd.zig @@ -283,6 +283,10 @@ pub const Stat = extern struct { pub fn ctime(self: @This()) timespec { return self.ctim; } + + pub fn birthtime(self: @This()) timespec { + return self.birthtim; + } }; pub const timespec = extern struct { diff --git a/lib/std/c/haiku.zig b/lib/std/c/haiku.zig index 5997855ea3..176d53e6ae 100644 --- a/lib/std/c/haiku.zig +++ b/lib/std/c/haiku.zig @@ -226,7 +226,7 @@ pub const Stat = extern struct { pub fn ctime(self: @This()) timespec { return self.ctim; } - pub fn crtime(self: @This()) timespec { + pub fn birthtime(self: @This()) timespec { return self.crtim; } }; diff --git a/lib/std/c/netbsd.zig b/lib/std/c/netbsd.zig index 6507134d13..b0708107ad 100644 --- a/lib/std/c/netbsd.zig +++ b/lib/std/c/netbsd.zig @@ -312,6 +312,10 @@ pub const Stat = extern struct { pub fn ctime(self: @This()) timespec { return self.ctim; } + + pub fn birthtime(self: @This()) timespec { + return self.birthtim; + } }; pub const timespec = extern struct { diff --git a/lib/std/c/openbsd.zig b/lib/std/c/openbsd.zig index 17187cf203..51cdbe3180 100644 --- a/lib/std/c/openbsd.zig +++ b/lib/std/c/openbsd.zig @@ -235,6 +235,10 @@ pub const Stat = extern struct { pub fn ctime(self: @This()) timespec { return self.ctim; } + + pub fn birthtime(self: @This()) timespec { + return self.birthtim; + } }; pub const timespec = extern struct { diff --git a/lib/std/c/wasi.zig b/lib/std/c/wasi.zig index 9c9148a783..c3635784dd 100644 --- a/lib/std/c/wasi.zig +++ b/lib/std/c/wasi.zig @@ -41,32 +41,20 @@ pub const Stat = extern struct { blksize: i32, blocks: i64, - atimesec: time_t, - atimensec: isize, - mtimesec: time_t, - mtimensec: isize, - ctimesec: time_t, - ctimensec: isize, + atim: timespec, + mtim: timespec, + ctim: timespec, pub fn atime(self: @This()) timespec { - return timespec{ - .tv_sec = self.atimesec, - .tv_nsec = self.atimensec, - }; + return self.atim; } pub fn mtime(self: @This()) timespec { - return timespec{ - .tv_sec = self.mtimesec, - .tv_nsec = self.mtimensec, - }; + return self.mtim; } pub fn ctime(self: @This()) timespec { - return timespec{ - .tv_sec = self.ctimesec, - .tv_nsec = self.ctimensec, - }; + return self.ctim; } }; diff --git a/lib/std/fs.zig b/lib/std/fs.zig index f7818ea68b..7b577b1b62 100644 --- a/lib/std/fs.zig +++ b/lib/std/fs.zig @@ -2231,6 +2231,31 @@ pub const Dir = struct { } pub const ChownError = File.ChownError; + + const Permissions = File.Permissions; + pub const SetPermissionsError = File.SetPermissionsError; + + /// Sets permissions according to the provided `Permissions` struct. + /// This method is *NOT* available on WASI + pub fn setPermissions(self: Dir, permissions: Permissions) SetPermissionsError!void { + const file: File = .{ + .handle = self.fd, + .capable_io_mode = .blocking, + }; + try file.setPermissions(permissions); + } + + const Metadata = File.Metadata; + pub const MetadataError = File.MetadataError; + + /// Returns a `Metadata` struct, representing the permissions on the directory + pub fn metadata(self: Dir) MetadataError!Metadata { + const file: File = .{ + .handle = self.fd, + .capable_io_mode = .blocking, + }; + return try file.metadata(); + } }; /// Returns a handle to the current working directory. It is not opened with iteration capability. diff --git a/lib/std/fs/file.zig b/lib/std/fs/file.zig index ff71c82d2b..079d5905a1 100644 --- a/lib/std/fs/file.zig +++ b/lib/std/fs/file.zig @@ -398,6 +398,485 @@ pub const File = struct { try os.fchown(self.handle, owner, group); } + /// Cross-platform representation of permissions on a file. + /// The `readonly` and `setReadonly` are the only methods available across all platforms. + /// Platform-specific functionality is available through the `inner` field. + pub const Permissions = struct { + /// You may use the `inner` field to use platform-specific functionality + inner: switch (builtin.os.tag) { + .windows => PermissionsWindows, + else => PermissionsUnix, + }, + + const Self = @This(); + + /// Returns `true` if permissions represent an unwritable file. + /// On Unix, `true` is returned only if no class has write permissions. + pub fn readOnly(self: Self) bool { + return self.inner.readOnly(); + } + + /// Sets whether write permissions are provided. + /// On Unix, this affects *all* classes. If this is undesired, use `unixSet` + /// This method *DOES NOT* set permissions on the filesystem: use `File.setPermissions(permissions)` + pub fn setReadOnly(self: *Self, read_only: bool) void { + self.inner.setReadOnly(read_only); + } + }; + + pub const PermissionsWindows = struct { + attributes: os.windows.DWORD, + + const Self = @This(); + + /// Returns `true` if permissions represent an unwritable file. + pub fn readOnly(self: Self) bool { + return self.attributes & os.windows.FILE_ATTRIBUTE_READONLY != 0; + } + + /// Sets whether write permissions are provided. + /// This method *DOES NOT* set permissions on the filesystem: use `File.setPermissions(permissions)` + pub fn setReadOnly(self: *Self, read_only: bool) void { + if (read_only) { + self.attributes |= os.windows.FILE_ATTRIBUTE_READONLY; + } else { + self.attributes &= ~@as(os.windows.DWORD, os.windows.FILE_ATTRIBUTE_READONLY); + } + } + }; + + pub const PermissionsUnix = struct { + mode: Mode, + + const Self = @This(); + + /// Returns `true` if permissions represent an unwritable file. + /// `true` is returned only if no class has write permissions. + pub fn readOnly(self: Self) bool { + return self.mode & 0o222 == 0; + } + + /// Sets whether write permissions are provided. + /// This affects *all* classes. If this is undesired, use `unixSet` + /// This method *DOES NOT* set permissions on the filesystem: use `File.setPermissions(permissions)` + pub fn setReadOnly(self: *Self, read_only: bool) void { + if (read_only) { + self.mode &= ~@as(Mode, 0o222); + } else { + self.mode |= @as(Mode, 0o222); + } + } + + pub const Class = enum(u2) { + user = 2, + group = 1, + other = 0, + }; + + pub const Permission = enum(u3) { + read = 0o4, + write = 0o2, + execute = 0o1, + }; + + /// Returns `true` if the chosen class has the selected permission. + /// This method is only available on Unix platforms. + pub fn unixHas(self: Self, class: Class, permission: Permission) bool { + const mask = @as(Mode, @enumToInt(permission)) << @as(u3, @enumToInt(class)) * 3; + return self.mode & mask != 0; + } + + /// Sets the permissions for the chosen class. Any permissions set to `null` are left unchanged. + /// This method *DOES NOT* set permissions on the filesystem: use `File.setPermissions(permissions)` + pub fn unixSet(self: *Self, class: Class, permissions: struct { + read: ?bool = null, + write: ?bool = null, + execute: ?bool = null, + }) void { + const shift = @as(u3, @enumToInt(class)) * 3; + if (permissions.read) |r| { + if (r) { + self.mode |= @as(Mode, 0o4) << shift; + } else { + self.mode &= ~(@as(Mode, 0o4) << shift); + } + } + if (permissions.write) |w| { + if (w) { + self.mode |= @as(Mode, 0o2) << shift; + } else { + self.mode &= ~(@as(Mode, 0o2) << shift); + } + } + if (permissions.execute) |x| { + if (x) { + self.mode |= @as(Mode, 0o1) << shift; + } else { + self.mode &= ~(@as(Mode, 0o1) << shift); + } + } + } + + /// Returns a `Permissions` struct representing the permissions from the passed mode. + pub fn unixNew(new_mode: Mode) Self { + return Self{ + .mode = new_mode, + }; + } + }; + + pub const SetPermissionsError = ChmodError; + + /// Sets permissions according to the provided `Permissions` struct. + /// This method is *NOT* available on WASI + pub fn setPermissions(self: File, permissions: Permissions) SetPermissionsError!void { + switch (builtin.os.tag) { + .windows => { + var io_status_block: windows.IO_STATUS_BLOCK = undefined; + var info = windows.FILE_BASIC_INFORMATION{ + .CreationTime = 0, + .LastAccessTime = 0, + .LastWriteTime = 0, + .ChangeTime = 0, + .FileAttributes = permissions.inner.attributes, + }; + const rc = windows.ntdll.NtSetInformationFile( + self.handle, + &io_status_block, + &info, + @sizeOf(windows.FILE_BASIC_INFORMATION), + .FileBasicInformation, + ); + switch (rc) { + .SUCCESS => return, + .INVALID_HANDLE => unreachable, + .ACCESS_DENIED => return error.AccessDenied, + else => return windows.unexpectedStatus(rc), + } + }, + .wasi => @compileError("Unsupported OS"), // Wasi filesystem does not *yet* support chmod + else => { + try self.chmod(permissions.inner.mode); + }, + } + } + + /// Cross-platform representation of file metadata. + /// Platform-specific functionality is available through the `inner` field. + pub const Metadata = struct { + /// You may use the `inner` field to use platform-specific functionality + inner: switch (builtin.os.tag) { + .windows => MetadataWindows, + .linux => MetadataLinux, + else => MetadataUnix, + }, + + const Self = @This(); + + /// Returns the size of the file + pub fn size(self: Self) u64 { + return self.inner.size(); + } + + /// Returns a `Permissions` struct, representing the permissions on the file + pub fn permissions(self: Self) Permissions { + return self.inner.permissions(); + } + + /// Returns the `Kind` of file. + /// On Windows, can only return: `.File`, `.Directory`, `.SymLink` or `.Unknown` + pub fn kind(self: Self) Kind { + return self.inner.kind(); + } + + /// Returns the last time the file was accessed in nanoseconds since UTC 1970-01-01 + pub fn accessed(self: Self) i128 { + return self.inner.accessed(); + } + + /// Returns the time the file was modified in nanoseconds since UTC 1970-01-01 + pub fn modified(self: Self) i128 { + return self.inner.modified(); + } + + /// Returns the time the file was created in nanoseconds since UTC 1970-01-01 + /// On Windows, this cannot return null + /// On Linux, this returns null if the filesystem does not support creation times, or if the kernel is older than 4.11 + /// On Unices, this returns null if the filesystem or OS does not support creation times + /// On MacOS, this returns the ctime if the filesystem does not support creation times; this is insanity, and yet another reason to hate on Apple + pub fn created(self: Self) ?i128 { + return self.inner.created(); + } + }; + + pub const MetadataUnix = struct { + stat: os.Stat, + + const Self = @This(); + + /// Returns the size of the file + pub fn size(self: Self) u64 { + return @intCast(u64, self.stat.size); + } + + /// Returns a `Permissions` struct, representing the permissions on the file + pub fn permissions(self: Self) Permissions { + return Permissions{ .inner = PermissionsUnix{ .mode = self.stat.mode } }; + } + + /// Returns the `Kind` of the file + pub fn kind(self: Self) Kind { + if (builtin.os.tag == .wasi and !builtin.link_libc) return switch (self.stat.filetype) { + .BLOCK_DEVICE => Kind.BlockDevice, + .CHARACTER_DEVICE => Kind.CharacterDevice, + .DIRECTORY => Kind.Directory, + .SYMBOLIC_LINK => Kind.SymLink, + .REGULAR_FILE => Kind.File, + .SOCKET_STREAM, .SOCKET_DGRAM => Kind.UnixDomainSocket, + else => Kind.Unknown, + }; + + const m = self.stat.mode & os.S.IFMT; + + switch (m) { + os.S.IFBLK => return Kind.BlockDevice, + os.S.IFCHR => return Kind.CharacterDevice, + os.S.IFDIR => return Kind.Directory, + os.S.IFIFO => return Kind.NamedPipe, + os.S.IFLNK => return Kind.SymLink, + os.S.IFREG => return Kind.File, + os.S.IFSOCK => return Kind.UnixDomainSocket, + else => {}, + } + + if (builtin.os.tag == .solaris) switch (m) { + os.S.IFDOOR => return Kind.Door, + os.S.IFPORT => return Kind.EventPort, + else => {}, + }; + + return .Unknown; + } + + /// Returns the last time the file was accessed in nanoseconds since UTC 1970-01-01 + pub fn accessed(self: Self) i128 { + const atime = self.stat.atime(); + return @as(i128, atime.tv_sec) * std.time.ns_per_s + atime.tv_nsec; + } + + /// Returns the last time the file was modified in nanoseconds since UTC 1970-01-01 + pub fn modified(self: Self) i128 { + const mtime = self.stat.mtime(); + return @as(i128, mtime.tv_sec) * std.time.ns_per_s + mtime.tv_nsec; + } + + /// Returns the time the file was created in nanoseconds since UTC 1970-01-01 + /// Returns null if this is not supported by the OS or filesystem + pub fn created(self: Self) ?i128 { + if (!@hasDecl(@TypeOf(self.stat), "birthtime")) return null; + const birthtime = self.stat.birthtime(); + + // If the filesystem doesn't support this the value *should* be: + // On FreeBSD: tv_nsec = 0, tv_sec = -1 + // On NetBSD and OpenBSD: tv_nsec = 0, tv_sec = 0 + // On MacOS, it is set to ctime -- we cannot detect this!! + switch (builtin.os.tag) { + .freebsd => if (birthtime.tv_sec == -1 and birthtime.tv_nsec == 0) return null, + .netbsd, .openbsd => if (birthtime.tv_sec == 0 and birthtime.tv_nsec == 0) return null, + .macos => {}, + else => @compileError("Creation time detection not implemented for OS"), + } + + return @as(i128, birthtime.tv_sec) * std.time.ns_per_s + birthtime.tv_nsec; + } + }; + + /// `MetadataUnix`, but using Linux's `statx` syscall. + /// On Linux versions below 4.11, `statx` will be filled with data from stat. + pub const MetadataLinux = struct { + statx: os.linux.Statx, + + const Self = @This(); + + /// Returns the size of the file + pub fn size(self: Self) u64 { + return self.statx.size; + } + + /// Returns a `Permissions` struct, representing the permissions on the file + pub fn permissions(self: Self) Permissions { + return Permissions{ .inner = PermissionsUnix{ .mode = self.statx.mode } }; + } + + /// Returns the `Kind` of the file + pub fn kind(self: Self) Kind { + const m = self.statx.mode & os.S.IFMT; + + switch (m) { + os.S.IFBLK => return Kind.BlockDevice, + os.S.IFCHR => return Kind.CharacterDevice, + os.S.IFDIR => return Kind.Directory, + os.S.IFIFO => return Kind.NamedPipe, + os.S.IFLNK => return Kind.SymLink, + os.S.IFREG => return Kind.File, + os.S.IFSOCK => return Kind.UnixDomainSocket, + else => {}, + } + + return .Unknown; + } + + /// Returns the last time the file was accessed in nanoseconds since UTC 1970-01-01 + pub fn accessed(self: Self) i128 { + return @as(i128, self.statx.atime.tv_sec) * std.time.ns_per_s + self.statx.atime.tv_nsec; + } + + /// Returns the last time the file was modified in nanoseconds since UTC 1970-01-01 + pub fn modified(self: Self) i128 { + return @as(i128, self.statx.mtime.tv_sec) * std.time.ns_per_s + self.statx.mtime.tv_nsec; + } + + /// Returns the time the file was created in nanoseconds since UTC 1970-01-01 + /// Returns null if this is not supported by the filesystem, or on kernels before than version 4.11 + pub fn created(self: Self) ?i128 { + if (self.statx.mask & os.linux.STATX_BTIME == 0) return null; + return @as(i128, self.statx.btime.tv_sec) * std.time.ns_per_s + self.statx.btime.tv_nsec; + } + }; + + pub const MetadataWindows = struct { + attributes: windows.DWORD, + reparse_tag: windows.DWORD, + _size: u64, + access_time: i128, + modified_time: i128, + creation_time: i128, + + const Self = @This(); + + /// Returns the size of the file + pub fn size(self: Self) u64 { + return self._size; + } + + /// Returns a `Permissions` struct, representing the permissions on the file + pub fn permissions(self: Self) Permissions { + return Permissions{ .inner = PermissionsWindows{ .attributes = self.attributes } }; + } + + /// Returns the `Kind` of the file. + /// Can only return: `.File`, `.Directory`, `.SymLink` or `.Unknown` + pub fn kind(self: Self) Kind { + if (self.attributes & windows.FILE_ATTRIBUTE_REPARSE_POINT != 0) { + if (self.reparse_tag & 0x20000000 != 0) { + return .SymLink; + } + } else if (self.attributes & windows.FILE_ATTRIBUTE_DIRECTORY != 0) { + return .Directory; + } else { + return .File; + } + return .Unknown; + } + + /// Returns the last time the file was accessed in nanoseconds since UTC 1970-01-01 + pub fn accessed(self: Self) i128 { + return self.access_time; + } + + /// Returns the time the file was modified in nanoseconds since UTC 1970-01-01 + pub fn modified(self: Self) i128 { + return self.modified_time; + } + + /// Returns the time the file was created in nanoseconds since UTC 1970-01-01 + /// This never returns null, only returning an optional for compatibility with other OSes + pub fn created(self: Self) ?i128 { + return self.creation_time; + } + }; + + pub const MetadataError = os.FStatError; + + pub fn metadata(self: File) MetadataError!Metadata { + return Metadata{ + .inner = switch (builtin.os.tag) { + .windows => blk: { + var io_status_block: windows.IO_STATUS_BLOCK = undefined; + var info: windows.FILE_ALL_INFORMATION = undefined; + + const rc = windows.ntdll.NtQueryInformationFile(self.handle, &io_status_block, &info, @sizeOf(windows.FILE_ALL_INFORMATION), .FileAllInformation); + switch (rc) { + .SUCCESS => {}, + .BUFFER_OVERFLOW => {}, + .INVALID_PARAMETER => unreachable, + .ACCESS_DENIED => return error.AccessDenied, + else => return windows.unexpectedStatus(rc), + } + + const reparse_tag: windows.DWORD = reparse_blk: { + if (info.BasicInformation.FileAttributes & windows.FILE_ATTRIBUTE_REPARSE_POINT != 0) { + var reparse_buf: [windows.MAXIMUM_REPARSE_DATA_BUFFER_SIZE]u8 = undefined; + try windows.DeviceIoControl(self.handle, windows.FSCTL_GET_REPARSE_POINT, null, reparse_buf[0..]); + const reparse_struct = @ptrCast(*const windows.REPARSE_DATA_BUFFER, @alignCast(@alignOf(windows.REPARSE_DATA_BUFFER), &reparse_buf[0])); + break :reparse_blk reparse_struct.ReparseTag; + } + break :reparse_blk 0; + }; + + break :blk MetadataWindows{ + .attributes = info.BasicInformation.FileAttributes, + .reparse_tag = reparse_tag, + ._size = @bitCast(u64, info.StandardInformation.EndOfFile), + .access_time = windows.fromSysTime(info.BasicInformation.LastAccessTime), + .modified_time = windows.fromSysTime(info.BasicInformation.LastWriteTime), + .creation_time = windows.fromSysTime(info.BasicInformation.CreationTime), + }; + }, + .linux => blk: { + var stx = mem.zeroes(os.linux.Statx); + const rcx = os.linux.statx(self.handle, "\x00", os.linux.AT.EMPTY_PATH, os.linux.STATX_TYPE | os.linux.STATX_MODE | os.linux.STATX_ATIME | os.linux.STATX_MTIME | os.linux.STATX_BTIME, &stx); + + switch (os.errno(rcx)) { + .SUCCESS => {}, + // NOSYS happens when `statx` is unsupported, which is the case on kernel versions before 4.11 + // Here, we call `fstat` and fill `stx` with the data we need + .NOSYS => { + const st = try os.fstat(self.handle); + + stx.mode = @intCast(u16, st.mode); + + // Hacky conversion from timespec to statx_timestamp + stx.atime = std.mem.zeroes(os.linux.statx_timestamp); + stx.atime.tv_sec = st.atim.tv_sec; + stx.atime.tv_nsec = @intCast(u32, st.atim.tv_nsec); // Guaranteed to succeed (tv_nsec is always below 10^9) + + stx.mtime = std.mem.zeroes(os.linux.statx_timestamp); + stx.mtime.tv_sec = st.mtim.tv_sec; + stx.mtime.tv_nsec = @intCast(u32, st.mtim.tv_nsec); + + stx.mask = os.linux.STATX_BASIC_STATS | os.linux.STATX_MTIME; + }, + .BADF => unreachable, + .FAULT => unreachable, + .NOMEM => return error.SystemResources, + else => |err| return os.unexpectedErrno(err), + } + + break :blk MetadataLinux{ + .statx = stx, + }; + }, + else => blk: { + const st = try os.fstat(self.handle); + break :blk MetadataUnix{ + .stat = st, + }; + }, + }, + }; + } + pub const UpdateTimesError = os.FutimensError || windows.SetFileTimeError; /// The underlying file system may have a different granularity than nanoseconds, diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index 309d9c3b07..b00b0609b2 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -1101,3 +1101,79 @@ test "chown" { defer dir.close(); try dir.chown(null, null); } + +test "File.Metadata" { + var tmp = tmpDir(.{}); + defer tmp.cleanup(); + + const file = try tmp.dir.createFile("test_file", .{ .read = true }); + defer file.close(); + + const metadata = try file.metadata(); + try testing.expect(metadata.kind() == .File); + try testing.expect(metadata.size() == 0); + _ = metadata.accessed(); + _ = metadata.modified(); + _ = metadata.created(); +} + +test "File.Permissions" { + if (builtin.os.tag == .wasi) + return error.SkipZigTest; + + var tmp = tmpDir(.{}); + defer tmp.cleanup(); + + const file = try tmp.dir.createFile("test_file", .{ .read = true }); + defer file.close(); + + const metadata = try file.metadata(); + var permissions = metadata.permissions(); + + try testing.expect(!permissions.readOnly()); + permissions.setReadOnly(true); + try testing.expect(permissions.readOnly()); + + try file.setPermissions(permissions); + const new_permissions = (try file.metadata()).permissions(); + try testing.expect(new_permissions.readOnly()); + + // Must be set to non-read-only to delete + permissions.setReadOnly(false); + try file.setPermissions(permissions); +} + +test "File.PermissionsUnix" { + if (builtin.os.tag == .windows or builtin.os.tag == .wasi) + return error.SkipZigTest; + + var tmp = tmpDir(.{}); + defer tmp.cleanup(); + + const file = try tmp.dir.createFile("test_file", .{ .mode = 0o666, .read = true }); + defer file.close(); + + const metadata = try file.metadata(); + var permissions = metadata.permissions(); + + permissions.setReadOnly(true); + try testing.expect(permissions.readOnly()); + try testing.expect(!permissions.inner.unixHas(.user, .write)); + permissions.inner.unixSet(.user, .{ .write = true }); + try testing.expect(!permissions.readOnly()); + try testing.expect(permissions.inner.unixHas(.user, .write)); + try testing.expect(permissions.inner.mode & 0o400 != 0); + + permissions.setReadOnly(true); + try file.setPermissions(permissions); + permissions = (try file.metadata()).permissions(); + try testing.expect(permissions.readOnly()); + + // Must be set to non-read-only to delete + permissions.setReadOnly(false); + try file.setPermissions(permissions); + + const permissions_unix = File.PermissionsUnix.unixNew(0o754); + try testing.expect(permissions_unix.unixHas(.user, .execute)); + try testing.expect(!permissions_unix.unixHas(.other, .execute)); +} diff --git a/lib/std/os/wasi.zig b/lib/std/os/wasi.zig index 029deca3fd..0b2538cb88 100644 --- a/lib/std/os/wasi.zig +++ b/lib/std/os/wasi.zig @@ -88,7 +88,7 @@ pub const mode_t = u32; pub const time_t = i64; // match https://github.com/CraneStation/wasi-libc -pub const timespec = struct { +pub const timespec = extern struct { tv_sec: time_t, tv_nsec: isize, |
