aboutsummaryrefslogtreecommitdiff
path: root/lib/std
diff options
context:
space:
mode:
authorAndrew Kelley <andrew@ziglang.org>2025-12-11 21:00:14 -0800
committerAndrew Kelley <andrew@ziglang.org>2025-12-23 22:15:09 -0800
commit0e230993d51d0ecded40d5235ada4f2f64036b26 (patch)
tree4caa6e7f003c791cd96a54b63226b78eaa649282 /lib/std
parentc4cefd68358470d464bf66bc41985cadd874db73 (diff)
downloadzig-0e230993d51d0ecded40d5235ada4f2f64036b26.tar.gz
zig-0e230993d51d0ecded40d5235ada4f2f64036b26.zip
std.Io.Dir: add setFilePermissions and setFileOwner
Diffstat (limited to 'lib/std')
-rw-r--r--lib/std/Io.zig2
-rw-r--r--lib/std/Io/Dir.zig41
-rw-r--r--lib/std/Io/Threaded.zig179
-rw-r--r--lib/std/os/linux.zig4
-rw-r--r--lib/std/posix.zig184
-rw-r--r--lib/std/posix/test.zig2
-rw-r--r--lib/std/tar.zig6
-rw-r--r--lib/std/zig/parser_test.zig14
8 files changed, 232 insertions, 200 deletions
diff --git a/lib/std/Io.zig b/lib/std/Io.zig
index 9db215c747..b76cbebe3a 100644
--- a/lib/std/Io.zig
+++ b/lib/std/Io.zig
@@ -684,7 +684,9 @@ pub const VTable = struct {
dirSymLink: *const fn (?*anyopaque, Dir, target_path: []const u8, sym_link_path: []const u8, Dir.SymLinkFlags) Dir.SymLinkError!void,
dirReadLink: *const fn (?*anyopaque, Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLinkError!usize,
dirSetOwner: *const fn (?*anyopaque, Dir, ?File.Uid, ?File.Gid) Dir.SetOwnerError!void,
+ dirSetFileOwner: *const fn (?*anyopaque, Dir, []const u8, ?File.Uid, ?File.Gid, Dir.SetFileOwnerOptions) Dir.SetFileOwnerError!void,
dirSetPermissions: *const fn (?*anyopaque, Dir, Dir.Permissions) Dir.SetPermissionsError!void,
+ dirSetFilePermissions: *const fn (?*anyopaque, Dir, []const u8, File.Permissions, Dir.SetFilePermissionsOptions) Dir.SetFilePermissionsError!void,
dirSetTimestamps: *const fn (?*anyopaque, Dir, []const u8, last_accessed: Timestamp, last_modified: Timestamp, Dir.SetTimestampsOptions) Dir.SetTimestampsError!void,
dirSetTimestampsNow: *const fn (?*anyopaque, Dir, []const u8, Dir.SetTimestampsOptions) Dir.SetTimestampsError!void,
diff --git a/lib/std/Io/Dir.zig b/lib/std/Io/Dir.zig
index 4e70a7be89..085667edca 100644
--- a/lib/std/Io/Dir.zig
+++ b/lib/std/Io/Dir.zig
@@ -1698,6 +1698,29 @@ pub fn setPermissions(dir: Dir, io: Io, new_permissions: File.Permissions) SetPe
return io.vtable.dirSetPermissions(io.userdata, dir, new_permissions);
}
+pub const SetFilePermissionsError = PathNameError || SetPermissionsError || error{
+ ProcessFdQuotaExceeded,
+ SystemFdQuotaExceeded,
+ /// `SetFilePermissionsOptions.follow_symlinks` was set to false, which is
+ /// not allowed by the file system or operating system.
+ OperationNotSupported,
+};
+
+pub const SetFilePermissionsOptions = struct {
+ follow_symlinks: bool = true,
+};
+
+/// Also known as "chmodat".
+pub fn setFilePermissions(
+ dir: Dir,
+ io: Io,
+ sub_path: []const u8,
+ new_permissions: File.Permissions,
+ options: SetFilePermissionsOptions,
+) SetFilePermissionsError!void {
+ return io.vtable.dirSetFilePermissions(io.userdata, sub_path, dir, new_permissions, options);
+}
+
pub const SetOwnerError = File.SetOwnerError;
/// Also known as "chown".
@@ -1711,6 +1734,24 @@ pub fn setOwner(dir: Dir, io: Io, owner: ?File.Uid, group: ?File.Gid) SetOwnerEr
return io.vtable.dirSetOwner(io.userdata, dir, owner, group);
}
+pub const SetFileOwnerError = PathNameError || SetOwnerError;
+
+pub const SetFileOwnerOptions = struct {
+ follow_symlinks: bool = true,
+};
+
+/// Also known as "fchownat".
+pub fn setFileOwner(
+ dir: Dir,
+ io: Io,
+ sub_path: []const u8,
+ owner: ?File.Uid,
+ group: ?File.Gid,
+ options: SetFileOwnerOptions,
+) SetOwnerError!void {
+ return io.vtable.dirSetFileOwner(io.userdata, dir, sub_path, owner, group, options);
+}
+
pub const SetTimestampsError = File.SetTimestampsError || PathNameError;
pub const SetTimestampsOptions = struct {
diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig
index 61974f4717..2fb0f3ad1f 100644
--- a/lib/std/Io/Threaded.zig
+++ b/lib/std/Io/Threaded.zig
@@ -76,6 +76,7 @@ old_sig_pipe: if (have_sig_pipe) posix.Sigaction else void,
use_sendfile: UseSendfile = .default,
use_copy_file_range: UseCopyFileRange = .default,
use_fcopyfile: UseFcopyfile = .default,
+use_fchmodat2: UseFchmodat2 = .default,
stderr_writer: File.Writer = .{
.io = undefined,
@@ -124,6 +125,15 @@ pub const UseFcopyfile = if (have_fcopyfile) enum {
pub const default: UseFcopyfile = .disabled;
};
+pub const UseFchmodat2 = if (have_fchmodat2 and !have_fchmodat_flags) enum {
+ enabled,
+ disabled,
+ pub const default: UseFchmodat2 = .enabled;
+} else enum {
+ disabled,
+ pub const default: UseFchmodat2 = .disabled;
+};
+
const Thread = struct {
/// The value that needs to be passed to pthread_kill or tgkill in order to
/// send a signal.
@@ -712,7 +722,9 @@ pub fn io(t: *Threaded) Io {
.dirSymLink = dirSymLink,
.dirReadLink = dirReadLink,
.dirSetOwner = dirSetOwner,
+ .dirSetFileOwner = dirSetFileOwner,
.dirSetPermissions = dirSetPermissions,
+ .dirSetFilePermissions = dirSetFilePermissions,
.dirSetTimestamps = dirSetTimestamps,
.dirSetTimestampsNow = dirSetTimestampsNow,
@@ -842,7 +854,9 @@ pub fn ioBasic(t: *Threaded) Io {
.dirSymLink = dirSymLink,
.dirReadLink = dirReadLink,
.dirSetOwner = dirSetOwner,
+ .dirSetFileOwner = dirSetFileOwner,
.dirSetPermissions = dirSetPermissions,
+ .dirSetFilePermissions = dirSetFilePermissions,
.dirSetTimestamps = dirSetTimestamps,
.dirSetTimestampsNow = dirSetTimestampsNow,
@@ -921,6 +935,11 @@ const have_copy_file_range = switch (native_os) {
else => false,
};
const have_fcopyfile = is_darwin;
+const have_fchmodat2 = native_os == .linux and
+ (builtin.os.isAtLeast(.linux, .{ .major = 6, .minor = 6, .patch = 0 }) orelse true) and
+ (builtin.abi.isAndroid() or !std.c.versionCheck(.{ .major = 2, .minor = 32, .patch = 0 }));
+const have_fchmodat_flags = native_os != .linux or
+ (!builtin.abi.isAndroid() and std.c.versionCheck(.{ .major = 2, .minor = 32, .patch = 0 }));
const openat_sym = if (posix.lfs64_abi) posix.system.openat64 else posix.system.openat;
const fstat_sym = if (posix.lfs64_abi) posix.system.fstat64 else posix.system.fstat;
@@ -4862,11 +4881,11 @@ const dirSetPermissions = switch (native_os) {
};
fn dirSetPermissionsWindows(userdata: ?*anyopaque, dir: Dir, permissions: Dir.Permissions) Dir.SetPermissionsError!void {
- // TODO I think we can actually set permissions on a dir on windows?
- _ = userdata;
+ const t: *Threaded = @ptrCast(@alignCast(userdata));
+ _ = t;
_ = dir;
_ = permissions;
- return error.Unexpected;
+ @panic("TODO");
}
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
return setPermissionsPosix(current_thread, dir.handle, permissions.toMode());
}
+fn dirSetFilePermissions(
+ userdata: ?*anyopaque,
+ dir: Dir,
+ sub_path: []const u8,
+ permissions: Dir.Permissions,
+ options: Dir.SetFilePermissionsOptions,
+) Dir.SetFilePermissionsError!void {
+ if (!Dir.Permissions.has_executable_bit) return error.Unexpected;
+ if (is_windows) @panic("TODO");
+ const t: *Threaded = @ptrCast(@alignCast(userdata));
+ const current_thread = Thread.getCurrent(t);
+
+ var path_buffer: [posix.PATH_MAX]u8 = undefined;
+ const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
+
+ const mode = permissions.toMode();
+ const flags: u32 = if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0;
+
+ return posixFchmodat(t, current_thread, dir.handle, sub_path_posix, mode, flags);
+}
+
+fn posixFchmodat(
+ t: *Threaded,
+ current_thread: *Thread,
+ dir_fd: posix.fd_t,
+ path: [*:0]const u8,
+ mode: posix.mode_t,
+ flags: u32,
+) Dir.SetFilePermissionsError!void {
+ // No special handling for linux is needed if we can use the libc fallback
+ // or `flags` is empty. Glibc only added the fallback in 2.32.
+ if (have_fchmodat_flags or flags == 0) {
+ try current_thread.beginSyscall();
+ while (true) {
+ const rc = if (have_fchmodat_flags)
+ posix.system.fchmodat(dir_fd, path, mode, flags)
+ else
+ posix.system.fchmodat(dir_fd, path, mode);
+ switch (posix.errno(rc)) {
+ .SUCCESS => return current_thread.endSyscall(),
+ .CANCELED => return current_thread.endSyscallCanceled(),
+ .INTR => {
+ try current_thread.checkCancel();
+ continue;
+ },
+ else => |e| {
+ current_thread.endSyscall();
+ switch (e) {
+ .BADF => |err| return errnoBug(err),
+ .FAULT => |err| return errnoBug(err),
+ .INVAL => |err| return errnoBug(err),
+ .ACCES => return error.AccessDenied,
+ .IO => return error.InputOutput,
+ .LOOP => return error.SymLinkLoop,
+ .MFILE => return error.ProcessFdQuotaExceeded,
+ .NAMETOOLONG => return error.NameTooLong,
+ .NFILE => return error.SystemFdQuotaExceeded,
+ .NOENT => return error.FileNotFound,
+ .NOTDIR => return error.FileNotFound,
+ .NOMEM => return error.SystemResources,
+ .OPNOTSUPP => return error.OperationNotSupported,
+ .PERM => return error.PermissionDenied,
+ .ROFS => return error.ReadOnlyFileSystem,
+ else => |err| return posix.unexpectedErrno(err),
+ }
+ },
+ }
+ }
+ }
+
+ if (@atomicLoad(UseFchmodat2, &t.use_fchmodat2, .monotonic) == .disabled)
+ return fchmodatFallback(current_thread, dir_fd, path, mode, flags);
+
+ comptime assert(native_os == .linux);
+
+ try current_thread.beginSyscall();
+ while (true) {
+ switch (std.os.linux.errno(std.os.linux.fchmodat2(dir_fd, path, mode, flags))) {
+ .SUCCESS => return current_thread.endSyscall(),
+ .CANCELED => return current_thread.endSyscallCanceled(),
+ .INTR => {
+ try current_thread.checkCancel();
+ continue;
+ },
+ else => |e| {
+ current_thread.endSyscall();
+ switch (e) {
+ .BADF => |err| return errnoBug(err),
+ .FAULT => |err| return errnoBug(err),
+ .INVAL => |err| return errnoBug(err),
+ .ACCES => return error.AccessDenied,
+ .IO => return error.InputOutput,
+ .LOOP => return error.SymLinkLoop,
+ .NOENT => return error.FileNotFound,
+ .NOMEM => return error.SystemResources,
+ .NOTDIR => return error.FileNotFound,
+ .OPNOTSUPP => return error.OperationNotSupported,
+ .PERM => return error.PermissionDenied,
+ .ROFS => return error.ReadOnlyFileSystem,
+ .NOSYS => {
+ @atomicStore(UseFchmodat2, &t.use_fchmodat2, .disabled, .monotonic);
+ return fchmodatFallback(current_thread, dir_fd, path, mode, flags);
+ },
+ else => |err| return posix.unexpectedErrno(err),
+ }
+ },
+ }
+ }
+}
+
+fn fchmodatFallback(
+ current_thread: *Thread,
+ dir_fd: posix.fd_t,
+ path: [*:0]const u8,
+ mode: posix.mode_t,
+ flags: u32,
+) Dir.SetFilePermissionsError!void {
+ _ = current_thread;
+ _ = dir_fd;
+ _ = path;
+ _ = mode;
+ _ = flags;
+ // I deleted the previous fallback implementation because it looked wrong to me. Please cross-reference
+ // fhmodat.c in musl libc before blindly restoring the implementation.
+ @panic("TODO");
+}
+
const dirSetOwner = switch (native_os) {
.windows => dirSetOwnerUnsupported,
else => dirSetOwnerPosix,
@@ -4927,6 +5073,33 @@ fn setOwnerPosix(current_thread: *Thread, fd: posix.fd_t, uid: posix.uid_t, gid:
}
}
+fn dirSetFileOwner(
+ userdata: ?*anyopaque,
+ dir: Dir,
+ sub_path: []const u8,
+ owner: ?File.Uid,
+ group: ?File.Gid,
+ options: Dir.SetFileOwnerOptions,
+) Dir.SetFileOwnerError!void {
+ const t: *Threaded = @ptrCast(@alignCast(userdata));
+ const current_thread = Thread.getCurrent(t);
+
+ if (is_windows) {
+ @panic("TODO");
+ }
+
+ var path_buffer: [posix.PATH_MAX]u8 = undefined;
+ const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
+
+ _ = current_thread;
+ _ = dir;
+ _ = sub_path_posix;
+ _ = owner;
+ _ = group;
+ _ = options;
+ @panic("TODO");
+}
+
const fileSync = switch (native_os) {
.windows => fileSyncWindows,
else => fileSyncPosix,
diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig
index a12654ee44..c8e9180dbf 100644
--- a/lib/std/os/linux.zig
+++ b/lib/std/os/linux.zig
@@ -1420,7 +1420,7 @@ pub fn chmod(path: [*:0]const u8, mode: mode_t) usize {
if (@hasField(SYS, "chmod")) {
return syscall2(.chmod, @intFromPtr(path), mode);
} else {
- return fchmodat(AT.FDCWD, path, mode, 0);
+ return fchmodat(AT.FDCWD, path, mode);
}
}
@@ -1432,7 +1432,7 @@ pub fn fchown(fd: i32, owner: uid_t, group: gid_t) usize {
}
}
-pub fn fchmodat(fd: i32, path: [*:0]const u8, mode: mode_t, _: u32) usize {
+pub fn fchmodat(fd: i32, path: [*:0]const u8, mode: mode_t) usize {
return syscall3(.fchmodat, @bitCast(@as(isize, fd)), @intFromPtr(path), mode);
}
diff --git a/lib/std/posix.zig b/lib/std/posix.zig
index aa270e611d..da2b0e8786 100644
--- a/lib/std/posix.zig
+++ b/lib/std/posix.zig
@@ -310,190 +310,6 @@ pub fn close(fd: fd_t) void {
}
}
-pub const FChmodAtError = std.Io.File.SetPermissionsError || error{
- /// A component of `path` exceeded `NAME_MAX`, or the entire path exceeded
- /// `PATH_MAX`.
- NameTooLong,
- /// `path` resolves to a symbolic link, and `AT.SYMLINK_NOFOLLOW` was set
- /// in `flags`. This error only occurs on Linux, where changing the mode of
- /// a symbolic link has no meaning and can cause undefined behaviour on
- /// certain filesystems.
- ///
- /// The procfs fallback was used but procfs was not mounted.
- OperationNotSupported,
- /// The procfs fallback was used but the process exceeded its open file
- /// limit.
- ProcessFdQuotaExceeded,
- /// The procfs fallback was used but the system exceeded it open file limit.
- SystemFdQuotaExceeded,
-};
-
-/// Changes the `mode` of `path` relative to the directory referred to by
-/// `dirfd`. 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.
-///
-/// On Linux the `fchmodat2` syscall will be used if available, otherwise a
-/// workaround using procfs will be employed. Changing the mode of a symbolic
-/// link with `AT.SYMLINK_NOFOLLOW` set will also return
-/// `OperationNotSupported`, as:
-///
-/// 1. Permissions on the link are ignored when resolving its target.
-/// 2. This operation has been known to invoke undefined behaviour across
-/// different filesystems[1].
-///
-/// [1]: https://sourceware.org/legacy-ml/libc-alpha/2020-02/msg00467.html.
-pub inline fn fchmodat(dirfd: fd_t, path: []const u8, mode: mode_t, flags: u32) FChmodAtError!void {
- if (!fs.has_executable_bit) @compileError("fchmodat unsupported by target OS");
-
- // No special handling for linux is needed if we can use the libc fallback
- // or `flags` is empty. Glibc only added the fallback in 2.32.
- const skip_fchmodat_fallback = native_os != .linux or
- (!builtin.abi.isAndroid() and std.c.versionCheck(.{ .major = 2, .minor = 32, .patch = 0 })) or
- flags == 0;
-
- // This function is marked inline so that when flags is comptime-known,
- // skip_fchmodat_fallback will be comptime-known true.
- if (skip_fchmodat_fallback)
- return fchmodat1(dirfd, path, mode, flags);
-
- return fchmodat2(dirfd, path, mode, flags);
-}
-
-fn fchmodat1(dirfd: fd_t, path: []const u8, mode: mode_t, flags: u32) FChmodAtError!void {
- const path_c = try toPosixPath(path);
- while (true) {
- const res = system.fchmodat(dirfd, &path_c, mode, flags);
- switch (errno(res)) {
- .SUCCESS => return,
- .INTR => continue,
- .BADF => unreachable,
- .FAULT => unreachable,
- .INVAL => unreachable,
- .ACCES => return error.AccessDenied,
- .IO => return error.InputOutput,
- .LOOP => return error.SymLinkLoop,
- .MFILE => return error.ProcessFdQuotaExceeded,
- .NAMETOOLONG => return error.NameTooLong,
- .NFILE => return error.SystemFdQuotaExceeded,
- .NOENT => return error.FileNotFound,
- .NOTDIR => return error.FileNotFound,
- .NOMEM => return error.SystemResources,
- .OPNOTSUPP => return error.OperationNotSupported,
- .PERM => return error.PermissionDenied,
- .ROFS => return error.ReadOnlyFileSystem,
- else => |err| return unexpectedErrno(err),
- }
- }
-}
-
-fn fchmodat2(dirfd: fd_t, path: []const u8, mode: mode_t, flags: u32) FChmodAtError!void {
- const global = struct {
- var has_fchmodat2: bool = true;
- };
- const path_c = try toPosixPath(path);
- const use_fchmodat2 = (builtin.os.isAtLeast(.linux, .{ .major = 6, .minor = 6, .patch = 0 }) orelse false) and
- @atomicLoad(bool, &global.has_fchmodat2, .monotonic);
- while (use_fchmodat2) {
- // Later on this should be changed to `system.fchmodat2`
- // when the musl/glibc add a wrapper.
- const res = linux.fchmodat2(dirfd, &path_c, mode, flags);
- switch (linux.errno(res)) {
- .SUCCESS => return,
- .INTR => continue,
- .BADF => unreachable,
- .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,
- .OPNOTSUPP => return error.OperationNotSupported,
- .PERM => return error.PermissionDenied,
- .ROFS => return error.ReadOnlyFileSystem,
-
- .NOSYS => {
- @atomicStore(bool, &global.has_fchmodat2, false, .monotonic);
- break;
- },
- else => |err| return unexpectedErrno(err),
- }
- }
-
- // Fallback to changing permissions using procfs:
- //
- // 1. Open `path` as a `PATH` descriptor.
- // 2. Stat the fd and check if it isn't a symbolic link.
- // 3. Generate the procfs reference to the fd via `/proc/self/fd/{fd}`.
- // 4. Pass the procfs path to `chmod` with the `mode`.
- var pathfd: fd_t = undefined;
- while (true) {
- const rc = system.openat(dirfd, &path_c, .{ .PATH = true, .NOFOLLOW = true, .CLOEXEC = true }, @as(mode_t, 0));
- switch (errno(rc)) {
- .SUCCESS => {
- pathfd = @intCast(rc);
- break;
- },
- .INTR => continue,
- .FAULT => unreachable,
- .INVAL => unreachable,
- .ACCES => return error.AccessDenied,
- .PERM => return error.PermissionDenied,
- .LOOP => return error.SymLinkLoop,
- .MFILE => return error.ProcessFdQuotaExceeded,
- .NAMETOOLONG => return error.NameTooLong,
- .NFILE => return error.SystemFdQuotaExceeded,
- .NOENT => return error.FileNotFound,
- .NOMEM => return error.SystemResources,
- else => |err| return unexpectedErrno(err),
- }
- }
- defer close(pathfd);
-
- const path_mode = if (linux.wrapped.statx(
- pathfd,
- "",
- AT.EMPTY_PATH,
- .{ .TYPE = true },
- )) |stx| blk: {
- assert(stx.mask.TYPE);
- break :blk stx.mode;
- } else |err| switch (err) {
- error.NameTooLong => unreachable,
- error.FileNotFound => unreachable,
- else => |e| return e,
- };
- // Even though we only wanted TYPE, the kernel can still fill in the additional bits.
- if ((path_mode & S.IFMT) == S.IFLNK)
- return error.OperationNotSupported;
-
- var procfs_buf: ["/proc/self/fd/-2147483648\x00".len]u8 = undefined;
- const proc_path = std.fmt.bufPrintSentinel(procfs_buf[0..], "/proc/self/fd/{d}", .{pathfd}, 0) catch unreachable;
- while (true) {
- const res = system.chmod(proc_path, mode);
- switch (errno(res)) {
- // Getting NOENT here means that procfs isn't mounted.
- .NOENT => return error.OperationNotSupported,
-
- .SUCCESS => return,
- .INTR => continue,
- .BADF => unreachable,
- .FAULT => unreachable,
- .INVAL => unreachable,
- .ACCES => return error.AccessDenied,
- .IO => return error.InputOutput,
- .LOOP => return error.SymLinkLoop,
- .NOMEM => return error.SystemResources,
- .NOTDIR => return error.FileNotFound,
- .PERM => return error.PermissionDenied,
- .ROFS => return error.ReadOnlyFileSystem,
- else => |err| return unexpectedErrno(err),
- }
- }
-}
-
pub const RebootError = error{
PermissionDenied,
} || UnexpectedError;
diff --git a/lib/std/posix/test.zig b/lib/std/posix/test.zig
index b5255ad2a1..1764d7ad35 100644
--- a/lib/std/posix/test.zig
+++ b/lib/std/posix/test.zig
@@ -895,7 +895,7 @@ fn expectMode(dir: posix.fd_t, file: []const u8, mode: posix.mode_t) !void {
}
test "fchmodat smoke test" {
- if (!std.fs.has_executable_bit) return error.SkipZigTest;
+ if (!Io.File.Permissions.has_executable_bit) return error.SkipZigTest;
var tmp = tmpDir(.{});
defer tmp.cleanup();
diff --git a/lib/std/tar.zig b/lib/std/tar.zig
index 7bb20a9959..92dc8b88f9 100644
--- a/lib/std/tar.zig
+++ b/lib/std/tar.zig
@@ -1122,7 +1122,7 @@ fn normalizePath(bytes: []u8) []u8 {
fn filePermissions(mode: u32, options: PipeOptions) Io.File.Permissions {
const default_mode = 0o666;
- if (!std.fs.has_executable_bit or options.mode_mode == .ignore)
+ if (!Io.File.Permissions.has_executable_bit or options.mode_mode == .ignore)
return .fromMode(default_mode);
const S = std.posix.S;
@@ -1137,7 +1137,7 @@ fn filePermissions(mode: u32, options: PipeOptions) Io.File.Permissions {
}
test filePermissions {
- if (!std.fs.has_executable_bit) return error.SkipZigTest;
+ if (!Io.File.Permissions.has_executable_bit) return error.SkipZigTest;
try testing.expectEqual(0o666, filePermissions(0o744, PipeOptions{ .mode_mode = .ignore }));
try testing.expectEqual(0o777, filePermissions(0o744, PipeOptions{}));
try testing.expectEqual(0o666, filePermissions(0o644, PipeOptions{}));
@@ -1145,7 +1145,7 @@ test filePermissions {
}
test "executable bit" {
- if (!std.fs.has_executable_bit) return error.SkipZigTest;
+ if (!Io.File.Permissions.has_executable_bit) return error.SkipZigTest;
const io = testing.io;
const S = std.posix.S;
diff --git a/lib/std/zig/parser_test.zig b/lib/std/zig/parser_test.zig
index 3ce2603947..6064d3b540 100644
--- a/lib/std/zig/parser_test.zig
+++ b/lib/std/zig/parser_test.zig
@@ -6334,7 +6334,7 @@ var fixed_buffer_mem: [100 * 1024]u8 = undefined;
fn testParse(source: [:0]const u8, allocator: mem.Allocator, anything_changed: *bool) ![]u8 {
var buffer: [64]u8 = undefined;
- const stderr, _ = std.debug.lockStderrWriter(&buffer);
+ const stderr = std.debug.lockStderrWriter(&buffer);
defer std.debug.unlockStderrWriter();
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: *
for (tree.errors) |parse_error| {
const loc = tree.tokenLocation(0, parse_error.token);
- try stderr.print("(memory buffer):{d}:{d}: error: ", .{ loc.line + 1, loc.column + 1 });
- try tree.renderError(parse_error, stderr);
- try stderr.print("\n{s}\n", .{source[loc.line_start..loc.line_end]});
+ try stderr.printUnescaped("(memory buffer):{d}:{d}: error: ", .{ loc.line + 1, loc.column + 1 });
+ try tree.renderError(parse_error, &stderr.interface);
+ try stderr.interface.print("\n{s}\n", .{source[loc.line_start..loc.line_end]});
{
var i: usize = 0;
while (i < loc.column) : (i += 1) {
- try stderr.writeAll(" ");
+ try stderr.writeAllUnescaped(" ");
}
- try stderr.writeAll("^");
+ try stderr.writeAllUnescaped("^");
}
- try stderr.writeAll("\n");
+ try stderr.writeAllUnescaped("\n");
}
if (tree.errors.len != 0) {
return error.ParseError;