Commit 796687f156
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