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 {
548548}
549549
550550test {
551 _ = net;
551552 _ = Reader;
552553 _ = Writer;
553554 _ = tty;
......@@ -688,42 +689,7 @@ pub const UnexpectedError = error{
688689 Unexpected,
689690};
690691
691pub const Dir = struct {
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
692pub const Dir = @import("Io/Dir.zig");
727693pub const File = @import("Io/File.zig");
728694
729695pub 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 @@
1const File = @This();
2
13const builtin = @import("builtin");
4
25const std = @import("../std.zig");
36const Io = std.Io;
4const File = @This();
57const assert = std.debug.assert;
68
79handle: Handle,
lib/std/Io/net.zig+4
......@@ -593,3 +593,7 @@ pub const InterfaceIndexError = error{
593593pub fn interfaceIndex(io: Io, name: []const u8) InterfaceIndexError!u32 {
594594 return io.vtable.netInterfaceIndex(io.userdata, name);
595595}
596
597test {
598 _ = HostName;
599}
lib/std/Io/net/HostName.zig+25
......@@ -629,3 +629,28 @@ pub const ResolvConf = struct {
629629 @panic("TODO");
630630 }
631631};
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(
107107 source_path: []const u8,
108108 dest_path: []const u8,
109109 args: Dir.CopyFileOptions,
110) !Dir.PrevStatus {
110) !std.Io.Dir.PrevStatus {
111111 assert(path.isAbsolute(source_path));
112112 assert(path.isAbsolute(dest_path));
113113 const my_cwd = cwd();
114114 return Dir.updateFile(my_cwd, source_path, my_cwd, dest_path, args);
115115}
116116
117test updateFileAbsolute {}
118
117119/// Same as `Dir.copyFile`, except asserts that both `source_path` and `dest_path`
118120/// are absolute. See `Dir.copyFile` for a function that operates on both
119121/// absolute and relative paths.
......@@ -131,6 +133,8 @@ pub fn copyFileAbsolute(
131133 return Dir.copyFile(my_cwd, source_path, my_cwd, dest_path, args);
132134}
133135
136test copyFileAbsolute {}
137
134138/// Create a new directory, based on an absolute path.
135139/// Asserts that the path is absolute. See `Dir.makeDir` for a function that operates
136140/// on both absolute and relative paths.
......@@ -142,12 +146,16 @@ pub fn makeDirAbsolute(absolute_path: []const u8) !void {
142146 return posix.mkdir(absolute_path, Dir.default_mode);
143147}
144148
149test makeDirAbsolute {}
150
145151/// Same as `makeDirAbsolute` except the parameter is null-terminated.
146152pub fn makeDirAbsoluteZ(absolute_path_z: [*:0]const u8) !void {
147153 assert(path.isAbsoluteZ(absolute_path_z));
148154 return posix.mkdirZ(absolute_path_z, Dir.default_mode);
149155}
150156
157test makeDirAbsoluteZ {}
158
151159/// Same as `makeDirAbsolute` except the parameter is a null-terminated WTF-16 LE-encoded string.
152160pub fn makeDirAbsoluteW(absolute_path_w: [*:0]const u16) !void {
153161 assert(path.isAbsoluteWindowsW(absolute_path_w));
......@@ -702,16 +710,10 @@ pub fn realpathAlloc(allocator: Allocator, pathname: []const u8) ![]u8 {
702710}
703711
704712test {
705 if (native_os != .wasi) {
706 _ = &makeDirAbsolute;
707 _ = &makeDirAbsoluteZ;
708 _ = &copyFileAbsolute;
709 _ = &updateFileAbsolute;
710 }
711 _ = &AtomicFile;
712 _ = &Dir;
713 _ = &File;
714 _ = &path;
713 _ = AtomicFile;
714 _ = Dir;
715 _ = File;
716 _ = path;
715717 _ = @import("fs/test.zig");
716718 _ = @import("fs/get_app_data_dir.zig");
717719}
lib/std/fs/Dir.zig-68
......@@ -2630,74 +2630,6 @@ pub const CopyFileOptions = struct {
26302630 override_mode: ?File.Mode = null,
26312631};
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
27012633pub const CopyFileError = File.OpenError || File.StatError ||
27022634 AtomicFile.InitError || AtomicFile.FinishError ||
27032635 File.ReadError || File.WriteError;
lib/std/fs/File.zig+1-7
......@@ -1144,13 +1144,7 @@ pub const Reader = struct {
11441144 fn stream(io_reader: *std.Io.Reader, w: *std.Io.Writer, limit: std.Io.Limit) std.Io.Reader.StreamError!usize {
11451145 const r: *Reader = @alignCast(@fieldParentPtr("interface", io_reader));
11461146 switch (r.mode) {
1147 .positional, .streaming => return w.sendFile(r, limit) catch |write_err| switch (write_err) {
1148 error.Unimplemented => {
1149 r.mode = r.mode.toReading();
1150 return 0;
1151 },
1152 else => |e| return e,
1153 },
1147 .positional, .streaming => @panic("TODO"),
11541148 .positional_reading => {
11551149 const dest = limit.slice(try w.writableSliceGreedy(1));
11561150 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 {
939939 }
940940}
941941
942pub const PReadError = std.Io.ReadPositionalError;
942pub const PReadError = std.Io.File.ReadPositionalError;
943943
944944/// Number of bytes read is returned. Upon reading end-of-file, zero is returned.
945945///