mirror of
https://codeberg.org/ziglang/zig.git
synced 2025-12-06 13:54:21 +00:00
Linux: Add fchmodat fallback when flags is nonzero
The check for determining whether to use the fallback code has been moved into an inline function as per Andrew's comments in #17954.
This commit is contained in:
parent
cf6751ae55
commit
bc69d62669
2 changed files with 144 additions and 4 deletions
143
lib/std/os.zig
143
lib/std/os.zig
|
|
@ -348,17 +348,58 @@ pub fn fchmod(fd: fd_t, mode: mode_t) FChmodError!void {
|
||||||
}
|
}
|
||||||
|
|
||||||
const FChmodAtError = FChmodError || error{
|
const FChmodAtError = FChmodError || error{
|
||||||
|
/// A component of `path` exceeded `NAME_MAX`, or the entire path exceeded
|
||||||
|
/// `PATH_MAX`.
|
||||||
NameTooLong,
|
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);
|
||||||
|
|
||||||
|
inline fn skipFchmodatFallback(flags: u32) bool {
|
||||||
|
return builtin.os.tag != .linux or
|
||||||
|
flags == 0 or
|
||||||
|
std.c.versionCheck(std.SemanticVersion{ .major = 2, .minor = 32, .patch = 0 }).ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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");
|
if (!std.fs.has_executable_bit) @compileError("fchmodat unsupported by target OS");
|
||||||
|
|
||||||
const path_c = try toPosixPath(path);
|
const path_c = try toPosixPath(path);
|
||||||
|
|
||||||
while (true) {
|
// 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.
|
||||||
|
while (skipFchmodatFallback(flags)) {
|
||||||
const res = system.fchmodat(dirfd, &path_c, mode, flags);
|
const res = system.fchmodat(dirfd, &path_c, mode, flags);
|
||||||
|
|
||||||
switch (system.getErrno(res)) {
|
switch (system.getErrno(res)) {
|
||||||
.SUCCESS => return,
|
.SUCCESS => return,
|
||||||
.INTR => continue,
|
.INTR => continue,
|
||||||
|
|
@ -368,7 +409,103 @@ pub fn fchmodat(dirfd: fd_t, path: []const u8, mode: mode_t, flags: u32) FChmodA
|
||||||
.ACCES => return error.AccessDenied,
|
.ACCES => return error.AccessDenied,
|
||||||
.IO => return error.InputOutput,
|
.IO => return error.InputOutput,
|
||||||
.LOOP => return error.SymLinkLoop,
|
.LOOP => return error.SymLinkLoop,
|
||||||
|
.MFILE => return error.ProcessFdQuotaExceeded,
|
||||||
|
.NAMETOOLONG => return error.NameTooLong,
|
||||||
|
.NFILE => return error.SystemFdQuotaExceeded,
|
||||||
.NOENT => return error.FileNotFound,
|
.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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const use_fchmodat2 = (comptime 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,
|
.NOMEM => return error.SystemResources,
|
||||||
.NOTDIR => return error.FileNotFound,
|
.NOTDIR => return error.FileNotFound,
|
||||||
.PERM => return error.AccessDenied,
|
.PERM => return error.AccessDenied,
|
||||||
|
|
|
||||||
|
|
@ -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
|
// report a nice error here with the file path if it fails instead of
|
||||||
// just returning the error code.
|
// just returning the error code.
|
||||||
// chmod does not interact with umask, so we use a conservative -rwxr--r-- here.
|
// 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…
Add table
Reference in a new issue