aboutsummaryrefslogtreecommitdiff
path: root/lib/std
diff options
context:
space:
mode:
authorVeikka Tuominen <git@vexu.eu>2022-02-14 12:33:49 +0200
committerGitHub <noreply@github.com>2022-02-14 12:33:49 +0200
commit90f2a8d9c5885cdb302757244a5bb2971fdbabe0 (patch)
tree336baece47cb908d26611d8d9048d1ecf5e2e7ef /lib/std
parent8ed792b640ef3601dfb773d670915d74fbbbad13 (diff)
parent11b4cc589c85b6330824ac9a64bfb0829cc1adb0 (diff)
downloadzig-90f2a8d9c5885cdb302757244a5bb2971fdbabe0.tar.gz
zig-90f2a8d9c5885cdb302757244a5bb2971fdbabe0.zip
Merge pull request #10486 from ominitay/metadata
std: Implement cross-platform metadata API
Diffstat (limited to 'lib/std')
-rw-r--r--lib/std/c/darwin.zig31
-rw-r--r--lib/std/c/freebsd.zig4
-rw-r--r--lib/std/c/haiku.zig2
-rw-r--r--lib/std/c/netbsd.zig4
-rw-r--r--lib/std/c/openbsd.zig4
-rw-r--r--lib/std/c/wasi.zig24
-rw-r--r--lib/std/fs.zig25
-rw-r--r--lib/std/fs/file.zig479
-rw-r--r--lib/std/fs/test.zig76
-rw-r--r--lib/std/os/wasi.zig2
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,