Merge pull request 'Linux: Nuke Stat bits in favour of Statx' (#30122) from The-King-of-Toasters/zig:statx into master

Reviewed-on: https://codeberg.org/ziglang/zig/pulls/30122
Reviewed-by: Alex Rønne Petersen <alex@alexrp.com>
This commit is contained in:
Alex Rønne Petersen 2025-12-14 05:25:29 +01:00
commit 2f2b097576
29 changed files with 355 additions and 1080 deletions

View file

@ -1533,12 +1533,19 @@ fn dirStatPathLinux(
dir.handle,
sub_path_posix,
flags,
linux.STATX_INO | linux.STATX_SIZE | linux.STATX_TYPE | linux.STATX_MODE | linux.STATX_ATIME | linux.STATX_MTIME | linux.STATX_CTIME,
.{ .TYPE = true, .MODE = true, .ATIME = true, .MTIME = true, .CTIME = true, .INO = true, .SIZE = true },
&statx,
);
switch (linux.errno(rc)) {
.SUCCESS => {
current_thread.endSyscall();
assert(statx.mask.TYPE);
assert(statx.mask.MODE);
assert(statx.mask.ATIME);
assert(statx.mask.MTIME);
assert(statx.mask.CTIME);
assert(statx.mask.INO);
assert(statx.mask.SIZE);
return statFromLinux(&statx);
},
.INTR => {
@ -1725,12 +1732,19 @@ fn fileStatLinux(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File
file.handle,
"",
linux.AT.EMPTY_PATH,
linux.STATX_INO | linux.STATX_SIZE | linux.STATX_TYPE | linux.STATX_MODE | linux.STATX_ATIME | linux.STATX_MTIME | linux.STATX_CTIME,
.{ .TYPE = true, .MODE = true, .ATIME = true, .MTIME = true, .CTIME = true, .INO = true, .SIZE = true },
&statx,
);
switch (linux.errno(rc)) {
.SUCCESS => {
current_thread.endSyscall();
assert(statx.mask.TYPE);
assert(statx.mask.MODE);
assert(statx.mask.ATIME);
assert(statx.mask.MTIME);
assert(statx.mask.CTIME);
assert(statx.mask.INO);
assert(statx.mask.SIZE);
return statFromLinux(&statx);
},
.INTR => {

View file

@ -7586,166 +7586,6 @@ pub const EAI = if (builtin.abi.isAndroid()) enum(c_int) {
pub const dl_iterate_phdr_callback = *const fn (info: *dl_phdr_info, size: usize, data: ?*anyopaque) callconv(.c) c_int;
pub const Stat = switch (native_os) {
.linux => switch (native_arch) {
.sparc64 => extern struct {
dev: u64,
__pad1: u16,
ino: ino_t,
mode: u32,
nlink: u32,
uid: u32,
gid: u32,
rdev: u64,
__pad2: u16,
size: off_t,
blksize: isize,
blocks: i64,
atim: timespec,
mtim: timespec,
ctim: timespec,
__reserved: [2]usize,
pub fn atime(self: @This()) timespec {
return self.atim;
}
pub fn mtime(self: @This()) timespec {
return self.mtim;
}
pub fn ctime(self: @This()) timespec {
return self.ctim;
}
},
.mips, .mipsel => if (builtin.target.abi.isMusl()) extern struct {
dev: dev_t,
__pad0: [2]i32,
ino: ino_t,
mode: mode_t,
nlink: nlink_t,
uid: uid_t,
gid: gid_t,
rdev: dev_t,
__pad1: [2]i32,
size: off_t,
atim: timespec,
mtim: timespec,
ctim: timespec,
blksize: blksize_t,
__pad3: i32,
blocks: blkcnt_t,
__pad4: [14]i32,
pub fn atime(self: @This()) timespec {
return self.atim;
}
pub fn mtime(self: @This()) timespec {
return self.mtim;
}
pub fn ctime(self: @This()) timespec {
return self.ctim;
}
} else extern struct {
dev: u32,
__pad0: [3]u32,
ino: ino_t,
mode: mode_t,
nlink: nlink_t,
uid: uid_t,
gid: gid_t,
rdev: u32,
__pad1: [3]u32,
size: off_t,
atim: timespec,
mtim: timespec,
ctim: timespec,
blksize: blksize_t,
__pad3: u32,
blocks: blkcnt_t,
__pad4: [14]u32,
pub fn atime(self: @This()) timespec {
return self.atim;
}
pub fn mtime(self: @This()) timespec {
return self.mtim;
}
pub fn ctime(self: @This()) timespec {
return self.ctim;
}
},
.mips64, .mips64el => if (builtin.target.abi.isMusl()) extern struct {
dev: dev_t,
__pad0: [3]i32,
ino: ino_t,
mode: mode_t,
nlink: nlink_t,
uid: uid_t,
gid: gid_t,
rdev: dev_t,
__pad1: [2]u32,
size: off_t,
__pad2: i32,
atim: timespec,
mtim: timespec,
ctim: timespec,
blksize: blksize_t,
__pad3: u32,
blocks: blkcnt_t,
__pad4: [14]i32,
pub fn atime(self: @This()) timespec {
return self.atim;
}
pub fn mtime(self: @This()) timespec {
return self.mtim;
}
pub fn ctime(self: @This()) timespec {
return self.ctim;
}
} else extern struct {
dev: dev_t,
__pad0: [3]u32,
ino: ino_t,
mode: mode_t,
nlink: nlink_t,
uid: uid_t,
gid: gid_t,
rdev: dev_t,
__pad1: [3]u32,
size: off_t,
atim: timespec,
mtim: timespec,
ctim: timespec,
blksize: blksize_t,
__pad3: u32,
blocks: blkcnt_t,
__pad4: [14]i32,
pub fn atime(self: @This()) timespec {
return self.atim;
}
pub fn mtime(self: @This()) timespec {
return self.mtim;
}
pub fn ctime(self: @This()) timespec {
return self.ctim;
}
},
else => std.os.linux.Stat, // libc stat is the same as kernel stat.
},
.emscripten => emscripten.Stat,
.wasi => extern struct {
// Match wasi-libc's `struct stat` in lib/libc/include/wasm-wasi-musl/__struct_stat.h
@ -10422,6 +10262,7 @@ pub const fstat = switch (native_os) {
else => private.fstat,
},
.netbsd => private.__fstat50,
.linux => {},
else => private.fstat,
};
@ -10430,8 +10271,12 @@ pub const fstatat = switch (native_os) {
.x86_64 => private.@"fstatat$INODE64",
else => private.fstatat,
},
.linux => {},
else => private.fstatat,
};
pub extern "c" fn statx(dirfd: fd_t, path: [*:0]const u8, flags: u32, mask: linux.STATX, buf: *linux.Statx) c_int;
pub extern "c" fn getpwent() ?*passwd;
pub extern "c" fn endpwent() void;
pub extern "c" fn setpwent() void;
@ -10505,8 +10350,6 @@ pub extern "c" fn inotify_init1(flags: c_uint) c_int;
pub extern "c" fn inotify_add_watch(fd: fd_t, pathname: [*:0]const u8, mask: u32) c_int;
pub extern "c" fn inotify_rm_watch(fd: fd_t, wd: c_int) c_int;
pub extern "c" fn fstat64(fd: fd_t, buf: *Stat) c_int;
pub extern "c" fn fstatat64(dirfd: fd_t, noalias path: [*:0]const u8, noalias stat_buf: *Stat, flags: u32) c_int;
pub extern "c" fn fallocate64(fd: fd_t, mode: c_int, offset: off_t, len: off_t) c_int;
pub extern "c" fn fopen64(noalias filename: [*:0]const u8, noalias modes: [*:0]const u8) ?*FILE;
pub extern "c" fn ftruncate64(fd: c_int, length: off_t) c_int;
@ -11552,7 +11395,6 @@ const private = struct {
extern "c" fn sigprocmask(how: c_int, noalias set: ?*const sigset_t, noalias oset: ?*sigset_t) c_int;
extern "c" fn socket(domain: c_uint, sock_type: c_uint, protocol: c_uint) c_int;
extern "c" fn socketpair(domain: c_uint, sock_type: c_uint, protocol: c_uint, sv: *[2]fd_t) c_int;
extern "c" fn stat(noalias path: [*:0]const u8, noalias buf: *Stat) c_int;
extern "c" fn sigaltstack(ss: ?*stack_t, old_ss: ?*stack_t) c_int;
extern "c" fn sysconf(sc: c_int) c_long;
extern "c" fn shm_open(name: [*:0]const u8, flag: c_int, mode: mode_t) c_int;

View file

@ -95,15 +95,14 @@ pub fn clone(
pub const ARCH = arch_bits.ARCH;
pub const HWCAP = arch_bits.HWCAP;
pub const SC = arch_bits.SC;
pub const Stat = arch_bits.Stat;
pub const VDSO = arch_bits.VDSO;
pub const blkcnt_t = arch_bits.blkcnt_t;
pub const blksize_t = arch_bits.blksize_t;
pub const dev_t = arch_bits.dev_t;
pub const ino_t = arch_bits.ino_t;
pub const mode_t = arch_bits.mode_t;
pub const nlink_t = arch_bits.nlink_t;
pub const off_t = arch_bits.off_t;
pub const blkcnt_t = u64;
pub const blksize_t = u32;
pub const dev_t = u64;
pub const ino_t = u64;
pub const mode_t = u32;
pub const nlink_t = u32;
pub const off_t = i64;
pub const time_t = arch_bits.time_t;
pub const user_desc = arch_bits.user_desc;
@ -2199,61 +2198,13 @@ pub fn accept4(fd: i32, noalias addr: ?*sockaddr, noalias len: ?*socklen_t, flag
return syscall4(.accept4, @as(usize, @bitCast(@as(isize, fd))), @intFromPtr(addr), @intFromPtr(len), flags);
}
pub fn fstat(fd: i32, stat_buf: *Stat) usize {
if (native_arch == .riscv32 or native_arch.isLoongArch()) {
// riscv32 and loongarch have made the interesting decision to not implement some of
// the older stat syscalls, including this one.
@compileError("No fstat syscall on this architecture.");
} else if (@hasField(SYS, "fstat64")) {
return syscall2(.fstat64, @as(usize, @bitCast(@as(isize, fd))), @intFromPtr(stat_buf));
} else {
return syscall2(.fstat, @as(usize, @bitCast(@as(isize, fd))), @intFromPtr(stat_buf));
}
}
pub fn stat(pathname: [*:0]const u8, statbuf: *Stat) usize {
if (native_arch == .riscv32 or native_arch.isLoongArch()) {
// riscv32 and loongarch have made the interesting decision to not implement some of
// the older stat syscalls, including this one.
@compileError("No stat syscall on this architecture.");
} else if (@hasField(SYS, "stat64")) {
return syscall2(.stat64, @intFromPtr(pathname), @intFromPtr(statbuf));
} else {
return syscall2(.stat, @intFromPtr(pathname), @intFromPtr(statbuf));
}
}
pub fn lstat(pathname: [*:0]const u8, statbuf: *Stat) usize {
if (native_arch == .riscv32 or native_arch.isLoongArch()) {
// riscv32 and loongarch have made the interesting decision to not implement some of
// the older stat syscalls, including this one.
@compileError("No lstat syscall on this architecture.");
} else if (@hasField(SYS, "lstat64")) {
return syscall2(.lstat64, @intFromPtr(pathname), @intFromPtr(statbuf));
} else {
return syscall2(.lstat, @intFromPtr(pathname), @intFromPtr(statbuf));
}
}
pub fn fstatat(dirfd: i32, path: [*:0]const u8, stat_buf: *Stat, flags: u32) usize {
if (native_arch == .riscv32 or native_arch.isLoongArch()) {
// riscv32 and loongarch have made the interesting decision to not implement some of
// the older stat syscalls, including this one.
@compileError("No fstatat syscall on this architecture.");
} else if (@hasField(SYS, "fstatat64")) {
return syscall4(.fstatat64, @as(usize, @bitCast(@as(isize, dirfd))), @intFromPtr(path), @intFromPtr(stat_buf), flags);
} else {
return syscall4(.fstatat, @as(usize, @bitCast(@as(isize, dirfd))), @intFromPtr(path), @intFromPtr(stat_buf), flags);
}
}
pub fn statx(dirfd: i32, path: [*:0]const u8, flags: u32, mask: u32, statx_buf: *Statx) usize {
pub fn statx(dirfd: i32, path: [*:0]const u8, flags: u32, mask: STATX, statx_buf: *Statx) usize {
return syscall5(
.statx,
@as(usize, @bitCast(@as(isize, dirfd))),
@intFromPtr(path),
flags,
mask,
@as(u32, @bitCast(mask)),
@intFromPtr(statx_buf),
);
}
@ -6940,97 +6891,162 @@ pub const utsname = extern struct {
};
pub const HOST_NAME_MAX = 64;
pub const STATX_TYPE = 0x0001;
pub const STATX_MODE = 0x0002;
pub const STATX_NLINK = 0x0004;
pub const STATX_UID = 0x0008;
pub const STATX_GID = 0x0010;
pub const STATX_ATIME = 0x0020;
pub const STATX_MTIME = 0x0040;
pub const STATX_CTIME = 0x0080;
pub const STATX_INO = 0x0100;
pub const STATX_SIZE = 0x0200;
pub const STATX_BLOCKS = 0x0400;
pub const STATX_BASIC_STATS = 0x07ff;
/// Flags used to request specific members in `Statx` be filled out.
/// The `Statx.mask` member will be updated with what information the kernel
/// returned. Callers must check this field since support varies by kernel
/// version and filesystem.
pub const STATX = packed struct(u32) {
/// Want `mode & S.IFMT`.
TYPE: bool = false,
/// Want `mode & ~S.IFMT`.
MODE: bool = false,
/// Want the `nlink` member.
NLINK: bool = false,
/// Want the `uid` member.
UID: bool = false,
/// Want the `gid` member.
GID: bool = false,
/// Want the `atime` member.
ATIME: bool = false,
/// Want the `mtime` member.
MTIME: bool = false,
/// Want the `ctime` member.
CTIME: bool = false,
/// Want the `ino` member.
INO: bool = false,
/// Want the `size` member.
SIZE: bool = false,
/// Want the `blocks` member.
BLOCKS: bool = false,
/// Want the `btime` member.
BTIME: bool = false,
/// Want the `mnt_id` member.
MNT_ID: bool = false,
/// Want the `dio_mem_align` and `dio_offset_align` members.
DIOALIGN: bool = false,
/// Want the `stx_mnt_id` member.
MNT_ID_UNIQUE: bool = false,
/// Want the `sub` member.
SUBVOL: bool = false,
/// Want the `atomic_write_unit_min`, `atomic_write_unit_max` and
/// `atomic_write_segments_max` members.
WRITE_ATOMIC: bool = false,
/// Want the `dio_read_offset_align` member.
DIO_READ_ALIGN: bool = false,
__pad: u13 = 0,
/// Reserved for future expansion; must not be set.
__RESERVED: bool = false,
pub const STATX_BTIME = 0x0800;
pub const BASIC_STATS: STATX = @bitCast(@as(u32, 0x7ff));
};
pub const STATX_ATTR_COMPRESSED = 0x0004;
pub const STATX_ATTR_IMMUTABLE = 0x0010;
pub const STATX_ATTR_APPEND = 0x0020;
pub const STATX_ATTR_NODUMP = 0x0040;
pub const STATX_ATTR_ENCRYPTED = 0x0800;
pub const STATX_ATTR_AUTOMOUNT = 0x1000;
/// Attributes about the state or features of a file as a bitmask.
/// Flags marked [I] correspond to the `FS_IOC_SETFLAGS` values semantically.
/// See [FS_IOC_SETFLAGS(2const)](https://man7.org/linux/man-pages/man2/FS_IOC_GETFLAGS.2const.html)
/// for more.
pub const STATX_ATTR = packed struct(u64) {
__pad1: u3 = 0,
/// [I] File is compressed by the fs.
COMPRESSED: bool = false,
__pad2: u1 = 0,
/// [I] File is marked immutable.
IMMUTABLE: bool = false,
/// [I] File is append-only.
APPEND: bool = false,
/// [I] File is not to be dumped.
NODUMP: bool = false,
/// [I] File requires a key to decrypt in the filesystem.
ENCRYPTED: bool = false,
/// File names a directory that triggers an automount.
AUTOMOUNT: bool = false,
/// File names the root of a mount.
MOUNT_ROOT: bool = false,
/// [I] File is protected by the `dm-verity` device.
VERITY: bool = false,
/// File is currently in the CPU direct access state.
/// Does not correspond to the per-inode DAX flag that some filesystems support.
DAX: bool = false,
/// File supports atomic write operations.
WRITE_ATOMIC: bool = false,
__pad3: u50 = 0,
};
pub const statx_timestamp = extern struct {
/// Number of seconds before or after `1970-01-01T00:00:00Z`.
sec: i64,
/// Number of nanoseconds (0..999,999,999) after `sec`.
nsec: u32,
// Reserved for future increases in resolution.
__pad1: u32,
};
/// Renamed to `Statx` to not conflict with the `statx` function.
pub const Statx = extern struct {
/// Mask of bits indicating filled fields
mask: u32,
/// Block size for filesystem I/O
/// Mask of bits indicating filled fields.
mask: STATX,
/// Block size for filesystem I/O.
blksize: u32,
/// Extra file attribute indicators
attributes: u64,
/// Number of hard links
/// Extra file attribute indicators.
attributes: STATX_ATTR,
/// Number of hard links.
nlink: u32,
/// User ID of owner
/// User ID of owner.
uid: uid_t,
/// Group ID of owner
/// Group ID of owner.
gid: gid_t,
/// File type and mode
/// File type and mode.
mode: u16,
__pad1: u16,
/// Inode number
__spare0: u16,
/// Inode number.
ino: u64,
/// Total size in bytes
/// Total size in bytes.
size: u64,
/// Number of 512B blocks allocated
/// Number of 512B blocks allocated.
blocks: u64,
/// Mask to show what's supported in `attributes`.
attributes_mask: u64,
/// Last access file timestamp
attributes_mask: STATX_ATTR,
/// Last access file timestamp.
atime: statx_timestamp,
/// Creation file timestamp
/// Creation file timestamp.
btime: statx_timestamp,
/// Last status change file timestamp
/// Last status change file timestamp.
ctime: statx_timestamp,
/// Last modification file timestamp
/// Last modification file timestamp.
mtime: statx_timestamp,
/// Major ID, if this file represents a device.
rdev_major: u32,
/// Minor ID, if this file represents a device.
rdev_minor: u32,
/// Major ID of the device containing the filesystem where this file resides.
dev_major: u32,
/// Minor ID of the device containing the filesystem where this file resides.
dev_minor: u32,
__pad2: [14]u64,
/// Mount ID
mnt_id: u64,
/// Memory buffer alignment for direct I/O.
dio_mem_align: u32,
/// File offset alignment for direct I/O.
dio_offset_align: u32,
/// Subvolume identifier.
subvol: u64,
/// Min atomic write unit in bytes.
atomic_write_unit_min: u32,
/// Max atomic write unit in bytes.
atomic_write_unit_max: u32,
/// Max atomic write segment count.
atomic_write_segments_max: u32,
/// File offset alignment for direct I/O reads.
dio_read_offset_align: u32,
/// Optimised max atomic write unit in bytes.
atomic_write_unit_max_opt: u32,
__spare2: [1]u32,
__spare3: [8]u64,
};
comptime {
assert(@sizeOf(Statx) == 0x100);
}
pub const addrinfo = extern struct {
flags: AI,
family: i32,
@ -9970,6 +9986,46 @@ pub const wrapped = struct {
}
}
pub const StatxError = std.posix.UnexpectedError || error{
/// Search permission is denied for one of the directories in `path`.
AccessDenied,
/// Too many symbolic links were encountered traversing `path`.
SymLinkLoop,
/// `path` is too long.
NameTooLong,
/// One of:
/// - A component of `path` does not exist.
/// - A component of `path` is not a directory.
/// - `path` is a relative and `dirfd` is not a directory file descriptor.
FileNotFound,
/// Insufficient memory is available.
SystemResources,
};
pub fn statx(dirfd: fd_t, path: [*:0]const u8, flags: u32, mask: STATX) StatxError!Statx {
const use_c = std.c.versionCheck(if (builtin.abi.isAndroid())
.{ .major = 30, .minor = 0, .patch = 0 }
else
.{ .major = 2, .minor = 28, .patch = 0 });
const sys = if (use_c) std.c else std.os.linux;
var stx = std.mem.zeroes(Statx);
const rc = sys.statx(dirfd, path, flags, mask, &stx);
return switch (sys.errno(rc)) {
.SUCCESS => stx,
.ACCES => error.AccessDenied,
.BADF => invalidApiUsage(),
.FAULT => invalidApiUsage(),
.INVAL => invalidApiUsage(),
.LOOP => error.SymLinkLoop,
.NAMETOOLONG => error.NameTooLong,
.NOENT => error.FileNotFound,
.NOTDIR => error.FileNotFound,
.NOMEM => error.SystemResources,
else => |err| unexpectedErrno(err),
};
}
const unexpectedErrno = std.posix.unexpectedErrno;
fn invalidApiUsage() error{Unexpected} {

View file

@ -958,7 +958,7 @@ pub fn statx(
fd: linux.fd_t,
path: [:0]const u8,
flags: u32,
mask: u32,
mask: linux.STATX,
buf: *linux.Statx,
) !*linux.io_uring_sqe {
const sqe = try self.get_sqe();
@ -2691,7 +2691,7 @@ test "statx" {
tmp.dir.fd,
path,
0,
linux.STATX_SIZE,
.{ .SIZE = true },
&buf,
);
try testing.expectEqual(linux.IORING_OP.STATX, sqe.opcode);
@ -2718,7 +2718,7 @@ test "statx" {
.flags = 0,
}, cqe);
try testing.expect(buf.mask & linux.STATX_SIZE == linux.STATX_SIZE);
try testing.expect(buf.mask.SIZE);
try testing.expectEqual(@as(u64, 6), buf.size);
}

View file

@ -143,43 +143,4 @@ pub const VDSO = struct {
pub const CGT_VER = "LINUX_2.6.39";
};
pub const blksize_t = i32;
pub const nlink_t = u32;
pub const time_t = i64;
pub const mode_t = u32;
pub const off_t = i64;
pub const ino_t = u64;
pub const dev_t = u64;
pub const blkcnt_t = i64;
// The `stat` definition used by the Linux kernel.
pub const Stat = extern struct {
dev: dev_t,
ino: ino_t,
mode: mode_t,
nlink: nlink_t,
uid: std.os.linux.uid_t,
gid: std.os.linux.gid_t,
rdev: dev_t,
__pad: u64,
size: off_t,
blksize: blksize_t,
__pad2: i32,
blocks: blkcnt_t,
atim: std.os.linux.timespec,
mtim: std.os.linux.timespec,
ctim: std.os.linux.timespec,
__unused: [2]u32,
pub fn atime(self: @This()) std.os.linux.timespec {
return self.atim;
}
pub fn mtime(self: @This()) std.os.linux.timespec {
return self.mtim;
}
pub fn ctime(self: @This()) std.os.linux.timespec {
return self.ctim;
}
};

View file

@ -179,43 +179,4 @@ pub const HWCAP = struct {
pub const EVTSTRM = 1 << 21;
};
pub const blksize_t = i32;
pub const nlink_t = u32;
pub const time_t = i32;
pub const mode_t = u32;
pub const off_t = i64;
pub const ino_t = u64;
pub const dev_t = u64;
pub const blkcnt_t = i64;
// The `stat` definition used by the Linux kernel.
pub const Stat = extern struct {
dev: dev_t,
__dev_padding: u32,
__ino_truncated: u32,
mode: mode_t,
nlink: nlink_t,
uid: std.os.linux.uid_t,
gid: std.os.linux.gid_t,
rdev: dev_t,
__rdev_padding: u32,
size: off_t,
blksize: blksize_t,
blocks: blkcnt_t,
atim: std.os.linux.timespec,
mtim: std.os.linux.timespec,
ctim: std.os.linux.timespec,
ino: ino_t,
pub fn atime(self: @This()) std.os.linux.timespec {
return self.atim;
}
pub fn mtime(self: @This()) std.os.linux.timespec {
return self.mtim;
}
pub fn ctime(self: @This()) std.os.linux.timespec {
return self.ctim;
}
};

View file

@ -119,45 +119,6 @@ pub fn clone() callconv(.naked) u32 {
);
}
pub const blksize_t = i32;
pub const nlink_t = u32;
pub const time_t = i64;
pub const mode_t = u32;
pub const off_t = i64;
pub const ino_t = u64;
pub const dev_t = u64;
pub const blkcnt_t = i64;
// The `stat` definition used by the Linux kernel.
pub const Stat = extern struct {
dev: dev_t,
ino: ino_t,
mode: mode_t,
nlink: nlink_t,
uid: std.os.linux.uid_t,
gid: std.os.linux.gid_t,
rdev: dev_t,
__pad: u32,
size: off_t,
blksize: blksize_t,
__pad2: i32,
blocks: blkcnt_t,
atim: std.os.linux.timespec,
mtim: std.os.linux.timespec,
ctim: std.os.linux.timespec,
__unused: [2]u32,
pub fn atime(self: @This()) std.os.linux.timespec {
return self.atim;
}
pub fn mtime(self: @This()) std.os.linux.timespec {
return self.mtim;
}
pub fn ctime(self: @This()) std.os.linux.timespec {
return self.ctim;
}
};
pub const VDSO = void;

View file

@ -420,10 +420,10 @@ pub const io_uring_sqe = extern struct {
fd: linux.fd_t,
path: [*:0]const u8,
flags: u32,
mask: u32,
mask: linux.STATX,
buf: *linux.Statx,
) void {
sqe.prep_rw(.STATX, fd, @intFromPtr(path), mask, @intFromPtr(buf));
sqe.prep_rw(.STATX, fd, @intFromPtr(path), @as(u32, @bitCast(mask)), @intFromPtr(buf));
sqe.rw_flags = flags;
}

View file

@ -125,46 +125,7 @@ pub fn clone() callconv(.naked) u64 {
);
}
pub const blksize_t = i32;
pub const nlink_t = u32;
pub const time_t = i64;
pub const mode_t = u32;
pub const off_t = i64;
pub const ino_t = u64;
pub const dev_t = u32;
pub const blkcnt_t = i64;
// The `stat` definition used by the Linux kernel.
pub const Stat = extern struct {
dev: dev_t,
ino: ino_t,
mode: mode_t,
nlink: nlink_t,
uid: std.os.linux.uid_t,
gid: std.os.linux.gid_t,
rdev: dev_t,
_pad1: u64,
size: off_t,
blksize: blksize_t,
_pad2: i32,
blocks: blkcnt_t,
atim: std.os.linux.timespec,
mtim: std.os.linux.timespec,
ctim: std.os.linux.timespec,
_pad3: [2]u32,
pub fn atime(self: @This()) std.os.linux.timespec {
return self.atim;
}
pub fn mtime(self: @This()) std.os.linux.timespec {
return self.mtim;
}
pub fn ctime(self: @This()) std.os.linux.timespec {
return self.ctim;
}
};
pub const VDSO = struct {
pub const CGT_SYM = "__vdso_clock_gettime";

View file

@ -142,45 +142,7 @@ pub fn restore_rt() callconv(.naked) noreturn {
);
}
pub const blksize_t = i32;
pub const nlink_t = u32;
pub const time_t = i32;
pub const mode_t = u32;
pub const off_t = i64;
pub const ino_t = u64;
pub const dev_t = u64;
pub const blkcnt_t = i64;
pub const Stat = extern struct {
dev: dev_t,
__pad: i16,
__ino_truncated: i32,
mode: mode_t,
nlink: nlink_t,
uid: std.os.linux.uid_t,
gid: std.os.linux.gid_t,
rdev: dev_t,
__pad2: i16,
size: off_t,
blksize: blksize_t,
blocks: blkcnt_t,
atim: std.os.linux.timespec,
mtim: std.os.linux.timespec,
ctim: std.os.linux.timespec,
ino: ino_t,
pub fn atime(self: @This()) std.os.linux.timespec {
return self.atim;
}
pub fn mtime(self: @This()) std.os.linux.timespec {
return self.mtim;
}
pub fn ctime(self: @This()) std.os.linux.timespec {
return self.ctim;
}
};
// No VDSO used as of glibc 112a0ae18b831bf31f44d81b82666980312511d6.
pub const VDSO = void;

View file

@ -230,43 +230,4 @@ pub const VDSO = struct {
pub const CGT_VER = "LINUX_2.6";
};
pub const blksize_t = u32;
pub const nlink_t = u32;
pub const time_t = i32;
pub const mode_t = u32;
pub const off_t = i64;
pub const ino_t = u64;
pub const dev_t = u64;
pub const blkcnt_t = i64;
// The `stat64` definition used by the Linux kernel.
pub const Stat = extern struct {
dev: dev_t,
__pad0: [2]u32, // -1 because our dev_t is u64 (kernel dev_t is really u32).
ino: ino_t,
mode: mode_t,
nlink: nlink_t,
uid: std.os.linux.uid_t,
gid: std.os.linux.gid_t,
rdev: dev_t,
__pad1: [2]u32,
size: off_t,
atim: std.os.linux.timespec,
mtim: std.os.linux.timespec,
ctim: std.os.linux.timespec,
blksize: blksize_t,
__pad3: u32,
blocks: blkcnt_t,
pub fn atime(self: @This()) std.os.linux.timespec {
return self.atim;
}
pub fn mtime(self: @This()) std.os.linux.timespec {
return self.mtim;
}
pub fn ctime(self: @This()) std.os.linux.timespec {
return self.ctim;
}
};

View file

@ -184,55 +184,4 @@ pub const VDSO = struct {
pub const CGT_VER = "LINUX_2.6";
};
pub const blksize_t = u32;
pub const nlink_t = u32;
pub const time_t = i32;
pub const mode_t = u32;
pub const off_t = i64;
pub const ino_t = u64;
pub const dev_t = u64;
pub const blkcnt_t = i64;
// The `stat` definition used by the Linux kernel.
pub const Stat = extern struct {
dev: dev_t,
__pad0: [2]u32, // -1 because our dev_t is u64 (kernel dev_t is really u32).
ino: ino_t,
mode: mode_t,
nlink: nlink_t,
uid: std.os.linux.uid_t,
gid: std.os.linux.gid_t,
rdev: dev_t,
__pad1: [2]u32, // -1 because our dev_t is u64 (kernel dev_t is really u32).
size: off_t,
atim: u32,
atim_nsec: u32,
mtim: u32,
mtim_nsec: u32,
ctim: u32,
ctim_nsec: u32,
blksize: blksize_t,
__pad3: u32,
blocks: blkcnt_t,
pub fn atime(self: @This()) std.os.linux.timespec {
return .{
.sec = self.atim,
.nsec = self.atim_nsec,
};
}
pub fn mtime(self: @This()) std.os.linux.timespec {
return .{
.sec = self.mtim,
.nsec = self.mtim_nsec,
};
}
pub fn ctime(self: @This()) std.os.linux.timespec {
return .{
.sec = self.ctim,
.nsec = self.ctim_nsec,
};
}
};

View file

@ -184,55 +184,4 @@ pub const VDSO = struct {
pub const CGT_VER = "LINUX_2.6";
};
pub const blksize_t = u32;
pub const nlink_t = u32;
pub const time_t = i32;
pub const mode_t = u32;
pub const off_t = i64;
pub const ino_t = u64;
pub const dev_t = u64;
pub const blkcnt_t = i64;
// The `stat` definition used by the Linux kernel.
pub const Stat = extern struct {
dev: dev_t,
__pad0: [2]u32, // -1 because our dev_t is u64 (kernel dev_t is really u32).
ino: ino_t,
mode: mode_t,
nlink: nlink_t,
uid: std.os.linux.uid_t,
gid: std.os.linux.gid_t,
rdev: dev_t,
__pad1: [2]u32, // -1 because our dev_t is u64 (kernel dev_t is really u32).
size: off_t,
atim: u32,
atim_nsec: u32,
mtim: u32,
mtim_nsec: u32,
ctim: u32,
ctim_nsec: u32,
blksize: blksize_t,
__pad3: u32,
blocks: blkcnt_t,
pub fn atime(self: @This()) std.os.linux.timespec {
return .{
.sec = self.atim,
.nsec = self.atim_nsec,
};
}
pub fn mtime(self: @This()) std.os.linux.timespec {
return .{
.sec = self.mtim,
.nsec = self.mtim_nsec,
};
}
pub fn ctime(self: @This()) std.os.linux.timespec {
return .{
.sec = self.ctim,
.nsec = self.ctim_nsec,
};
}
};

View file

@ -131,43 +131,4 @@ pub fn clone() callconv(.naked) u32 {
pub const VDSO = void;
pub const blksize_t = u32;
pub const nlink_t = u32;
pub const time_t = i32;
pub const mode_t = u32;
pub const off_t = i64;
pub const ino_t = u64;
pub const dev_t = u64;
pub const blkcnt_t = i64;
// The `stat64` definition used by the Linux kernel.
pub const Stat = extern struct {
dev: dev_t,
ino: ino_t,
mode: mode_t,
nlink: nlink_t,
uid: std.os.linux.uid_t,
gid: std.os.linux.gid_t,
rdev: dev_t,
_pad0: [2]u32,
size: off_t,
blksize: blksize_t,
_pad1: u32,
blocks: blkcnt_t,
atim: std.os.linux.timespec,
mtim: std.os.linux.timespec,
ctim: std.os.linux.timespec,
_pad2: [2]u32,
pub fn atime(self: @This()) std.os.linux.timespec {
return self.atim;
}
pub fn mtime(self: @This()) std.os.linux.timespec {
return self.mtim;
}
pub fn ctime(self: @This()) std.os.linux.timespec {
return self.ctim;
}
};

View file

@ -269,42 +269,4 @@ pub const VDSO = struct {
pub const CGT_VER = "LINUX_2.6.15";
};
pub const blksize_t = i32;
pub const nlink_t = u32;
pub const time_t = i32;
pub const mode_t = u32;
pub const off_t = i64;
pub const ino_t = u64;
pub const dev_t = u64;
pub const blkcnt_t = i64;
// The `stat` definition used by the Linux kernel.
pub const Stat = extern struct {
dev: dev_t,
ino: ino_t,
mode: mode_t,
nlink: nlink_t,
uid: std.os.linux.uid_t,
gid: std.os.linux.gid_t,
rdev: dev_t,
__rdev_padding: i16,
size: off_t,
blksize: blksize_t,
blocks: blkcnt_t,
atim: std.os.linux.timespec,
mtim: std.os.linux.timespec,
ctim: std.os.linux.timespec,
__unused: [2]u32,
pub fn atime(self: @This()) std.os.linux.timespec {
return self.atim;
}
pub fn mtime(self: @This()) std.os.linux.timespec {
return self.mtim;
}
pub fn ctime(self: @This()) std.os.linux.timespec {
return self.ctim;
}
};

View file

@ -254,41 +254,4 @@ pub const VDSO = struct {
pub const CGT_VER = "LINUX_2.6.15";
};
pub const blksize_t = i64;
pub const nlink_t = u64;
pub const time_t = i64;
pub const mode_t = u32;
pub const off_t = i64;
pub const ino_t = u64;
pub const dev_t = u64;
pub const blkcnt_t = i64;
// The `stat` definition used by the Linux kernel.
pub const Stat = extern struct {
dev: dev_t,
ino: ino_t,
nlink: nlink_t,
mode: mode_t,
uid: std.os.linux.uid_t,
gid: std.os.linux.gid_t,
rdev: dev_t,
size: off_t,
blksize: blksize_t,
blocks: blkcnt_t,
atim: std.os.linux.timespec,
mtim: std.os.linux.timespec,
ctim: std.os.linux.timespec,
__unused: [3]u64,
pub fn atime(self: @This()) std.os.linux.timespec {
return self.atim;
}
pub fn mtime(self: @This()) std.os.linux.timespec {
return self.mtim;
}
pub fn ctime(self: @This()) std.os.linux.timespec {
return self.ctim;
}
};

View file

@ -124,46 +124,7 @@ pub fn clone() callconv(.naked) u32 {
);
}
pub const blksize_t = i32;
pub const nlink_t = u32;
pub const time_t = i64;
pub const mode_t = u32;
pub const off_t = i64;
pub const ino_t = u64;
pub const dev_t = u64;
pub const blkcnt_t = i64;
// The `stat` definition used by the Linux kernel.
pub const Stat = extern struct {
dev: dev_t,
ino: ino_t,
mode: mode_t,
nlink: nlink_t,
uid: std.os.linux.uid_t,
gid: std.os.linux.gid_t,
rdev: dev_t,
__pad: u32,
size: off_t,
blksize: blksize_t,
__pad2: i32,
blocks: blkcnt_t,
atim: std.os.linux.timespec,
mtim: std.os.linux.timespec,
ctim: std.os.linux.timespec,
__unused: [2]u32,
pub fn atime(self: @This()) std.os.linux.timespec {
return self.atim;
}
pub fn mtime(self: @This()) std.os.linux.timespec {
return self.mtim;
}
pub fn ctime(self: @This()) std.os.linux.timespec {
return self.ctim;
}
};
pub const VDSO = struct {
pub const CGT_SYM = "__vdso_clock_gettime";

View file

@ -124,46 +124,7 @@ pub fn clone() callconv(.naked) u64 {
);
}
pub const blksize_t = i32;
pub const nlink_t = u32;
pub const time_t = i64;
pub const mode_t = u32;
pub const off_t = i64;
pub const ino_t = u64;
pub const dev_t = u64;
pub const blkcnt_t = i64;
// The `stat` definition used by the Linux kernel.
pub const Stat = extern struct {
dev: dev_t,
ino: ino_t,
mode: mode_t,
nlink: nlink_t,
uid: std.os.linux.uid_t,
gid: std.os.linux.gid_t,
rdev: dev_t,
__pad: u64,
size: off_t,
blksize: blksize_t,
__pad2: i32,
blocks: blkcnt_t,
atim: std.os.linux.timespec,
mtim: std.os.linux.timespec,
ctim: std.os.linux.timespec,
__unused: [2]u32,
pub fn atime(self: @This()) std.os.linux.timespec {
return self.atim;
}
pub fn mtime(self: @This()) std.os.linux.timespec {
return self.mtim;
}
pub fn ctime(self: @This()) std.os.linux.timespec {
return self.ctim;
}
};
pub const VDSO = struct {
pub const CGT_SYM = "__vdso_clock_gettime";

View file

@ -152,44 +152,7 @@ pub fn restore_rt() callconv(.naked) noreturn {
);
}
pub const blksize_t = i64;
pub const nlink_t = u64;
pub const time_t = i64;
pub const mode_t = u32;
pub const off_t = i64;
pub const ino_t = u64;
pub const dev_t = u64;
pub const blkcnt_t = i64;
// The `stat` definition used by the Linux kernel.
pub const Stat = extern struct {
dev: dev_t,
ino: ino_t,
nlink: nlink_t,
mode: mode_t,
uid: std.os.linux.uid_t,
gid: std.os.linux.gid_t,
rdev: dev_t,
size: off_t,
atim: std.os.linux.timespec,
mtim: std.os.linux.timespec,
ctim: std.os.linux.timespec,
blksize: blksize_t,
blocks: blkcnt_t,
__unused: [3]c_ulong,
pub fn atime(self: @This()) std.os.linux.timespec {
return self.atim;
}
pub fn mtime(self: @This()) std.os.linux.timespec {
return self.mtim;
}
pub fn ctime(self: @This()) std.os.linux.timespec {
return self.ctim;
}
};
pub const VDSO = struct {
pub const CGT_SYM = "__kernel_clock_gettime";

View file

@ -228,46 +228,4 @@ pub const VDSO = struct {
pub const CGT_VER = "LINUX_2.6";
};
pub const off_t = i64;
pub const ino_t = u64;
pub const time_t = i64;
pub const mode_t = u32;
pub const dev_t = u64;
pub const nlink_t = u32;
pub const blksize_t = i64;
pub const blkcnt_t = i64;
// The `stat64` definition used by the kernel.
pub const Stat = extern struct {
dev: dev_t,
ino: ino_t,
nlink: nlink_t,
_pad: i32,
mode: mode_t,
uid: std.os.linux.uid_t,
gid: std.os.linux.gid_t,
__pad0: u32,
rdev: dev_t,
size: i64,
blksize: blksize_t,
blocks: blkcnt_t,
atim: std.os.linux.timespec,
mtim: std.os.linux.timespec,
ctim: std.os.linux.timespec,
__unused: [3]u64,
pub fn atime(self: @This()) std.os.linux.timespec {
return self.atim;
}
pub fn mtime(self: @This()) std.os.linux.timespec {
return self.mtim;
}
pub fn ctime(self: @This()) std.os.linux.timespec {
return self.ctim;
}
};

View file

@ -10,8 +10,6 @@ const expectEqual = std.testing.expectEqual;
const fs = std.fs;
test "fallocate" {
if (builtin.cpu.arch.isMIPS64() and (builtin.abi == .gnuabin32 or builtin.abi == .muslabin32)) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/23809
var tmp = std.testing.tmpDir(.{});
defer tmp.cleanup();
@ -84,26 +82,22 @@ test "statx" {
var file = try tmp.dir.createFile(tmp_file_name, .{});
defer file.close();
var statx_buf: linux.Statx = undefined;
switch (linux.errno(linux.statx(file.handle, "", linux.AT.EMPTY_PATH, linux.STATX_BASIC_STATS, &statx_buf))) {
var buf: linux.Statx = undefined;
switch (linux.errno(linux.statx(file.handle, "", linux.AT.EMPTY_PATH, .BASIC_STATS, &buf))) {
.SUCCESS => {},
else => unreachable,
}
if (builtin.cpu.arch == .riscv32 or builtin.cpu.arch.isLoongArch()) return error.SkipZigTest; // No fstatat, so the rest of the test is meaningless.
var stat_buf: linux.Stat = undefined;
switch (linux.errno(linux.fstatat(file.handle, "", &stat_buf, linux.AT.EMPTY_PATH))) {
.SUCCESS => {},
else => unreachable,
}
try expect(stat_buf.mode == statx_buf.mode);
try expect(@as(u32, @bitCast(stat_buf.uid)) == statx_buf.uid);
try expect(@as(u32, @bitCast(stat_buf.gid)) == statx_buf.gid);
try expect(@as(u64, @bitCast(@as(i64, stat_buf.size))) == statx_buf.size);
try expect(@as(u64, @bitCast(@as(i64, stat_buf.blksize))) == statx_buf.blksize);
try expect(@as(u64, @bitCast(@as(i64, stat_buf.blocks))) == statx_buf.blocks);
const uid = linux.getuid();
const gid = linux.getgid();
if (buf.mask.MODE)
try expectEqual(@as(linux.mode_t, linux.S.IFREG), buf.mode & linux.S.IFMT);
if (buf.mask.UID)
try expectEqual(uid, buf.uid);
if (buf.mask.GID)
try expectEqual(gid, buf.gid);
if (buf.mask.SIZE)
try expectEqual(@as(u64, 0), buf.size);
}
test "user and group ids" {

View file

@ -132,14 +132,7 @@ pub fn restore_rt() callconv(.naked) noreturn {
}
}
pub const mode_t = u32;
pub const time_t = i32;
pub const nlink_t = u32;
pub const blksize_t = i32;
pub const blkcnt_t = i32;
pub const off_t = i64;
pub const ino_t = u64;
pub const dev_t = u64;
pub const VDSO = struct {
pub const CGT_SYM = "__vdso_clock_gettime";
@ -155,36 +148,3 @@ pub const ARCH = struct {
pub const GET_FS = 0x1003;
pub const GET_GS = 0x1004;
};
// The `stat` definition used by the Linux kernel.
pub const Stat = extern struct {
dev: dev_t,
ino: ino_t,
nlink: nlink_t,
mode: mode_t,
uid: std.os.linux.uid_t,
gid: std.os.linux.gid_t,
__pad0: u32,
rdev: dev_t,
size: off_t,
blksize: blksize_t,
blocks: i64,
atim: std.os.linux.timespec,
mtim: std.os.linux.timespec,
ctim: std.os.linux.timespec,
__unused: [3]i32,
pub fn atime(self: @This()) std.os.linux.timespec {
return self.atim;
}
pub fn mtime(self: @This()) std.os.linux.timespec {
return self.mtim;
}
pub fn ctime(self: @This()) std.os.linux.timespec {
return self.ctim;
}
};

View file

@ -195,46 +195,7 @@ pub const VDSO = struct {
pub const CGT_VER = "LINUX_2.6";
};
pub const blksize_t = i32;
pub const nlink_t = u32;
pub const time_t = i32;
pub const mode_t = u32;
pub const off_t = i64;
pub const ino_t = u64;
pub const dev_t = u64;
pub const blkcnt_t = i64;
// The `stat` definition used by the Linux kernel.
pub const Stat = extern struct {
dev: dev_t,
__dev_padding: u32,
__ino_truncated: u32,
mode: mode_t,
nlink: nlink_t,
uid: std.os.linux.uid_t,
gid: std.os.linux.gid_t,
rdev: dev_t,
__rdev_padding: u32,
size: off_t,
blksize: blksize_t,
blocks: blkcnt_t,
atim: std.os.linux.timespec,
mtim: std.os.linux.timespec,
ctim: std.os.linux.timespec,
ino: ino_t,
pub fn atime(self: @This()) std.os.linux.timespec {
return self.atim;
}
pub fn mtime(self: @This()) std.os.linux.timespec {
return self.mtim;
}
pub fn ctime(self: @This()) std.os.linux.timespec {
return self.ctim;
}
};
pub const user_desc = extern struct {
entry_number: u32,

View file

@ -132,14 +132,7 @@ pub fn restore_rt() callconv(.naked) noreturn {
}
}
pub const mode_t = u64;
pub const time_t = i64;
pub const nlink_t = u64;
pub const blksize_t = i64;
pub const blkcnt_t = i64;
pub const off_t = i64;
pub const ino_t = u64;
pub const dev_t = u64;
pub const VDSO = struct {
pub const CGT_SYM = "__vdso_clock_gettime";
@ -155,36 +148,3 @@ pub const ARCH = struct {
pub const GET_FS = 0x1003;
pub const GET_GS = 0x1004;
};
// The `stat` definition used by the Linux kernel.
pub const Stat = extern struct {
dev: dev_t,
ino: ino_t,
nlink: u64,
mode: u32,
uid: std.os.linux.uid_t,
gid: std.os.linux.gid_t,
__pad0: u32,
rdev: dev_t,
size: off_t,
blksize: i64,
blocks: i64,
atim: std.os.linux.timespec,
mtim: std.os.linux.timespec,
ctim: std.os.linux.timespec,
__unused: [3]i64,
pub fn atime(self: @This()) std.os.linux.timespec {
return self.atim;
}
pub fn mtime(self: @This()) std.os.linux.timespec {
return self.mtim;
}
pub fn ctime(self: @This()) std.os.linux.timespec {
return self.ctim;
}
};

View file

@ -119,7 +119,18 @@ pub const STDIN_FILENO = system.STDIN_FILENO;
pub const STDOUT_FILENO = system.STDOUT_FILENO;
pub const SYS = system.SYS;
pub const Sigaction = system.Sigaction;
pub const Stat = system.Stat;
pub const Stat = switch (native_os) {
// Has no concept of `stat`.
.windows => void,
// The `stat` bits/wrappers are removed due to having to maintain the
// different varying `struct stat`s per target and libc, leading to runtime
// errors.
//
// Users targeting linux should add a comptime check and use `statx`,
// similar to how `std.fs.File.stat` does.
.linux => void,
else => system.Stat,
};
pub const T = system.T;
pub const TCP = system.TCP;
pub const VDSO = system.VDSO;
@ -480,15 +491,21 @@ fn fchmodat2(dirfd: fd_t, path: []const u8, mode: mode_t, flags: u32) FChmodAtEr
}
defer close(pathfd);
const stat = fstatatZ(pathfd, "", AT.EMPTY_PATH) catch |err| switch (err) {
const path_mode = if (linux.wrapped.statx(
pathfd,
"",
AT.EMPTY_PATH,
.{ .TYPE = true },
)) |stx| blk: {
assert(stx.mask.TYPE);
break :blk stx.mode;
} else |err| switch (err) {
error.NameTooLong => unreachable,
error.FileNotFound => unreachable,
error.Streaming => unreachable,
error.BadPathName => return error.Unexpected,
error.Canceled => return error.Canceled,
else => |e| return e,
};
if ((stat.mode & S.IFMT) == S.IFLNK)
// Even though we only wanted TYPE, the kernel can still fill in the additional bits.
if ((path_mode & S.IFMT) == S.IFLNK)
return error.OperationNotSupported;
var procfs_buf: ["/proc/self/fd/-2147483648\x00".len]u8 = undefined;
@ -3843,13 +3860,9 @@ pub fn fstat(fd: fd_t) FStatError!Stat {
if (native_os == .wasi and !builtin.link_libc) {
return Stat.fromFilestat(try std.os.fstat_wasi(fd));
}
if (native_os == .windows) {
@compileError("fstat is not yet implemented on Windows");
}
const fstat_sym = if (lfs64_abi) system.fstat64 else system.fstat;
var stat = mem.zeroes(Stat);
switch (errno(fstat_sym(fd, &stat))) {
switch (errno(system.fstat(fd, &stat))) {
.SUCCESS => return stat,
.INVAL => unreachable,
.BADF => unreachable, // Always a race condition.
@ -3889,9 +3902,8 @@ pub fn fstatatZ(dirfd: fd_t, pathname: [*:0]const u8, flags: u32) FStatAtError!S
@compileError("use std.Io instead");
}
const fstatat_sym = if (lfs64_abi) system.fstatat64 else system.fstatat;
var stat = mem.zeroes(Stat);
switch (errno(fstatat_sym(dirfd, pathname, &stat, flags))) {
switch (errno(system.fstatat(dirfd, pathname, &stat, flags))) {
.SUCCESS => return stat,
.INVAL => unreachable,
.BADF => unreachable, // Always a race condition.

View file

@ -16,6 +16,7 @@ const AtomicRmwOp = std.builtin.AtomicRmwOp;
const AtomicOrder = std.builtin.AtomicOrder;
const native_os = builtin.target.os.tag;
const tmpDir = std.testing.tmpDir;
const AT = posix.AT;
// NOTE: several additional tests are in test/standalone/posix/. Any tests that mutate
// process-wide POSIX state (cwd, signals, etc) cannot be Zig unit tests and should be over there.
@ -123,10 +124,24 @@ fn testReadlink(target_path: []const u8, symlink_path: []const u8) !void {
try expect(mem.eql(u8, target_path, given));
}
test "linkat with different directories" {
if ((builtin.cpu.arch == .riscv32 or builtin.cpu.arch.isLoongArch()) and builtin.os.tag == .linux and !builtin.link_libc) return error.SkipZigTest; // No `fstatat()`.
if (builtin.cpu.arch.isMIPS64()) return error.SkipZigTest; // `nstat.nlink` assertion is failing with LLVM 20+ for unclear reasons.
fn getLinkInfo(fd: posix.fd_t) !struct { posix.ino_t, posix.nlink_t } {
if (native_os == .linux) {
const stx = try linux.wrapped.statx(
fd,
"",
posix.AT.EMPTY_PATH,
.{ .INO = true, .NLINK = true },
);
std.debug.assert(stx.mask.INO);
std.debug.assert(stx.mask.NLINK);
return .{ stx.ino, stx.nlink };
}
const st = try posix.fstat(fd);
return .{ st.ino, st.nlink };
}
test "linkat with different directories" {
switch (native_os) {
.wasi, .linux, .illumos => {},
else => return error.SkipZigTest,
@ -153,19 +168,49 @@ test "linkat with different directories" {
defer nfd.close();
{
const estat = try posix.fstat(efd.handle);
const nstat = try posix.fstat(nfd.handle);
try testing.expectEqual(estat.ino, nstat.ino);
try testing.expectEqual(@as(@TypeOf(nstat.nlink), 2), nstat.nlink);
const eino, _ = try getLinkInfo(efd.handle);
const nino, const nlink = try getLinkInfo(nfd.handle);
try testing.expectEqual(eino, nino);
try testing.expectEqual(@as(posix.nlink_t, 2), nlink);
}
// Test 2: remove link
try posix.unlinkat(subdir.fd, link_name, 0);
_, const elink = try getLinkInfo(efd.handle);
try testing.expectEqual(@as(posix.nlink_t, 1), elink);
}
{
const estat = try posix.fstat(efd.handle);
try testing.expectEqual(@as(@TypeOf(estat.nlink), 1), estat.nlink);
}
test "fstatat" {
if (posix.Stat == void) return error.SkipZigTest;
if (native_os == .wasi and !builtin.link_libc) return error.SkipZigTest;
var tmp = tmpDir(.{});
defer tmp.cleanup();
// create dummy file
const contents = "nonsense";
try tmp.dir.writeFile(.{ .sub_path = "file.txt", .data = contents });
// fetch file's info on the opened fd directly
const file = try tmp.dir.openFile("file.txt", .{});
const stat = try posix.fstat(file.handle);
defer file.close();
// now repeat but using `fstatat` instead
const statat = try posix.fstatat(tmp.dir.fd, "file.txt", posix.AT.SYMLINK_NOFOLLOW);
try expectEqual(stat.dev, statat.dev);
try expectEqual(stat.ino, statat.ino);
try expectEqual(stat.nlink, statat.nlink);
try expectEqual(stat.mode, statat.mode);
try expectEqual(stat.uid, statat.uid);
try expectEqual(stat.gid, statat.gid);
try expectEqual(stat.rdev, statat.rdev);
try expectEqual(stat.size, statat.size);
try expectEqual(stat.blksize, statat.blksize);
// The stat.blocks/statat.blocks count is managed by the filesystem and may
// change if the file is stored in a journal or "inline".
// try expectEqual(stat.blocks, statat.blocks);
}
test "readlinkat" {
@ -906,14 +951,31 @@ test "pwrite with empty buffer" {
try expectEqual(rc, 0);
}
fn getFileMode(dir: posix.fd_t, path: []const u8) !posix.mode_t {
const path_z = try posix.toPosixPath(path);
const mode: posix.mode_t = if (native_os == .linux) blk: {
const stx = try linux.wrapped.statx(
dir,
&path_z,
posix.AT.SYMLINK_NOFOLLOW,
.{ .MODE = true },
);
std.debug.assert(stx.mask.MODE);
break :blk stx.mode;
} else blk: {
const st = try posix.fstatatZ(dir, &path_z, posix.AT.SYMLINK_NOFOLLOW);
break :blk st.mode;
};
return mode & 0b111_111_111;
}
fn expectMode(dir: posix.fd_t, file: []const u8, mode: posix.mode_t) !void {
const st = try posix.fstatat(dir, file, posix.AT.SYMLINK_NOFOLLOW);
try expectEqual(mode, st.mode & 0b111_111_111);
const actual = try getFileMode(dir, file);
try expectEqual(mode, actual & 0b111_111_111);
}
test "fchmodat smoke test" {
if (builtin.cpu.arch.isMIPS64() and (builtin.abi == .gnuabin32 or builtin.abi == .muslabin32)) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/23808
if (!std.fs.has_executable_bit) return error.SkipZigTest;
var tmp = tmpDir(.{});
@ -928,13 +990,8 @@ test "fchmodat smoke test" {
);
posix.close(fd);
if ((builtin.cpu.arch == .riscv32 or builtin.cpu.arch.isLoongArch()) and builtin.os.tag == .linux and !builtin.link_libc) return error.SkipZigTest; // No `fstatat()`.
try posix.symlinkat("regfile", tmp.dir.fd, "symlink");
const sym_mode = blk: {
const st = try posix.fstatat(tmp.dir.fd, "symlink", posix.AT.SYMLINK_NOFOLLOW);
break :blk st.mode & 0b111_111_111;
};
const sym_mode = try getFileMode(tmp.dir.fd, "symlink");
try posix.fchmodat(tmp.dir.fd, "regfile", 0o640, 0);
try expectMode(tmp.dir.fd, "regfile", 0o640);

View file

@ -54,6 +54,20 @@ pub fn init(file: std.Io.File, gpa: std.mem.Allocator) !MappedFile {
},
};
}
if (is_linux) {
const statx = try linux.wrapped.statx(
mf.file.handle,
"",
std.posix.AT.EMPTY_PATH,
.{ .TYPE = true, .SIZE = true, .BLOCKS = true },
);
assert(statx.mask.TYPE);
assert(statx.mask.SIZE);
assert(statx.mask.BLOCKS);
if (!std.posix.S.ISREG(statx.mode)) return error.PathAlreadyExists;
break :stat .{ statx.size, @max(std.heap.pageSize(), statx.blksize) };
}
const stat = try std.posix.fstat(mf.file.handle);
if (!std.posix.S.ISREG(stat.mode)) return error.PathAlreadyExists;
break :stat .{ @bitCast(stat.size), @max(std.heap.pageSize(), stat.blksize) };

View file

@ -23,16 +23,19 @@ const c_string = @cImport(
// Version of glibc this test is being built to run against
const glibc_ver = builtin.os.versionRange().gnuLibCVersion().?;
extern "c" fn fstatat(dirfd: i32, path: [*:0]const u8, buf: [*]const u8, flag: u32) c_int;
extern "c" fn stat(noalias path: [*:0]const u8, noalias buf: [*]const u8) c_int;
// PR #17034 - fstat moved between libc_nonshared and libc
fn checkStat() !void {
const cwdFd = std.fs.cwd().fd;
var stat = std.mem.zeroes(std.c.Stat);
var result = std.c.fstatat(cwdFd, "a_file_that_definitely_does_not_exist", &stat, 0);
var buf: [256]u8 = @splat(0);
var result = fstatat(cwdFd, "a_file_that_definitely_does_not_exist", &buf, 0);
assert(result == -1);
assert(std.posix.errno(result) == .NOENT);
result = std.c.stat("a_file_that_definitely_does_not_exist", &stat);
result = stat("a_file_that_definitely_does_not_exist", &buf);
assert(result == -1);
assert(std.posix.errno(result) == .NOENT);
}

View file

@ -48,20 +48,29 @@ fn test_symlink(a: std.mem.Allocator, tmp: std.testing.TmpDir) !void {
try std.testing.expectEqualStrings(target_name, given);
}
fn getLinkInfo(fd: std.posix.fd_t) !struct { std.posix.ino_t, std.posix.nlink_t } {
if (builtin.target.os.tag == .linux) {
const stx = try std.os.linux.wrapped.statx(
fd,
"",
std.posix.AT.EMPTY_PATH,
.{ .INO = true, .NLINK = true },
);
std.debug.assert(stx.mask.INO);
std.debug.assert(stx.mask.NLINK);
return .{ stx.ino, stx.nlink };
}
const st = try std.posix.fstat(fd);
return .{ st.ino, st.nlink };
}
fn test_link(tmp: std.testing.TmpDir) !void {
switch (builtin.target.os.tag) {
.linux, .illumos => {},
else => return,
}
if ((builtin.cpu.arch == .riscv32 or builtin.cpu.arch.isLoongArch()) and builtin.target.os.tag == .linux and !builtin.link_libc) {
return; // No `fstat()`.
}
if (builtin.cpu.arch.isMIPS64()) {
return; // `nstat.nlink` assertion is failing with LLVM 20+ for unclear reasons.
}
const target_name = "link-target";
const link_name = "newlink";
@ -78,17 +87,16 @@ fn test_link(tmp: std.testing.TmpDir) !void {
defer nfd.close();
{
const estat = try std.posix.fstat(efd.handle);
const nstat = try std.posix.fstat(nfd.handle);
try std.testing.expectEqual(estat.ino, nstat.ino);
try std.testing.expectEqual(@as(@TypeOf(nstat.nlink), 2), nstat.nlink);
const eino, _ = try getLinkInfo(efd.handle);
const nino, const nlink = try getLinkInfo(nfd.handle);
try std.testing.expectEqual(eino, nino);
try std.testing.expectEqual(@as(std.posix.nlink_t, 2), nlink);
}
// Test 2: Remove the link and see the stats update
try std.posix.unlink(link_name);
{
const estat = try std.posix.fstat(efd.handle);
try std.testing.expectEqual(@as(@TypeOf(estat.nlink), 1), estat.nlink);
_, const elink = try getLinkInfo(efd.handle);
try std.testing.expectEqual(@as(std.posix.nlink_t, 1), elink);
}
}