| 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,7 +684,9 @@ pub const VTable = struct { |
| 684 | dirSymLink: *const fn (?*anyopaque, Dir, target_path: []const u8, sym_link_path: []const u8, Dir.SymLinkFlags) Dir.SymLinkError!void, | 684 | dirSymLink: *const fn (?*anyopaque, Dir, target_path: []const u8, sym_link_path: []const u8, Dir.SymLinkFlags) Dir.SymLinkError!void, |
| 685 | dirReadLink: *const fn (?*anyopaque, Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLinkError!usize, | 685 | dirReadLink: *const fn (?*anyopaque, Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLinkError!usize, |
| 686 | dirSetOwner: *const fn (?*anyopaque, Dir, ?File.Uid, ?File.Gid) Dir.SetOwnerError!void, | 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 | dirSetPermissions: *const fn (?*anyopaque, Dir, Dir.Permissions) Dir.SetPermissionsError!void, | 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 | dirSetTimestamps: *const fn (?*anyopaque, Dir, []const u8, last_accessed: Timestamp, last_modified: Timestamp, Dir.SetTimestampsOptions) Dir.SetTimestampsError!void, | 690 | dirSetTimestamps: *const fn (?*anyopaque, Dir, []const u8, last_accessed: Timestamp, last_modified: Timestamp, Dir.SetTimestampsOptions) Dir.SetTimestampsError!void, |
| 689 | dirSetTimestampsNow: *const fn (?*anyopaque, Dir, []const u8, Dir.SetTimestampsOptions) Dir.SetTimestampsError!void, | 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,6 +1698,29 @@ pub fn setPermissions(dir: Dir, io: Io, new_permissions: File.Permissions) SetPe |
| 1698 | return io.vtable.dirSetPermissions(io.userdata, dir, new_permissions); | 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 | pub const SetOwnerError = File.SetOwnerError; | 1724 | pub const SetOwnerError = File.SetOwnerError; |
| 1702 | 1725 | ||
| 1703 | /// Also known as "chown". | 1726 | /// Also known as "chown". |
| ... | @@ -1711,6 +1734,24 @@ pub fn setOwner(dir: Dir, io: Io, owner: ?File.Uid, group: ?File.Gid) SetOwnerEr | ... | @@ -1711,6 +1734,24 @@ pub fn setOwner(dir: Dir, io: Io, owner: ?File.Uid, group: ?File.Gid) SetOwnerEr |
| 1711 | return io.vtable.dirSetOwner(io.userdata, dir, owner, group); | 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 | pub const SetTimestampsError = File.SetTimestampsError || PathNameError; | 1755 | pub const SetTimestampsError = File.SetTimestampsError || PathNameError; |
| 1715 | 1756 | ||
| 1716 | pub const SetTimestampsOptions = struct { | 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,6 +76,7 @@ old_sig_pipe: if (have_sig_pipe) posix.Sigaction else void, |
| 76 | use_sendfile: UseSendfile = .default, | 76 | use_sendfile: UseSendfile = .default, |
| 77 | use_copy_file_range: UseCopyFileRange = .default, | 77 | use_copy_file_range: UseCopyFileRange = .default, |
| 78 | use_fcopyfile: UseFcopyfile = .default, | 78 | use_fcopyfile: UseFcopyfile = .default, |
| 79 | use_fchmodat2: UseFchmodat2 = .default, | ||
| 79 | 80 | ||
| 80 | stderr_writer: File.Writer = .{ | 81 | stderr_writer: File.Writer = .{ |
| 81 | .io = undefined, | 82 | .io = undefined, |
| ... | @@ -124,6 +125,15 @@ pub const UseFcopyfile = if (have_fcopyfile) enum { | ... | @@ -124,6 +125,15 @@ pub const UseFcopyfile = if (have_fcopyfile) enum { |
| 124 | pub const default: UseFcopyfile = .disabled; | 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 | const Thread = struct { | 137 | const Thread = struct { |
| 128 | /// The value that needs to be passed to pthread_kill or tgkill in order to | 138 | /// The value that needs to be passed to pthread_kill or tgkill in order to |
| 129 | /// send a signal. | 139 | /// send a signal. |
| ... | @@ -712,7 +722,9 @@ pub fn io(t: *Threaded) Io { | ... | @@ -712,7 +722,9 @@ pub fn io(t: *Threaded) Io { |
| 712 | .dirSymLink = dirSymLink, | 722 | .dirSymLink = dirSymLink, |
| 713 | .dirReadLink = dirReadLink, | 723 | .dirReadLink = dirReadLink, |
| 714 | .dirSetOwner = dirSetOwner, | 724 | .dirSetOwner = dirSetOwner, |
| 725 | .dirSetFileOwner = dirSetFileOwner, | ||
| 715 | .dirSetPermissions = dirSetPermissions, | 726 | .dirSetPermissions = dirSetPermissions, |
| 727 | .dirSetFilePermissions = dirSetFilePermissions, | ||
| 716 | .dirSetTimestamps = dirSetTimestamps, | 728 | .dirSetTimestamps = dirSetTimestamps, |
| 717 | .dirSetTimestampsNow = dirSetTimestampsNow, | 729 | .dirSetTimestampsNow = dirSetTimestampsNow, |
| 718 | 730 | ||
| ... | @@ -842,7 +854,9 @@ pub fn ioBasic(t: *Threaded) Io { | ... | @@ -842,7 +854,9 @@ pub fn ioBasic(t: *Threaded) Io { |
| 842 | .dirSymLink = dirSymLink, | 854 | .dirSymLink = dirSymLink, |
| 843 | .dirReadLink = dirReadLink, | 855 | .dirReadLink = dirReadLink, |
| 844 | .dirSetOwner = dirSetOwner, | 856 | .dirSetOwner = dirSetOwner, |
| 857 | .dirSetFileOwner = dirSetFileOwner, | ||
| 845 | .dirSetPermissions = dirSetPermissions, | 858 | .dirSetPermissions = dirSetPermissions, |
| 859 | .dirSetFilePermissions = dirSetFilePermissions, | ||
| 846 | .dirSetTimestamps = dirSetTimestamps, | 860 | .dirSetTimestamps = dirSetTimestamps, |
| 847 | .dirSetTimestampsNow = dirSetTimestampsNow, | 861 | .dirSetTimestampsNow = dirSetTimestampsNow, |
| 848 | 862 | ||
| ... | @@ -921,6 +935,11 @@ const have_copy_file_range = switch (native_os) { | ... | @@ -921,6 +935,11 @@ const have_copy_file_range = switch (native_os) { |
| 921 | else => false, | 935 | else => false, |
| 922 | }; | 936 | }; |
| 923 | const have_fcopyfile = is_darwin; | 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 | const openat_sym = if (posix.lfs64_abi) posix.system.openat64 else posix.system.openat; | 944 | const openat_sym = if (posix.lfs64_abi) posix.system.openat64 else posix.system.openat; |
| 926 | const fstat_sym = if (posix.lfs64_abi) posix.system.fstat64 else posix.system.fstat; | 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,11 +4881,11 @@ const dirSetPermissions = switch (native_os) { |
| 4862 | }; | 4881 | }; |
| 4863 | 4882 | ||
| 4864 | fn dirSetPermissionsWindows(userdata: ?*anyopaque, dir: Dir, permissions: Dir.Permissions) Dir.SetPermissionsError!void { | 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? | 4884 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 4866 | _ = userdata; | 4885 | _ = t; |
| 4867 | _ = dir; | 4886 | _ = dir; |
| 4868 | _ = permissions; | 4887 | _ = permissions; |
| 4869 | return error.Unexpected; | 4888 | @panic("TODO"); |
| 4870 | } | 4889 | } |
| 4871 | 4890 | ||
| 4872 | fn dirSetPermissionsPosix(userdata: ?*anyopaque, dir: Dir, permissions: Dir.Permissions) Dir.SetPermissionsError!void { | 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,6 +4894,133 @@ fn dirSetPermissionsPosix(userdata: ?*anyopaque, dir: Dir, permissions: Dir.Perm |
| 4875 | return setPermissionsPosix(current_thread, dir.handle, permissions.toMode()); | 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 | const dirSetOwner = switch (native_os) { | 5024 | const dirSetOwner = switch (native_os) { |
| 4879 | .windows => dirSetOwnerUnsupported, | 5025 | .windows => dirSetOwnerUnsupported, |
| 4880 | else => dirSetOwnerPosix, | 5026 | else => dirSetOwnerPosix, |
| ... | @@ -4927,6 +5073,33 @@ fn setOwnerPosix(current_thread: *Thread, fd: posix.fd_t, uid: posix.uid_t, gid: | ... | @@ -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 | const fileSync = switch (native_os) { | 5103 | const fileSync = switch (native_os) { |
| 4931 | .windows => fileSyncWindows, | 5104 | .windows => fileSyncWindows, |
| 4932 | else => fileSyncPosix, | 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,7 +1420,7 @@ pub fn chmod(path: [*:0]const u8, mode: mode_t) usize { |
| 1420 | if (@hasField(SYS, "chmod")) { | 1420 | if (@hasField(SYS, "chmod")) { |
| 1421 | return syscall2(.chmod, @intFromPtr(path), mode); | 1421 | return syscall2(.chmod, @intFromPtr(path), mode); |
| 1422 | } else { | 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,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 | return syscall3(.fchmodat, @bitCast(@as(isize, fd)), @intFromPtr(path), mode); | 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,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 | pub const RebootError = error{ | 313 | pub const RebootError = error{ |
| 498 | PermissionDenied, | 314 | PermissionDenied, |
| 499 | } || UnexpectedError; | 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,7 +895,7 @@ fn expectMode(dir: posix.fd_t, file: []const u8, mode: posix.mode_t) !void { |
| 895 | } | 895 | } |
| 896 | 896 | ||
| 897 | test "fchmodat smoke test" { | 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 | var tmp = tmpDir(.{}); | 900 | var tmp = tmpDir(.{}); |
| 901 | defer tmp.cleanup(); | 901 | defer tmp.cleanup(); |
lib/std/tar.zig+3-3| ... | @@ -1122,7 +1122,7 @@ fn normalizePath(bytes: []u8) []u8 { | ... | @@ -1122,7 +1122,7 @@ fn normalizePath(bytes: []u8) []u8 { |
| 1122 | fn filePermissions(mode: u32, options: PipeOptions) Io.File.Permissions { | 1122 | fn filePermissions(mode: u32, options: PipeOptions) Io.File.Permissions { |
| 1123 | const default_mode = 0o666; | 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 | return .fromMode(default_mode); | 1126 | return .fromMode(default_mode); |
| 1127 | 1127 | ||
| 1128 | const S = std.posix.S; | 1128 | const S = std.posix.S; |
| ... | @@ -1137,7 +1137,7 @@ fn filePermissions(mode: u32, options: PipeOptions) Io.File.Permissions { | ... | @@ -1137,7 +1137,7 @@ fn filePermissions(mode: u32, options: PipeOptions) Io.File.Permissions { |
| 1137 | } | 1137 | } |
| 1138 | 1138 | ||
| 1139 | test filePermissions { | 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 | try testing.expectEqual(0o666, filePermissions(0o744, PipeOptions{ .mode_mode = .ignore })); | 1141 | try testing.expectEqual(0o666, filePermissions(0o744, PipeOptions{ .mode_mode = .ignore })); |
| 1142 | try testing.expectEqual(0o777, filePermissions(0o744, PipeOptions{})); | 1142 | try testing.expectEqual(0o777, filePermissions(0o744, PipeOptions{})); |
| 1143 | try testing.expectEqual(0o666, filePermissions(0o644, PipeOptions{})); | 1143 | try testing.expectEqual(0o666, filePermissions(0o644, PipeOptions{})); |
| ... | @@ -1145,7 +1145,7 @@ test filePermissions { | ... | @@ -1145,7 +1145,7 @@ test filePermissions { |
| 1145 | } | 1145 | } |
| 1146 | 1146 | ||
| 1147 | test "executable bit" { | 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 | const io = testing.io; | 1150 | const io = testing.io; |
| 1151 | const S = std.posix.S; | 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,7 +6334,7 @@ var fixed_buffer_mem: [100 * 1024]u8 = undefined; |
| 6334 | 6334 | ||
| 6335 | fn testParse(source: [:0]const u8, allocator: mem.Allocator, anything_changed: *bool) ![]u8 { | 6335 | fn testParse(source: [:0]const u8, allocator: mem.Allocator, anything_changed: *bool) ![]u8 { |
| 6336 | var buffer: [64]u8 = undefined; | 6336 | var buffer: [64]u8 = undefined; |
| 6337 | const stderr, _ = std.debug.lockStderrWriter(&buffer); | 6337 | const stderr = std.debug.lockStderrWriter(&buffer); |
| 6338 | defer std.debug.unlockStderrWriter(); | 6338 | defer std.debug.unlockStderrWriter(); |
| 6339 | 6339 | ||
| 6340 | var tree = try std.zig.Ast.parse(allocator, source, .zig); | 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,17 +6342,17 @@ fn testParse(source: [:0]const u8, allocator: mem.Allocator, anything_changed: * |
| 6342 | 6342 | ||
| 6343 | for (tree.errors) |parse_error| { | 6343 | for (tree.errors) |parse_error| { |
| 6344 | const loc = tree.tokenLocation(0, parse_error.token); | 6344 | const loc = tree.tokenLocation(0, parse_error.token); |
| 6345 | try stderr.print("(memory buffer):{d}:{d}: error: ", .{ loc.line + 1, loc.column + 1 }); | 6345 | try stderr.printUnescaped("(memory buffer):{d}:{d}: error: ", .{ loc.line + 1, loc.column + 1 }); |
| 6346 | try tree.renderError(parse_error, stderr); | 6346 | try tree.renderError(parse_error, &stderr.interface); |
| 6347 | try stderr.print("\n{s}\n", .{source[loc.line_start..loc.line_end]}); | 6347 | try stderr.interface.print("\n{s}\n", .{source[loc.line_start..loc.line_end]}); |
| 6348 | { | 6348 | { |
| 6349 | var i: usize = 0; | 6349 | var i: usize = 0; |
| 6350 | while (i < loc.column) : (i += 1) { | 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 | if (tree.errors.len != 0) { | 6357 | if (tree.errors.len != 0) { |
| 6358 | return error.ParseError; | 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,7 +1717,7 @@ fn deleteFileFallible(io: Io, dir: Io.Dir, deleted_file: *DeletedFile) DeletedFi |
| 1717 | } | 1717 | } |
| 1718 | 1718 | ||
| 1719 | fn setExecutable(file: Io.File) !void { | 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 | const S = std.posix.S; | 1722 | const S = std.posix.S; |
| 1723 | const mode = Io.File.default_mode | S.IXUSR | S.IXGRP | S.IXOTH; | 1723 | const mode = Io.File.default_mode | S.IXUSR | S.IXGRP | S.IXOTH; |
| ... | @@ -2183,7 +2183,7 @@ test "tarball without root folder" { | ... | @@ -2183,7 +2183,7 @@ test "tarball without root folder" { |
| 2183 | } | 2183 | } |
| 2184 | 2184 | ||
| 2185 | test "set executable bit based on file content" { | 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 | const gpa = std.testing.allocator; | 2187 | const gpa = std.testing.allocator; |
| 2188 | const io = std.testing.io; | 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,6 +1327,7 @@ fn getLDMOption(target: *const std.Target) ?[]const u8 { |
| 1327 | } | 1327 | } |
| 1328 | fn wasmLink(lld: *Lld, arena: Allocator) !void { | 1328 | fn wasmLink(lld: *Lld, arena: Allocator) !void { |
| 1329 | const comp = lld.base.comp; | 1329 | const comp = lld.base.comp; |
| 1330 | const diags = &comp.link_diags; | ||
| 1330 | const shared_memory = comp.config.shared_memory; | 1331 | const shared_memory = comp.config.shared_memory; |
| 1331 | const export_memory = comp.config.export_memory; | 1332 | const export_memory = comp.config.export_memory; |
| 1332 | const import_memory = comp.config.import_memory; | 1333 | const import_memory = comp.config.import_memory; |
| ... | @@ -1566,17 +1567,12 @@ fn wasmLink(lld: *Lld, arena: Allocator) !void { | ... | @@ -1566,17 +1567,12 @@ fn wasmLink(lld: *Lld, arena: Allocator) !void { |
| 1566 | // is not the case, it means we will get "exec format error" when trying to run | 1567 | // is not the case, it means we will get "exec format error" when trying to run |
| 1567 | // it, and then can react to that in the same way as trying to run an ELF file | 1568 | // it, and then can react to that in the same way as trying to run an ELF file |
| 1568 | // from a foreign CPU architecture. | 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 | comp.config.output_mode == .Exe) | 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 | // chmod does not interact with umask, so we use a conservative -rwxr--r-- here. | 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) { | 1574 | Io.Dir.cwd().setFilePermissions(full_out_path, .fromMode(0o744), .{}) catch |err| |
| 1577 | error.OperationNotSupported => unreachable, // Not a symlink. | 1575 | return diags.fail("{s}: failed to enable executable permissions: {t}", .{ full_out_path, err }); |
| 1578 | else => |e| return e, | ||
| 1579 | }; | ||
| 1580 | } | 1576 | } |
| 1581 | } | 1577 | } |
| 1582 | } | 1578 | } |
src/link/Wasm.zig+1-1| ... | @@ -3002,7 +3002,7 @@ pub fn createEmpty( | ... | @@ -3002,7 +3002,7 @@ pub fn createEmpty( |
| 3002 | wasm.base.file = try emit.root_dir.handle.createFile(io, emit.sub_path, .{ | 3002 | wasm.base.file = try emit.root_dir.handle.createFile(io, emit.sub_path, .{ |
| 3003 | .truncate = true, | 3003 | .truncate = true, |
| 3004 | .read = true, | 3004 | .read = true, |
| 3005 | .mode = if (fs.has_executable_bit) | 3005 | .mode = if (Io.File.Permissions.has_executable_bit) |
| 3006 | if (target.os.tag == .wasi and output_mode == .Exe) | 3006 | if (target.os.tag == .wasi and output_mode == .Exe) |
| 3007 | Io.File.default_mode | 0b001_000_000 | 3007 | Io.File.default_mode | 0b001_000_000 |
| 3008 | else | 3008 | else |