mirror of
https://github.com/ziglang/zig.git
synced 2025-12-24 15:13:08 +00:00
Merge pull request #18547 from ziglang/gh-fork-dump-fchmod-fixes
Add `fchmodat` fallback on Linux when `flags` is nonzero.
This commit is contained in:
commit
4debd4338c
@ -5,10 +5,10 @@ const page_size = std.mem.page_size;
|
||||
const iovec = std.os.iovec;
|
||||
const iovec_const = std.os.iovec_const;
|
||||
|
||||
/// If not linking libc, returns struct{pub const ok = false;}
|
||||
/// If linking musl libc, returns struct{pub const ok = true;}
|
||||
/// If linking gnu libc (glibc), the `ok` value will be true if the target
|
||||
/// version is greater than or equal to `glibc_version`.
|
||||
/// If not linking libc, returns false.
|
||||
/// If linking musl libc, returns true.
|
||||
/// If linking gnu libc (glibc), returns true if the target version is greater
|
||||
/// than or equal to `glibc_version`.
|
||||
/// If linking a libc other than these, returns `false`.
|
||||
pub inline fn versionCheck(comptime glibc_version: std.SemanticVersion) bool {
|
||||
return comptime blk: {
|
||||
|
||||
149
lib/std/os.zig
149
lib/std/os.zig
@ -348,17 +348,61 @@ pub fn fchmod(fd: fd_t, mode: mode_t) FChmodError!void {
|
||||
}
|
||||
|
||||
const FChmodAtError = FChmodError || error{
|
||||
/// A component of `path` exceeded `NAME_MAX`, or the entire path exceeded
|
||||
/// `PATH_MAX`.
|
||||
NameTooLong,
|
||||
/// `path` resolves to a symbolic link, and `AT.SYMLINK_NOFOLLOW` was set
|
||||
/// in `flags`. This error only occurs on Linux, where changing the mode of
|
||||
/// a symbolic link has no meaning and can cause undefined behaviour on
|
||||
/// certain filesystems.
|
||||
///
|
||||
/// The procfs fallback was used but procfs was not mounted.
|
||||
OperationNotSupported,
|
||||
/// The procfs fallback was used but the process exceeded its open file
|
||||
/// limit.
|
||||
ProcessFdQuotaExceeded,
|
||||
/// The procfs fallback was used but the system exceeded it open file limit.
|
||||
SystemFdQuotaExceeded,
|
||||
};
|
||||
|
||||
pub fn fchmodat(dirfd: fd_t, path: []const u8, mode: mode_t, flags: u32) FChmodAtError!void {
|
||||
var has_fchmodat2_syscall = std.atomic.Value(bool).init(true);
|
||||
|
||||
/// Changes the `mode` of `path` relative to the directory referred to by
|
||||
/// `dirfd`. The process must have the correct privileges in order to do this
|
||||
/// successfully, or must have the effective user ID matching the owner of the
|
||||
/// file.
|
||||
///
|
||||
/// On Linux the `fchmodat2` syscall will be used if available, otherwise a
|
||||
/// workaround using procfs will be employed. Changing the mode of a symbolic
|
||||
/// link with `AT.SYMLINK_NOFOLLOW` set will also return
|
||||
/// `OperationNotSupported`, as:
|
||||
///
|
||||
/// 1. Permissions on the link are ignored when resolving its target.
|
||||
/// 2. This operation has been known to invoke undefined behaviour across
|
||||
/// different filesystems[1].
|
||||
///
|
||||
/// [1]: https://sourceware.org/legacy-ml/libc-alpha/2020-02/msg00467.html.
|
||||
pub inline fn fchmodat(dirfd: fd_t, path: []const u8, mode: mode_t, flags: u32) FChmodAtError!void {
|
||||
if (!std.fs.has_executable_bit) @compileError("fchmodat unsupported by target OS");
|
||||
|
||||
const path_c = try toPosixPath(path);
|
||||
// No special handling for linux is needed if we can use the libc fallback
|
||||
// or `flags` is empty. Glibc only added the fallback in 2.32.
|
||||
const skip_fchmodat_fallback = builtin.os.tag != .linux or
|
||||
std.c.versionCheck(.{ .major = 2, .minor = 32, .patch = 0 }) or
|
||||
flags == 0;
|
||||
|
||||
// This function is marked inline so that when flags is comptime-known,
|
||||
// skip_fchmodat_fallback will be comptime-known true.
|
||||
if (skip_fchmodat_fallback)
|
||||
return fchmodat1(dirfd, path, mode, flags);
|
||||
|
||||
return fchmodat2(dirfd, path, mode, flags);
|
||||
}
|
||||
|
||||
fn fchmodat1(dirfd: fd_t, path: []const u8, mode: mode_t, flags: u32) FChmodAtError!void {
|
||||
const path_c = try toPosixPath(path);
|
||||
while (true) {
|
||||
const res = system.fchmodat(dirfd, &path_c, mode, flags);
|
||||
|
||||
switch (system.getErrno(res)) {
|
||||
.SUCCESS => return,
|
||||
.INTR => continue,
|
||||
@ -368,7 +412,106 @@ pub fn fchmodat(dirfd: fd_t, path: []const u8, mode: mode_t, flags: u32) FChmodA
|
||||
.ACCES => return error.AccessDenied,
|
||||
.IO => return error.InputOutput,
|
||||
.LOOP => return error.SymLinkLoop,
|
||||
.MFILE => return error.ProcessFdQuotaExceeded,
|
||||
.NAMETOOLONG => return error.NameTooLong,
|
||||
.NFILE => return error.SystemFdQuotaExceeded,
|
||||
.NOENT => return error.FileNotFound,
|
||||
.NOTDIR => return error.FileNotFound,
|
||||
.NOMEM => return error.SystemResources,
|
||||
.OPNOTSUPP => return error.OperationNotSupported,
|
||||
.PERM => return error.AccessDenied,
|
||||
.ROFS => return error.ReadOnlyFileSystem,
|
||||
else => |err| return unexpectedErrno(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn fchmodat2(dirfd: fd_t, path: []const u8, mode: mode_t, flags: u32) FChmodAtError!void {
|
||||
const path_c = try toPosixPath(path);
|
||||
const use_fchmodat2 = (builtin.os.isAtLeast(.linux, .{ .major = 6, .minor = 6, .patch = 0 }) orelse false) and
|
||||
has_fchmodat2_syscall.load(.Monotonic);
|
||||
while (use_fchmodat2) {
|
||||
// Later on this should be changed to `system.fchmodat2`
|
||||
// when the musl/glibc add a wrapper.
|
||||
const res = linux.fchmodat2(dirfd, &path_c, mode, flags);
|
||||
switch (linux.getErrno(res)) {
|
||||
.SUCCESS => return,
|
||||
.INTR => continue,
|
||||
.BADF => unreachable,
|
||||
.FAULT => unreachable,
|
||||
.INVAL => unreachable,
|
||||
.ACCES => return error.AccessDenied,
|
||||
.IO => return error.InputOutput,
|
||||
.LOOP => return error.SymLinkLoop,
|
||||
.NOENT => return error.FileNotFound,
|
||||
.NOMEM => return error.SystemResources,
|
||||
.NOTDIR => return error.FileNotFound,
|
||||
.OPNOTSUPP => return error.OperationNotSupported,
|
||||
.PERM => return error.AccessDenied,
|
||||
.ROFS => return error.ReadOnlyFileSystem,
|
||||
|
||||
.NOSYS => { // Use fallback.
|
||||
has_fchmodat2_syscall.store(false, .Monotonic);
|
||||
break;
|
||||
},
|
||||
else => |err| return unexpectedErrno(err),
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to changing permissions using procfs:
|
||||
//
|
||||
// 1. Open `path` as an `O.PATH` descriptor.
|
||||
// 2. Stat the fd and check if it isn't a symbolic link.
|
||||
// 3. Generate the procfs reference to the fd via `/proc/self/fd/{fd}`.
|
||||
// 4. Pass the procfs path to `chmod` with the `mode`.
|
||||
var pathfd: fd_t = undefined;
|
||||
while (true) {
|
||||
const rc = system.openat(dirfd, &path_c, O.PATH | O.NOFOLLOW | O.CLOEXEC, @as(mode_t, 0));
|
||||
switch (system.getErrno(rc)) {
|
||||
.SUCCESS => {
|
||||
pathfd = @as(fd_t, @intCast(rc));
|
||||
break;
|
||||
},
|
||||
.INTR => continue,
|
||||
.FAULT => unreachable,
|
||||
.INVAL => unreachable,
|
||||
.ACCES => return error.AccessDenied,
|
||||
.PERM => return error.AccessDenied,
|
||||
.LOOP => return error.SymLinkLoop,
|
||||
.MFILE => return error.ProcessFdQuotaExceeded,
|
||||
.NAMETOOLONG => return error.NameTooLong,
|
||||
.NFILE => return error.SystemFdQuotaExceeded,
|
||||
.NOENT => return error.FileNotFound,
|
||||
.NOMEM => return error.SystemResources,
|
||||
else => |err| return unexpectedErrno(err),
|
||||
}
|
||||
}
|
||||
defer close(pathfd);
|
||||
|
||||
const stat = fstatatZ(pathfd, "", AT.EMPTY_PATH) catch |err| switch (err) {
|
||||
error.NameTooLong => unreachable,
|
||||
error.FileNotFound => unreachable,
|
||||
else => |e| return e,
|
||||
};
|
||||
if ((stat.mode & S.IFMT) == S.IFLNK)
|
||||
return error.OperationNotSupported;
|
||||
|
||||
var procfs_buf: ["/proc/self/fd/-2147483648\x00".len]u8 = undefined;
|
||||
const proc_path = std.fmt.bufPrintZ(procfs_buf[0..], "/proc/self/fd/{d}", .{pathfd}) catch unreachable;
|
||||
while (true) {
|
||||
const res = system.chmod(proc_path, mode);
|
||||
switch (system.getErrno(res)) {
|
||||
// Getting NOENT here means that procfs isn't mounted.
|
||||
.NOENT => return error.OperationNotSupported,
|
||||
|
||||
.SUCCESS => return,
|
||||
.INTR => continue,
|
||||
.BADF => unreachable,
|
||||
.FAULT => unreachable,
|
||||
.INVAL => unreachable,
|
||||
.ACCES => return error.AccessDenied,
|
||||
.IO => return error.InputOutput,
|
||||
.LOOP => return error.SymLinkLoop,
|
||||
.NOMEM => return error.SystemResources,
|
||||
.NOTDIR => return error.FileNotFound,
|
||||
.PERM => return error.AccessDenied,
|
||||
|
||||
@ -796,13 +796,7 @@ pub fn chmod(path: [*:0]const u8, mode: mode_t) usize {
|
||||
if (@hasField(SYS, "chmod")) {
|
||||
return syscall2(.chmod, @intFromPtr(path), mode);
|
||||
} else {
|
||||
return syscall4(
|
||||
.fchmodat,
|
||||
@as(usize, @bitCast(@as(isize, AT.FDCWD))),
|
||||
@intFromPtr(path),
|
||||
mode,
|
||||
0,
|
||||
);
|
||||
return fchmodat(AT.FDCWD, path, mode, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@ -814,8 +808,12 @@ pub fn fchown(fd: i32, owner: uid_t, group: gid_t) usize {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fchmodat(fd: i32, path: [*:0]const u8, mode: mode_t, flags: u32) usize {
|
||||
return syscall4(.fchmodat, @as(usize, @bitCast(@as(isize, fd))), @intFromPtr(path), mode, flags);
|
||||
pub fn fchmodat(fd: i32, path: [*:0]const u8, mode: mode_t, _: u32) usize {
|
||||
return syscall3(.fchmodat, @bitCast(@as(isize, fd)), @intFromPtr(path), mode);
|
||||
}
|
||||
|
||||
pub fn fchmodat2(fd: i32, path: [*:0]const u8, mode: mode_t, flags: u32) usize {
|
||||
return syscall4(.fchmodat2, @bitCast(@as(isize, fd)), @intFromPtr(path), mode, flags);
|
||||
}
|
||||
|
||||
/// Can only be called on 32 bit systems. For 64 bit see `lseek`.
|
||||
|
||||
@ -443,6 +443,7 @@ pub const X86 = enum(usize) {
|
||||
futex_waitv = 449,
|
||||
set_mempolicy_home_node = 450,
|
||||
cachestat = 451,
|
||||
fchmodat2 = 452,
|
||||
};
|
||||
|
||||
pub const X64 = enum(usize) {
|
||||
@ -809,6 +810,8 @@ pub const X64 = enum(usize) {
|
||||
futex_waitv = 449,
|
||||
set_mempolicy_home_node = 450,
|
||||
cachestat = 451,
|
||||
fchmodat2 = 452,
|
||||
map_shadow_stack = 453,
|
||||
};
|
||||
|
||||
pub const Arm = enum(usize) {
|
||||
@ -1218,6 +1221,7 @@ pub const Arm = enum(usize) {
|
||||
futex_waitv = 449,
|
||||
set_mempolicy_home_node = 450,
|
||||
cachestat = 451,
|
||||
fchmodat2 = 452,
|
||||
|
||||
breakpoint = arm_base + 1,
|
||||
cacheflush = arm_base + 2,
|
||||
@ -1611,6 +1615,7 @@ pub const Sparc64 = enum(usize) {
|
||||
futex_waitv = 449,
|
||||
set_mempolicy_home_node = 450,
|
||||
cachestat = 451,
|
||||
fchmodat2 = 452,
|
||||
};
|
||||
|
||||
pub const Mips = enum(usize) {
|
||||
@ -2035,6 +2040,7 @@ pub const Mips = enum(usize) {
|
||||
futex_waitv = Linux + 449,
|
||||
set_mempolicy_home_node = Linux + 450,
|
||||
cachestat = Linux + 451,
|
||||
fchmodat2 = Linux + 452,
|
||||
};
|
||||
|
||||
pub const Mips64 = enum(usize) {
|
||||
@ -2395,6 +2401,7 @@ pub const Mips64 = enum(usize) {
|
||||
futex_waitv = Linux + 449,
|
||||
set_mempolicy_home_node = Linux + 450,
|
||||
cachestat = Linux + 451,
|
||||
fchmodat2 = Linux + 452,
|
||||
};
|
||||
|
||||
pub const PowerPC = enum(usize) {
|
||||
@ -2830,6 +2837,7 @@ pub const PowerPC = enum(usize) {
|
||||
futex_waitv = 449,
|
||||
set_mempolicy_home_node = 450,
|
||||
cachestat = 451,
|
||||
fchmodat2 = 452,
|
||||
};
|
||||
|
||||
pub const PowerPC64 = enum(usize) {
|
||||
@ -3237,6 +3245,7 @@ pub const PowerPC64 = enum(usize) {
|
||||
futex_waitv = 449,
|
||||
set_mempolicy_home_node = 450,
|
||||
cachestat = 451,
|
||||
fchmodat2 = 452,
|
||||
};
|
||||
|
||||
pub const Arm64 = enum(usize) {
|
||||
@ -3547,6 +3556,7 @@ pub const Arm64 = enum(usize) {
|
||||
futex_waitv = 449,
|
||||
set_mempolicy_home_node = 450,
|
||||
cachestat = 451,
|
||||
fchmodat2 = 452,
|
||||
};
|
||||
|
||||
pub const RiscV64 = enum(usize) {
|
||||
@ -3858,6 +3868,7 @@ pub const RiscV64 = enum(usize) {
|
||||
futex_waitv = 449,
|
||||
set_mempolicy_home_node = 450,
|
||||
cachestat = 451,
|
||||
fchmodat2 = 452,
|
||||
|
||||
riscv_flush_icache = arch_specific_syscall + 15,
|
||||
};
|
||||
|
||||
@ -1217,16 +1217,46 @@ test "pwrite with empty buffer" {
|
||||
_ = try os.pwrite(file.handle, bytes, 0);
|
||||
}
|
||||
|
||||
fn expectMode(dir: os.fd_t, file: []const u8, mode: os.mode_t) !void {
|
||||
const st = try os.fstatat(dir, file, os.AT.SYMLINK_NOFOLLOW);
|
||||
try expectEqual(mode, st.mode & 0b111_111_111);
|
||||
}
|
||||
|
||||
test "fchmodat smoke test" {
|
||||
if (!std.fs.has_executable_bit) return error.SkipZigTest;
|
||||
|
||||
var tmp = tmpDir(.{});
|
||||
defer tmp.cleanup();
|
||||
|
||||
try expectError(error.FileNotFound, os.fchmodat(tmp.dir.fd, "foo.txt", 0o666, 0));
|
||||
const fd = try os.openat(tmp.dir.fd, "foo.txt", os.O.RDWR | os.O.CREAT | os.O.EXCL, 0o666);
|
||||
try expectError(error.FileNotFound, os.fchmodat(tmp.dir.fd, "regfile", 0o666, 0));
|
||||
const fd = try os.openat(
|
||||
tmp.dir.fd,
|
||||
"regfile",
|
||||
os.O.WRONLY | os.O.CREAT | os.O.EXCL | os.O.TRUNC,
|
||||
0o644,
|
||||
);
|
||||
os.close(fd);
|
||||
try os.fchmodat(tmp.dir.fd, "foo.txt", 0o755, 0);
|
||||
const st = try os.fstatat(tmp.dir.fd, "foo.txt", 0);
|
||||
try expectEqual(@as(os.mode_t, 0o755), st.mode & 0b111_111_111);
|
||||
try os.symlinkat("regfile", tmp.dir.fd, "symlink");
|
||||
const sym_mode = blk: {
|
||||
const st = try os.fstatat(tmp.dir.fd, "symlink", os.AT.SYMLINK_NOFOLLOW);
|
||||
break :blk st.mode & 0b111_111_111;
|
||||
};
|
||||
|
||||
try os.fchmodat(tmp.dir.fd, "regfile", 0o640, 0);
|
||||
try expectMode(tmp.dir.fd, "regfile", 0o640);
|
||||
try os.fchmodat(tmp.dir.fd, "regfile", 0o600, os.AT.SYMLINK_NOFOLLOW);
|
||||
try expectMode(tmp.dir.fd, "regfile", 0o600);
|
||||
|
||||
try os.fchmodat(tmp.dir.fd, "symlink", 0o640, 0);
|
||||
try expectMode(tmp.dir.fd, "regfile", 0o640);
|
||||
try expectMode(tmp.dir.fd, "symlink", sym_mode);
|
||||
|
||||
var test_link = true;
|
||||
os.fchmodat(tmp.dir.fd, "symlink", 0o600, os.AT.SYMLINK_NOFOLLOW) catch |err| switch (err) {
|
||||
error.OperationNotSupported => test_link = false,
|
||||
else => |e| return e,
|
||||
};
|
||||
if (test_link)
|
||||
try expectMode(tmp.dir.fd, "symlink", 0o600);
|
||||
try expectMode(tmp.dir.fd, "regfile", 0o640);
|
||||
}
|
||||
|
||||
@ -4917,7 +4917,10 @@ fn linkWithLLD(wasm: *Wasm, arena: Allocator, prog_node: *std.Progress.Node) !vo
|
||||
// report a nice error here with the file path if it fails instead of
|
||||
// just returning the error code.
|
||||
// chmod does not interact with umask, so we use a conservative -rwxr--r-- here.
|
||||
try std.os.fchmodat(fs.cwd().fd, full_out_path, 0o744, 0);
|
||||
std.os.fchmodat(fs.cwd().fd, full_out_path, 0o744, 0) catch |err| switch (err) {
|
||||
error.OperationNotSupported => unreachable, // Not a symlink.
|
||||
else => |e| return e,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user