std.Io: implement dirMake

In the future, it might be nice to introduce a type for file system path
names. This would be a way to avoid having InvalidFileName in the error
set, since construction of such type could validate it above the
interface.
This commit is contained in:
Andrew Kelley 2025-10-08 22:26:18 -07:00
parent e85df854aa
commit eadfefa002
2 changed files with 44 additions and 10 deletions

View File

@ -155,8 +155,6 @@ pub const MakeError = error{
NoSpaceLeft,
NotDir,
ReadOnlyFileSystem,
/// WASI-only; file paths must be valid UTF-8.
InvalidUtf8,
/// Windows-only; file paths provided by the user must be valid WTF-8.
/// https://simonsapin.github.io/wtf-8/
InvalidWtf8,
@ -164,6 +162,8 @@ pub const MakeError = error{
NoDevice,
/// On Windows, `\\server` or `\\server\share` was not found.
NetworkNotFound,
/// File system cannot encode the requested file name bytes.
InvalidFileName,
} || Io.Cancelable || Io.UnexpectedError;
/// Creates a single directory with a relative or absolute path.

View File

@ -160,7 +160,11 @@ pub fn io(pool: *Pool) Io {
.conditionWait = conditionWait,
.conditionWake = conditionWake,
.dirMake = dirMake,
.dirMake = switch (builtin.os.tag) {
.windows => @panic("TODO"),
.wasi => @panic("TODO"),
else => dirMakePosix,
},
.dirStat = dirStat,
.dirStatPath = dirStatPath,
.fileStat = switch (builtin.os.tag) {
@ -759,14 +763,35 @@ fn conditionWake(userdata: ?*anyopaque, cond: *Io.Condition, wake: Io.Condition.
}
}
fn dirMake(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode: Io.Dir.Mode) Io.Dir.MakeError!void {
fn dirMakePosix(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode: Io.Dir.Mode) Io.Dir.MakeError!void {
const pool: *Pool = @ptrCast(@alignCast(userdata));
try pool.checkCancel();
_ = dir;
_ = sub_path;
_ = mode;
@panic("TODO");
var path_buffer: [posix.PATH_MAX]u8 = undefined;
const sub_path_posix = try toPosixPath(sub_path, &path_buffer);
while (true) {
try pool.checkCancel();
switch (posix.errno(posix.system.mkdirat(dir.handle, sub_path_posix, mode))) {
.SUCCESS => return,
.INTR => continue,
.ACCES => return error.AccessDenied,
.BADF => |err| return errnoBug(err),
.PERM => return error.PermissionDenied,
.DQUOT => return error.DiskQuota,
.EXIST => return error.PathAlreadyExists,
.FAULT => |err| return errnoBug(err),
.LOOP => return error.SymLinkLoop,
.MLINK => return error.LinkQuotaExceeded,
.NAMETOOLONG => return error.NameTooLong,
.NOENT => return error.FileNotFound,
.NOMEM => return error.SystemResources,
.NOSPC => return error.NoSpaceLeft,
.NOTDIR => return error.NotDir,
.ROFS => return error.ReadOnlyFileSystem,
// dragonfly: when dir_fd is unlinked from filesystem
.NOTCONN => return error.FileNotFound,
.ILSEQ => return error.InvalidFileName,
else => |err| return posix.unexpectedErrno(err),
}
}
}
fn dirStat(userdata: ?*anyopaque, dir: Io.Dir) Io.Dir.StatError!Io.Dir.Stat {
@ -2267,3 +2292,12 @@ fn timestampToPosix(nanoseconds: i96) std.posix.timespec {
.nsec = @intCast(@mod(nanoseconds, std.time.ns_per_s)),
};
}
fn toPosixPath(file_path: []const u8, buffer: *[posix.PATH_MAX]u8) error{ NameTooLong, InvalidFileName }![:0]u8 {
if (std.mem.containsAtLeastScalar2(u8, file_path, 0, 1)) return error.InvalidFileName;
// >= rather than > to make room for the null byte
if (file_path.len >= buffer.len) return error.NameTooLong;
@memcpy(buffer[0..file_path.len], file_path);
buffer[file_path.len] = 0;
return buffer[0..file_path.len :0];
}