authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-03-02 18:32:15-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-03-03 02:37:45-05:00
logbb5006d7287607e49b71d25d263ebcc3d5845c60
treefb6111622cb48a2af6ac232174ae0dcb8e66f813
parent426c13dddfa9e19cf505fc26561d2a8637165d64

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.

5 files changed, 100 insertions(+), 32 deletions(-)

lib/std/c.zig+2
...@@ -171,7 +171,9 @@ pub extern "c" fn dup(fd: c.fd_t) c_int;...@@ -171,7 +171,9 @@ pub extern "c" fn dup(fd: c.fd_t) c_int;
171pub extern "c" fn dup2(old_fd: c.fd_t, new_fd: c.fd_t) c_int;171pub extern "c" fn dup2(old_fd: c.fd_t, new_fd: c.fd_t) c_int;
172pub extern "c" fn readlink(noalias path: [*:0]const u8, noalias buf: [*]u8, bufsize: usize) isize;172pub extern "c" fn readlink(noalias path: [*:0]const u8, noalias buf: [*]u8, bufsize: usize) isize;
173pub extern "c" fn readlinkat(dirfd: c.fd_t, noalias path: [*:0]const u8, noalias buf: [*]u8, bufsize: usize) isize;173pub extern "c" fn readlinkat(dirfd: c.fd_t, noalias path: [*:0]const u8, noalias buf: [*]u8, bufsize: usize) isize;
174pub extern "c" fn chmod(path: [*:0]const u8, mode: c.mode_t) c_int;
174pub extern "c" fn fchmod(fd: c.fd_t, mode: c.mode_t) c_int;175pub extern "c" fn fchmod(fd: c.fd_t, mode: c.mode_t) c_int;
176pub extern "c" fn fchmodat(fd: c.fd_t, path: [*:0]const u8, mode: c.mode_t, flags: c_uint) c_int;
175pub extern "c" fn fchown(fd: c.fd_t, owner: c.uid_t, group: c.gid_t) c_int;177pub extern "c" fn fchown(fd: c.fd_t, owner: c.uid_t, group: c.gid_t) c_int;
176pub extern "c" fn umask(mode: c.mode_t) c.mode_t;178pub extern "c" fn umask(mode: c.mode_t) c.mode_t;
177179
lib/std/fs.zig+5
...@@ -11,6 +11,11 @@ const math = std.math;...@@ -11,6 +11,11 @@ const math = std.math;
1111
12const is_darwin = builtin.os.tag.isDarwin();12const is_darwin = builtin.os.tag.isDarwin();
1313
14pub const has_executable_bit = switch (builtin.os.tag) {
15 .windows, .wasi => false,
16 else => true,
17};
18
14pub const path = @import("fs/path.zig");19pub const path = @import("fs/path.zig");
15pub const File = @import("fs/file.zig").File;20pub const File = @import("fs/file.zig").File;
16pub const wasi = @import("fs/wasi.zig");21pub const wasi = @import("fs/wasi.zig");
lib/std/os.zig+32-3
...@@ -302,8 +302,7 @@ pub const FChmodError = error{...@@ -302,8 +302,7 @@ pub const FChmodError = error{
302/// successfully, or must have the effective user ID matching the owner302/// successfully, or must have the effective user ID matching the owner
303/// of the file.303/// of the file.
304pub fn fchmod(fd: fd_t, mode: mode_t) FChmodError!void {304pub fn fchmod(fd: fd_t, mode: mode_t) FChmodError!void {
305 if (builtin.os.tag == .windows or builtin.os.tag == .wasi)305 if (!std.fs.has_executable_bit) @compileError("fchmod unsupported by target OS");
306 @compileError("Unsupported OS");
307306
308 while (true) {307 while (true) {
309 const res = system.fchmod(fd, mode);308 const res = system.fchmod(fd, mode);
...@@ -311,8 +310,38 @@ pub fn fchmod(fd: fd_t, mode: mode_t) FChmodError!void {...@@ -311,8 +310,38 @@ pub fn fchmod(fd: fd_t, mode: mode_t) FChmodError!void {
311 switch (system.getErrno(res)) {310 switch (system.getErrno(res)) {
312 .SUCCESS => return,311 .SUCCESS => return,
313 .INTR => continue,312 .INTR => continue,
314 .BADF => unreachable, // Can be reached if the fd refers to a non-iterable directory.313 .BADF => unreachable,
314 .FAULT => unreachable,
315 .INVAL => unreachable,
316 .ACCES => return error.AccessDenied,
317 .IO => return error.InputOutput,
318 .LOOP => return error.SymLinkLoop,
319 .NOENT => return error.FileNotFound,
320 .NOMEM => return error.SystemResources,
321 .NOTDIR => return error.FileNotFound,
322 .PERM => return error.AccessDenied,
323 .ROFS => return error.ReadOnlyFileSystem,
324 else => |err| return unexpectedErrno(err),
325 }
326 }
327}
328
329const FChmodAtError = FChmodError || error{
330 NameTooLong,
331};
315332
333pub fn fchmodat(dirfd: fd_t, path: []const u8, mode: mode_t, flags: u32) FChmodAtError!void {
334 if (!std.fs.has_executable_bit) @compileError("fchmodat unsupported by target OS");
335
336 const path_c = try toPosixPath(path);
337
338 while (true) {
339 const res = system.fchmodat(dirfd, &path_c, mode, flags);
340
341 switch (system.getErrno(res)) {
342 .SUCCESS => return,
343 .INTR => continue,
344 .BADF => unreachable,
316 .FAULT => unreachable,345 .FAULT => unreachable,
317 .INVAL => unreachable,346 .INVAL => unreachable,
318 .ACCES => return error.AccessDenied,347 .ACCES => return error.AccessDenied,
lib/std/os/linux.zig+18
...@@ -769,6 +769,20 @@ pub fn fchmod(fd: i32, mode: mode_t) usize {...@@ -769,6 +769,20 @@ pub fn fchmod(fd: i32, mode: mode_t) usize {
769 return syscall2(.fchmod, @bitCast(usize, @as(isize, fd)), mode);769 return syscall2(.fchmod, @bitCast(usize, @as(isize, fd)), mode);
770}770}
771771
772pub fn chmod(path: [*:0]const u8, mode: mode_t) usize {
773 if (@hasField(SYS, "chmod")) {
774 return syscall2(.chmod, @ptrToInt(path), mode);
775 } else {
776 return syscall4(
777 .fchmodat,
778 @bitCast(usize, @as(isize, AT.FDCWD)),
779 @ptrToInt(path),
780 mode,
781 0,
782 );
783 }
784}
785
772pub fn fchown(fd: i32, owner: uid_t, group: gid_t) usize {786pub fn fchown(fd: i32, owner: uid_t, group: gid_t) usize {
773 if (@hasField(SYS, "fchown32")) {787 if (@hasField(SYS, "fchown32")) {
774 return syscall3(.fchown32, @bitCast(usize, @as(isize, fd)), owner, group);788 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 {...@@ -777,6 +791,10 @@ pub fn fchown(fd: i32, owner: uid_t, group: gid_t) usize {
777 }791 }
778}792}
779793
794pub fn fchmodat(fd: i32, path: [*:0]const u8, mode: mode_t, flags: u32) usize {
795 return syscall4(.fchmodat, @bitCast(usize, @as(isize, fd)), @ptrToInt(path), mode, flags);
796}
797
780/// Can only be called on 32 bit systems. For 64 bit see `lseek`.798/// Can only be called on 32 bit systems. For 64 bit see `lseek`.
781pub fn llseek(fd: i32, offset: u64, result: ?*u64, whence: usize) usize {799pub fn llseek(fd: i32, offset: u64, result: ?*u64, whence: usize) usize {
782 // NOTE: The offset parameter splitting is independent from the target800 // NOTE: The offset parameter splitting is independent from the target
lib/std/os/test.zig+43-29
...@@ -531,17 +531,17 @@ test "memfd_create" {...@@ -531,17 +531,17 @@ test "memfd_create" {
531 else => return error.SkipZigTest,531 else => return error.SkipZigTest,
532 }532 }
533533
534 const fd = std.os.memfd_create("test", 0) catch |err| switch (err) {534 const fd = os.memfd_create("test", 0) catch |err| switch (err) {
535 // Related: https://github.com/ziglang/zig/issues/4019535 // Related: https://github.com/ziglang/zig/issues/4019
536 error.SystemOutdated => return error.SkipZigTest,536 error.SystemOutdated => return error.SkipZigTest,
537 else => |e| return e,537 else => |e| return e,
538 };538 };
539 defer std.os.close(fd);539 defer os.close(fd);
540 try expect((try std.os.write(fd, "test")) == 4);540 try expect((try os.write(fd, "test")) == 4);
541 try std.os.lseek_SET(fd, 0);541 try os.lseek_SET(fd, 0);
542542
543 var buf: [10]u8 = undefined;543 var buf: [10]u8 = undefined;
544 const bytes_read = try std.os.read(fd, &buf);544 const bytes_read = try os.read(fd, &buf);
545 try expect(bytes_read == 4);545 try expect(bytes_read == 4);
546 try expect(mem.eql(u8, buf[0..4], "test"));546 try expect(mem.eql(u8, buf[0..4], "test"));
547}547}
...@@ -688,7 +688,7 @@ test "signalfd" {...@@ -688,7 +688,7 @@ test "signalfd" {
688 .linux, .solaris => {},688 .linux, .solaris => {},
689 else => return error.SkipZigTest,689 else => return error.SkipZigTest,
690 }690 }
691 _ = std.os.signalfd;691 _ = os.signalfd;
692}692}
693693
694test "sync" {694test "sync" {
...@@ -757,11 +757,11 @@ test "shutdown socket" {...@@ -757,11 +757,11 @@ test "shutdown socket" {
757 if (native_os == .wasi)757 if (native_os == .wasi)
758 return error.SkipZigTest;758 return error.SkipZigTest;
759 if (native_os == .windows) {759 if (native_os == .windows) {
760 _ = try std.os.windows.WSAStartup(2, 2);760 _ = try os.windows.WSAStartup(2, 2);
761 }761 }
762 defer {762 defer {
763 if (native_os == .windows) {763 if (native_os == .windows) {
764 std.os.windows.WSACleanup() catch unreachable;764 os.windows.WSACleanup() catch unreachable;
765 }765 }
766 }766 }
767 const sock = try os.socket(os.AF.INET, os.SOCK.STREAM, 0);767 const sock = try os.socket(os.AF.INET, os.SOCK.STREAM, 0);
...@@ -855,13 +855,13 @@ test "dup & dup2" {...@@ -855,13 +855,13 @@ test "dup & dup2" {
855 var file = try tmp.dir.createFile("os_dup_test", .{});855 var file = try tmp.dir.createFile("os_dup_test", .{});
856 defer file.close();856 defer file.close();
857857
858 var duped = std.fs.File{ .handle = try std.os.dup(file.handle) };858 var duped = std.fs.File{ .handle = try os.dup(file.handle) };
859 defer duped.close();859 defer duped.close();
860 try duped.writeAll("dup");860 try duped.writeAll("dup");
861861
862 // Tests aren't run in parallel so using the next fd shouldn't be an issue.862 // Tests aren't run in parallel so using the next fd shouldn't be an issue.
863 const new_fd = duped.handle + 1;863 const new_fd = duped.handle + 1;
864 try std.os.dup2(file.handle, new_fd);864 try os.dup2(file.handle, new_fd);
865 var dup2ed = std.fs.File{ .handle = new_fd };865 var dup2ed = std.fs.File{ .handle = new_fd };
866 defer dup2ed.close();866 defer dup2ed.close();
867 try dup2ed.writeAll("dup2");867 try dup2ed.writeAll("dup2");
...@@ -909,46 +909,46 @@ test "POSIX file locking with fcntl" {...@@ -909,46 +909,46 @@ test "POSIX file locking with fcntl" {
909 const fd = file.handle;909 const fd = file.handle;
910910
911 // Place an exclusive lock on the first byte, and a shared lock on the second byte:911 // Place an exclusive lock on the first byte, and a shared lock on the second byte:
912 var struct_flock = std.mem.zeroInit(std.os.Flock, .{ .type = std.os.F.WRLCK });912 var struct_flock = std.mem.zeroInit(os.Flock, .{ .type = os.F.WRLCK });
913 _ = try std.os.fcntl(fd, std.os.F.SETLK, @ptrToInt(&struct_flock));913 _ = try os.fcntl(fd, os.F.SETLK, @ptrToInt(&struct_flock));
914 struct_flock.start = 1;914 struct_flock.start = 1;
915 struct_flock.type = std.os.F.RDLCK;915 struct_flock.type = os.F.RDLCK;
916 _ = try std.os.fcntl(fd, std.os.F.SETLK, @ptrToInt(&struct_flock));916 _ = try os.fcntl(fd, os.F.SETLK, @ptrToInt(&struct_flock));
917917
918 // Check the locks in a child process:918 // Check the locks in a child process:
919 const pid = try std.os.fork();919 const pid = try os.fork();
920 if (pid == 0) {920 if (pid == 0) {
921 // child expects be denied the exclusive lock:921 // child expects be denied the exclusive lock:
922 struct_flock.start = 0;922 struct_flock.start = 0;
923 struct_flock.type = std.os.F.WRLCK;923 struct_flock.type = os.F.WRLCK;
924 try expectError(error.Locked, std.os.fcntl(fd, std.os.F.SETLK, @ptrToInt(&struct_flock)));924 try expectError(error.Locked, os.fcntl(fd, os.F.SETLK, @ptrToInt(&struct_flock)));
925 // child expects to get the shared lock:925 // child expects to get the shared lock:
926 struct_flock.start = 1;926 struct_flock.start = 1;
927 struct_flock.type = std.os.F.RDLCK;927 struct_flock.type = os.F.RDLCK;
928 _ = try std.os.fcntl(fd, std.os.F.SETLK, @ptrToInt(&struct_flock));928 _ = try os.fcntl(fd, os.F.SETLK, @ptrToInt(&struct_flock));
929 // child waits for the exclusive lock in order to test deadlock:929 // child waits for the exclusive lock in order to test deadlock:
930 struct_flock.start = 0;930 struct_flock.start = 0;
931 struct_flock.type = std.os.F.WRLCK;931 struct_flock.type = os.F.WRLCK;
932 _ = try std.os.fcntl(fd, std.os.F.SETLKW, @ptrToInt(&struct_flock));932 _ = try os.fcntl(fd, os.F.SETLKW, @ptrToInt(&struct_flock));
933 // child exits without continuing:933 // child exits without continuing:
934 std.os.exit(0);934 os.exit(0);
935 } else {935 } else {
936 // parent waits for child to get shared lock:936 // parent waits for child to get shared lock:
937 std.time.sleep(1 * std.time.ns_per_ms);937 std.time.sleep(1 * std.time.ns_per_ms);
938 // parent expects deadlock when attempting to upgrade the shared lock to exclusive:938 // parent expects deadlock when attempting to upgrade the shared lock to exclusive:
939 struct_flock.start = 1;939 struct_flock.start = 1;
940 struct_flock.type = std.os.F.WRLCK;940 struct_flock.type = os.F.WRLCK;
941 try expectError(error.DeadLock, std.os.fcntl(fd, std.os.F.SETLKW, @ptrToInt(&struct_flock)));941 try expectError(error.DeadLock, os.fcntl(fd, os.F.SETLKW, @ptrToInt(&struct_flock)));
942 // parent releases exclusive lock:942 // parent releases exclusive lock:
943 struct_flock.start = 0;943 struct_flock.start = 0;
944 struct_flock.type = std.os.F.UNLCK;944 struct_flock.type = os.F.UNLCK;
945 _ = try std.os.fcntl(fd, std.os.F.SETLK, @ptrToInt(&struct_flock));945 _ = try os.fcntl(fd, os.F.SETLK, @ptrToInt(&struct_flock));
946 // parent releases shared lock:946 // parent releases shared lock:
947 struct_flock.start = 1;947 struct_flock.start = 1;
948 struct_flock.type = std.os.F.UNLCK;948 struct_flock.type = os.F.UNLCK;
949 _ = try std.os.fcntl(fd, std.os.F.SETLK, @ptrToInt(&struct_flock));949 _ = try os.fcntl(fd, os.F.SETLK, @ptrToInt(&struct_flock));
950 // parent waits for child:950 // parent waits for child:
951 const result = std.os.waitpid(pid, 0);951 const result = os.waitpid(pid, 0);
952 try expect(result.status == 0 * 256);952 try expect(result.status == 0 * 256);
953 }953 }
954}954}
...@@ -1182,3 +1182,17 @@ test "pwrite with empty buffer" {...@@ -1182,3 +1182,17 @@ test "pwrite with empty buffer" {
11821182
1183 _ = try os.pwrite(file.handle, bytes, 0);1183 _ = try os.pwrite(file.handle, bytes, 0);
1184}1184}
1185
1186test "fchmodat smoke test" {
1187 if (!std.fs.has_executable_bit) return error.SkipZigTest;
1188
1189 var tmp = tmpDir(.{});
1190 defer tmp.cleanup();
1191
1192 try expectError(error.FileNotFound, os.fchmodat(tmp.dir.fd, "foo.txt", 0o666, 0));
1193 const fd = try os.openat(tmp.dir.fd, "foo.txt", os.O.RDWR | os.O.CREAT | os.O.EXCL, 0o666);
1194 os.close(fd);
1195 try os.fchmodat(tmp.dir.fd, "foo.txt", 0o755, 0);
1196 const st = try os.fstatat(tmp.dir.fd, "foo.txt", 0);
1197 try expectEqual(@as(os.mode_t, 0o755), st.mode & 0b111_111_111);
1198}