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-02 16:30:59-07:00
log43eea7beecaeb29d048cf971d6d08d0297109ce0
tree715928a1485823da385abd27cb374bf9b3d84414
parente34bb9a4131c2f0f328a53419d237877152917fa

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://simonsapin.github.io/wtf-8/).
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://simonsapin.github.io/wtf-8/).
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
...@@ -106,13 +106,15 @@ pub fn updateFileAbsolute(...@@ -106,13 +106,15 @@ pub fn updateFileAbsolute(
106 source_path: []const u8,106 source_path: []const u8,
107 dest_path: []const u8,107 dest_path: []const u8,
108 args: Dir.CopyFileOptions,108 args: Dir.CopyFileOptions,
109) !Dir.PrevStatus {109) !std.Io.Dir.PrevStatus {
110 assert(path.isAbsolute(source_path));110 assert(path.isAbsolute(source_path));
111 assert(path.isAbsolute(dest_path));111 assert(path.isAbsolute(dest_path));
112 const my_cwd = cwd();112 const my_cwd = cwd();
113 return Dir.updateFile(my_cwd, source_path, my_cwd, dest_path, args);113 return Dir.updateFile(my_cwd, source_path, my_cwd, dest_path, args);
114}114}
115115
116test updateFileAbsolute {}
117
116/// Same as `Dir.copyFile`, except asserts that both `source_path` and `dest_path`118/// Same as `Dir.copyFile`, except asserts that both `source_path` and `dest_path`
117/// are absolute. See `Dir.copyFile` for a function that operates on both119/// are absolute. See `Dir.copyFile` for a function that operates on both
118/// absolute and relative paths.120/// absolute and relative paths.
...@@ -130,6 +132,8 @@ pub fn copyFileAbsolute(...@@ -130,6 +132,8 @@ pub fn copyFileAbsolute(
130 return Dir.copyFile(my_cwd, source_path, my_cwd, dest_path, args);132 return Dir.copyFile(my_cwd, source_path, my_cwd, dest_path, args);
131}133}
132134
135test copyFileAbsolute {}
136
133/// Create a new directory, based on an absolute path.137/// Create a new directory, based on an absolute path.
134/// Asserts that the path is absolute. See `Dir.makeDir` for a function that operates138/// Asserts that the path is absolute. See `Dir.makeDir` for a function that operates
135/// on both absolute and relative paths.139/// on both absolute and relative paths.
...@@ -141,12 +145,16 @@ pub fn makeDirAbsolute(absolute_path: []const u8) !void {...@@ -141,12 +145,16 @@ pub fn makeDirAbsolute(absolute_path: []const u8) !void {
141 return posix.mkdir(absolute_path, Dir.default_mode);145 return posix.mkdir(absolute_path, Dir.default_mode);
142}146}
143147
148test makeDirAbsolute {}
149
144/// Same as `makeDirAbsolute` except the parameter is null-terminated.150/// Same as `makeDirAbsolute` except the parameter is null-terminated.
145pub fn makeDirAbsoluteZ(absolute_path_z: [*:0]const u8) !void {151pub fn makeDirAbsoluteZ(absolute_path_z: [*:0]const u8) !void {
146 assert(path.isAbsoluteZ(absolute_path_z));152 assert(path.isAbsoluteZ(absolute_path_z));
147 return posix.mkdirZ(absolute_path_z, Dir.default_mode);153 return posix.mkdirZ(absolute_path_z, Dir.default_mode);
148}154}
149155
156test makeDirAbsoluteZ {}
157
150/// Same as `makeDirAbsolute` except the parameter is a null-terminated WTF-16 LE-encoded string.158/// Same as `makeDirAbsolute` except the parameter is a null-terminated WTF-16 LE-encoded string.
151pub fn makeDirAbsoluteW(absolute_path_w: [*:0]const u16) !void {159pub fn makeDirAbsoluteW(absolute_path_w: [*:0]const u16) !void {
152 assert(path.isAbsoluteWindowsW(absolute_path_w));160 assert(path.isAbsoluteWindowsW(absolute_path_w));
...@@ -693,16 +701,10 @@ pub fn realpathAlloc(allocator: Allocator, pathname: []const u8) ![]u8 {...@@ -693,16 +701,10 @@ pub fn realpathAlloc(allocator: Allocator, pathname: []const u8) ![]u8 {
693}701}
694702
695test {703test {
696 if (native_os != .wasi) {704 _ = AtomicFile;
697 _ = &makeDirAbsolute;705 _ = Dir;
698 _ = &makeDirAbsoluteZ;706 _ = File;
699 _ = &copyFileAbsolute;707 _ = path;
700 _ = &updateFileAbsolute;
701 }
702 _ = &AtomicFile;
703 _ = &Dir;
704 _ = &File;
705 _ = &path;
706 _ = @import("fs/test.zig");708 _ = @import("fs/test.zig");
707 _ = @import("fs/get_app_data_dir.zig");709 _ = @import("fs/get_app_data_dir.zig");
708}710}
lib/std/fs/Dir.zig-68
...@@ -2539,74 +2539,6 @@ pub const CopyFileOptions = struct {...@@ -2539,74 +2539,6 @@ pub const CopyFileOptions = struct {
2539 override_mode: ?File.Mode = null,2539 override_mode: ?File.Mode = null,
2540};2540};
25412541
2542pub const PrevStatus = enum {
2543 stale,
2544 fresh,
2545};
2546
2547/// Check the file size, mtime, and mode of `source_path` and `dest_path`. If they are equal, does nothing.
2548/// Otherwise, atomically copies `source_path` to `dest_path`. The destination file gains the mtime,
2549/// atime, and mode of the source file so that the next call to `updateFile` will not need a copy.
2550/// Returns the previous status of the file before updating.
2551/// If any of the directories do not exist for dest_path, they are created.
2552/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
2553/// On WASI, both paths should be encoded as valid UTF-8.
2554/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
2555pub fn updateFile(
2556 source_dir: Dir,
2557 source_path: []const u8,
2558 dest_dir: Dir,
2559 dest_path: []const u8,
2560 options: CopyFileOptions,
2561) !PrevStatus {
2562 var src_file = try source_dir.openFile(source_path, .{});
2563 defer src_file.close();
2564
2565 const src_stat = try src_file.stat();
2566 const actual_mode = options.override_mode orelse src_stat.mode;
2567 check_dest_stat: {
2568 const dest_stat = blk: {
2569 var dest_file = dest_dir.openFile(dest_path, .{}) catch |err| switch (err) {
2570 error.FileNotFound => break :check_dest_stat,
2571 else => |e| return e,
2572 };
2573 defer dest_file.close();
2574
2575 break :blk try dest_file.stat();
2576 };
2577
2578 if (src_stat.size == dest_stat.size and
2579 src_stat.mtime == dest_stat.mtime and
2580 actual_mode == dest_stat.mode)
2581 {
2582 return PrevStatus.fresh;
2583 }
2584 }
2585
2586 if (fs.path.dirname(dest_path)) |dirname| {
2587 try dest_dir.makePath(dirname);
2588 }
2589
2590 var buffer: [1000]u8 = undefined; // Used only when direct fd-to-fd is not available.
2591 var atomic_file = try dest_dir.atomicFile(dest_path, .{
2592 .mode = actual_mode,
2593 .write_buffer = &buffer,
2594 });
2595 defer atomic_file.deinit();
2596
2597 var src_reader: File.Reader = .initSize(src_file, &.{}, src_stat.size);
2598 const dest_writer = &atomic_file.file_writer.interface;
2599
2600 _ = dest_writer.sendFileAll(&src_reader, .unlimited) catch |err| switch (err) {
2601 error.ReadFailed => return src_reader.err.?,
2602 error.WriteFailed => return atomic_file.file_writer.err.?,
2603 };
2604 try atomic_file.flush();
2605 try atomic_file.file_writer.file.updateTimes(src_stat.atime, src_stat.mtime);
2606 try atomic_file.renameIntoPlace();
2607 return .stale;
2608}
2609
2610pub const CopyFileError = File.OpenError || File.StatError ||2542pub const CopyFileError = File.OpenError || File.StatError ||
2611 AtomicFile.InitError || AtomicFile.FinishError ||2543 AtomicFile.InitError || AtomicFile.FinishError ||
2612 File.ReadError || File.WriteError;2544 File.ReadError || File.WriteError;
lib/std/fs/File.zig+1-7
...@@ -1143,13 +1143,7 @@ pub const Reader = struct {...@@ -1143,13 +1143,7 @@ pub const Reader = struct {
1143 fn stream(io_reader: *std.Io.Reader, w: *std.Io.Writer, limit: std.Io.Limit) std.Io.Reader.StreamError!usize {1143 fn stream(io_reader: *std.Io.Reader, w: *std.Io.Writer, limit: std.Io.Limit) std.Io.Reader.StreamError!usize {
1144 const r: *Reader = @alignCast(@fieldParentPtr("interface", io_reader));1144 const r: *Reader = @alignCast(@fieldParentPtr("interface", io_reader));
1145 switch (r.mode) {1145 switch (r.mode) {
1146 .positional, .streaming => return w.sendFile(r, limit) catch |write_err| switch (write_err) {1146 .positional, .streaming => @panic("TODO"),
1147 error.Unimplemented => {
1148 r.mode = r.mode.toReading();
1149 return 0;
1150 },
1151 else => |e| return e,
1152 },
1153 .positional_reading => {1147 .positional_reading => {
1154 const dest = limit.slice(try w.writableSliceGreedy(1));1148 const dest = limit.slice(try w.writableSliceGreedy(1));
1155 const n = try readPositional(r, dest);1149 const n = try readPositional(r, dest);
lib/std/posix.zig+1-1
...@@ -940,7 +940,7 @@ pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize {...@@ -940,7 +940,7 @@ pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize {
940 }940 }
941}941}
942942
943pub const PReadError = std.Io.ReadPositionalError;943pub const PReadError = std.Io.File.ReadPositionalError;
944944
945/// Number of bytes read is returned. Upon reading end-of-file, zero is returned.945/// Number of bytes read is returned. Upon reading end-of-file, zero is returned.
946///946///