std.Io: implement dirOpenFile

This commit is contained in:
Andrew Kelley 2025-10-09 00:47:04 -07:00
parent 8a1e6c8c39
commit b1733b7bce
5 changed files with 140 additions and 229 deletions

View file

@ -657,9 +657,9 @@ pub const VTable = struct {
dirMake: *const fn (?*anyopaque, Dir, sub_path: []const u8, mode: Dir.Mode) Dir.MakeError!void, dirMake: *const fn (?*anyopaque, Dir, sub_path: []const u8, mode: Dir.Mode) Dir.MakeError!void,
dirStat: *const fn (?*anyopaque, Dir) Dir.StatError!Dir.Stat, dirStat: *const fn (?*anyopaque, Dir) Dir.StatError!Dir.Stat,
dirStatPath: *const fn (?*anyopaque, Dir, sub_path: []const u8, Dir.StatPathOptions) Dir.StatPathError!File.Stat, dirStatPath: *const fn (?*anyopaque, Dir, sub_path: []const u8, Dir.StatPathOptions) Dir.StatPathError!File.Stat,
dirCreateFile: *const fn (?*anyopaque, Dir, sub_path: []const u8, File.CreateFlags) File.OpenError!File,
dirOpenFile: *const fn (?*anyopaque, Dir, sub_path: []const u8, File.OpenFlags) File.OpenError!File,
fileStat: *const fn (?*anyopaque, File) File.StatError!File.Stat, fileStat: *const fn (?*anyopaque, File) File.StatError!File.Stat,
createFile: *const fn (?*anyopaque, Dir, sub_path: []const u8, File.CreateFlags) File.OpenError!File,
fileOpen: *const fn (?*anyopaque, Dir, sub_path: []const u8, File.OpenFlags) File.OpenError!File,
fileClose: *const fn (?*anyopaque, File) void, fileClose: *const fn (?*anyopaque, File) void,
pwrite: *const fn (?*anyopaque, File, buffer: []const u8, offset: std.posix.off_t) File.PWriteError!usize, pwrite: *const fn (?*anyopaque, File, buffer: []const u8, offset: std.posix.off_t) File.PWriteError!usize,
/// Returns 0 on end of stream. /// Returns 0 on end of stream.

View file

@ -39,11 +39,11 @@ pub const OpenError = error{
} || PathNameError || Io.Cancelable || Io.UnexpectedError; } || PathNameError || Io.Cancelable || Io.UnexpectedError;
pub fn openFile(dir: Dir, io: Io, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File { pub fn openFile(dir: Dir, io: Io, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File {
return io.vtable.fileOpen(io.userdata, dir, sub_path, flags); return io.vtable.dirOpenFile(io.userdata, dir, sub_path, flags);
} }
pub fn createFile(dir: Dir, io: Io, sub_path: []const u8, flags: File.CreateFlags) File.OpenError!File { pub fn createFile(dir: Dir, io: Io, sub_path: []const u8, flags: File.CreateFlags) File.OpenError!File {
return io.vtable.createFile(io.userdata, dir, sub_path, flags); return io.vtable.dirCreateFile(io.userdata, dir, sub_path, flags);
} }
pub const WriteFileOptions = struct { pub const WriteFileOptions = struct {

View file

@ -178,12 +178,12 @@ pub fn io(pool: *Pool) Io {
.wasi => fileStatWasi, .wasi => fileStatWasi,
else => fileStatPosix, else => fileStatPosix,
}, },
.createFile = switch (builtin.os.tag) { .dirCreateFile = switch (builtin.os.tag) {
.windows => @panic("TODO"), .windows => @panic("TODO"),
.wasi => @panic("TODO"), .wasi => @panic("TODO"),
else => createFilePosix, else => dirCreateFilePosix,
}, },
.fileOpen = fileOpen, .dirOpenFile = dirOpenFile,
.fileClose = fileClose, .fileClose = fileClose,
.pwrite = pwrite, .pwrite = pwrite,
.fileReadStreaming = fileReadStreaming, .fileReadStreaming = fileReadStreaming,
@ -966,8 +966,9 @@ fn fileStatWasi(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File.
} }
const have_flock = @TypeOf(posix.system.flock) != void; const have_flock = @TypeOf(posix.system.flock) != void;
const openat_sym = if (posix.lfs64_abi) posix.system.openat64 else posix.system.openat;
fn createFilePosix( fn dirCreateFilePosix(
userdata: ?*anyopaque, userdata: ?*anyopaque,
dir: Io.Dir, dir: Io.Dir,
sub_path: []const u8, sub_path: []const u8,
@ -1003,8 +1004,6 @@ fn createFilePosix(
}, },
}; };
const openat_sym = if (posix.lfs64_abi) posix.system.openat64 else posix.system.openat;
const fd: posix.fd_t = while (true) { const fd: posix.fd_t = while (true) {
try pool.checkCancel(); try pool.checkCancel();
const rc = openat_sym(dir.handle, sub_path_posix, os_flags, flags.mode); const rc = openat_sym(dir.handle, sub_path_posix, os_flags, flags.mode);
@ -1088,24 +1087,139 @@ fn createFilePosix(
return .{ .handle = fd }; return .{ .handle = fd };
} }
fn fileOpen( fn dirOpenFile(
userdata: ?*anyopaque, userdata: ?*anyopaque,
dir: Io.Dir, dir: Io.Dir,
sub_path: []const u8, sub_path: []const u8,
flags: Io.File.OpenFlags, flags: Io.File.OpenFlags,
) Io.File.OpenError!Io.File { ) Io.File.OpenError!Io.File {
const pool: *Pool = @ptrCast(@alignCast(userdata)); const pool: *Pool = @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) {
.wasi => .{
.read = flags.mode != .write_only,
.write = flags.mode != .read_only,
},
else => .{
.ACCMODE = switch (flags.mode) {
.read_only => .RDONLY,
.write_only => .WRONLY,
.read_write => .RDWR,
},
},
};
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;
// Use the O locking flags if the os supports them to acquire the lock
// atomically.
const has_flock_open_flags = @hasField(posix.O, "EXLOCK");
if (has_flock_open_flags) {
// Note that the NONBLOCK flag is removed after the openat() call
// is successful.
switch (flags.lock) {
.none => {},
.shared => {
os_flags.SHLOCK = true;
os_flags.NONBLOCK = flags.lock_nonblocking;
},
.exclusive => {
os_flags.EXLOCK = true;
os_flags.NONBLOCK = flags.lock_nonblocking;
},
}
}
const fd: posix.fd_t = while (true) {
try pool.checkCancel(); try pool.checkCancel();
const fs_dir: std.fs.Dir = .{ .fd = dir.handle }; const rc = openat_sym(dir.handle, sub_path_posix, os_flags, 0);
const fs_file = try fs_dir.openFile(sub_path, flags); switch (posix.errno(rc)) {
return .{ .handle = fs_file.handle }; .SUCCESS => break @intCast(rc),
.INTR => continue,
.FAULT => |err| return errnoBug(err),
.INVAL => return error.BadPathName,
.BADF => |err| return errnoBug(err),
.ACCES => return error.AccessDenied,
.FBIG => return error.FileTooBig,
.OVERFLOW => return error.FileTooBig,
.ISDIR => return error.IsDir,
.LOOP => return error.SymLinkLoop,
.MFILE => return error.ProcessFdQuotaExceeded,
.NAMETOOLONG => return error.NameTooLong,
.NFILE => return error.SystemFdQuotaExceeded,
.NODEV => return error.NoDevice,
.NOENT => return error.FileNotFound,
.SRCH => return error.ProcessNotFound,
.NOMEM => return error.SystemResources,
.NOSPC => return error.NoSpaceLeft,
.NOTDIR => return error.NotDir,
.PERM => return error.PermissionDenied,
.EXIST => return error.PathAlreadyExists,
.BUSY => return error.DeviceBusy,
.OPNOTSUPP => return error.FileLocksNotSupported,
//.AGAIN => return error.WouldBlock,
.TXTBSY => return error.FileBusy,
.NXIO => return error.NoDevice,
.ILSEQ => return error.BadPathName,
else => |err| return posix.unexpectedErrno(err),
}
};
errdefer posix.close(fd);
if (have_flock and !has_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) {
.none => unreachable,
.shared => posix.LOCK.SH | lock_nonblocking,
.exclusive => posix.LOCK.EX | lock_nonblocking,
};
while (true) {
try pool.checkCancel();
switch (posix.errno(posix.system.flock(fd, lock_flags))) {
.SUCCESS => break,
.INTR => continue,
.BADF => |err| return errnoBug(err),
.INVAL => |err| return errnoBug(err), // invalid parameters
.NOLCK => return error.SystemResources,
//.AGAIN => return error.WouldBlock,
.OPNOTSUPP => return error.FileLocksNotSupported,
else => |err| return posix.unexpectedErrno(err),
}
}
}
if (has_flock_open_flags and flags.lock_nonblocking) {
var fl_flags: usize = while (true) {
try pool.checkCancel();
switch (posix.errno(posix.system.fcntl(fd, posix.F.GETFL, 0))) {
.SUCCESS => break,
.INTR => continue,
else => |err| return posix.unexpectedErrno(err),
}
};
fl_flags &= ~@as(usize, 1 << @bitOffsetOf(posix.O, "NONBLOCK"));
while (true) {
try pool.checkCancel();
switch (posix.errno(posix.fcntl(fd, posix.F.SETFL, fl_flags))) {
.SUCCESS => break,
.INTR => continue,
else => |err| return posix.unexpectedErrno(err),
}
}
}
return .{ .handle = fd };
} }
fn fileClose(userdata: ?*anyopaque, file: Io.File) void { fn fileClose(userdata: ?*anyopaque, file: Io.File) void {
const pool: *Pool = @ptrCast(@alignCast(userdata)); const pool: *Pool = @ptrCast(@alignCast(userdata));
_ = pool; _ = pool;
const fs_file: std.fs.File = .{ .handle = file.handle }; posix.close(file.handle);
return fs_file.close();
} }
fn fileReadStreaming(userdata: ?*anyopaque, file: Io.File, data: [][]u8) Io.File.ReadStreamingError!usize { fn fileReadStreaming(userdata: ?*anyopaque, file: Io.File, data: [][]u8) Io.File.ReadStreamingError!usize {

View file

@ -260,12 +260,6 @@ pub fn openFileAbsolute(absolute_path: []const u8, flags: File.OpenFlags) File.O
return cwd().openFile(absolute_path, flags); return cwd().openFile(absolute_path, flags);
} }
/// Same as `openFileAbsolute` but the path parameter is null-terminated.
pub fn openFileAbsoluteZ(absolute_path_c: [*:0]const u8, flags: File.OpenFlags) File.OpenError!File {
assert(path.isAbsoluteZ(absolute_path_c));
return cwd().openFileZ(absolute_path_c, flags);
}
/// Same as `openFileAbsolute` but the path parameter is WTF-16-encoded. /// Same as `openFileAbsolute` but the path parameter is WTF-16-encoded.
pub fn openFileAbsoluteW(absolute_path_w: []const u16, flags: File.OpenFlags) File.OpenError!File { pub fn openFileAbsoluteW(absolute_path_w: []const u16, flags: File.OpenFlags) File.OpenError!File {
assert(path.isAbsoluteWindowsWTF16(absolute_path_w)); assert(path.isAbsoluteWindowsWTF16(absolute_path_w));
@ -284,11 +278,6 @@ pub fn accessAbsolute(absolute_path: []const u8, flags: File.OpenFlags) Dir.Acce
assert(path.isAbsolute(absolute_path)); assert(path.isAbsolute(absolute_path));
try cwd().access(absolute_path, flags); try cwd().access(absolute_path, flags);
} }
/// Same as `accessAbsolute` but the path parameter is null-terminated.
pub fn accessAbsoluteZ(absolute_path: [*:0]const u8, flags: File.OpenFlags) Dir.AccessError!void {
assert(path.isAbsoluteZ(absolute_path));
try cwd().accessZ(absolute_path, flags);
}
/// Same as `accessAbsolute` but the path parameter is WTF-16 encoded. /// Same as `accessAbsolute` but the path parameter is WTF-16 encoded.
pub fn accessAbsoluteW(absolute_path: [*:0]const u16, flags: File.OpenFlags) Dir.AccessError!void { pub fn accessAbsoluteW(absolute_path: [*:0]const u16, flags: File.OpenFlags) Dir.AccessError!void {
assert(path.isAbsoluteWindowsW(absolute_path)); assert(path.isAbsoluteWindowsW(absolute_path));
@ -309,12 +298,6 @@ pub fn createFileAbsolute(absolute_path: []const u8, flags: File.CreateFlags) Fi
return cwd().createFile(absolute_path, flags); return cwd().createFile(absolute_path, flags);
} }
/// Same as `createFileAbsolute` but the path parameter is null-terminated.
pub fn createFileAbsoluteZ(absolute_path_c: [*:0]const u8, flags: File.CreateFlags) File.OpenError!File {
assert(path.isAbsoluteZ(absolute_path_c));
return cwd().createFileZ(absolute_path_c, flags);
}
/// Same as `createFileAbsolute` but the path parameter is WTF-16 encoded. /// Same as `createFileAbsolute` but the path parameter is WTF-16 encoded.
pub fn createFileAbsoluteW(absolute_path_w: [*:0]const u16, flags: File.CreateFlags) File.OpenError!File { pub fn createFileAbsoluteW(absolute_path_w: [*:0]const u16, flags: File.CreateFlags) File.OpenError!File {
assert(path.isAbsoluteWindowsW(absolute_path_w)); assert(path.isAbsoluteWindowsW(absolute_path_w));
@ -333,12 +316,6 @@ pub fn deleteFileAbsolute(absolute_path: []const u8) Dir.DeleteFileError!void {
return cwd().deleteFile(absolute_path); return cwd().deleteFile(absolute_path);
} }
/// Same as `deleteFileAbsolute` except the parameter is null-terminated.
pub fn deleteFileAbsoluteZ(absolute_path_c: [*:0]const u8) Dir.DeleteFileError!void {
assert(path.isAbsoluteZ(absolute_path_c));
return cwd().deleteFileZ(absolute_path_c);
}
/// Same as `deleteFileAbsolute` except the parameter is WTF-16 encoded. /// Same as `deleteFileAbsolute` except the parameter is WTF-16 encoded.
pub fn deleteFileAbsoluteW(absolute_path_w: [*:0]const u16) Dir.DeleteFileError!void { pub fn deleteFileAbsoluteW(absolute_path_w: [*:0]const u16) Dir.DeleteFileError!void {
assert(path.isAbsoluteWindowsW(absolute_path_w)); assert(path.isAbsoluteWindowsW(absolute_path_w));
@ -383,12 +360,6 @@ pub fn readlinkAbsoluteW(pathname_w: [*:0]const u16, buffer: *[max_path_bytes]u8
return posix.readlinkW(mem.span(pathname_w), buffer); return posix.readlinkW(mem.span(pathname_w), buffer);
} }
/// Same as `readLink`, except the path parameter is null-terminated.
pub fn readLinkAbsoluteZ(pathname_c: [*:0]const u8, buffer: *[max_path_bytes]u8) ![]u8 {
assert(path.isAbsoluteZ(pathname_c));
return posix.readlinkZ(pathname_c, buffer);
}
/// Creates a symbolic link named `sym_link_path` which contains the string `target_path`. /// Creates a symbolic link named `sym_link_path` which contains the string `target_path`.
/// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent /// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent
/// one; the latter case is known as a dangling link. /// one; the latter case is known as a dangling link.
@ -426,28 +397,11 @@ pub fn symLinkAbsoluteW(
return windows.CreateSymbolicLink(null, mem.span(sym_link_path_w), mem.span(target_path_w), flags.is_directory); return windows.CreateSymbolicLink(null, mem.span(sym_link_path_w), mem.span(target_path_w), flags.is_directory);
} }
/// Same as `symLinkAbsolute` except the parameters are null-terminated pointers.
/// See also `symLinkAbsolute`.
pub fn symLinkAbsoluteZ(
target_path_c: [*:0]const u8,
sym_link_path_c: [*:0]const u8,
flags: Dir.SymLinkFlags,
) !void {
assert(path.isAbsoluteZ(target_path_c));
assert(path.isAbsoluteZ(sym_link_path_c));
if (native_os == .windows) {
const target_path_w = try windows.cStrToPrefixedFileW(null, target_path_c);
const sym_link_path_w = try windows.cStrToPrefixedFileW(null, sym_link_path_c);
return windows.CreateSymbolicLink(null, sym_link_path_w.span(), target_path_w.span(), flags.is_directory);
}
return posix.symlinkZ(target_path_c, sym_link_path_c);
}
pub const OpenSelfExeError = posix.OpenError || SelfExePathError || posix.FlockError; pub const OpenSelfExeError = posix.OpenError || SelfExePathError || posix.FlockError;
pub fn openSelfExe(flags: File.OpenFlags) OpenSelfExeError!File { pub fn openSelfExe(flags: File.OpenFlags) OpenSelfExeError!File {
if (native_os == .linux or native_os == .serenity) { if (native_os == .linux or native_os == .serenity) {
return openFileAbsoluteZ("/proc/self/exe", flags); return openFileAbsolute("/proc/self/exe", flags);
} }
if (native_os == .windows) { if (native_os == .windows) {
// If ImagePathName is a symlink, then it will contain the path of the symlink, // If ImagePathName is a symlink, then it will contain the path of the symlink,
@ -463,7 +417,7 @@ pub fn openSelfExe(flags: File.OpenFlags) OpenSelfExeError!File {
var buf: [max_path_bytes]u8 = undefined; var buf: [max_path_bytes]u8 = undefined;
const self_exe_path = try selfExePath(&buf); const self_exe_path = try selfExePath(&buf);
buf[self_exe_path.len] = 0; buf[self_exe_path.len] = 0;
return openFileAbsoluteZ(buf[0..self_exe_path.len :0].ptr, flags); return openFileAbsolute(buf[0..self_exe_path.len :0], flags);
} }
// This is `posix.ReadLinkError || posix.RealPathError` with impossible errors excluded // This is `posix.ReadLinkError || posix.RealPathError` with impossible errors excluded

View file

@ -885,94 +885,9 @@ pub fn openFile(self: Dir, sub_path: []const u8, flags: File.OpenFlags) File.Ope
const fd = try posix.openatWasi(self.fd, sub_path, .{}, .{}, .{}, base, .{}); const fd = try posix.openatWasi(self.fd, sub_path, .{}, .{}, .{}, base, .{});
return .{ .handle = fd }; return .{ .handle = fd };
} }
const path_c = try posix.toPosixPath(sub_path); var threaded: Io.Threaded = .init_single_threaded;
return self.openFileZ(&path_c, flags); const io = threaded.io();
} return .adaptFromNewApi(try Io.Dir.openFile(self.adaptToNewApi(), io, sub_path, flags));
/// Same as `openFile` but the path parameter is null-terminated.
pub fn openFileZ(self: Dir, sub_path: [*:0]const u8, flags: File.OpenFlags) File.OpenError!File {
switch (native_os) {
.windows => {
const path_w = try windows.cStrToPrefixedFileW(self.fd, sub_path);
return self.openFileW(path_w.span(), flags);
},
// Use the libc API when libc is linked because it implements things
// such as opening absolute file paths.
.wasi => if (!builtin.link_libc) {
return openFile(self, mem.sliceTo(sub_path, 0), flags);
},
else => {},
}
var os_flags: posix.O = switch (native_os) {
.wasi => .{
.read = flags.mode != .write_only,
.write = flags.mode != .read_only,
},
else => .{
.ACCMODE = switch (flags.mode) {
.read_only => .RDONLY,
.write_only => .WRONLY,
.read_write => .RDWR,
},
},
};
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;
// Use the O locking flags if the os supports them to acquire the lock
// atomically.
const has_flock_open_flags = @hasField(posix.O, "EXLOCK");
if (has_flock_open_flags) {
// Note that the NONBLOCK flag is removed after the openat() call
// is successful.
switch (flags.lock) {
.none => {},
.shared => {
os_flags.SHLOCK = true;
os_flags.NONBLOCK = flags.lock_nonblocking;
},
.exclusive => {
os_flags.EXLOCK = true;
os_flags.NONBLOCK = flags.lock_nonblocking;
},
}
}
const fd = try posix.openatZ(self.fd, sub_path, os_flags, 0);
errdefer posix.close(fd);
if (have_flock and !has_flock_open_flags and flags.lock != .none) {
// TODO: integrate async I/O
const lock_nonblocking: i32 = if (flags.lock_nonblocking) posix.LOCK.NB else 0;
try posix.flock(fd, switch (flags.lock) {
.none => unreachable,
.shared => posix.LOCK.SH | lock_nonblocking,
.exclusive => posix.LOCK.EX | lock_nonblocking,
});
}
if (has_flock_open_flags and flags.lock_nonblocking) {
var fl_flags = posix.fcntl(fd, posix.F.GETFL, 0) catch |err| switch (err) {
error.FileBusy => unreachable,
error.Locked => unreachable,
error.PermissionDenied => unreachable,
error.DeadLock => unreachable,
error.LockedRegionLimitExceeded => unreachable,
else => |e| return e,
};
fl_flags &= ~@as(usize, 1 << @bitOffsetOf(posix.O, "NONBLOCK"));
_ = posix.fcntl(fd, posix.F.SETFL, fl_flags) catch |err| switch (err) {
error.FileBusy => unreachable,
error.Locked => unreachable,
error.PermissionDenied => unreachable,
error.DeadLock => unreachable,
error.LockedRegionLimitExceeded => unreachable,
else => |e| return e,
};
}
return .{ .handle = fd };
} }
/// Same as `openFile` but Windows-only and the path parameter is /// Same as `openFile` but Windows-only and the path parameter is
@ -1048,82 +963,10 @@ pub fn createFile(self: Dir, sub_path: []const u8, flags: File.CreateFlags) File
}, .{}), }, .{}),
}; };
} }
const path_c = try posix.toPosixPath(sub_path); var threaded: Io.Threaded = .init_single_threaded;
return self.createFileZ(&path_c, flags); const io = threaded.io();
} const new_file = try Io.Dir.createFile(self.adaptToNewApi(), io, sub_path, flags);
return .adaptFromNewApi(new_file);
/// Same as `createFile` but the path parameter is null-terminated.
pub fn createFileZ(self: Dir, sub_path_c: [*:0]const u8, flags: File.CreateFlags) File.OpenError!File {
switch (native_os) {
.windows => {
const path_w = try windows.cStrToPrefixedFileW(self.fd, sub_path_c);
return self.createFileW(path_w.span(), flags);
},
.wasi => {
return createFile(self, mem.sliceTo(sub_path_c, 0), flags);
},
else => {},
}
var os_flags: posix.O = .{
.ACCMODE = if (flags.read) .RDWR else .WRONLY,
.CREAT = true,
.TRUNC = flags.truncate,
.EXCL = flags.exclusive,
};
if (@hasField(posix.O, "LARGEFILE")) os_flags.LARGEFILE = true;
if (@hasField(posix.O, "CLOEXEC")) os_flags.CLOEXEC = true;
// 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.
const has_flock_open_flags = @hasField(posix.O, "EXLOCK");
if (has_flock_open_flags) switch (flags.lock) {
.none => {},
.shared => {
os_flags.SHLOCK = true;
os_flags.NONBLOCK = flags.lock_nonblocking;
},
.exclusive => {
os_flags.EXLOCK = true;
os_flags.NONBLOCK = flags.lock_nonblocking;
},
};
const fd = try posix.openatZ(self.fd, sub_path_c, os_flags, flags.mode);
errdefer posix.close(fd);
if (have_flock and !has_flock_open_flags and flags.lock != .none) {
// TODO: integrate async I/O
const lock_nonblocking: i32 = if (flags.lock_nonblocking) posix.LOCK.NB else 0;
try posix.flock(fd, switch (flags.lock) {
.none => unreachable,
.shared => posix.LOCK.SH | lock_nonblocking,
.exclusive => posix.LOCK.EX | lock_nonblocking,
});
}
if (has_flock_open_flags and flags.lock_nonblocking) {
var fl_flags = posix.fcntl(fd, posix.F.GETFL, 0) catch |err| switch (err) {
error.FileBusy => unreachable,
error.Locked => unreachable,
error.PermissionDenied => unreachable,
error.DeadLock => unreachable,
error.LockedRegionLimitExceeded => unreachable,
else => |e| return e,
};
fl_flags &= ~@as(usize, 1 << @bitOffsetOf(posix.O, "NONBLOCK"));
_ = posix.fcntl(fd, posix.F.SETFL, fl_flags) catch |err| switch (err) {
error.FileBusy => unreachable,
error.Locked => unreachable,
error.PermissionDenied => unreachable,
error.DeadLock => unreachable,
error.LockedRegionLimitExceeded => unreachable,
else => |e| return e,
};
}
return .{ .handle = fd };
} }
/// Same as `createFile` but Windows-only and the path parameter is /// Same as `createFile` but Windows-only and the path parameter is