Commit b1733b7bce

Andrew Kelley <andrew@ziglang.org>
2025-10-09 09:47:04
std.Io: implement dirOpenFile
1 parent 8a1e6c8
Changed files (5)
lib/std/fs/Dir.zig
@@ -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, .{});
         return .{ .handle = fd };
     }
-    const path_c = try posix.toPosixPath(sub_path);
-    return self.openFileZ(&path_c, 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 };
+    var threaded: Io.Threaded = .init_single_threaded;
+    const io = threaded.io();
+    return .adaptFromNewApi(try Io.Dir.openFile(self.adaptToNewApi(), io, sub_path, flags));
 }
 
 /// 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);
-    return self.createFileZ(&path_c, flags);
-}
-
-/// 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 };
+    var threaded: Io.Threaded = .init_single_threaded;
+    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 Windows-only and the path parameter is
lib/std/Io/Dir.zig
@@ -39,11 +39,11 @@ pub const OpenError = error{
 } || PathNameError || Io.Cancelable || Io.UnexpectedError;
 
 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 {
-    return io.vtable.createFile(io.userdata, dir, sub_path, flags);
+    return io.vtable.dirCreateFile(io.userdata, dir, sub_path, flags);
 }
 
 pub const WriteFileOptions = struct {
lib/std/Io/Threaded.zig
@@ -178,12 +178,12 @@ pub fn io(pool: *Pool) Io {
                 .wasi => fileStatWasi,
                 else => fileStatPosix,
             },
-            .createFile = switch (builtin.os.tag) {
+            .dirCreateFile = switch (builtin.os.tag) {
                 .windows => @panic("TODO"),
                 .wasi => @panic("TODO"),
-                else => createFilePosix,
+                else => dirCreateFilePosix,
             },
-            .fileOpen = fileOpen,
+            .dirOpenFile = dirOpenFile,
             .fileClose = fileClose,
             .pwrite = pwrite,
             .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 openat_sym = if (posix.lfs64_abi) posix.system.openat64 else posix.system.openat;
 
-fn createFilePosix(
+fn dirCreateFilePosix(
     userdata: ?*anyopaque,
     dir: Io.Dir,
     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) {
         try pool.checkCancel();
         const rc = openat_sym(dir.handle, sub_path_posix, os_flags, flags.mode);
@@ -1088,24 +1087,139 @@ fn createFilePosix(
     return .{ .handle = fd };
 }
 
-fn fileOpen(
+fn dirOpenFile(
     userdata: ?*anyopaque,
     dir: Io.Dir,
     sub_path: []const u8,
     flags: Io.File.OpenFlags,
 ) Io.File.OpenError!Io.File {
     const pool: *Pool = @ptrCast(@alignCast(userdata));
-    try pool.checkCancel();
-    const fs_dir: std.fs.Dir = .{ .fd = dir.handle };
-    const fs_file = try fs_dir.openFile(sub_path, flags);
-    return .{ .handle = fs_file.handle };
+
+    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();
+        const rc = openat_sym(dir.handle, sub_path_posix, os_flags, 0);
+        switch (posix.errno(rc)) {
+            .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 {
     const pool: *Pool = @ptrCast(@alignCast(userdata));
     _ = pool;
-    const fs_file: std.fs.File = .{ .handle = file.handle };
-    return fs_file.close();
+    posix.close(file.handle);
 }
 
 fn fileReadStreaming(userdata: ?*anyopaque, file: Io.File, data: [][]u8) Io.File.ReadStreamingError!usize {
lib/std/fs.zig
@@ -260,12 +260,6 @@ pub fn openFileAbsolute(absolute_path: []const u8, flags: File.OpenFlags) File.O
     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.
 pub fn openFileAbsoluteW(absolute_path_w: []const u16, flags: File.OpenFlags) File.OpenError!File {
     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));
     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.
 pub fn accessAbsoluteW(absolute_path: [*:0]const u16, flags: File.OpenFlags) Dir.AccessError!void {
     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);
 }
 
-/// 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.
 pub fn createFileAbsoluteW(absolute_path_w: [*:0]const u16, flags: File.CreateFlags) File.OpenError!File {
     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);
 }
 
-/// 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.
 pub fn deleteFileAbsoluteW(absolute_path_w: [*:0]const u16) Dir.DeleteFileError!void {
     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);
 }
 
-/// 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`.
 /// 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.
@@ -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);
 }
 
-/// 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 fn openSelfExe(flags: File.OpenFlags) OpenSelfExeError!File {
     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 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;
     const self_exe_path = try selfExePath(&buf);
     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
lib/std/Io.zig
@@ -657,9 +657,9 @@ pub const VTable = struct {
     dirMake: *const fn (?*anyopaque, Dir, sub_path: []const u8, mode: Dir.Mode) Dir.MakeError!void,
     dirStat: *const fn (?*anyopaque, Dir) Dir.StatError!Dir.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,
-    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,
     pwrite: *const fn (?*anyopaque, File, buffer: []const u8, offset: std.posix.off_t) File.PWriteError!usize,
     /// Returns 0 on end of stream.