std.Io: extract Dir to separate file

This commit is contained in:
Andrew Kelley 2025-09-10 22:34:19 -07:00
parent e34bb9a413
commit 43eea7beec
9 changed files with 162 additions and 124 deletions

View file

@ -548,6 +548,7 @@ pub fn PollFiles(comptime StreamEnum: type) type {
} }
test { test {
_ = net;
_ = Reader; _ = Reader;
_ = Writer; _ = Writer;
_ = tty; _ = tty;
@ -688,42 +689,7 @@ pub const UnexpectedError = error{
Unexpected, Unexpected,
}; };
pub const Dir = struct { pub const Dir = @import("Io/Dir.zig");
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 File = @import("Io/File.zig"); pub const File = @import("Io/File.zig");
pub const Timestamp = enum(i96) { pub const Timestamp = enum(i96) {

113
lib/std/Io/Dir.zig Normal file
View file

@ -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://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 || 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://simonsapin.github.io/wtf-8/).
/// * 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;
}

View file

@ -1,7 +1,9 @@
const File = @This();
const builtin = @import("builtin"); const builtin = @import("builtin");
const std = @import("../std.zig"); const std = @import("../std.zig");
const Io = std.Io; const Io = std.Io;
const File = @This();
const assert = std.debug.assert; const assert = std.debug.assert;
handle: Handle, handle: Handle,

View file

@ -593,3 +593,7 @@ pub const InterfaceIndexError = error{
pub fn interfaceIndex(io: Io, name: []const u8) InterfaceIndexError!u32 { pub fn interfaceIndex(io: Io, name: []const u8) InterfaceIndexError!u32 {
return io.vtable.netInterfaceIndex(io.userdata, name); return io.vtable.netInterfaceIndex(io.userdata, name);
} }
test {
_ = HostName;
}

View file

@ -629,3 +629,28 @@ pub const ResolvConf = struct {
@panic("TODO"); @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);
}

View file

@ -106,13 +106,15 @@ pub fn updateFileAbsolute(
source_path: []const u8, source_path: []const u8,
dest_path: []const u8, dest_path: []const u8,
args: Dir.CopyFileOptions, args: Dir.CopyFileOptions,
) !Dir.PrevStatus { ) !std.Io.Dir.PrevStatus {
assert(path.isAbsolute(source_path)); assert(path.isAbsolute(source_path));
assert(path.isAbsolute(dest_path)); assert(path.isAbsolute(dest_path));
const my_cwd = cwd(); const my_cwd = cwd();
return Dir.updateFile(my_cwd, source_path, my_cwd, dest_path, args); 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` /// 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 /// are absolute. See `Dir.copyFile` for a function that operates on both
/// absolute and relative paths. /// absolute and relative paths.
@ -130,6 +132,8 @@ pub fn copyFileAbsolute(
return Dir.copyFile(my_cwd, source_path, my_cwd, dest_path, args); return Dir.copyFile(my_cwd, source_path, my_cwd, dest_path, args);
} }
test copyFileAbsolute {}
/// Create a new directory, based on an absolute path. /// Create a new directory, based on an absolute path.
/// Asserts that the path is absolute. See `Dir.makeDir` for a function that operates /// Asserts that the path is absolute. See `Dir.makeDir` for a function that operates
/// on both absolute and relative paths. /// on both absolute and relative paths.
@ -141,12 +145,16 @@ pub fn makeDirAbsolute(absolute_path: []const u8) !void {
return posix.mkdir(absolute_path, Dir.default_mode); return posix.mkdir(absolute_path, Dir.default_mode);
} }
test makeDirAbsolute {}
/// Same as `makeDirAbsolute` except the parameter is null-terminated. /// Same as `makeDirAbsolute` except the parameter is null-terminated.
pub fn makeDirAbsoluteZ(absolute_path_z: [*:0]const u8) !void { pub fn makeDirAbsoluteZ(absolute_path_z: [*:0]const u8) !void {
assert(path.isAbsoluteZ(absolute_path_z)); assert(path.isAbsoluteZ(absolute_path_z));
return posix.mkdirZ(absolute_path_z, Dir.default_mode); 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. /// 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 { pub fn makeDirAbsoluteW(absolute_path_w: [*:0]const u16) !void {
assert(path.isAbsoluteWindowsW(absolute_path_w)); assert(path.isAbsoluteWindowsW(absolute_path_w));
@ -693,16 +701,10 @@ pub fn realpathAlloc(allocator: Allocator, pathname: []const u8) ![]u8 {
} }
test { test {
if (native_os != .wasi) { _ = AtomicFile;
_ = &makeDirAbsolute; _ = Dir;
_ = &makeDirAbsoluteZ; _ = File;
_ = &copyFileAbsolute; _ = path;
_ = &updateFileAbsolute;
}
_ = &AtomicFile;
_ = &Dir;
_ = &File;
_ = &path;
_ = @import("fs/test.zig"); _ = @import("fs/test.zig");
_ = @import("fs/get_app_data_dir.zig"); _ = @import("fs/get_app_data_dir.zig");
} }

View file

@ -2539,74 +2539,6 @@ pub const CopyFileOptions = struct {
override_mode: ?File.Mode = null, 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://simonsapin.github.io/wtf-8/).
/// 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 || pub const CopyFileError = File.OpenError || File.StatError ||
AtomicFile.InitError || AtomicFile.FinishError || AtomicFile.InitError || AtomicFile.FinishError ||
File.ReadError || File.WriteError; File.ReadError || File.WriteError;

View file

@ -1143,13 +1143,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 { 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)); const r: *Reader = @alignCast(@fieldParentPtr("interface", io_reader));
switch (r.mode) { switch (r.mode) {
.positional, .streaming => return w.sendFile(r, limit) catch |write_err| switch (write_err) { .positional, .streaming => @panic("TODO"),
error.Unimplemented => {
r.mode = r.mode.toReading();
return 0;
},
else => |e| return e,
},
.positional_reading => { .positional_reading => {
const dest = limit.slice(try w.writableSliceGreedy(1)); const dest = limit.slice(try w.writableSliceGreedy(1));
const n = try readPositional(r, dest); const n = try readPositional(r, dest);

View file

@ -940,7 +940,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. /// Number of bytes read is returned. Upon reading end-of-file, zero is returned.
/// ///