mirror of
https://github.com/ziglang/zig.git
synced 2026-02-12 20:37:54 +00:00
Merge pull request #10486 from ominitay/metadata
std: Implement cross-platform metadata API
This commit is contained in:
commit
90f2a8d9c5
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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));
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user