Commit 796687f156

Ominitay <37453713+Ominitay@users.noreply.github.com>
2021-11-05 16:19:49
Add `chmod` and `chown`
1 parent dfd6048
Changed files (6)
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,
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);
+}
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
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;
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.
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