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

This commit is contained in:
Andrew Kelley
2026-03-26 21:45:52 -07:00
parent 6f467e436d
commit 2e4482cd14
3 changed files with 56 additions and 45 deletions
+12 -4
View File
@@ -549,6 +549,10 @@ pub const OpenFileOptions = struct {
/// controlling TTY for the current process.
allow_ctty: bool = false,
follow_symlinks: bool = true,
/// If supported by the operating system, attempted path resolution that
/// would escape the directory instead returns `error.AccessDenied`. If
/// unsupported, this option is ignored.
resolve_beneath: bool = false,
pub const Mode = enum { read_only, write_only, read_write };
@@ -570,13 +574,13 @@ pub const OpenFileOptions = struct {
/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
/// On WASI, `sub_path` should be encoded as valid UTF-8.
/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
pub fn openFile(dir: Dir, io: Io, sub_path: []const u8, flags: OpenFileOptions) File.OpenError!File {
return io.vtable.dirOpenFile(io.userdata, dir, sub_path, flags);
pub fn openFile(dir: Dir, io: Io, sub_path: []const u8, options: OpenFileOptions) File.OpenError!File {
return io.vtable.dirOpenFile(io.userdata, dir, sub_path, options);
}
pub fn openFileAbsolute(io: Io, absolute_path: []const u8, flags: OpenFileOptions) File.OpenError!File {
pub fn openFileAbsolute(io: Io, absolute_path: []const u8, options: OpenFileOptions) File.OpenError!File {
assert(path.isAbsolute(absolute_path));
return openFile(.cwd(), io, absolute_path, flags);
return openFile(.cwd(), io, absolute_path, options);
}
pub const CreateFileOptions = struct {
@@ -618,6 +622,10 @@ pub const CreateFileOptions = struct {
/// is available to proceed.
lock_nonblocking: bool = false,
permissions: Permissions = .default_file,
/// If supported by the operating system, attempted path resolution that
/// would escape the directory instead returns `error.AccessDenied`. If
/// unsupported, this option is ignored.
resolve_beneath: bool = false,
};
/// Creates, opens, or overwrites a file with write access.
+42 -40
View File
@@ -4217,7 +4217,7 @@ fn dirCreateFilePosix(
userdata: ?*anyopaque,
dir: Dir,
sub_path: []const u8,
flags: Dir.CreateFileOptions,
options: Dir.CreateFileOptions,
) File.OpenError!File {
const t: *Threaded = @ptrCast(@alignCast(userdata));
_ = t;
@@ -4225,34 +4225,35 @@ fn dirCreateFilePosix(
var path_buffer: [posix.PATH_MAX]u8 = undefined;
const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
var os_flags: posix.O = .{
.ACCMODE = if (flags.read) .RDWR else .WRONLY,
var flags: posix.O = .{
.ACCMODE = if (options.read) .RDWR else .WRONLY,
.CREAT = true,
.TRUNC = flags.truncate,
.EXCL = flags.exclusive,
.TRUNC = options.truncate,
.EXCL = options.exclusive,
};
if (@hasField(posix.O, "LARGEFILE")) os_flags.LARGEFILE = true;
if (@hasField(posix.O, "CLOEXEC")) os_flags.CLOEXEC = true;
if (@hasField(posix.O, "LARGEFILE")) flags.LARGEFILE = true;
if (@hasField(posix.O, "CLOEXEC")) flags.CLOEXEC = true;
if (@hasField(posix.O, "RESOLVE_BENEATH")) flags.RESOLVE_BENEATH = options.resolve_beneath;
// Use the O locking flags if the os supports them to acquire the lock
// atomically. Note that the NONBLOCK flag is removed after the openat()
// call is successful.
if (have_flock_open_flags) switch (flags.lock) {
if (have_flock_open_flags) switch (options.lock) {
.none => {},
.shared => {
os_flags.SHLOCK = true;
os_flags.NONBLOCK = flags.lock_nonblocking;
flags.SHLOCK = true;
flags.NONBLOCK = options.lock_nonblocking;
},
.exclusive => {
os_flags.EXLOCK = true;
os_flags.NONBLOCK = flags.lock_nonblocking;
flags.EXLOCK = true;
flags.NONBLOCK = options.lock_nonblocking;
},
};
const fd: posix.fd_t = fd: {
const syscall: Syscall = try .start();
while (true) {
const rc = openat_sym(dir.handle, sub_path_posix, os_flags, flags.permissions.toMode());
const rc = openat_sym(dir.handle, sub_path_posix, flags, options.permissions.toMode());
switch (posix.errno(rc)) {
.SUCCESS => {
syscall.finish();
@@ -4298,9 +4299,9 @@ fn dirCreateFilePosix(
};
errdefer closeFd(fd);
if (have_flock and !have_flock_open_flags and flags.lock != .none) {
const lock_nonblocking: i32 = if (flags.lock_nonblocking) posix.LOCK.NB else 0;
const lock_flags = switch (flags.lock) {
if (have_flock and !have_flock_open_flags and options.lock != .none) {
const lock_nonblocking: i32 = if (options.lock_nonblocking) posix.LOCK.NB else 0;
const lock_flags = switch (options.lock) {
.none => unreachable,
.shared => posix.LOCK.SH | lock_nonblocking,
.exclusive => posix.LOCK.EX | lock_nonblocking,
@@ -4332,7 +4333,7 @@ fn dirCreateFilePosix(
}
}
if (have_flock_open_flags and flags.lock_nonblocking) {
if (have_flock_open_flags and options.lock_nonblocking) {
var fl_flags: usize = fl: {
const syscall: Syscall = try .start();
while (true) {
@@ -4785,45 +4786,46 @@ fn dirOpenFilePosix(
userdata: ?*anyopaque,
dir: Dir,
sub_path: []const u8,
flags: Dir.OpenFileOptions,
options: Dir.OpenFileOptions,
) File.OpenError!File {
const t: *Threaded = @ptrCast(@alignCast(userdata));
var path_buffer: [posix.PATH_MAX]u8 = undefined;
const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
var os_flags: posix.O = switch (native_os) {
var flags: posix.O = switch (native_os) {
.wasi => .{
.read = flags.mode != .write_only,
.write = flags.mode != .read_only,
.NOFOLLOW = !flags.follow_symlinks,
.read = options.mode != .write_only,
.write = options.mode != .read_only,
.NOFOLLOW = !options.follow_symlinks,
},
else => .{
.ACCMODE = switch (flags.mode) {
.ACCMODE = switch (options.mode) {
.read_only => .RDONLY,
.write_only => .WRONLY,
.read_write => .RDWR,
},
.NOFOLLOW = !flags.follow_symlinks,
.NOFOLLOW = !options.follow_symlinks,
},
};
if (@hasField(posix.O, "CLOEXEC")) os_flags.CLOEXEC = true;
if (@hasField(posix.O, "LARGEFILE")) os_flags.LARGEFILE = true;
if (@hasField(posix.O, "NOCTTY")) os_flags.NOCTTY = !flags.allow_ctty;
if (@hasField(posix.O, "PATH") and flags.path_only) os_flags.PATH = true;
if (@hasField(posix.O, "CLOEXEC")) flags.CLOEXEC = true;
if (@hasField(posix.O, "LARGEFILE")) flags.LARGEFILE = true;
if (@hasField(posix.O, "NOCTTY")) flags.NOCTTY = !options.allow_ctty;
if (@hasField(posix.O, "PATH")) flags.PATH = options.path_only;
if (@hasField(posix.O, "RESOLVE_BENEATH")) flags.RESOLVE_BENEATH = options.resolve_beneath;
// Use the O locking flags if the os supports them to acquire the lock
// Use the O locking options if the os supports them to acquire the lock
// atomically. Note that the NONBLOCK flag is removed after the openat()
// call is successful.
if (have_flock_open_flags) switch (flags.lock) {
if (have_flock_open_flags) switch (options.lock) {
.none => {},
.shared => {
os_flags.SHLOCK = true;
os_flags.NONBLOCK = flags.lock_nonblocking;
flags.SHLOCK = true;
flags.NONBLOCK = options.lock_nonblocking;
},
.exclusive => {
os_flags.EXLOCK = true;
os_flags.NONBLOCK = flags.lock_nonblocking;
flags.EXLOCK = true;
flags.NONBLOCK = options.lock_nonblocking;
},
};
@@ -4832,7 +4834,7 @@ fn dirOpenFilePosix(
const fd: posix.fd_t = fd: {
const syscall: Syscall = try .start();
while (true) {
const rc = openat_sym(dir.handle, sub_path_posix, os_flags, mode);
const rc = openat_sym(dir.handle, sub_path_posix, flags, mode);
switch (posix.errno(rc)) {
.SUCCESS => {
syscall.finish();
@@ -4878,7 +4880,7 @@ fn dirOpenFilePosix(
};
errdefer closeFd(fd);
if (!flags.allow_directory) {
if (!options.allow_directory) {
const is_dir = is_dir: {
const stat = fileStat(t, .{
.handle = fd,
@@ -4893,9 +4895,9 @@ fn dirOpenFilePosix(
if (is_dir) return error.IsDir;
}
if (have_flock and !have_flock_open_flags and flags.lock != .none) {
const lock_nonblocking: i32 = if (flags.lock_nonblocking) posix.LOCK.NB else 0;
const lock_flags = switch (flags.lock) {
if (have_flock and !have_flock_open_flags and options.lock != .none) {
const lock_nonblocking: i32 = if (options.lock_nonblocking) posix.LOCK.NB else 0;
const lock_flags = switch (options.lock) {
.none => unreachable,
.shared => posix.LOCK.SH | lock_nonblocking,
.exclusive => posix.LOCK.EX | lock_nonblocking,
@@ -4926,7 +4928,7 @@ fn dirOpenFilePosix(
}
}
if (have_flock_open_flags and flags.lock_nonblocking) {
if (have_flock_open_flags and options.lock_nonblocking) {
var fl_flags: usize = fl: {
const syscall: Syscall = try .start();
while (true) {
+2 -1
View File
@@ -8535,7 +8535,8 @@ pub const O = switch (native_os) {
CREAT: bool = false,
TRUNC: bool = false,
EXCL: bool = false,
_12: u3 = 0,
RESOLVE_BENEATH: bool = false,
_13: u2 = 0,
EVTONLY: bool = false,
_16: u1 = 0,
NOCTTY: bool = false,