diff --git a/lib/std/c.zig b/lib/std/c.zig index f827bd9185..b5e8ed3896 100644 --- a/lib/std/c.zig +++ b/lib/std/c.zig @@ -147,6 +147,8 @@ 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 fchmod(fd: c.fd_t, mode: c.mode_t) 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 rmdir(path: [*:0]const u8) c_int; pub extern "c" fn getenv(name: [*:0]const u8) ?[*:0]u8; diff --git a/lib/std/fs.zig b/lib/std/fs.zig index fca5304bf2..e43f439ad5 100644 --- a/lib/std/fs.zig +++ b/lib/std/fs.zig @@ -2178,6 +2178,37 @@ pub const Dir = struct { }; return file.stat(); } + + pub const ChmodError = File.ChmodError; + + /// Changes the mode of the directory. + /// 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 directory. Additionally, the directory must have been opened + /// with `OpenDirOptions{ .iterate = true }`. + pub fn chmod(self: Dir, new_mode: File.Mode) ChmodError!void { + const file: File = .{ + .handle = self.fd, + .capable_io_mode = .blocking, + }; + try file.chmod(new_mode); + } + + /// Changes the owner and group of the directory. + /// The process must have the correct privileges in order to do this + /// successfully. The group may be changed by the owner of the directory to + /// any group of which the owner is a member. Additionally, the directory + /// must have been opened with `OpenDirOptions{ .iterate = true }`. If the + /// owner or group is specified as `null`, the ID is not changed. + pub fn chown(self: Dir, owner: ?File.Uid, group: ?File.Gid) ChownError!void { + const file: File = .{ + .handle = self.fd, + .capable_io_mode = .blocking, + }; + try file.chown(owner, group); + } + + pub const ChownError = File.ChownError; }; /// Returns a handle to the current working directory. It is not opened with iteration capability. diff --git a/lib/std/fs/file.zig b/lib/std/fs/file.zig index 1025719ca2..268de8f3c8 100644 --- a/lib/std/fs/file.zig +++ b/lib/std/fs/file.zig @@ -31,6 +31,8 @@ pub const File = struct { pub const Handle = os.fd_t; pub const Mode = os.mode_t; pub const INode = os.ino_t; + pub const Uid = os.uid_t; + pub const Gid = os.gid_t; pub const Kind = enum { BlockDevice, @@ -362,6 +364,27 @@ pub const File = struct { }; } + pub const ChmodError = std.os.FChmodError; + + /// Changes the mode of the file. + /// 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. + pub fn chmod(self: File, new_mode: Mode) ChmodError!void { + try os.fchmod(self.handle, new_mode); + } + + pub const ChownError = std.os.FChownError; + + /// Changes the owner and group of the file. + /// The process must have the correct privileges in order to do this + /// successfully. The group may be changed by the owner of the file to + /// any group of which the owner is a member. If the owner or group is + /// specified as `null`, the ID is not changed. + pub fn chown(self: File, owner: ?Uid, group: ?Gid) ChownError!void { + try os.fchown(self.handle, owner, group); + } + pub const UpdateTimesError = os.FutimensError || windows.SetFileTimeError; /// The underlying file system may have a different granularity than nanoseconds, diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index 3d1b0d7794..f2b584d6d4 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -1022,3 +1022,43 @@ test ". and .. in absolute functions" { try fs.deleteDirAbsolute(subdir_path); } + +test "chmod" { + 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 = 0o600 }); + defer file.close(); + try testing.expect((try file.stat()).mode & 0o7777 == 0o600); + + try file.chmod(0o644); + try testing.expect((try file.stat()).mode & 0o7777 == 0o644); + + try tmp.dir.makeDir("test_dir"); + var dir = try tmp.dir.openDir("test_dir", .{ .iterate = true }); + defer dir.close(); + + try dir.chmod(0o700); + try testing.expect((try dir.stat()).mode & 0o7777 == 0o700); +} + +test "chown" { + 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", .{}); + defer file.close(); + try file.chown(null, null); + + try tmp.dir.makeDir("test_dir"); + + var dir = try tmp.dir.openDir("test_dir", .{ .iterate = true }); + defer dir.close(); + try dir.chown(null, null); +} diff --git a/lib/std/os.zig b/lib/std/os.zig index 98468cde7e..cfb0ccd6c7 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -255,6 +255,87 @@ pub fn close(fd: fd_t) void { } } +pub const FChmodError = error{ + AccessDenied, + InputOutput, + SymLinkLoop, + FileNotFound, + SystemResources, + ReadOnlyFileSystem, +} || UnexpectedError; + +/// Changes the mode of the file referred to by the file descriptor. +/// 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. +pub fn fchmod(fd: fd_t, mode: mode_t) FChmodError!void { + if (builtin.os.tag == .windows or builtin.os.tag == .wasi) + @compileError("Unsupported OS"); + + while (true) { + const res = system.fchmod(fd, mode); + + switch (system.getErrno(res)) { + .SUCCESS => return, + .INTR => continue, + .BADF => unreachable, // Can be reached if the fd refers to a directory opened without `OpenDirOptions{ .iterate = true }` + + .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), + } + } +} + +pub const FChownError = error{ + AccessDenied, + InputOutput, + SymLinkLoop, + FileNotFound, + SystemResources, + ReadOnlyFileSystem, +} || UnexpectedError; + +/// Changes the owner and group of the file referred to by the file descriptor. +/// The process must have the correct privileges in order to do this +/// successfully. The group may be changed by the owner of the directory to +/// any group of which the owner is a member. If the owner or group is +/// specified as `null`, the ID is not changed. +pub fn fchown(fd: fd_t, owner: ?uid_t, group: ?gid_t) FChownError!void { + if (builtin.os.tag == .windows or builtin.os.tag == .wasi) + @compileError("Unsupported OS"); + + while (true) { + const res = system.fchown(fd, owner orelse @as(u32, 0) -% 1, group orelse @as(u32, 0) -% 1); + + switch (system.getErrno(res)) { + .SUCCESS => return, + .INTR => continue, + .BADF => unreachable, // Can be reached if the fd refers to a directory opened without `OpenDirOptions{ .iterate = true }` + + .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), + } + } +} + pub const GetRandomError = OpenError; /// Obtain a series of random bytes. These bytes can be used to seed user-space diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig index 914e55c99c..d4c9d09120 100644 --- a/lib/std/os/linux.zig +++ b/lib/std/os/linux.zig @@ -733,6 +733,14 @@ pub fn close(fd: i32) usize { return syscall1(.close, @bitCast(usize, @as(isize, fd))); } +pub fn fchmod(fd: i32, mode: mode_t) usize { + return syscall2(.fchmod, @bitCast(usize, @as(isize, fd)), mode); +} + +pub fn fchown(fd: i32, owner: uid_t, group: gid_t) usize { + return syscall3(.fchown, @bitCast(usize, @as(isize, fd)), owner, group); +} + /// 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