authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-03-26 21:45:52-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-03-27 01:23:04-07:00
log2e4482cd14d6af958c2e16d7db4d6cf73bd043b8
treedcfadac560383626ccbc5687aee62c6a225d0ace
parent6f467e436dab4f6b12e6dc5f29e96f8ce5e060ad

std.Io.Dir: add resolve_beneath flag and implement for freebsd,macos


3 files changed, 56 insertions(+), 45 deletions(-)

lib/std/Io/Dir.zig+12-4
...@@ -549,6 +549,10 @@ pub const OpenFileOptions = struct {...@@ -549,6 +549,10 @@ pub const OpenFileOptions = struct {
549 /// controlling TTY for the current process.549 /// controlling TTY for the current process.
550 allow_ctty: bool = false,550 allow_ctty: bool = false,
551 follow_symlinks: bool = true,551 follow_symlinks: bool = true,
552 /// If supported by the operating system, attempted path resolution that
553 /// would escape the directory instead returns `error.AccessDenied`. If
554 /// unsupported, this option is ignored.
555 resolve_beneath: bool = false,
552556
553 pub const Mode = enum { read_only, write_only, read_write };557 pub const Mode = enum { read_only, write_only, read_write };
554558
...@@ -570,13 +574,13 @@ pub const OpenFileOptions = struct {...@@ -570,13 +574,13 @@ pub const OpenFileOptions = struct {
570/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).574/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
571/// On WASI, `sub_path` should be encoded as valid UTF-8.575/// On WASI, `sub_path` should be encoded as valid UTF-8.
572/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.576/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
573pub fn openFile(dir: Dir, io: Io, sub_path: []const u8, flags: OpenFileOptions) File.OpenError!File {577pub fn openFile(dir: Dir, io: Io, sub_path: []const u8, options: OpenFileOptions) File.OpenError!File {
574 return io.vtable.dirOpenFile(io.userdata, dir, sub_path, flags);578 return io.vtable.dirOpenFile(io.userdata, dir, sub_path, options);
575}579}
576580
577pub fn openFileAbsolute(io: Io, absolute_path: []const u8, flags: OpenFileOptions) File.OpenError!File {581pub fn openFileAbsolute(io: Io, absolute_path: []const u8, options: OpenFileOptions) File.OpenError!File {
578 assert(path.isAbsolute(absolute_path));582 assert(path.isAbsolute(absolute_path));
579 return openFile(.cwd(), io, absolute_path, flags);583 return openFile(.cwd(), io, absolute_path, options);
580}584}
581585
582pub const CreateFileOptions = struct {586pub const CreateFileOptions = struct {
...@@ -618,6 +622,10 @@ pub const CreateFileOptions = struct {...@@ -618,6 +622,10 @@ pub const CreateFileOptions = struct {
618 /// is available to proceed.622 /// is available to proceed.
619 lock_nonblocking: bool = false,623 lock_nonblocking: bool = false,
620 permissions: Permissions = .default_file,624 permissions: Permissions = .default_file,
625 /// If supported by the operating system, attempted path resolution that
626 /// would escape the directory instead returns `error.AccessDenied`. If
627 /// unsupported, this option is ignored.
628 resolve_beneath: bool = false,
621};629};
622630
623/// Creates, opens, or overwrites a file with write access.631/// Creates, opens, or overwrites a file with write access.
lib/std/Io/Threaded.zig+42-40
...@@ -4217,7 +4217,7 @@ fn dirCreateFilePosix(...@@ -4217,7 +4217,7 @@ fn dirCreateFilePosix(
4217 userdata: ?*anyopaque,4217 userdata: ?*anyopaque,
4218 dir: Dir,4218 dir: Dir,
4219 sub_path: []const u8,4219 sub_path: []const u8,
4220 flags: Dir.CreateFileOptions,4220 options: Dir.CreateFileOptions,
4221) File.OpenError!File {4221) File.OpenError!File {
4222 const t: *Threaded = @ptrCast(@alignCast(userdata));4222 const t: *Threaded = @ptrCast(@alignCast(userdata));
4223 _ = t;4223 _ = t;
...@@ -4225,34 +4225,35 @@ fn dirCreateFilePosix(...@@ -4225,34 +4225,35 @@ fn dirCreateFilePosix(
4225 var path_buffer: [posix.PATH_MAX]u8 = undefined;4225 var path_buffer: [posix.PATH_MAX]u8 = undefined;
4226 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);4226 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
42274227
4228 var os_flags: posix.O = .{4228 var flags: posix.O = .{
4229 .ACCMODE = if (flags.read) .RDWR else .WRONLY,4229 .ACCMODE = if (options.read) .RDWR else .WRONLY,
4230 .CREAT = true,4230 .CREAT = true,
4231 .TRUNC = flags.truncate,4231 .TRUNC = options.truncate,
4232 .EXCL = flags.exclusive,4232 .EXCL = options.exclusive,
4233 };4233 };
4234 if (@hasField(posix.O, "LARGEFILE")) os_flags.LARGEFILE = true;4234 if (@hasField(posix.O, "LARGEFILE")) flags.LARGEFILE = true;
4235 if (@hasField(posix.O, "CLOEXEC")) os_flags.CLOEXEC = true;4235 if (@hasField(posix.O, "CLOEXEC")) flags.CLOEXEC = true;
4236 if (@hasField(posix.O, "RESOLVE_BENEATH")) flags.RESOLVE_BENEATH = options.resolve_beneath;
42364237
4237 // Use the O locking flags if the os supports them to acquire the lock4238 // Use the O locking flags if the os supports them to acquire the lock
4238 // atomically. Note that the NONBLOCK flag is removed after the openat()4239 // atomically. Note that the NONBLOCK flag is removed after the openat()
4239 // call is successful.4240 // call is successful.
4240 if (have_flock_open_flags) switch (flags.lock) {4241 if (have_flock_open_flags) switch (options.lock) {
4241 .none => {},4242 .none => {},
4242 .shared => {4243 .shared => {
4243 os_flags.SHLOCK = true;4244 flags.SHLOCK = true;
4244 os_flags.NONBLOCK = flags.lock_nonblocking;4245 flags.NONBLOCK = options.lock_nonblocking;
4245 },4246 },
4246 .exclusive => {4247 .exclusive => {
4247 os_flags.EXLOCK = true;4248 flags.EXLOCK = true;
4248 os_flags.NONBLOCK = flags.lock_nonblocking;4249 flags.NONBLOCK = options.lock_nonblocking;
4249 },4250 },
4250 };4251 };
42514252
4252 const fd: posix.fd_t = fd: {4253 const fd: posix.fd_t = fd: {
4253 const syscall: Syscall = try .start();4254 const syscall: Syscall = try .start();
4254 while (true) {4255 while (true) {
4255 const rc = openat_sym(dir.handle, sub_path_posix, os_flags, flags.permissions.toMode());4256 const rc = openat_sym(dir.handle, sub_path_posix, flags, options.permissions.toMode());
4256 switch (posix.errno(rc)) {4257 switch (posix.errno(rc)) {
4257 .SUCCESS => {4258 .SUCCESS => {
4258 syscall.finish();4259 syscall.finish();
...@@ -4298,9 +4299,9 @@ fn dirCreateFilePosix(...@@ -4298,9 +4299,9 @@ fn dirCreateFilePosix(
4298 };4299 };
4299 errdefer closeFd(fd);4300 errdefer closeFd(fd);
43004301
4301 if (have_flock and !have_flock_open_flags and flags.lock != .none) {4302 if (have_flock and !have_flock_open_flags and options.lock != .none) {
4302 const lock_nonblocking: i32 = if (flags.lock_nonblocking) posix.LOCK.NB else 0;4303 const lock_nonblocking: i32 = if (options.lock_nonblocking) posix.LOCK.NB else 0;
4303 const lock_flags = switch (flags.lock) {4304 const lock_flags = switch (options.lock) {
4304 .none => unreachable,4305 .none => unreachable,
4305 .shared => posix.LOCK.SH | lock_nonblocking,4306 .shared => posix.LOCK.SH | lock_nonblocking,
4306 .exclusive => posix.LOCK.EX | lock_nonblocking,4307 .exclusive => posix.LOCK.EX | lock_nonblocking,
...@@ -4332,7 +4333,7 @@ fn dirCreateFilePosix(...@@ -4332,7 +4333,7 @@ fn dirCreateFilePosix(
4332 }4333 }
4333 }4334 }
43344335
4335 if (have_flock_open_flags and flags.lock_nonblocking) {4336 if (have_flock_open_flags and options.lock_nonblocking) {
4336 var fl_flags: usize = fl: {4337 var fl_flags: usize = fl: {
4337 const syscall: Syscall = try .start();4338 const syscall: Syscall = try .start();
4338 while (true) {4339 while (true) {
...@@ -4785,45 +4786,46 @@ fn dirOpenFilePosix(...@@ -4785,45 +4786,46 @@ fn dirOpenFilePosix(
4785 userdata: ?*anyopaque,4786 userdata: ?*anyopaque,
4786 dir: Dir,4787 dir: Dir,
4787 sub_path: []const u8,4788 sub_path: []const u8,
4788 flags: Dir.OpenFileOptions,4789 options: Dir.OpenFileOptions,
4789) File.OpenError!File {4790) File.OpenError!File {
4790 const t: *Threaded = @ptrCast(@alignCast(userdata));4791 const t: *Threaded = @ptrCast(@alignCast(userdata));
47914792
4792 var path_buffer: [posix.PATH_MAX]u8 = undefined;4793 var path_buffer: [posix.PATH_MAX]u8 = undefined;
4793 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);4794 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
47944795
4795 var os_flags: posix.O = switch (native_os) {4796 var flags: posix.O = switch (native_os) {
4796 .wasi => .{4797 .wasi => .{
4797 .read = flags.mode != .write_only,4798 .read = options.mode != .write_only,
4798 .write = flags.mode != .read_only,4799 .write = options.mode != .read_only,
4799 .NOFOLLOW = !flags.follow_symlinks,4800 .NOFOLLOW = !options.follow_symlinks,
4800 },4801 },
4801 else => .{4802 else => .{
4802 .ACCMODE = switch (flags.mode) {4803 .ACCMODE = switch (options.mode) {
4803 .read_only => .RDONLY,4804 .read_only => .RDONLY,
4804 .write_only => .WRONLY,4805 .write_only => .WRONLY,
4805 .read_write => .RDWR,4806 .read_write => .RDWR,
4806 },4807 },
4807 .NOFOLLOW = !flags.follow_symlinks,4808 .NOFOLLOW = !options.follow_symlinks,
4808 },4809 },
4809 };4810 };
4810 if (@hasField(posix.O, "CLOEXEC")) os_flags.CLOEXEC = true;4811 if (@hasField(posix.O, "CLOEXEC")) flags.CLOEXEC = true;
4811 if (@hasField(posix.O, "LARGEFILE")) os_flags.LARGEFILE = true;4812 if (@hasField(posix.O, "LARGEFILE")) flags.LARGEFILE = true;
4812 if (@hasField(posix.O, "NOCTTY")) os_flags.NOCTTY = !flags.allow_ctty;4813 if (@hasField(posix.O, "NOCTTY")) flags.NOCTTY = !options.allow_ctty;
4813 if (@hasField(posix.O, "PATH") and flags.path_only) os_flags.PATH = true;4814 if (@hasField(posix.O, "PATH")) flags.PATH = options.path_only;
4815 if (@hasField(posix.O, "RESOLVE_BENEATH")) flags.RESOLVE_BENEATH = options.resolve_beneath;
48144816
4815 // Use the O locking flags if the os supports them to acquire the lock4817 // Use the O locking options if the os supports them to acquire the lock
4816 // atomically. Note that the NONBLOCK flag is removed after the openat()4818 // atomically. Note that the NONBLOCK flag is removed after the openat()
4817 // call is successful.4819 // call is successful.
4818 if (have_flock_open_flags) switch (flags.lock) {4820 if (have_flock_open_flags) switch (options.lock) {
4819 .none => {},4821 .none => {},
4820 .shared => {4822 .shared => {
4821 os_flags.SHLOCK = true;4823 flags.SHLOCK = true;
4822 os_flags.NONBLOCK = flags.lock_nonblocking;4824 flags.NONBLOCK = options.lock_nonblocking;
4823 },4825 },
4824 .exclusive => {4826 .exclusive => {
4825 os_flags.EXLOCK = true;4827 flags.EXLOCK = true;
4826 os_flags.NONBLOCK = flags.lock_nonblocking;4828 flags.NONBLOCK = options.lock_nonblocking;
4827 },4829 },
4828 };4830 };
48294831
...@@ -4832,7 +4834,7 @@ fn dirOpenFilePosix(...@@ -4832,7 +4834,7 @@ fn dirOpenFilePosix(
4832 const fd: posix.fd_t = fd: {4834 const fd: posix.fd_t = fd: {
4833 const syscall: Syscall = try .start();4835 const syscall: Syscall = try .start();
4834 while (true) {4836 while (true) {
4835 const rc = openat_sym(dir.handle, sub_path_posix, os_flags, mode);4837 const rc = openat_sym(dir.handle, sub_path_posix, flags, mode);
4836 switch (posix.errno(rc)) {4838 switch (posix.errno(rc)) {
4837 .SUCCESS => {4839 .SUCCESS => {
4838 syscall.finish();4840 syscall.finish();
...@@ -4878,7 +4880,7 @@ fn dirOpenFilePosix(...@@ -4878,7 +4880,7 @@ fn dirOpenFilePosix(
4878 };4880 };
4879 errdefer closeFd(fd);4881 errdefer closeFd(fd);
48804882
4881 if (!flags.allow_directory) {4883 if (!options.allow_directory) {
4882 const is_dir = is_dir: {4884 const is_dir = is_dir: {
4883 const stat = fileStat(t, .{4885 const stat = fileStat(t, .{
4884 .handle = fd,4886 .handle = fd,
...@@ -4893,9 +4895,9 @@ fn dirOpenFilePosix(...@@ -4893,9 +4895,9 @@ fn dirOpenFilePosix(
4893 if (is_dir) return error.IsDir;4895 if (is_dir) return error.IsDir;
4894 }4896 }
48954897
4896 if (have_flock and !have_flock_open_flags and flags.lock != .none) {4898 if (have_flock and !have_flock_open_flags and options.lock != .none) {
4897 const lock_nonblocking: i32 = if (flags.lock_nonblocking) posix.LOCK.NB else 0;4899 const lock_nonblocking: i32 = if (options.lock_nonblocking) posix.LOCK.NB else 0;
4898 const lock_flags = switch (flags.lock) {4900 const lock_flags = switch (options.lock) {
4899 .none => unreachable,4901 .none => unreachable,
4900 .shared => posix.LOCK.SH | lock_nonblocking,4902 .shared => posix.LOCK.SH | lock_nonblocking,
4901 .exclusive => posix.LOCK.EX | lock_nonblocking,4903 .exclusive => posix.LOCK.EX | lock_nonblocking,
...@@ -4926,7 +4928,7 @@ fn dirOpenFilePosix(...@@ -4926,7 +4928,7 @@ fn dirOpenFilePosix(
4926 }4928 }
4927 }4929 }
49284930
4929 if (have_flock_open_flags and flags.lock_nonblocking) {4931 if (have_flock_open_flags and options.lock_nonblocking) {
4930 var fl_flags: usize = fl: {4932 var fl_flags: usize = fl: {
4931 const syscall: Syscall = try .start();4933 const syscall: Syscall = try .start();
4932 while (true) {4934 while (true) {
lib/std/c.zig+2-1
...@@ -8535,7 +8535,8 @@ pub const O = switch (native_os) {...@@ -8535,7 +8535,8 @@ pub const O = switch (native_os) {
8535 CREAT: bool = false,8535 CREAT: bool = false,
8536 TRUNC: bool = false,8536 TRUNC: bool = false,
8537 EXCL: bool = false,8537 EXCL: bool = false,
8538 _12: u3 = 0,8538 RESOLVE_BENEATH: bool = false,
8539 _13: u2 = 0,
8539 EVTONLY: bool = false,8540 EVTONLY: bool = false,
8540 _16: u1 = 0,8541 _16: u1 = 0,
8541 NOCTTY: bool = false,8542 NOCTTY: bool = false,