| author | |
| committer | |
| log | 0e230993d51d0ecded40d5235ada4f2f64036b26 |
| tree | 4caa6e7f003c791cd96a54b63226b78eaa649282 |
| parent | c4cefd68358470d464bf66bc41985cadd874db73 |
11 files changed, 239 insertions(+), 211 deletions(-)
lib/std/Io.zig+2| ... | ... | @@ -684,7 +684,9 @@ pub const VTable = struct { |
| 684 | 684 | dirSymLink: *const fn (?*anyopaque, Dir, target_path: []const u8, sym_link_path: []const u8, Dir.SymLinkFlags) Dir.SymLinkError!void, |
| 685 | 685 | dirReadLink: *const fn (?*anyopaque, Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLinkError!usize, |
| 686 | 686 | dirSetOwner: *const fn (?*anyopaque, Dir, ?File.Uid, ?File.Gid) Dir.SetOwnerError!void, |
| 687 | dirSetFileOwner: *const fn (?*anyopaque, Dir, []const u8, ?File.Uid, ?File.Gid, Dir.SetFileOwnerOptions) Dir.SetFileOwnerError!void, | |
| 687 | 688 | dirSetPermissions: *const fn (?*anyopaque, Dir, Dir.Permissions) Dir.SetPermissionsError!void, |
| 689 | dirSetFilePermissions: *const fn (?*anyopaque, Dir, []const u8, File.Permissions, Dir.SetFilePermissionsOptions) Dir.SetFilePermissionsError!void, | |
| 688 | 690 | dirSetTimestamps: *const fn (?*anyopaque, Dir, []const u8, last_accessed: Timestamp, last_modified: Timestamp, Dir.SetTimestampsOptions) Dir.SetTimestampsError!void, |
| 689 | 691 | dirSetTimestampsNow: *const fn (?*anyopaque, Dir, []const u8, Dir.SetTimestampsOptions) Dir.SetTimestampsError!void, |
| 690 | 692 |
lib/std/Io/Dir.zig+41| ... | ... | @@ -1698,6 +1698,29 @@ pub fn setPermissions(dir: Dir, io: Io, new_permissions: File.Permissions) SetPe |
| 1698 | 1698 | return io.vtable.dirSetPermissions(io.userdata, dir, new_permissions); |
| 1699 | 1699 | } |
| 1700 | 1700 | |
| 1701 | pub const SetFilePermissionsError = PathNameError || SetPermissionsError || error{ | |
| 1702 | ProcessFdQuotaExceeded, | |
| 1703 | SystemFdQuotaExceeded, | |
| 1704 | /// `SetFilePermissionsOptions.follow_symlinks` was set to false, which is | |
| 1705 | /// not allowed by the file system or operating system. | |
| 1706 | OperationNotSupported, | |
| 1707 | }; | |
| 1708 | ||
| 1709 | pub const SetFilePermissionsOptions = struct { | |
| 1710 | follow_symlinks: bool = true, | |
| 1711 | }; | |
| 1712 | ||
| 1713 | /// Also known as "chmodat". | |
| 1714 | pub fn setFilePermissions( | |
| 1715 | dir: Dir, | |
| 1716 | io: Io, | |
| 1717 | sub_path: []const u8, | |
| 1718 | new_permissions: File.Permissions, | |
| 1719 | options: SetFilePermissionsOptions, | |
| 1720 | ) SetFilePermissionsError!void { | |
| 1721 | return io.vtable.dirSetFilePermissions(io.userdata, sub_path, dir, new_permissions, options); | |
| 1722 | } | |
| 1723 | ||
| 1701 | 1724 | pub const SetOwnerError = File.SetOwnerError; |
| 1702 | 1725 | |
| 1703 | 1726 | /// Also known as "chown". |
| ... | ... | @@ -1711,6 +1734,24 @@ pub fn setOwner(dir: Dir, io: Io, owner: ?File.Uid, group: ?File.Gid) SetOwnerEr |
| 1711 | 1734 | return io.vtable.dirSetOwner(io.userdata, dir, owner, group); |
| 1712 | 1735 | } |
| 1713 | 1736 | |
| 1737 | pub const SetFileOwnerError = PathNameError || SetOwnerError; | |
| 1738 | ||
| 1739 | pub const SetFileOwnerOptions = struct { | |
| 1740 | follow_symlinks: bool = true, | |
| 1741 | }; | |
| 1742 | ||
| 1743 | /// Also known as "fchownat". | |
| 1744 | pub fn setFileOwner( | |
| 1745 | dir: Dir, | |
| 1746 | io: Io, | |
| 1747 | sub_path: []const u8, | |
| 1748 | owner: ?File.Uid, | |
| 1749 | group: ?File.Gid, | |
| 1750 | options: SetFileOwnerOptions, | |
| 1751 | ) SetOwnerError!void { | |
| 1752 | return io.vtable.dirSetFileOwner(io.userdata, dir, sub_path, owner, group, options); | |
| 1753 | } | |
| 1754 | ||
| 1714 | 1755 | pub const SetTimestampsError = File.SetTimestampsError || PathNameError; |
| 1715 | 1756 | |
| 1716 | 1757 | pub const SetTimestampsOptions = struct { |
lib/std/Io/Threaded.zig+176-3| ... | ... | @@ -76,6 +76,7 @@ old_sig_pipe: if (have_sig_pipe) posix.Sigaction else void, |
| 76 | 76 | use_sendfile: UseSendfile = .default, |
| 77 | 77 | use_copy_file_range: UseCopyFileRange = .default, |
| 78 | 78 | use_fcopyfile: UseFcopyfile = .default, |
| 79 | use_fchmodat2: UseFchmodat2 = .default, | |
| 79 | 80 | |
| 80 | 81 | stderr_writer: File.Writer = .{ |
| 81 | 82 | .io = undefined, |
| ... | ... | @@ -124,6 +125,15 @@ pub const UseFcopyfile = if (have_fcopyfile) enum { |
| 124 | 125 | pub const default: UseFcopyfile = .disabled; |
| 125 | 126 | }; |
| 126 | 127 | |
| 128 | pub const UseFchmodat2 = if (have_fchmodat2 and !have_fchmodat_flags) enum { | |
| 129 | enabled, | |
| 130 | disabled, | |
| 131 | pub const default: UseFchmodat2 = .enabled; | |
| 132 | } else enum { | |
| 133 | disabled, | |
| 134 | pub const default: UseFchmodat2 = .disabled; | |
| 135 | }; | |
| 136 | ||
| 127 | 137 | const Thread = struct { |
| 128 | 138 | /// The value that needs to be passed to pthread_kill or tgkill in order to |
| 129 | 139 | /// send a signal. |
| ... | ... | @@ -712,7 +722,9 @@ pub fn io(t: *Threaded) Io { |
| 712 | 722 | .dirSymLink = dirSymLink, |
| 713 | 723 | .dirReadLink = dirReadLink, |
| 714 | 724 | .dirSetOwner = dirSetOwner, |
| 725 | .dirSetFileOwner = dirSetFileOwner, | |
| 715 | 726 | .dirSetPermissions = dirSetPermissions, |
| 727 | .dirSetFilePermissions = dirSetFilePermissions, | |
| 716 | 728 | .dirSetTimestamps = dirSetTimestamps, |
| 717 | 729 | .dirSetTimestampsNow = dirSetTimestampsNow, |
| 718 | 730 | |
| ... | ... | @@ -842,7 +854,9 @@ pub fn ioBasic(t: *Threaded) Io { |
| 842 | 854 | .dirSymLink = dirSymLink, |
| 843 | 855 | .dirReadLink = dirReadLink, |
| 844 | 856 | .dirSetOwner = dirSetOwner, |
| 857 | .dirSetFileOwner = dirSetFileOwner, | |
| 845 | 858 | .dirSetPermissions = dirSetPermissions, |
| 859 | .dirSetFilePermissions = dirSetFilePermissions, | |
| 846 | 860 | .dirSetTimestamps = dirSetTimestamps, |
| 847 | 861 | .dirSetTimestampsNow = dirSetTimestampsNow, |
| 848 | 862 | |
| ... | ... | @@ -921,6 +935,11 @@ const have_copy_file_range = switch (native_os) { |
| 921 | 935 | else => false, |
| 922 | 936 | }; |
| 923 | 937 | const have_fcopyfile = is_darwin; |
| 938 | const have_fchmodat2 = native_os == .linux and | |
| 939 | (builtin.os.isAtLeast(.linux, .{ .major = 6, .minor = 6, .patch = 0 }) orelse true) and | |
| 940 | (builtin.abi.isAndroid() or !std.c.versionCheck(.{ .major = 2, .minor = 32, .patch = 0 })); | |
| 941 | const have_fchmodat_flags = native_os != .linux or | |
| 942 | (!builtin.abi.isAndroid() and std.c.versionCheck(.{ .major = 2, .minor = 32, .patch = 0 })); | |
| 924 | 943 | |
| 925 | 944 | const openat_sym = if (posix.lfs64_abi) posix.system.openat64 else posix.system.openat; |
| 926 | 945 | const fstat_sym = if (posix.lfs64_abi) posix.system.fstat64 else posix.system.fstat; |
| ... | ... | @@ -4862,11 +4881,11 @@ const dirSetPermissions = switch (native_os) { |
| 4862 | 4881 | }; |
| 4863 | 4882 | |
| 4864 | 4883 | fn dirSetPermissionsWindows(userdata: ?*anyopaque, dir: Dir, permissions: Dir.Permissions) Dir.SetPermissionsError!void { |
| 4865 | // TODO I think we can actually set permissions on a dir on windows? | |
| 4866 | _ = userdata; | |
| 4884 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | |
| 4885 | _ = t; | |
| 4867 | 4886 | _ = dir; |
| 4868 | 4887 | _ = permissions; |
| 4869 | return error.Unexpected; | |
| 4888 | @panic("TODO"); | |
| 4870 | 4889 | } |
| 4871 | 4890 | |
| 4872 | 4891 | fn dirSetPermissionsPosix(userdata: ?*anyopaque, dir: Dir, permissions: Dir.Permissions) Dir.SetPermissionsError!void { |
| ... | ... | @@ -4875,6 +4894,133 @@ fn dirSetPermissionsPosix(userdata: ?*anyopaque, dir: Dir, permissions: Dir.Perm |
| 4875 | 4894 | return setPermissionsPosix(current_thread, dir.handle, permissions.toMode()); |
| 4876 | 4895 | } |
| 4877 | 4896 | |
| 4897 | fn dirSetFilePermissions( | |
| 4898 | userdata: ?*anyopaque, | |
| 4899 | dir: Dir, | |
| 4900 | sub_path: []const u8, | |
| 4901 | permissions: Dir.Permissions, | |
| 4902 | options: Dir.SetFilePermissionsOptions, | |
| 4903 | ) Dir.SetFilePermissionsError!void { | |
| 4904 | if (!Dir.Permissions.has_executable_bit) return error.Unexpected; | |
| 4905 | if (is_windows) @panic("TODO"); | |
| 4906 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | |
| 4907 | const current_thread = Thread.getCurrent(t); | |
| 4908 | ||
| 4909 | var path_buffer: [posix.PATH_MAX]u8 = undefined; | |
| 4910 | const sub_path_posix = try pathToPosix(sub_path, &path_buffer); | |
| 4911 | ||
| 4912 | const mode = permissions.toMode(); | |
| 4913 | const flags: u32 = if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0; | |
| 4914 | ||
| 4915 | return posixFchmodat(t, current_thread, dir.handle, sub_path_posix, mode, flags); | |
| 4916 | } | |
| 4917 | ||
| 4918 | fn posixFchmodat( | |
| 4919 | t: *Threaded, | |
| 4920 | current_thread: *Thread, | |
| 4921 | dir_fd: posix.fd_t, | |
| 4922 | path: [*:0]const u8, | |
| 4923 | mode: posix.mode_t, | |
| 4924 | flags: u32, | |
| 4925 | ) Dir.SetFilePermissionsError!void { | |
| 4926 | // No special handling for linux is needed if we can use the libc fallback | |
| 4927 | // or `flags` is empty. Glibc only added the fallback in 2.32. | |
| 4928 | if (have_fchmodat_flags or flags == 0) { | |
| 4929 | try current_thread.beginSyscall(); | |
| 4930 | while (true) { | |
| 4931 | const rc = if (have_fchmodat_flags) | |
| 4932 | posix.system.fchmodat(dir_fd, path, mode, flags) | |
| 4933 | else | |
| 4934 | posix.system.fchmodat(dir_fd, path, mode); | |
| 4935 | switch (posix.errno(rc)) { | |
| 4936 | .SUCCESS => return current_thread.endSyscall(), | |
| 4937 | .CANCELED => return current_thread.endSyscallCanceled(), | |
| 4938 | .INTR => { | |
| 4939 | try current_thread.checkCancel(); | |
| 4940 | continue; | |
| 4941 | }, | |
| 4942 | else => |e| { | |
| 4943 | current_thread.endSyscall(); | |
| 4944 | switch (e) { | |
| 4945 | .BADF => |err| return errnoBug(err), | |
| 4946 | .FAULT => |err| return errnoBug(err), | |
| 4947 | .INVAL => |err| return errnoBug(err), | |
| 4948 | .ACCES => return error.AccessDenied, | |
| 4949 | .IO => return error.InputOutput, | |
| 4950 | .LOOP => return error.SymLinkLoop, | |
| 4951 | .MFILE => return error.ProcessFdQuotaExceeded, | |
| 4952 | .NAMETOOLONG => return error.NameTooLong, | |
| 4953 | .NFILE => return error.SystemFdQuotaExceeded, | |
| 4954 | .NOENT => return error.FileNotFound, | |
| 4955 | .NOTDIR => return error.FileNotFound, | |
| 4956 | .NOMEM => return error.SystemResources, | |
| 4957 | .OPNOTSUPP => return error.OperationNotSupported, | |
| 4958 | .PERM => return error.PermissionDenied, | |
| 4959 | .ROFS => return error.ReadOnlyFileSystem, | |
| 4960 | else => |err| return posix.unexpectedErrno(err), | |
| 4961 | } | |
| 4962 | }, | |
| 4963 | } | |
| 4964 | } | |
| 4965 | } | |
| 4966 | ||
| 4967 | if (@atomicLoad(UseFchmodat2, &t.use_fchmodat2, .monotonic) == .disabled) | |
| 4968 | return fchmodatFallback(current_thread, dir_fd, path, mode, flags); | |
| 4969 | ||
| 4970 | comptime assert(native_os == .linux); | |
| 4971 | ||
| 4972 | try current_thread.beginSyscall(); | |
| 4973 | while (true) { | |
| 4974 | switch (std.os.linux.errno(std.os.linux.fchmodat2(dir_fd, path, mode, flags))) { | |
| 4975 | .SUCCESS => return current_thread.endSyscall(), | |
| 4976 | .CANCELED => return current_thread.endSyscallCanceled(), | |
| 4977 | .INTR => { | |
| 4978 | try current_thread.checkCancel(); | |
| 4979 | continue; | |
| 4980 | }, | |
| 4981 | else => |e| { | |
| 4982 | current_thread.endSyscall(); | |
| 4983 | switch (e) { | |
| 4984 | .BADF => |err| return errnoBug(err), | |
| 4985 | .FAULT => |err| return errnoBug(err), | |
| 4986 | .INVAL => |err| return errnoBug(err), | |
| 4987 | .ACCES => return error.AccessDenied, | |
| 4988 | .IO => return error.InputOutput, | |
| 4989 | .LOOP => return error.SymLinkLoop, | |
| 4990 | .NOENT => return error.FileNotFound, | |
| 4991 | .NOMEM => return error.SystemResources, | |
| 4992 | .NOTDIR => return error.FileNotFound, | |
| 4993 | .OPNOTSUPP => return error.OperationNotSupported, | |
| 4994 | .PERM => return error.PermissionDenied, | |
| 4995 | .ROFS => return error.ReadOnlyFileSystem, | |
| 4996 | .NOSYS => { | |
| 4997 | @atomicStore(UseFchmodat2, &t.use_fchmodat2, .disabled, .monotonic); | |
| 4998 | return fchmodatFallback(current_thread, dir_fd, path, mode, flags); | |
| 4999 | }, | |
| 5000 | else => |err| return posix.unexpectedErrno(err), | |
| 5001 | } | |
| 5002 | }, | |
| 5003 | } | |
| 5004 | } | |
| 5005 | } | |
| 5006 | ||
| 5007 | fn fchmodatFallback( | |
| 5008 | current_thread: *Thread, | |
| 5009 | dir_fd: posix.fd_t, | |
| 5010 | path: [*:0]const u8, | |
| 5011 | mode: posix.mode_t, | |
| 5012 | flags: u32, | |
| 5013 | ) Dir.SetFilePermissionsError!void { | |
| 5014 | _ = current_thread; | |
| 5015 | _ = dir_fd; | |
| 5016 | _ = path; | |
| 5017 | _ = mode; | |
| 5018 | _ = flags; | |
| 5019 | // I deleted the previous fallback implementation because it looked wrong to me. Please cross-reference | |
| 5020 | // fhmodat.c in musl libc before blindly restoring the implementation. | |
| 5021 | @panic("TODO"); | |
| 5022 | } | |
| 5023 | ||
| 4878 | 5024 | const dirSetOwner = switch (native_os) { |
| 4879 | 5025 | .windows => dirSetOwnerUnsupported, |
| 4880 | 5026 | else => dirSetOwnerPosix, |
| ... | ... | @@ -4927,6 +5073,33 @@ fn setOwnerPosix(current_thread: *Thread, fd: posix.fd_t, uid: posix.uid_t, gid: |
| 4927 | 5073 | } |
| 4928 | 5074 | } |
| 4929 | 5075 | |
| 5076 | fn dirSetFileOwner( | |
| 5077 | userdata: ?*anyopaque, | |
| 5078 | dir: Dir, | |
| 5079 | sub_path: []const u8, | |
| 5080 | owner: ?File.Uid, | |
| 5081 | group: ?File.Gid, | |
| 5082 | options: Dir.SetFileOwnerOptions, | |
| 5083 | ) Dir.SetFileOwnerError!void { | |
| 5084 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | |
| 5085 | const current_thread = Thread.getCurrent(t); | |
| 5086 | ||
| 5087 | if (is_windows) { | |
| 5088 | @panic("TODO"); | |
| 5089 | } | |
| 5090 | ||
| 5091 | var path_buffer: [posix.PATH_MAX]u8 = undefined; | |
| 5092 | const sub_path_posix = try pathToPosix(sub_path, &path_buffer); | |
| 5093 | ||
| 5094 | _ = current_thread; | |
| 5095 | _ = dir; | |
| 5096 | _ = sub_path_posix; | |
| 5097 | _ = owner; | |
| 5098 | _ = group; | |
| 5099 | _ = options; | |
| 5100 | @panic("TODO"); | |
| 5101 | } | |
| 5102 | ||
| 4930 | 5103 | const fileSync = switch (native_os) { |
| 4931 | 5104 | .windows => fileSyncWindows, |
| 4932 | 5105 | else => fileSyncPosix, |
lib/std/os/linux.zig+2-2| ... | ... | @@ -1420,7 +1420,7 @@ pub fn chmod(path: [*:0]const u8, mode: mode_t) usize { |
| 1420 | 1420 | if (@hasField(SYS, "chmod")) { |
| 1421 | 1421 | return syscall2(.chmod, @intFromPtr(path), mode); |
| 1422 | 1422 | } else { |
| 1423 | return fchmodat(AT.FDCWD, path, mode, 0); | |
| 1423 | return fchmodat(AT.FDCWD, path, mode); | |
| 1424 | 1424 | } |
| 1425 | 1425 | } |
| 1426 | 1426 | |
| ... | ... | @@ -1432,7 +1432,7 @@ pub fn fchown(fd: i32, owner: uid_t, group: gid_t) usize { |
| 1432 | 1432 | } |
| 1433 | 1433 | } |
| 1434 | 1434 | |
| 1435 | pub fn fchmodat(fd: i32, path: [*:0]const u8, mode: mode_t, _: u32) usize { | |
| 1435 | pub fn fchmodat(fd: i32, path: [*:0]const u8, mode: mode_t) usize { | |
| 1436 | 1436 | return syscall3(.fchmodat, @bitCast(@as(isize, fd)), @intFromPtr(path), mode); |
| 1437 | 1437 | } |
| 1438 | 1438 |
lib/std/posix.zig-184| ... | ... | @@ -310,190 +310,6 @@ pub fn close(fd: fd_t) void { |
| 310 | 310 | } |
| 311 | 311 | } |
| 312 | 312 | |
| 313 | pub const FChmodAtError = std.Io.File.SetPermissionsError || error{ | |
| 314 | /// A component of `path` exceeded `NAME_MAX`, or the entire path exceeded | |
| 315 | /// `PATH_MAX`. | |
| 316 | NameTooLong, | |
| 317 | /// `path` resolves to a symbolic link, and `AT.SYMLINK_NOFOLLOW` was set | |
| 318 | /// in `flags`. This error only occurs on Linux, where changing the mode of | |
| 319 | /// a symbolic link has no meaning and can cause undefined behaviour on | |
| 320 | /// certain filesystems. | |
| 321 | /// | |
| 322 | /// The procfs fallback was used but procfs was not mounted. | |
| 323 | OperationNotSupported, | |
| 324 | /// The procfs fallback was used but the process exceeded its open file | |
| 325 | /// limit. | |
| 326 | ProcessFdQuotaExceeded, | |
| 327 | /// The procfs fallback was used but the system exceeded it open file limit. | |
| 328 | SystemFdQuotaExceeded, | |
| 329 | }; | |
| 330 | ||
| 331 | /// Changes the `mode` of `path` relative to the directory referred to by | |
| 332 | /// `dirfd`. The process must have the correct privileges in order to do this | |
| 333 | /// successfully, or must have the effective user ID matching the owner of the | |
| 334 | /// file. | |
| 335 | /// | |
| 336 | /// On Linux the `fchmodat2` syscall will be used if available, otherwise a | |
| 337 | /// workaround using procfs will be employed. Changing the mode of a symbolic | |
| 338 | /// link with `AT.SYMLINK_NOFOLLOW` set will also return | |
| 339 | /// `OperationNotSupported`, as: | |
| 340 | /// | |
| 341 | /// 1. Permissions on the link are ignored when resolving its target. | |
| 342 | /// 2. This operation has been known to invoke undefined behaviour across | |
| 343 | /// different filesystems[1]. | |
| 344 | /// | |
| 345 | /// [1]: https://sourceware.org/legacy-ml/libc-alpha/2020-02/msg00467.html. | |
| 346 | pub inline fn fchmodat(dirfd: fd_t, path: []const u8, mode: mode_t, flags: u32) FChmodAtError!void { | |
| 347 | if (!fs.has_executable_bit) @compileError("fchmodat unsupported by target OS"); | |
| 348 | ||
| 349 | // No special handling for linux is needed if we can use the libc fallback | |
| 350 | // or `flags` is empty. Glibc only added the fallback in 2.32. | |
| 351 | const skip_fchmodat_fallback = native_os != .linux or | |
| 352 | (!builtin.abi.isAndroid() and std.c.versionCheck(.{ .major = 2, .minor = 32, .patch = 0 })) or | |
| 353 | flags == 0; | |
| 354 | ||
| 355 | // This function is marked inline so that when flags is comptime-known, | |
| 356 | // skip_fchmodat_fallback will be comptime-known true. | |
| 357 | if (skip_fchmodat_fallback) | |
| 358 | return fchmodat1(dirfd, path, mode, flags); | |
| 359 | ||
| 360 | return fchmodat2(dirfd, path, mode, flags); | |
| 361 | } | |
| 362 | ||
| 363 | fn fchmodat1(dirfd: fd_t, path: []const u8, mode: mode_t, flags: u32) FChmodAtError!void { | |
| 364 | const path_c = try toPosixPath(path); | |
| 365 | while (true) { | |
| 366 | const res = system.fchmodat(dirfd, &path_c, mode, flags); | |
| 367 | switch (errno(res)) { | |
| 368 | .SUCCESS => return, | |
| 369 | .INTR => continue, | |
| 370 | .BADF => unreachable, | |
| 371 | .FAULT => unreachable, | |
| 372 | .INVAL => unreachable, | |
| 373 | .ACCES => return error.AccessDenied, | |
| 374 | .IO => return error.InputOutput, | |
| 375 | .LOOP => return error.SymLinkLoop, | |
| 376 | .MFILE => return error.ProcessFdQuotaExceeded, | |
| 377 | .NAMETOOLONG => return error.NameTooLong, | |
| 378 | .NFILE => return error.SystemFdQuotaExceeded, | |
| 379 | .NOENT => return error.FileNotFound, | |
| 380 | .NOTDIR => return error.FileNotFound, | |
| 381 | .NOMEM => return error.SystemResources, | |
| 382 | .OPNOTSUPP => return error.OperationNotSupported, | |
| 383 | .PERM => return error.PermissionDenied, | |
| 384 | .ROFS => return error.ReadOnlyFileSystem, | |
| 385 | else => |err| return unexpectedErrno(err), | |
| 386 | } | |
| 387 | } | |
| 388 | } | |
| 389 | ||
| 390 | fn fchmodat2(dirfd: fd_t, path: []const u8, mode: mode_t, flags: u32) FChmodAtError!void { | |
| 391 | const global = struct { | |
| 392 | var has_fchmodat2: bool = true; | |
| 393 | }; | |
| 394 | const path_c = try toPosixPath(path); | |
| 395 | const use_fchmodat2 = (builtin.os.isAtLeast(.linux, .{ .major = 6, .minor = 6, .patch = 0 }) orelse false) and | |
| 396 | @atomicLoad(bool, &global.has_fchmodat2, .monotonic); | |
| 397 | while (use_fchmodat2) { | |
| 398 | // Later on this should be changed to `system.fchmodat2` | |
| 399 | // when the musl/glibc add a wrapper. | |
| 400 | const res = linux.fchmodat2(dirfd, &path_c, mode, flags); | |
| 401 | switch (linux.errno(res)) { | |
| 402 | .SUCCESS => return, | |
| 403 | .INTR => continue, | |
| 404 | .BADF => unreachable, | |
| 405 | .FAULT => unreachable, | |
| 406 | .INVAL => unreachable, | |
| 407 | .ACCES => return error.AccessDenied, | |
| 408 | .IO => return error.InputOutput, | |
| 409 | .LOOP => return error.SymLinkLoop, | |
| 410 | .NOENT => return error.FileNotFound, | |
| 411 | .NOMEM => return error.SystemResources, | |
| 412 | .NOTDIR => return error.FileNotFound, | |
| 413 | .OPNOTSUPP => return error.OperationNotSupported, | |
| 414 | .PERM => return error.PermissionDenied, | |
| 415 | .ROFS => return error.ReadOnlyFileSystem, | |
| 416 | ||
| 417 | .NOSYS => { | |
| 418 | @atomicStore(bool, &global.has_fchmodat2, false, .monotonic); | |
| 419 | break; | |
| 420 | }, | |
| 421 | else => |err| return unexpectedErrno(err), | |
| 422 | } | |
| 423 | } | |
| 424 | ||
| 425 | // Fallback to changing permissions using procfs: | |
| 426 | // | |
| 427 | // 1. Open `path` as a `PATH` descriptor. | |
| 428 | // 2. Stat the fd and check if it isn't a symbolic link. | |
| 429 | // 3. Generate the procfs reference to the fd via `/proc/self/fd/{fd}`. | |
| 430 | // 4. Pass the procfs path to `chmod` with the `mode`. | |
| 431 | var pathfd: fd_t = undefined; | |
| 432 | while (true) { | |
| 433 | const rc = system.openat(dirfd, &path_c, .{ .PATH = true, .NOFOLLOW = true, .CLOEXEC = true }, @as(mode_t, 0)); | |
| 434 | switch (errno(rc)) { | |
| 435 | .SUCCESS => { | |
| 436 | pathfd = @intCast(rc); | |
| 437 | break; | |
| 438 | }, | |
| 439 | .INTR => continue, | |
| 440 | .FAULT => unreachable, | |
| 441 | .INVAL => unreachable, | |
| 442 | .ACCES => return error.AccessDenied, | |
| 443 | .PERM => return error.PermissionDenied, | |
| 444 | .LOOP => return error.SymLinkLoop, | |
| 445 | .MFILE => return error.ProcessFdQuotaExceeded, | |
| 446 | .NAMETOOLONG => return error.NameTooLong, | |
| 447 | .NFILE => return error.SystemFdQuotaExceeded, | |
| 448 | .NOENT => return error.FileNotFound, | |
| 449 | .NOMEM => return error.SystemResources, | |
| 450 | else => |err| return unexpectedErrno(err), | |
| 451 | } | |
| 452 | } | |
| 453 | defer close(pathfd); | |
| 454 | ||
| 455 | const path_mode = if (linux.wrapped.statx( | |
| 456 | pathfd, | |
| 457 | "", | |
| 458 | AT.EMPTY_PATH, | |
| 459 | .{ .TYPE = true }, | |
| 460 | )) |stx| blk: { | |
| 461 | assert(stx.mask.TYPE); | |
| 462 | break :blk stx.mode; | |
| 463 | } else |err| switch (err) { | |
| 464 | error.NameTooLong => unreachable, | |
| 465 | error.FileNotFound => unreachable, | |
| 466 | else => |e| return e, | |
| 467 | }; | |
| 468 | // Even though we only wanted TYPE, the kernel can still fill in the additional bits. | |
| 469 | if ((path_mode & S.IFMT) == S.IFLNK) | |
| 470 | return error.OperationNotSupported; | |
| 471 | ||
| 472 | var procfs_buf: ["/proc/self/fd/-2147483648\x00".len]u8 = undefined; | |
| 473 | const proc_path = std.fmt.bufPrintSentinel(procfs_buf[0..], "/proc/self/fd/{d}", .{pathfd}, 0) catch unreachable; | |
| 474 | while (true) { | |
| 475 | const res = system.chmod(proc_path, mode); | |
| 476 | switch (errno(res)) { | |
| 477 | // Getting NOENT here means that procfs isn't mounted. | |
| 478 | .NOENT => return error.OperationNotSupported, | |
| 479 | ||
| 480 | .SUCCESS => return, | |
| 481 | .INTR => continue, | |
| 482 | .BADF => unreachable, | |
| 483 | .FAULT => unreachable, | |
| 484 | .INVAL => unreachable, | |
| 485 | .ACCES => return error.AccessDenied, | |
| 486 | .IO => return error.InputOutput, | |
| 487 | .LOOP => return error.SymLinkLoop, | |
| 488 | .NOMEM => return error.SystemResources, | |
| 489 | .NOTDIR => return error.FileNotFound, | |
| 490 | .PERM => return error.PermissionDenied, | |
| 491 | .ROFS => return error.ReadOnlyFileSystem, | |
| 492 | else => |err| return unexpectedErrno(err), | |
| 493 | } | |
| 494 | } | |
| 495 | } | |
| 496 | ||
| 497 | 313 | pub const RebootError = error{ |
| 498 | 314 | PermissionDenied, |
| 499 | 315 | } || UnexpectedError; |
lib/std/posix/test.zig+1-1| ... | ... | @@ -895,7 +895,7 @@ fn expectMode(dir: posix.fd_t, file: []const u8, mode: posix.mode_t) !void { |
| 895 | 895 | } |
| 896 | 896 | |
| 897 | 897 | test "fchmodat smoke test" { |
| 898 | if (!std.fs.has_executable_bit) return error.SkipZigTest; | |
| 898 | if (!Io.File.Permissions.has_executable_bit) return error.SkipZigTest; | |
| 899 | 899 | |
| 900 | 900 | var tmp = tmpDir(.{}); |
| 901 | 901 | defer tmp.cleanup(); |
lib/std/tar.zig+3-3| ... | ... | @@ -1122,7 +1122,7 @@ fn normalizePath(bytes: []u8) []u8 { |
| 1122 | 1122 | fn filePermissions(mode: u32, options: PipeOptions) Io.File.Permissions { |
| 1123 | 1123 | const default_mode = 0o666; |
| 1124 | 1124 | |
| 1125 | if (!std.fs.has_executable_bit or options.mode_mode == .ignore) | |
| 1125 | if (!Io.File.Permissions.has_executable_bit or options.mode_mode == .ignore) | |
| 1126 | 1126 | return .fromMode(default_mode); |
| 1127 | 1127 | |
| 1128 | 1128 | const S = std.posix.S; |
| ... | ... | @@ -1137,7 +1137,7 @@ fn filePermissions(mode: u32, options: PipeOptions) Io.File.Permissions { |
| 1137 | 1137 | } |
| 1138 | 1138 | |
| 1139 | 1139 | test filePermissions { |
| 1140 | if (!std.fs.has_executable_bit) return error.SkipZigTest; | |
| 1140 | if (!Io.File.Permissions.has_executable_bit) return error.SkipZigTest; | |
| 1141 | 1141 | try testing.expectEqual(0o666, filePermissions(0o744, PipeOptions{ .mode_mode = .ignore })); |
| 1142 | 1142 | try testing.expectEqual(0o777, filePermissions(0o744, PipeOptions{})); |
| 1143 | 1143 | try testing.expectEqual(0o666, filePermissions(0o644, PipeOptions{})); |
| ... | ... | @@ -1145,7 +1145,7 @@ test filePermissions { |
| 1145 | 1145 | } |
| 1146 | 1146 | |
| 1147 | 1147 | test "executable bit" { |
| 1148 | if (!std.fs.has_executable_bit) return error.SkipZigTest; | |
| 1148 | if (!Io.File.Permissions.has_executable_bit) return error.SkipZigTest; | |
| 1149 | 1149 | |
| 1150 | 1150 | const io = testing.io; |
| 1151 | 1151 | const S = std.posix.S; |
lib/std/zig/parser_test.zig+7-7| ... | ... | @@ -6334,7 +6334,7 @@ var fixed_buffer_mem: [100 * 1024]u8 = undefined; |
| 6334 | 6334 | |
| 6335 | 6335 | fn testParse(source: [:0]const u8, allocator: mem.Allocator, anything_changed: *bool) ![]u8 { |
| 6336 | 6336 | var buffer: [64]u8 = undefined; |
| 6337 | const stderr, _ = std.debug.lockStderrWriter(&buffer); | |
| 6337 | const stderr = std.debug.lockStderrWriter(&buffer); | |
| 6338 | 6338 | defer std.debug.unlockStderrWriter(); |
| 6339 | 6339 | |
| 6340 | 6340 | var tree = try std.zig.Ast.parse(allocator, source, .zig); |
| ... | ... | @@ -6342,17 +6342,17 @@ fn testParse(source: [:0]const u8, allocator: mem.Allocator, anything_changed: * |
| 6342 | 6342 | |
| 6343 | 6343 | for (tree.errors) |parse_error| { |
| 6344 | 6344 | const loc = tree.tokenLocation(0, parse_error.token); |
| 6345 | try stderr.print("(memory buffer):{d}:{d}: error: ", .{ loc.line + 1, loc.column + 1 }); | |
| 6346 | try tree.renderError(parse_error, stderr); | |
| 6347 | try stderr.print("\n{s}\n", .{source[loc.line_start..loc.line_end]}); | |
| 6345 | try stderr.printUnescaped("(memory buffer):{d}:{d}: error: ", .{ loc.line + 1, loc.column + 1 }); | |
| 6346 | try tree.renderError(parse_error, &stderr.interface); | |
| 6347 | try stderr.interface.print("\n{s}\n", .{source[loc.line_start..loc.line_end]}); | |
| 6348 | 6348 | { |
| 6349 | 6349 | var i: usize = 0; |
| 6350 | 6350 | while (i < loc.column) : (i += 1) { |
| 6351 | try stderr.writeAll(" "); | |
| 6351 | try stderr.writeAllUnescaped(" "); | |
| 6352 | 6352 | } |
| 6353 | try stderr.writeAll("^"); | |
| 6353 | try stderr.writeAllUnescaped("^"); | |
| 6354 | 6354 | } |
| 6355 | try stderr.writeAll("\n"); | |
| 6355 | try stderr.writeAllUnescaped("\n"); | |
| 6356 | 6356 | } |
| 6357 | 6357 | if (tree.errors.len != 0) { |
| 6358 | 6358 | return error.ParseError; |
src/Package/Fetch.zig+2-2| ... | ... | @@ -1717,7 +1717,7 @@ fn deleteFileFallible(io: Io, dir: Io.Dir, deleted_file: *DeletedFile) DeletedFi |
| 1717 | 1717 | } |
| 1718 | 1718 | |
| 1719 | 1719 | fn setExecutable(file: Io.File) !void { |
| 1720 | if (!std.fs.has_executable_bit) return; | |
| 1720 | if (!Io.File.Permissions.has_executable_bit) return; | |
| 1721 | 1721 | |
| 1722 | 1722 | const S = std.posix.S; |
| 1723 | 1723 | const mode = Io.File.default_mode | S.IXUSR | S.IXGRP | S.IXOTH; |
| ... | ... | @@ -2183,7 +2183,7 @@ test "tarball without root folder" { |
| 2183 | 2183 | } |
| 2184 | 2184 | |
| 2185 | 2185 | test "set executable bit based on file content" { |
| 2186 | if (!std.fs.has_executable_bit) return error.SkipZigTest; | |
| 2186 | if (!Io.File.Permissions.has_executable_bit) return error.SkipZigTest; | |
| 2187 | 2187 | const gpa = std.testing.allocator; |
| 2188 | 2188 | const io = std.testing.io; |
| 2189 | 2189 |
src/link/Lld.zig+4-8| ... | ... | @@ -1327,6 +1327,7 @@ fn getLDMOption(target: *const std.Target) ?[]const u8 { |
| 1327 | 1327 | } |
| 1328 | 1328 | fn wasmLink(lld: *Lld, arena: Allocator) !void { |
| 1329 | 1329 | const comp = lld.base.comp; |
| 1330 | const diags = &comp.link_diags; | |
| 1330 | 1331 | const shared_memory = comp.config.shared_memory; |
| 1331 | 1332 | const export_memory = comp.config.export_memory; |
| 1332 | 1333 | const import_memory = comp.config.import_memory; |
| ... | ... | @@ -1566,17 +1567,12 @@ fn wasmLink(lld: *Lld, arena: Allocator) !void { |
| 1566 | 1567 | // is not the case, it means we will get "exec format error" when trying to run |
| 1567 | 1568 | // it, and then can react to that in the same way as trying to run an ELF file |
| 1568 | 1569 | // from a foreign CPU architecture. |
| 1569 | if (fs.has_executable_bit and target.os.tag == .wasi and | |
| 1570 | if (Io.File.Permissions.has_executable_bit and target.os.tag == .wasi and | |
| 1570 | 1571 | comp.config.output_mode == .Exe) |
| 1571 | 1572 | { |
| 1572 | // TODO: what's our strategy for reporting linker errors from this function? | |
| 1573 | // report a nice error here with the file path if it fails instead of | |
| 1574 | // just returning the error code. | |
| 1575 | 1573 | // chmod does not interact with umask, so we use a conservative -rwxr--r-- here. |
| 1576 | std.posix.fchmodat(Io.Dir.cwd().handle, full_out_path, 0o744, 0) catch |err| switch (err) { | |
| 1577 | error.OperationNotSupported => unreachable, // Not a symlink. | |
| 1578 | else => |e| return e, | |
| 1579 | }; | |
| 1574 | Io.Dir.cwd().setFilePermissions(full_out_path, .fromMode(0o744), .{}) catch |err| | |
| 1575 | return diags.fail("{s}: failed to enable executable permissions: {t}", .{ full_out_path, err }); | |
| 1580 | 1576 | } |
| 1581 | 1577 | } |
| 1582 | 1578 | } |
src/link/Wasm.zig+1-1| ... | ... | @@ -3002,7 +3002,7 @@ pub fn createEmpty( |
| 3002 | 3002 | wasm.base.file = try emit.root_dir.handle.createFile(io, emit.sub_path, .{ |
| 3003 | 3003 | .truncate = true, |
| 3004 | 3004 | .read = true, |
| 3005 | .mode = if (fs.has_executable_bit) | |
| 3005 | .mode = if (Io.File.Permissions.has_executable_bit) | |
| 3006 | 3006 | if (target.os.tag == .wasi and output_mode == .Exe) |
| 3007 | 3007 | Io.File.default_mode | 0b001_000_000 |
| 3008 | 3008 | else |