Commit 0e9280ef1a

Andrew Kelley <andrew@ziglang.org>
2025-09-11 07:34:19
std.Io: extract Dir to separate file
1 parent fc1e3d5
lib/std/fs/Dir.zig
@@ -2630,74 +2630,6 @@ pub const CopyFileOptions = struct {
     override_mode: ?File.Mode = null,
 };
 
-pub const PrevStatus = enum {
-    stale,
-    fresh,
-};
-
-/// Check the file size, mtime, and mode of `source_path` and `dest_path`. If they are equal, does nothing.
-/// Otherwise, atomically copies `source_path` to `dest_path`. The destination file gains the mtime,
-/// atime, and mode of the source file so that the next call to `updateFile` will not need a copy.
-/// Returns the previous status of the file before updating.
-/// If any of the directories do not exist for dest_path, they are created.
-/// On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
-/// On WASI, both paths should be encoded as valid UTF-8.
-/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
-pub fn updateFile(
-    source_dir: Dir,
-    source_path: []const u8,
-    dest_dir: Dir,
-    dest_path: []const u8,
-    options: CopyFileOptions,
-) !PrevStatus {
-    var src_file = try source_dir.openFile(source_path, .{});
-    defer src_file.close();
-
-    const src_stat = try src_file.stat();
-    const actual_mode = options.override_mode orelse src_stat.mode;
-    check_dest_stat: {
-        const dest_stat = blk: {
-            var dest_file = dest_dir.openFile(dest_path, .{}) catch |err| switch (err) {
-                error.FileNotFound => break :check_dest_stat,
-                else => |e| return e,
-            };
-            defer dest_file.close();
-
-            break :blk try dest_file.stat();
-        };
-
-        if (src_stat.size == dest_stat.size and
-            src_stat.mtime == dest_stat.mtime and
-            actual_mode == dest_stat.mode)
-        {
-            return PrevStatus.fresh;
-        }
-    }
-
-    if (fs.path.dirname(dest_path)) |dirname| {
-        try dest_dir.makePath(dirname);
-    }
-
-    var buffer: [1000]u8 = undefined; // Used only when direct fd-to-fd is not available.
-    var atomic_file = try dest_dir.atomicFile(dest_path, .{
-        .mode = actual_mode,
-        .write_buffer = &buffer,
-    });
-    defer atomic_file.deinit();
-
-    var src_reader: File.Reader = .initSize(src_file, &.{}, src_stat.size);
-    const dest_writer = &atomic_file.file_writer.interface;
-
-    _ = dest_writer.sendFileAll(&src_reader, .unlimited) catch |err| switch (err) {
-        error.ReadFailed => return src_reader.err.?,
-        error.WriteFailed => return atomic_file.file_writer.err.?,
-    };
-    try atomic_file.flush();
-    try atomic_file.file_writer.file.updateTimes(src_stat.atime, src_stat.mtime);
-    try atomic_file.renameIntoPlace();
-    return .stale;
-}
-
 pub const CopyFileError = File.OpenError || File.StatError ||
     AtomicFile.InitError || AtomicFile.FinishError ||
     File.ReadError || File.WriteError;
lib/std/fs/File.zig
@@ -1144,13 +1144,7 @@ pub const Reader = struct {
     fn stream(io_reader: *std.Io.Reader, w: *std.Io.Writer, limit: std.Io.Limit) std.Io.Reader.StreamError!usize {
         const r: *Reader = @alignCast(@fieldParentPtr("interface", io_reader));
         switch (r.mode) {
-            .positional, .streaming => return w.sendFile(r, limit) catch |write_err| switch (write_err) {
-                error.Unimplemented => {
-                    r.mode = r.mode.toReading();
-                    return 0;
-                },
-                else => |e| return e,
-            },
+            .positional, .streaming => @panic("TODO"),
             .positional_reading => {
                 const dest = limit.slice(try w.writableSliceGreedy(1));
                 var data: [1][]u8 = .{dest};
lib/std/Io/net/HostName.zig
@@ -629,3 +629,28 @@ pub const ResolvConf = struct {
         @panic("TODO");
     }
 };
+
+test ResolvConf {
+    const input =
+        \\# Generated by resolvconf
+        \\nameserver 1.0.0.1
+        \\nameserver 1.1.1.1
+        \\nameserver fe80::e0e:76ff:fed4:cf22%eno1
+        \\options edns0
+        \\
+    ;
+    var reader: Io.Reader = .fixed(input);
+
+    var rc: ResolvConf = .{
+        .nameservers_buffer = undefined,
+        .nameservers_len = 0,
+        .search_buffer = undefined,
+        .search_len = 0,
+        .ndots = 1,
+        .timeout = 5,
+        .attempts = 2,
+    };
+
+    try rc.parse(&reader);
+    try std.testing.expect(false);
+}
lib/std/Io/Dir.zig
@@ -0,0 +1,113 @@
+const Dir = @This();
+
+const std = @import("../std.zig");
+const Io = std.Io;
+const File = Io.File;
+
+handle: Handle,
+
+pub fn cwd() Dir {
+    return .{ .handle = std.fs.cwd().fd };
+}
+
+pub const Handle = std.posix.fd_t;
+
+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);
+}
+
+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);
+}
+
+pub const WriteFileOptions = 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.
+    sub_path: []const u8,
+    data: []const u8,
+    flags: File.CreateFlags = .{},
+};
+
+pub const WriteFileError = File.WriteError || File.OpenError || Io.Cancelable;
+
+/// Writes content to the file system, using the file creation flags provided.
+pub fn writeFile(dir: Dir, io: Io, options: WriteFileOptions) WriteFileError!void {
+    var file = try dir.createFile(io, options.sub_path, options.flags);
+    defer file.close(io);
+    try file.writeAll(io, options.data);
+}
+
+pub const PrevStatus = enum {
+    stale,
+    fresh,
+};
+
+pub const UpdateFileError = File.OpenError;
+
+/// Check the file size, mtime, and mode of `source_path` and `dest_path`. If
+/// they are equal, does nothing. Otherwise, atomically copies `source_path` to
+/// `dest_path`. The destination file gains the mtime, atime, and mode of the
+/// source file so that the next call to `updateFile` will not need a copy.
+///
+/// Returns the previous status of the file before updating.
+///
+/// * On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
+/// * On WASI, both paths should be encoded as valid UTF-8.
+/// * On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
+pub fn updateFile(
+    source_dir: Dir,
+    io: Io,
+    source_path: []const u8,
+    dest_dir: Dir,
+    /// If directories in this path do not exist, they are created.
+    dest_path: []const u8,
+    options: std.fs.Dir.CopyFileOptions,
+) !PrevStatus {
+    var src_file = try source_dir.openFile(io, source_path, .{});
+    defer src_file.close();
+
+    const src_stat = try src_file.stat(io);
+    const actual_mode = options.override_mode orelse src_stat.mode;
+    check_dest_stat: {
+        const dest_stat = blk: {
+            var dest_file = dest_dir.openFile(io, dest_path, .{}) catch |err| switch (err) {
+                error.FileNotFound => break :check_dest_stat,
+                else => |e| return e,
+            };
+            defer dest_file.close(io);
+
+            break :blk try dest_file.stat(io);
+        };
+
+        if (src_stat.size == dest_stat.size and
+            src_stat.mtime == dest_stat.mtime and
+            actual_mode == dest_stat.mode)
+        {
+            return .fresh;
+        }
+    }
+
+    if (std.fs.path.dirname(dest_path)) |dirname| {
+        try dest_dir.makePath(io, dirname);
+    }
+
+    var buffer: [1000]u8 = undefined; // Used only when direct fd-to-fd is not available.
+    var atomic_file = try dest_dir.atomicFile(io, dest_path, .{
+        .mode = actual_mode,
+        .write_buffer = &buffer,
+    });
+    defer atomic_file.deinit();
+
+    var src_reader: File.Reader = .initSize(io, src_file, &.{}, src_stat.size);
+    const dest_writer = &atomic_file.file_writer.interface;
+
+    _ = dest_writer.sendFileAll(&src_reader, .unlimited) catch |err| switch (err) {
+        error.ReadFailed => return src_reader.err.?,
+        error.WriteFailed => return atomic_file.file_writer.err.?,
+    };
+    try atomic_file.flush();
+    try atomic_file.file_writer.file.updateTimes(src_stat.atime, src_stat.mtime);
+    try atomic_file.renameIntoPlace();
+    return .stale;
+}
lib/std/Io/File.zig
@@ -1,7 +1,9 @@
+const File = @This();
+
 const builtin = @import("builtin");
+
 const std = @import("../std.zig");
 const Io = std.Io;
-const File = @This();
 const assert = std.debug.assert;
 
 handle: Handle,
lib/std/Io/net.zig
@@ -593,3 +593,7 @@ pub const InterfaceIndexError = error{
 pub fn interfaceIndex(io: Io, name: []const u8) InterfaceIndexError!u32 {
     return io.vtable.netInterfaceIndex(io.userdata, name);
 }
+
+test {
+    _ = HostName;
+}
lib/std/fs.zig
@@ -107,13 +107,15 @@ pub fn updateFileAbsolute(
     source_path: []const u8,
     dest_path: []const u8,
     args: Dir.CopyFileOptions,
-) !Dir.PrevStatus {
+) !std.Io.Dir.PrevStatus {
     assert(path.isAbsolute(source_path));
     assert(path.isAbsolute(dest_path));
     const my_cwd = cwd();
     return Dir.updateFile(my_cwd, source_path, my_cwd, dest_path, args);
 }
 
+test updateFileAbsolute {}
+
 /// Same as `Dir.copyFile`, except asserts that both `source_path` and `dest_path`
 /// are absolute. See `Dir.copyFile` for a function that operates on both
 /// absolute and relative paths.
@@ -131,6 +133,8 @@ pub fn copyFileAbsolute(
     return Dir.copyFile(my_cwd, source_path, my_cwd, dest_path, args);
 }
 
+test copyFileAbsolute {}
+
 /// Create a new directory, based on an absolute path.
 /// Asserts that the path is absolute. See `Dir.makeDir` for a function that operates
 /// on both absolute and relative paths.
@@ -142,12 +146,16 @@ pub fn makeDirAbsolute(absolute_path: []const u8) !void {
     return posix.mkdir(absolute_path, Dir.default_mode);
 }
 
+test makeDirAbsolute {}
+
 /// Same as `makeDirAbsolute` except the parameter is null-terminated.
 pub fn makeDirAbsoluteZ(absolute_path_z: [*:0]const u8) !void {
     assert(path.isAbsoluteZ(absolute_path_z));
     return posix.mkdirZ(absolute_path_z, Dir.default_mode);
 }
 
+test makeDirAbsoluteZ {}
+
 /// Same as `makeDirAbsolute` except the parameter is a null-terminated WTF-16 LE-encoded string.
 pub fn makeDirAbsoluteW(absolute_path_w: [*:0]const u16) !void {
     assert(path.isAbsoluteWindowsW(absolute_path_w));
@@ -702,16 +710,10 @@ pub fn realpathAlloc(allocator: Allocator, pathname: []const u8) ![]u8 {
 }
 
 test {
-    if (native_os != .wasi) {
-        _ = &makeDirAbsolute;
-        _ = &makeDirAbsoluteZ;
-        _ = &copyFileAbsolute;
-        _ = &updateFileAbsolute;
-    }
-    _ = &AtomicFile;
-    _ = &Dir;
-    _ = &File;
-    _ = &path;
+    _ = AtomicFile;
+    _ = Dir;
+    _ = File;
+    _ = path;
     _ = @import("fs/test.zig");
     _ = @import("fs/get_app_data_dir.zig");
 }
lib/std/Io.zig
@@ -548,6 +548,7 @@ pub fn PollFiles(comptime StreamEnum: type) type {
 }
 
 test {
+    _ = net;
     _ = Reader;
     _ = Writer;
     _ = tty;
@@ -688,42 +689,7 @@ pub const UnexpectedError = error{
     Unexpected,
 };
 
-pub const Dir = struct {
-    handle: Handle,
-
-    pub fn cwd() Dir {
-        return .{ .handle = std.fs.cwd().fd };
-    }
-
-    pub const Handle = std.posix.fd_t;
-
-    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);
-    }
-
-    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);
-    }
-
-    pub const WriteFileOptions = struct {
-        /// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
-        /// 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.
-        sub_path: []const u8,
-        data: []const u8,
-        flags: File.CreateFlags = .{},
-    };
-
-    pub const WriteFileError = File.WriteError || File.OpenError || Cancelable;
-
-    /// Writes content to the file system, using the file creation flags provided.
-    pub fn writeFile(dir: Dir, io: Io, options: WriteFileOptions) WriteFileError!void {
-        var file = try dir.createFile(io, options.sub_path, options.flags);
-        defer file.close(io);
-        try file.writeAll(io, options.data);
-    }
-};
-
+pub const Dir = @import("Io/Dir.zig");
 pub const File = @import("Io/File.zig");
 
 pub const Timestamp = enum(i96) {
lib/std/posix.zig
@@ -939,7 +939,7 @@ pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize {
     }
 }
 
-pub const PReadError = std.Io.ReadPositionalError;
+pub const PReadError = std.Io.File.ReadPositionalError;
 
 /// Number of bytes read is returned. Upon reading end-of-file, zero is returned.
 ///