mirror of
https://github.com/ziglang/zig.git
synced 2025-02-08 21:50:33 +00:00
std: add fchmodat
Also add `std.fs.has_executable_bit` for doing conditional compilation. This adds the linux syscalls for chmod and fchmodat, as well as the extern libc function declarations. Only `fchmodat` is added to `std.os`, and it is not yet added to std.fs.
This commit is contained in:
parent
426c13dddf
commit
bb5006d728
@ -171,7 +171,9 @@ pub extern "c" fn dup(fd: c.fd_t) c_int;
|
||||
pub extern "c" fn dup2(old_fd: c.fd_t, new_fd: c.fd_t) c_int;
|
||||
pub extern "c" fn readlink(noalias path: [*:0]const u8, noalias buf: [*]u8, bufsize: usize) isize;
|
||||
pub extern "c" fn readlinkat(dirfd: c.fd_t, noalias path: [*:0]const u8, noalias buf: [*]u8, bufsize: usize) isize;
|
||||
pub extern "c" fn chmod(path: [*:0]const u8, mode: c.mode_t) c_int;
|
||||
pub extern "c" fn fchmod(fd: c.fd_t, mode: c.mode_t) c_int;
|
||||
pub extern "c" fn fchmodat(fd: c.fd_t, path: [*:0]const u8, mode: c.mode_t, flags: c_uint) c_int;
|
||||
pub extern "c" fn fchown(fd: c.fd_t, owner: c.uid_t, group: c.gid_t) c_int;
|
||||
pub extern "c" fn umask(mode: c.mode_t) c.mode_t;
|
||||
|
||||
|
@ -11,6 +11,11 @@ const math = std.math;
|
||||
|
||||
const is_darwin = builtin.os.tag.isDarwin();
|
||||
|
||||
pub const has_executable_bit = switch (builtin.os.tag) {
|
||||
.windows, .wasi => false,
|
||||
else => true,
|
||||
};
|
||||
|
||||
pub const path = @import("fs/path.zig");
|
||||
pub const File = @import("fs/file.zig").File;
|
||||
pub const wasi = @import("fs/wasi.zig");
|
||||
|
@ -302,8 +302,7 @@ pub const FChmodError = error{
|
||||
/// successfully, or must have the effective user ID matching the owner
|
||||
/// of the file.
|
||||
pub fn fchmod(fd: fd_t, mode: mode_t) FChmodError!void {
|
||||
if (builtin.os.tag == .windows or builtin.os.tag == .wasi)
|
||||
@compileError("Unsupported OS");
|
||||
if (!std.fs.has_executable_bit) @compileError("fchmod unsupported by target OS");
|
||||
|
||||
while (true) {
|
||||
const res = system.fchmod(fd, mode);
|
||||
@ -311,8 +310,38 @@ pub fn fchmod(fd: fd_t, mode: mode_t) FChmodError!void {
|
||||
switch (system.getErrno(res)) {
|
||||
.SUCCESS => return,
|
||||
.INTR => continue,
|
||||
.BADF => unreachable, // Can be reached if the fd refers to a non-iterable directory.
|
||||
.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,
|
||||
.PERM => return error.AccessDenied,
|
||||
.ROFS => return error.ReadOnlyFileSystem,
|
||||
else => |err| return unexpectedErrno(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const FChmodAtError = FChmodError || error{
|
||||
NameTooLong,
|
||||
};
|
||||
|
||||
pub 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);
|
||||
|
||||
while (true) {
|
||||
const res = system.fchmodat(dirfd, &path_c, mode, flags);
|
||||
|
||||
switch (system.getErrno(res)) {
|
||||
.SUCCESS => return,
|
||||
.INTR => continue,
|
||||
.BADF => unreachable,
|
||||
.FAULT => unreachable,
|
||||
.INVAL => unreachable,
|
||||
.ACCES => return error.AccessDenied,
|
||||
|
@ -769,6 +769,20 @@ pub fn fchmod(fd: i32, mode: mode_t) usize {
|
||||
return syscall2(.fchmod, @bitCast(usize, @as(isize, fd)), mode);
|
||||
}
|
||||
|
||||
pub fn chmod(path: [*:0]const u8, mode: mode_t) usize {
|
||||
if (@hasField(SYS, "chmod")) {
|
||||
return syscall2(.chmod, @ptrToInt(path), mode);
|
||||
} else {
|
||||
return syscall4(
|
||||
.fchmodat,
|
||||
@bitCast(usize, @as(isize, AT.FDCWD)),
|
||||
@ptrToInt(path),
|
||||
mode,
|
||||
0,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fchown(fd: i32, owner: uid_t, group: gid_t) usize {
|
||||
if (@hasField(SYS, "fchown32")) {
|
||||
return syscall3(.fchown32, @bitCast(usize, @as(isize, fd)), owner, group);
|
||||
@ -777,6 +791,10 @@ 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, @bitCast(usize, @as(isize, fd)), @ptrToInt(path), mode, flags);
|
||||
}
|
||||
|
||||
/// Can only be called on 32 bit systems. For 64 bit see `lseek`.
|
||||
pub fn llseek(fd: i32, offset: u64, result: ?*u64, whence: usize) usize {
|
||||
// NOTE: The offset parameter splitting is independent from the target
|
||||
|
@ -531,17 +531,17 @@ test "memfd_create" {
|
||||
else => return error.SkipZigTest,
|
||||
}
|
||||
|
||||
const fd = std.os.memfd_create("test", 0) catch |err| switch (err) {
|
||||
const fd = os.memfd_create("test", 0) catch |err| switch (err) {
|
||||
// Related: https://github.com/ziglang/zig/issues/4019
|
||||
error.SystemOutdated => return error.SkipZigTest,
|
||||
else => |e| return e,
|
||||
};
|
||||
defer std.os.close(fd);
|
||||
try expect((try std.os.write(fd, "test")) == 4);
|
||||
try std.os.lseek_SET(fd, 0);
|
||||
defer os.close(fd);
|
||||
try expect((try os.write(fd, "test")) == 4);
|
||||
try os.lseek_SET(fd, 0);
|
||||
|
||||
var buf: [10]u8 = undefined;
|
||||
const bytes_read = try std.os.read(fd, &buf);
|
||||
const bytes_read = try os.read(fd, &buf);
|
||||
try expect(bytes_read == 4);
|
||||
try expect(mem.eql(u8, buf[0..4], "test"));
|
||||
}
|
||||
@ -688,7 +688,7 @@ test "signalfd" {
|
||||
.linux, .solaris => {},
|
||||
else => return error.SkipZigTest,
|
||||
}
|
||||
_ = std.os.signalfd;
|
||||
_ = os.signalfd;
|
||||
}
|
||||
|
||||
test "sync" {
|
||||
@ -757,11 +757,11 @@ test "shutdown socket" {
|
||||
if (native_os == .wasi)
|
||||
return error.SkipZigTest;
|
||||
if (native_os == .windows) {
|
||||
_ = try std.os.windows.WSAStartup(2, 2);
|
||||
_ = try os.windows.WSAStartup(2, 2);
|
||||
}
|
||||
defer {
|
||||
if (native_os == .windows) {
|
||||
std.os.windows.WSACleanup() catch unreachable;
|
||||
os.windows.WSACleanup() catch unreachable;
|
||||
}
|
||||
}
|
||||
const sock = try os.socket(os.AF.INET, os.SOCK.STREAM, 0);
|
||||
@ -855,13 +855,13 @@ test "dup & dup2" {
|
||||
var file = try tmp.dir.createFile("os_dup_test", .{});
|
||||
defer file.close();
|
||||
|
||||
var duped = std.fs.File{ .handle = try std.os.dup(file.handle) };
|
||||
var duped = std.fs.File{ .handle = try os.dup(file.handle) };
|
||||
defer duped.close();
|
||||
try duped.writeAll("dup");
|
||||
|
||||
// Tests aren't run in parallel so using the next fd shouldn't be an issue.
|
||||
const new_fd = duped.handle + 1;
|
||||
try std.os.dup2(file.handle, new_fd);
|
||||
try os.dup2(file.handle, new_fd);
|
||||
var dup2ed = std.fs.File{ .handle = new_fd };
|
||||
defer dup2ed.close();
|
||||
try dup2ed.writeAll("dup2");
|
||||
@ -909,46 +909,46 @@ test "POSIX file locking with fcntl" {
|
||||
const fd = file.handle;
|
||||
|
||||
// Place an exclusive lock on the first byte, and a shared lock on the second byte:
|
||||
var struct_flock = std.mem.zeroInit(std.os.Flock, .{ .type = std.os.F.WRLCK });
|
||||
_ = try std.os.fcntl(fd, std.os.F.SETLK, @ptrToInt(&struct_flock));
|
||||
var struct_flock = std.mem.zeroInit(os.Flock, .{ .type = os.F.WRLCK });
|
||||
_ = try os.fcntl(fd, os.F.SETLK, @ptrToInt(&struct_flock));
|
||||
struct_flock.start = 1;
|
||||
struct_flock.type = std.os.F.RDLCK;
|
||||
_ = try std.os.fcntl(fd, std.os.F.SETLK, @ptrToInt(&struct_flock));
|
||||
struct_flock.type = os.F.RDLCK;
|
||||
_ = try os.fcntl(fd, os.F.SETLK, @ptrToInt(&struct_flock));
|
||||
|
||||
// Check the locks in a child process:
|
||||
const pid = try std.os.fork();
|
||||
const pid = try os.fork();
|
||||
if (pid == 0) {
|
||||
// child expects be denied the exclusive lock:
|
||||
struct_flock.start = 0;
|
||||
struct_flock.type = std.os.F.WRLCK;
|
||||
try expectError(error.Locked, std.os.fcntl(fd, std.os.F.SETLK, @ptrToInt(&struct_flock)));
|
||||
struct_flock.type = os.F.WRLCK;
|
||||
try expectError(error.Locked, os.fcntl(fd, os.F.SETLK, @ptrToInt(&struct_flock)));
|
||||
// child expects to get the shared lock:
|
||||
struct_flock.start = 1;
|
||||
struct_flock.type = std.os.F.RDLCK;
|
||||
_ = try std.os.fcntl(fd, std.os.F.SETLK, @ptrToInt(&struct_flock));
|
||||
struct_flock.type = os.F.RDLCK;
|
||||
_ = try os.fcntl(fd, os.F.SETLK, @ptrToInt(&struct_flock));
|
||||
// child waits for the exclusive lock in order to test deadlock:
|
||||
struct_flock.start = 0;
|
||||
struct_flock.type = std.os.F.WRLCK;
|
||||
_ = try std.os.fcntl(fd, std.os.F.SETLKW, @ptrToInt(&struct_flock));
|
||||
struct_flock.type = os.F.WRLCK;
|
||||
_ = try os.fcntl(fd, os.F.SETLKW, @ptrToInt(&struct_flock));
|
||||
// child exits without continuing:
|
||||
std.os.exit(0);
|
||||
os.exit(0);
|
||||
} else {
|
||||
// parent waits for child to get shared lock:
|
||||
std.time.sleep(1 * std.time.ns_per_ms);
|
||||
// parent expects deadlock when attempting to upgrade the shared lock to exclusive:
|
||||
struct_flock.start = 1;
|
||||
struct_flock.type = std.os.F.WRLCK;
|
||||
try expectError(error.DeadLock, std.os.fcntl(fd, std.os.F.SETLKW, @ptrToInt(&struct_flock)));
|
||||
struct_flock.type = os.F.WRLCK;
|
||||
try expectError(error.DeadLock, os.fcntl(fd, os.F.SETLKW, @ptrToInt(&struct_flock)));
|
||||
// parent releases exclusive lock:
|
||||
struct_flock.start = 0;
|
||||
struct_flock.type = std.os.F.UNLCK;
|
||||
_ = try std.os.fcntl(fd, std.os.F.SETLK, @ptrToInt(&struct_flock));
|
||||
struct_flock.type = os.F.UNLCK;
|
||||
_ = try os.fcntl(fd, os.F.SETLK, @ptrToInt(&struct_flock));
|
||||
// parent releases shared lock:
|
||||
struct_flock.start = 1;
|
||||
struct_flock.type = std.os.F.UNLCK;
|
||||
_ = try std.os.fcntl(fd, std.os.F.SETLK, @ptrToInt(&struct_flock));
|
||||
struct_flock.type = os.F.UNLCK;
|
||||
_ = try os.fcntl(fd, os.F.SETLK, @ptrToInt(&struct_flock));
|
||||
// parent waits for child:
|
||||
const result = std.os.waitpid(pid, 0);
|
||||
const result = os.waitpid(pid, 0);
|
||||
try expect(result.status == 0 * 256);
|
||||
}
|
||||
}
|
||||
@ -1182,3 +1182,17 @@ test "pwrite with empty buffer" {
|
||||
|
||||
_ = try os.pwrite(file.handle, bytes, 0);
|
||||
}
|
||||
|
||||
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);
|
||||
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);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user