authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-09-10 22:34:19-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-10-29 06:20:48-07:00
log0e9280ef1a80e8fdeaec40a41b9cb6f2c2d4a490
treec069e1a0b58fe0eb1729652406b6dcea440a764c
parentfc1e3d5bc9f4279ae6cd19577bb443aff8d4ccb6

std.Io: extract Dir to separate file


9 files changed, 162 insertions(+), 124 deletions(-)

lib/std/Io.zig+2-36
...@@ -548,6 +548,7 @@ pub fn PollFiles(comptime StreamEnum: type) type {...@@ -548,6 +548,7 @@ pub fn PollFiles(comptime StreamEnum: type) type {
548}548}
549549
550test {550test {
551 _ = net;
551 _ = Reader;552 _ = Reader;
552 _ = Writer;553 _ = Writer;
553 _ = tty;554 _ = tty;
...@@ -688,42 +689,7 @@ pub const UnexpectedError = error{...@@ -688,42 +689,7 @@ pub const UnexpectedError = error{
688 Unexpected,689 Unexpected,
689};690};
690691
691pub const Dir = struct {692pub const Dir = @import("Io/Dir.zig");
692 handle: Handle,
693
694 pub fn cwd() Dir {
695 return .{ .handle = std.fs.cwd().fd };
696 }
697
698 pub const Handle = std.posix.fd_t;
699
700 pub fn openFile(dir: Dir, io: Io, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File {
701 return io.vtable.fileOpen(io.userdata, dir, sub_path, flags);
702 }
703
704 pub fn createFile(dir: Dir, io: Io, sub_path: []const u8, flags: File.CreateFlags) File.OpenError!File {
705 return io.vtable.createFile(io.userdata, dir, sub_path, flags);
706 }
707
708 pub const WriteFileOptions = struct {
709 /// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
710 /// On WASI, `sub_path` should be encoded as valid UTF-8.
711 /// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
712 sub_path: []const u8,
713 data: []const u8,
714 flags: File.CreateFlags = .{},
715 };
716
717 pub const WriteFileError = File.WriteError || File.OpenError || Cancelable;
718
719 /// Writes content to the file system, using the file creation flags provided.
720 pub fn writeFile(dir: Dir, io: Io, options: WriteFileOptions) WriteFileError!void {
721 var file = try dir.createFile(io, options.sub_path, options.flags);
722 defer file.close(io);
723 try file.writeAll(io, options.data);
724 }
725};
726
727pub const File = @import("Io/File.zig");693pub const File = @import("Io/File.zig");
728694
729pub const Timestamp = enum(i96) {695pub const Timestamp = enum(i96) {
lib/std/Io/Dir.zig created+113
...@@ -0,0 +1,113 @@
1const Dir = @This();
2
3const std = @import("../std.zig");
4const Io = std.Io;
5const File = Io.File;
6
7handle: Handle,
8
9pub fn cwd() Dir {
10 return .{ .handle = std.fs.cwd().fd };
11}
12
13pub const Handle = std.posix.fd_t;
14
15pub fn openFile(dir: Dir, io: Io, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File {
16 return io.vtable.fileOpen(io.userdata, dir, sub_path, flags);
17}
18
19pub fn createFile(dir: Dir, io: Io, sub_path: []const u8, flags: File.CreateFlags) File.OpenError!File {
20 return io.vtable.createFile(io.userdata, dir, sub_path, flags);
21}
22
23pub const WriteFileOptions = struct {
24 /// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
25 /// On WASI, `sub_path` should be encoded as valid UTF-8.
26 /// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
27 sub_path: []const u8,
28 data: []const u8,
29 flags: File.CreateFlags = .{},
30};
31
32pub const WriteFileError = File.WriteError || File.OpenError || Io.Cancelable;
33
34/// Writes content to the file system, using the file creation flags provided.
35pub fn writeFile(dir: Dir, io: Io, options: WriteFileOptions) WriteFileError!void {
36 var file = try dir.createFile(io, options.sub_path, options.flags);
37 defer file.close(io);
38 try file.writeAll(io, options.data);
39}
40
41pub const PrevStatus = enum {
42 stale,
43 fresh,
44};
45
46pub const UpdateFileError = File.OpenError;
47
48/// Check the file size, mtime, and mode of `source_path` and `dest_path`. If
49/// they are equal, does nothing. Otherwise, atomically copies `source_path` to
50/// `dest_path`. The destination file gains the mtime, atime, and mode of the
51/// source file so that the next call to `updateFile` will not need a copy.
52///
53/// Returns the previous status of the file before updating.
54///
55/// * On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
56/// * On WASI, both paths should be encoded as valid UTF-8.
57/// * On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
58pub fn updateFile(
59 source_dir: Dir,
60 io: Io,
61 source_path: []const u8,
62 dest_dir: Dir,
63 /// If directories in this path do not exist, they are created.
64 dest_path: []const u8,
65 options: std.fs.Dir.CopyFileOptions,
66) !PrevStatus {
67 var src_file = try source_dir.openFile(io, source_path, .{});
68 defer src_file.close();
69
70 const src_stat = try src_file.stat(io);
71 const actual_mode = options.override_mode orelse src_stat.mode;
72 check_dest_stat: {
73 const dest_stat = blk: {
74 var dest_file = dest_dir.openFile(io, dest_path, .{}) catch |err| switch (err) {
75 error.FileNotFound => break :check_dest_stat,
76 else => |e| return e,
77 };
78 defer dest_file.close(io);
79
80 break :blk try dest_file.stat(io);
81 };
82
83 if (src_stat.size == dest_stat.size and
84 src_stat.mtime == dest_stat.mtime and
85 actual_mode == dest_stat.mode)
86 {
87 return .fresh;
88 }
89 }
90
91 if (std.fs.path.dirname(dest_path)) |dirname| {
92 try dest_dir.makePath(io, dirname);
93 }
94
95 var buffer: [1000]u8 = undefined; // Used only when direct fd-to-fd is not available.
96 var atomic_file = try dest_dir.atomicFile(io, dest_path, .{
97 .mode = actual_mode,
98 .write_buffer = &buffer,
99 });
100 defer atomic_file.deinit();
101
102 var src_reader: File.Reader = .initSize(io, src_file, &.{}, src_stat.size);
103 const dest_writer = &atomic_file.file_writer.interface;
104
105 _ = dest_writer.sendFileAll(&src_reader, .unlimited) catch |err| switch (err) {
106 error.ReadFailed => return src_reader.err.?,
107 error.WriteFailed => return atomic_file.file_writer.err.?,
108 };
109 try atomic_file.flush();
110 try atomic_file.file_writer.file.updateTimes(src_stat.atime, src_stat.mtime);
111 try atomic_file.renameIntoPlace();
112 return .stale;
113}
lib/std/Io/File.zig+3-1
...@@ -1,7 +1,9 @@...@@ -1,7 +1,9 @@
1const File = @This();
2
1const builtin = @import("builtin");3const builtin = @import("builtin");
4
2const std = @import("../std.zig");5const std = @import("../std.zig");
3const Io = std.Io;6const Io = std.Io;
4const File = @This();
5const assert = std.debug.assert;7const assert = std.debug.assert;
68
7handle: Handle,9handle: Handle,
lib/std/Io/net.zig+4
...@@ -593,3 +593,7 @@ pub const InterfaceIndexError = error{...@@ -593,3 +593,7 @@ pub const InterfaceIndexError = error{
593pub fn interfaceIndex(io: Io, name: []const u8) InterfaceIndexError!u32 {593pub fn interfaceIndex(io: Io, name: []const u8) InterfaceIndexError!u32 {
594 return io.vtable.netInterfaceIndex(io.userdata, name);594 return io.vtable.netInterfaceIndex(io.userdata, name);
595}595}
596
597test {
598 _ = HostName;
599}
lib/std/Io/net/HostName.zig+25
...@@ -629,3 +629,28 @@ pub const ResolvConf = struct {...@@ -629,3 +629,28 @@ pub const ResolvConf = struct {
629 @panic("TODO");629 @panic("TODO");
630 }630 }
631};631};
632
633test ResolvConf {
634 const input =
635 \\# Generated by resolvconf
636 \\nameserver 1.0.0.1
637 \\nameserver 1.1.1.1
638 \\nameserver fe80::e0e:76ff:fed4:cf22%eno1
639 \\options edns0
640 \\
641 ;
642 var reader: Io.Reader = .fixed(input);
643
644 var rc: ResolvConf = .{
645 .nameservers_buffer = undefined,
646 .nameservers_len = 0,
647 .search_buffer = undefined,
648 .search_len = 0,
649 .ndots = 1,
650 .timeout = 5,
651 .attempts = 2,
652 };
653
654 try rc.parse(&reader);
655 try std.testing.expect(false);
656}
lib/std/fs.zig+13-11
...@@ -107,13 +107,15 @@ pub fn updateFileAbsolute(...@@ -107,13 +107,15 @@ pub fn updateFileAbsolute(
107 source_path: []const u8,107 source_path: []const u8,
108 dest_path: []const u8,108 dest_path: []const u8,
109 args: Dir.CopyFileOptions,109 args: Dir.CopyFileOptions,
110) !Dir.PrevStatus {110) !std.Io.Dir.PrevStatus {
111 assert(path.isAbsolute(source_path));111 assert(path.isAbsolute(source_path));
112 assert(path.isAbsolute(dest_path));112 assert(path.isAbsolute(dest_path));
113 const my_cwd = cwd();113 const my_cwd = cwd();
114 return Dir.updateFile(my_cwd, source_path, my_cwd, dest_path, args);114 return Dir.updateFile(my_cwd, source_path, my_cwd, dest_path, args);
115}115}
116116
117test updateFileAbsolute {}
118
117/// Same as `Dir.copyFile`, except asserts that both `source_path` and `dest_path`119/// Same as `Dir.copyFile`, except asserts that both `source_path` and `dest_path`
118/// are absolute. See `Dir.copyFile` for a function that operates on both120/// are absolute. See `Dir.copyFile` for a function that operates on both
119/// absolute and relative paths.121/// absolute and relative paths.
...@@ -131,6 +133,8 @@ pub fn copyFileAbsolute(...@@ -131,6 +133,8 @@ pub fn copyFileAbsolute(
131 return Dir.copyFile(my_cwd, source_path, my_cwd, dest_path, args);133 return Dir.copyFile(my_cwd, source_path, my_cwd, dest_path, args);
132}134}
133135
136test copyFileAbsolute {}
137
134/// Create a new directory, based on an absolute path.138/// Create a new directory, based on an absolute path.
135/// Asserts that the path is absolute. See `Dir.makeDir` for a function that operates139/// Asserts that the path is absolute. See `Dir.makeDir` for a function that operates
136/// on both absolute and relative paths.140/// on both absolute and relative paths.
...@@ -142,12 +146,16 @@ pub fn makeDirAbsolute(absolute_path: []const u8) !void {...@@ -142,12 +146,16 @@ pub fn makeDirAbsolute(absolute_path: []const u8) !void {
142 return posix.mkdir(absolute_path, Dir.default_mode);146 return posix.mkdir(absolute_path, Dir.default_mode);
143}147}
144148
149test makeDirAbsolute {}
150
145/// Same as `makeDirAbsolute` except the parameter is null-terminated.151/// Same as `makeDirAbsolute` except the parameter is null-terminated.
146pub fn makeDirAbsoluteZ(absolute_path_z: [*:0]const u8) !void {152pub fn makeDirAbsoluteZ(absolute_path_z: [*:0]const u8) !void {
147 assert(path.isAbsoluteZ(absolute_path_z));153 assert(path.isAbsoluteZ(absolute_path_z));
148 return posix.mkdirZ(absolute_path_z, Dir.default_mode);154 return posix.mkdirZ(absolute_path_z, Dir.default_mode);
149}155}
150156
157test makeDirAbsoluteZ {}
158
151/// Same as `makeDirAbsolute` except the parameter is a null-terminated WTF-16 LE-encoded string.159/// Same as `makeDirAbsolute` except the parameter is a null-terminated WTF-16 LE-encoded string.
152pub fn makeDirAbsoluteW(absolute_path_w: [*:0]const u16) !void {160pub fn makeDirAbsoluteW(absolute_path_w: [*:0]const u16) !void {
153 assert(path.isAbsoluteWindowsW(absolute_path_w));161 assert(path.isAbsoluteWindowsW(absolute_path_w));
...@@ -702,16 +710,10 @@ pub fn realpathAlloc(allocator: Allocator, pathname: []const u8) ![]u8 {...@@ -702,16 +710,10 @@ pub fn realpathAlloc(allocator: Allocator, pathname: []const u8) ![]u8 {
702}710}
703711
704test {712test {
705 if (native_os != .wasi) {713 _ = AtomicFile;
706 _ = &makeDirAbsolute;714 _ = Dir;
707 _ = &makeDirAbsoluteZ;715 _ = File;
708 _ = &copyFileAbsolute;716 _ = path;
709 _ = &updateFileAbsolute;
710 }
711 _ = &AtomicFile;
712 _ = &Dir;
713 _ = &File;
714 _ = &path;
715 _ = @import("fs/test.zig");717 _ = @import("fs/test.zig");
716 _ = @import("fs/get_app_data_dir.zig");718 _ = @import("fs/get_app_data_dir.zig");
717}719}
lib/std/fs/Dir.zig-68
...@@ -2630,74 +2630,6 @@ pub const CopyFileOptions = struct {...@@ -2630,74 +2630,6 @@ pub const CopyFileOptions = struct {
2630 override_mode: ?File.Mode = null,2630 override_mode: ?File.Mode = null,
2631};2631};
26322632
2633pub const PrevStatus = enum {
2634 stale,
2635 fresh,
2636};
2637
2638/// Check the file size, mtime, and mode of `source_path` and `dest_path`. If they are equal, does nothing.
2639/// Otherwise, atomically copies `source_path` to `dest_path`. The destination file gains the mtime,
2640/// atime, and mode of the source file so that the next call to `updateFile` will not need a copy.
2641/// Returns the previous status of the file before updating.
2642/// If any of the directories do not exist for dest_path, they are created.
2643/// On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
2644/// On WASI, both paths should be encoded as valid UTF-8.
2645/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
2646pub fn updateFile(
2647 source_dir: Dir,
2648 source_path: []const u8,
2649 dest_dir: Dir,
2650 dest_path: []const u8,
2651 options: CopyFileOptions,
2652) !PrevStatus {
2653 var src_file = try source_dir.openFile(source_path, .{});
2654 defer src_file.close();
2655
2656 const src_stat = try src_file.stat();
2657 const actual_mode = options.override_mode orelse src_stat.mode;
2658 check_dest_stat: {
2659 const dest_stat = blk: {
2660 var dest_file = dest_dir.openFile(dest_path, .{}) catch |err| switch (err) {
2661 error.FileNotFound => break :check_dest_stat,
2662 else => |e| return e,
2663 };
2664 defer dest_file.close();
2665
2666 break :blk try dest_file.stat();
2667 };
2668
2669 if (src_stat.size == dest_stat.size and
2670 src_stat.mtime == dest_stat.mtime and
2671 actual_mode == dest_stat.mode)
2672 {
2673 return PrevStatus.fresh;
2674 }
2675 }
2676
2677 if (fs.path.dirname(dest_path)) |dirname| {
2678 try dest_dir.makePath(dirname);
2679 }
2680
2681 var buffer: [1000]u8 = undefined; // Used only when direct fd-to-fd is not available.
2682 var atomic_file = try dest_dir.atomicFile(dest_path, .{
2683 .mode = actual_mode,
2684 .write_buffer = &buffer,
2685 });
2686 defer atomic_file.deinit();
2687
2688 var src_reader: File.Reader = .initSize(src_file, &.{}, src_stat.size);
2689 const dest_writer = &atomic_file.file_writer.interface;
2690
2691 _ = dest_writer.sendFileAll(&src_reader, .unlimited) catch |err| switch (err) {
2692 error.ReadFailed => return src_reader.err.?,
2693 error.WriteFailed => return atomic_file.file_writer.err.?,
2694 };
2695 try atomic_file.flush();
2696 try atomic_file.file_writer.file.updateTimes(src_stat.atime, src_stat.mtime);
2697 try atomic_file.renameIntoPlace();
2698 return .stale;
2699}
2700
2701pub const CopyFileError = File.OpenError || File.StatError ||2633pub const CopyFileError = File.OpenError || File.StatError ||
2702 AtomicFile.InitError || AtomicFile.FinishError ||2634 AtomicFile.InitError || AtomicFile.FinishError ||
2703 File.ReadError || File.WriteError;2635 File.ReadError || File.WriteError;
lib/std/fs/File.zig+1-7
...@@ -1144,13 +1144,7 @@ pub const Reader = struct {...@@ -1144,13 +1144,7 @@ pub const Reader = struct {
1144 fn stream(io_reader: *std.Io.Reader, w: *std.Io.Writer, limit: std.Io.Limit) std.Io.Reader.StreamError!usize {1144 fn stream(io_reader: *std.Io.Reader, w: *std.Io.Writer, limit: std.Io.Limit) std.Io.Reader.StreamError!usize {
1145 const r: *Reader = @alignCast(@fieldParentPtr("interface", io_reader));1145 const r: *Reader = @alignCast(@fieldParentPtr("interface", io_reader));
1146 switch (r.mode) {1146 switch (r.mode) {
1147 .positional, .streaming => return w.sendFile(r, limit) catch |write_err| switch (write_err) {1147 .positional, .streaming => @panic("TODO"),
1148 error.Unimplemented => {
1149 r.mode = r.mode.toReading();
1150 return 0;
1151 },
1152 else => |e| return e,
1153 },
1154 .positional_reading => {1148 .positional_reading => {
1155 const dest = limit.slice(try w.writableSliceGreedy(1));1149 const dest = limit.slice(try w.writableSliceGreedy(1));
1156 var data: [1][]u8 = .{dest};1150 var data: [1][]u8 = .{dest};
lib/std/posix.zig+1-1
...@@ -939,7 +939,7 @@ pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize {...@@ -939,7 +939,7 @@ pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize {
939 }939 }
940}940}
941941
942pub const PReadError = std.Io.ReadPositionalError;942pub const PReadError = std.Io.File.ReadPositionalError;
943943
944/// Number of bytes read is returned. Upon reading end-of-file, zero is returned.944/// Number of bytes read is returned. Upon reading end-of-file, zero is returned.
945///945///