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 {
549549 /// controlling TTY for the current process.
550550 allow_ctty: bool = false,
551551 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
553557 pub const Mode = enum { read_only, write_only, read_write };
554558
......@@ -570,13 +574,13 @@ pub const OpenFileOptions = struct {
570574/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
571575/// On WASI, `sub_path` should be encoded as valid UTF-8.
572576/// 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 {
574 return io.vtable.dirOpenFile(io.userdata, dir, sub_path, flags);
577pub fn openFile(dir: Dir, io: Io, sub_path: []const u8, options: OpenFileOptions) File.OpenError!File {
578 return io.vtable.dirOpenFile(io.userdata, dir, sub_path, options);
575579}
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 {
578582 assert(path.isAbsolute(absolute_path));
579 return openFile(.cwd(), io, absolute_path, flags);
583 return openFile(.cwd(), io, absolute_path, options);
580584}
581585
582586pub const CreateFileOptions = struct {
......@@ -618,6 +622,10 @@ pub const CreateFileOptions = struct {
618622 /// is available to proceed.
619623 lock_nonblocking: bool = false,
620624 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,
621629};
622630
623631/// Creates, opens, or overwrites a file with write access.
lib/std/Io/Threaded.zig+42-40
......@@ -4217,7 +4217,7 @@ fn dirCreateFilePosix(
42174217 userdata: ?*anyopaque,
42184218 dir: Dir,
42194219 sub_path: []const u8,
4220 flags: Dir.CreateFileOptions,
4220 options: Dir.CreateFileOptions,
42214221) File.OpenError!File {
42224222 const t: *Threaded = @ptrCast(@alignCast(userdata));
42234223 _ = t;
......@@ -4225,34 +4225,35 @@ fn dirCreateFilePosix(
42254225 var path_buffer: [posix.PATH_MAX]u8 = undefined;
42264226 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
42274227
4228 var os_flags: posix.O = .{
4229 .ACCMODE = if (flags.read) .RDWR else .WRONLY,
4228 var flags: posix.O = .{
4229 .ACCMODE = if (options.read) .RDWR else .WRONLY,
42304230 .CREAT = true,
4231 .TRUNC = flags.truncate,
4232 .EXCL = flags.exclusive,
4231 .TRUNC = options.truncate,
4232 .EXCL = options.exclusive,
42334233 };
4234 if (@hasField(posix.O, "LARGEFILE")) os_flags.LARGEFILE = true;
4235 if (@hasField(posix.O, "CLOEXEC")) os_flags.CLOEXEC = true;
4234 if (@hasField(posix.O, "LARGEFILE")) flags.LARGEFILE = true;
4235 if (@hasField(posix.O, "CLOEXEC")) flags.CLOEXEC = true;
4236 if (@hasField(posix.O, "RESOLVE_BENEATH")) flags.RESOLVE_BENEATH = options.resolve_beneath;
42364237
42374238 // Use the O locking flags if the os supports them to acquire the lock
42384239 // atomically. Note that the NONBLOCK flag is removed after the openat()
42394240 // call is successful.
4240 if (have_flock_open_flags) switch (flags.lock) {
4241 if (have_flock_open_flags) switch (options.lock) {
42414242 .none => {},
42424243 .shared => {
4243 os_flags.SHLOCK = true;
4244 os_flags.NONBLOCK = flags.lock_nonblocking;
4244 flags.SHLOCK = true;
4245 flags.NONBLOCK = options.lock_nonblocking;
42454246 },
42464247 .exclusive => {
4247 os_flags.EXLOCK = true;
4248 os_flags.NONBLOCK = flags.lock_nonblocking;
4248 flags.EXLOCK = true;
4249 flags.NONBLOCK = options.lock_nonblocking;
42494250 },
42504251 };
42514252
42524253 const fd: posix.fd_t = fd: {
42534254 const syscall: Syscall = try .start();
42544255 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());
42564257 switch (posix.errno(rc)) {
42574258 .SUCCESS => {
42584259 syscall.finish();
......@@ -4298,9 +4299,9 @@ fn dirCreateFilePosix(
42984299 };
42994300 errdefer closeFd(fd);
43004301
4301 if (have_flock and !have_flock_open_flags and flags.lock != .none) {
4302 const lock_nonblocking: i32 = if (flags.lock_nonblocking) posix.LOCK.NB else 0;
4303 const lock_flags = switch (flags.lock) {
4302 if (have_flock and !have_flock_open_flags and options.lock != .none) {
4303 const lock_nonblocking: i32 = if (options.lock_nonblocking) posix.LOCK.NB else 0;
4304 const lock_flags = switch (options.lock) {
43044305 .none => unreachable,
43054306 .shared => posix.LOCK.SH | lock_nonblocking,
43064307 .exclusive => posix.LOCK.EX | lock_nonblocking,
......@@ -4332,7 +4333,7 @@ fn dirCreateFilePosix(
43324333 }
43334334 }
43344335
4335 if (have_flock_open_flags and flags.lock_nonblocking) {
4336 if (have_flock_open_flags and options.lock_nonblocking) {
43364337 var fl_flags: usize = fl: {
43374338 const syscall: Syscall = try .start();
43384339 while (true) {
......@@ -4785,45 +4786,46 @@ fn dirOpenFilePosix(
47854786 userdata: ?*anyopaque,
47864787 dir: Dir,
47874788 sub_path: []const u8,
4788 flags: Dir.OpenFileOptions,
4789 options: Dir.OpenFileOptions,
47894790) File.OpenError!File {
47904791 const t: *Threaded = @ptrCast(@alignCast(userdata));
47914792
47924793 var path_buffer: [posix.PATH_MAX]u8 = undefined;
47934794 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) {
47964797 .wasi => .{
4797 .read = flags.mode != .write_only,
4798 .write = flags.mode != .read_only,
4799 .NOFOLLOW = !flags.follow_symlinks,
4798 .read = options.mode != .write_only,
4799 .write = options.mode != .read_only,
4800 .NOFOLLOW = !options.follow_symlinks,
48004801 },
48014802 else => .{
4802 .ACCMODE = switch (flags.mode) {
4803 .ACCMODE = switch (options.mode) {
48034804 .read_only => .RDONLY,
48044805 .write_only => .WRONLY,
48054806 .read_write => .RDWR,
48064807 },
4807 .NOFOLLOW = !flags.follow_symlinks,
4808 .NOFOLLOW = !options.follow_symlinks,
48084809 },
48094810 };
4810 if (@hasField(posix.O, "CLOEXEC")) os_flags.CLOEXEC = true;
4811 if (@hasField(posix.O, "LARGEFILE")) os_flags.LARGEFILE = true;
4812 if (@hasField(posix.O, "NOCTTY")) os_flags.NOCTTY = !flags.allow_ctty;
4813 if (@hasField(posix.O, "PATH") and flags.path_only) os_flags.PATH = true;
4811 if (@hasField(posix.O, "CLOEXEC")) flags.CLOEXEC = true;
4812 if (@hasField(posix.O, "LARGEFILE")) flags.LARGEFILE = true;
4813 if (@hasField(posix.O, "NOCTTY")) flags.NOCTTY = !options.allow_ctty;
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 lock
4817 // Use the O locking options if the os supports them to acquire the lock
48164818 // atomically. Note that the NONBLOCK flag is removed after the openat()
48174819 // call is successful.
4818 if (have_flock_open_flags) switch (flags.lock) {
4820 if (have_flock_open_flags) switch (options.lock) {
48194821 .none => {},
48204822 .shared => {
4821 os_flags.SHLOCK = true;
4822 os_flags.NONBLOCK = flags.lock_nonblocking;
4823 flags.SHLOCK = true;
4824 flags.NONBLOCK = options.lock_nonblocking;
48234825 },
48244826 .exclusive => {
4825 os_flags.EXLOCK = true;
4826 os_flags.NONBLOCK = flags.lock_nonblocking;
4827 flags.EXLOCK = true;
4828 flags.NONBLOCK = options.lock_nonblocking;
48274829 },
48284830 };
48294831
......@@ -4832,7 +4834,7 @@ fn dirOpenFilePosix(
48324834 const fd: posix.fd_t = fd: {
48334835 const syscall: Syscall = try .start();
48344836 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);
48364838 switch (posix.errno(rc)) {
48374839 .SUCCESS => {
48384840 syscall.finish();
......@@ -4878,7 +4880,7 @@ fn dirOpenFilePosix(
48784880 };
48794881 errdefer closeFd(fd);
48804882
4881 if (!flags.allow_directory) {
4883 if (!options.allow_directory) {
48824884 const is_dir = is_dir: {
48834885 const stat = fileStat(t, .{
48844886 .handle = fd,
......@@ -4893,9 +4895,9 @@ fn dirOpenFilePosix(
48934895 if (is_dir) return error.IsDir;
48944896 }
48954897
4896 if (have_flock and !have_flock_open_flags and flags.lock != .none) {
4897 const lock_nonblocking: i32 = if (flags.lock_nonblocking) posix.LOCK.NB else 0;
4898 const lock_flags = switch (flags.lock) {
4898 if (have_flock and !have_flock_open_flags and options.lock != .none) {
4899 const lock_nonblocking: i32 = if (options.lock_nonblocking) posix.LOCK.NB else 0;
4900 const lock_flags = switch (options.lock) {
48994901 .none => unreachable,
49004902 .shared => posix.LOCK.SH | lock_nonblocking,
49014903 .exclusive => posix.LOCK.EX | lock_nonblocking,
......@@ -4926,7 +4928,7 @@ fn dirOpenFilePosix(
49264928 }
49274929 }
49284930
4929 if (have_flock_open_flags and flags.lock_nonblocking) {
4931 if (have_flock_open_flags and options.lock_nonblocking) {
49304932 var fl_flags: usize = fl: {
49314933 const syscall: Syscall = try .start();
49324934 while (true) {
lib/std/c.zig+2-1
......@@ -8535,7 +8535,8 @@ pub const O = switch (native_os) {
85358535 CREAT: bool = false,
85368536 TRUNC: bool = false,
85378537 EXCL: bool = false,
8538 _12: u3 = 0,
8538 RESOLVE_BENEATH: bool = false,
8539 _13: u2 = 0,
85398540 EVTONLY: bool = false,
85408541 _16: u1 = 0,
85418542 NOCTTY: bool = false,