std.Io: implement dirStatPath

This commit is contained in:
Andrew Kelley 2025-10-09 00:19:44 -07:00
parent 750b1431bf
commit 8a1e6c8c39
14 changed files with 406 additions and 291 deletions

View file

@ -1305,7 +1305,7 @@ fn hashFile(file: fs.File, bin_digest: *[Hasher.mac_length]u8) fs.File.PReadErro
} }
// Create/Write a file, close it, then grab its stat.mtime timestamp. // Create/Write a file, close it, then grab its stat.mtime timestamp.
fn testGetCurrentFileTimestamp(dir: fs.Dir) !i128 { fn testGetCurrentFileTimestamp(dir: fs.Dir) !Io.Timestamp {
const test_out_file = "test-filetimestamp.tmp"; const test_out_file = "test-filetimestamp.tmp";
var file = try dir.createFile(test_out_file, .{ var file = try dir.createFile(test_out_file, .{
@ -1333,8 +1333,8 @@ test "cache file and then recall it" {
// Wait for file timestamps to tick // Wait for file timestamps to tick
const initial_time = try testGetCurrentFileTimestamp(tmp.dir); const initial_time = try testGetCurrentFileTimestamp(tmp.dir);
while ((try testGetCurrentFileTimestamp(tmp.dir)) == initial_time) { while ((try testGetCurrentFileTimestamp(tmp.dir)).nanoseconds == initial_time.nanoseconds) {
try std.Io.Duration.sleep(.fromNanoseconds(1), io); try std.Io.Clock.Duration.sleep(.{ .clock = .boot, .raw = .fromNanoseconds(1) }, io);
} }
var digest1: HexDigest = undefined; var digest1: HexDigest = undefined;
@ -1399,8 +1399,8 @@ test "check that changing a file makes cache fail" {
// Wait for file timestamps to tick // Wait for file timestamps to tick
const initial_time = try testGetCurrentFileTimestamp(tmp.dir); const initial_time = try testGetCurrentFileTimestamp(tmp.dir);
while ((try testGetCurrentFileTimestamp(tmp.dir)) == initial_time) { while ((try testGetCurrentFileTimestamp(tmp.dir)).nanoseconds == initial_time.nanoseconds) {
try std.Io.Duration.sleep(.fromNanoseconds(1), io); try std.Io.Clock.Duration.sleep(.{ .clock = .boot, .raw = .fromNanoseconds(1) }, io);
} }
var digest1: HexDigest = undefined; var digest1: HexDigest = undefined;
@ -1517,8 +1517,8 @@ test "Manifest with files added after initial hash work" {
// Wait for file timestamps to tick // Wait for file timestamps to tick
const initial_time = try testGetCurrentFileTimestamp(tmp.dir); const initial_time = try testGetCurrentFileTimestamp(tmp.dir);
while ((try testGetCurrentFileTimestamp(tmp.dir)) == initial_time) { while ((try testGetCurrentFileTimestamp(tmp.dir)).nanoseconds == initial_time.nanoseconds) {
try std.Io.Duration.sleep(.fromNanoseconds(1), io); try std.Io.Clock.Duration.sleep(.{ .clock = .boot, .raw = .fromNanoseconds(1) }, io);
} }
var digest1: HexDigest = undefined; var digest1: HexDigest = undefined;
@ -1568,8 +1568,8 @@ test "Manifest with files added after initial hash work" {
// Wait for file timestamps to tick // Wait for file timestamps to tick
const initial_time2 = try testGetCurrentFileTimestamp(tmp.dir); const initial_time2 = try testGetCurrentFileTimestamp(tmp.dir);
while ((try testGetCurrentFileTimestamp(tmp.dir)) == initial_time2) { while ((try testGetCurrentFileTimestamp(tmp.dir)).nanoseconds == initial_time2.nanoseconds) {
try std.Io.Duration.sleep(.fromNanoseconds(1), io); try std.Io.Clock.Duration.sleep(.{ .clock = .boot, .raw = .fromNanoseconds(1) }, io);
} }
{ {

View file

@ -654,20 +654,20 @@ pub const VTable = struct {
conditionWait: *const fn (?*anyopaque, cond: *Condition, mutex: *Mutex) Cancelable!void, conditionWait: *const fn (?*anyopaque, cond: *Condition, mutex: *Mutex) Cancelable!void,
conditionWake: *const fn (?*anyopaque, cond: *Condition, wake: Condition.Wake) void, conditionWake: *const fn (?*anyopaque, cond: *Condition, wake: Condition.Wake) void,
dirMake: *const fn (?*anyopaque, dir: Dir, sub_path: []const u8, mode: Dir.Mode) Dir.MakeError!void, dirMake: *const fn (?*anyopaque, Dir, sub_path: []const u8, mode: Dir.Mode) Dir.MakeError!void,
dirStat: *const fn (?*anyopaque, dir: Dir) Dir.StatError!Dir.Stat, dirStat: *const fn (?*anyopaque, Dir) Dir.StatError!Dir.Stat,
dirStatPath: *const fn (?*anyopaque, dir: Dir, sub_path: []const u8) Dir.StatError!File.Stat, dirStatPath: *const fn (?*anyopaque, Dir, sub_path: []const u8, Dir.StatPathOptions) Dir.StatPathError!File.Stat,
fileStat: *const fn (?*anyopaque, file: File) File.StatError!File.Stat, fileStat: *const fn (?*anyopaque, File) File.StatError!File.Stat,
createFile: *const fn (?*anyopaque, dir: Dir, sub_path: []const u8, flags: File.CreateFlags) File.OpenError!File, createFile: *const fn (?*anyopaque, Dir, sub_path: []const u8, File.CreateFlags) File.OpenError!File,
fileOpen: *const fn (?*anyopaque, dir: Dir, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File, fileOpen: *const fn (?*anyopaque, Dir, sub_path: []const u8, File.OpenFlags) File.OpenError!File,
fileClose: *const fn (?*anyopaque, File) void, fileClose: *const fn (?*anyopaque, File) void,
pwrite: *const fn (?*anyopaque, file: File, buffer: []const u8, offset: std.posix.off_t) File.PWriteError!usize, pwrite: *const fn (?*anyopaque, File, buffer: []const u8, offset: std.posix.off_t) File.PWriteError!usize,
/// Returns 0 on end of stream. /// Returns 0 on end of stream.
fileReadStreaming: *const fn (?*anyopaque, file: File, data: [][]u8) File.ReadStreamingError!usize, fileReadStreaming: *const fn (?*anyopaque, File, data: [][]u8) File.ReadStreamingError!usize,
/// Returns 0 on end of stream. /// Returns 0 on end of stream.
fileReadPositional: *const fn (?*anyopaque, file: File, data: [][]u8, offset: u64) File.ReadPositionalError!usize, fileReadPositional: *const fn (?*anyopaque, File, data: [][]u8, offset: u64) File.ReadPositionalError!usize,
fileSeekBy: *const fn (?*anyopaque, file: File, offset: i64) File.SeekError!void, fileSeekBy: *const fn (?*anyopaque, File, offset: i64) File.SeekError!void,
fileSeekTo: *const fn (?*anyopaque, file: File, offset: u64) File.SeekError!void, fileSeekTo: *const fn (?*anyopaque, File, offset: u64) File.SeekError!void,
now: *const fn (?*anyopaque, Clock) Clock.Error!Timestamp, now: *const fn (?*anyopaque, Clock) Clock.Error!Timestamp,
sleep: *const fn (?*anyopaque, Timeout) SleepError!void, sleep: *const fn (?*anyopaque, Timeout) SleepError!void,
@ -795,6 +795,14 @@ pub const Clock = enum {
}; };
} }
pub fn subDuration(from: Clock.Timestamp, duration: Clock.Duration) Clock.Timestamp {
assert(from.clock == duration.clock);
return .{
.raw = from.raw.subDuration(duration.raw),
.clock = from.clock,
};
}
pub fn fromNow(io: Io, duration: Clock.Duration) Error!Clock.Timestamp { pub fn fromNow(io: Io, duration: Clock.Duration) Error!Clock.Timestamp {
return .{ return .{
.clock = duration.clock, .clock = duration.clock,
@ -855,6 +863,10 @@ pub const Timestamp = struct {
return .{ .nanoseconds = from.nanoseconds + duration.nanoseconds }; return .{ .nanoseconds = from.nanoseconds + duration.nanoseconds };
} }
pub fn subDuration(from: Timestamp, duration: Duration) Timestamp {
return .{ .nanoseconds = from.nanoseconds - duration.nanoseconds };
}
pub fn withClock(t: Timestamp, clock: Clock) Clock.Timestamp { pub fn withClock(t: Timestamp, clock: Clock) Clock.Timestamp {
return .{ .nanoseconds = t.nanoseconds, .clock = clock }; return .{ .nanoseconds = t.nanoseconds, .clock = clock };
} }

View file

@ -15,6 +15,29 @@ pub fn cwd() Dir {
pub const Handle = std.posix.fd_t; pub const Handle = std.posix.fd_t;
pub const PathNameError = error{
NameTooLong,
/// File system cannot encode the requested file name bytes.
/// Could be due to invalid WTF-8 on Windows, invalid UTF-8 on WASI,
/// invalid characters on Windows, etc. Filesystem and operating specific.
BadPathName,
};
pub const OpenError = error{
FileNotFound,
NotDir,
AccessDenied,
PermissionDenied,
SymLinkLoop,
ProcessFdQuotaExceeded,
SystemFdQuotaExceeded,
NoDevice,
SystemResources,
DeviceBusy,
/// On Windows, `\\server` or `\\server\share` was not found.
NetworkNotFound,
} || PathNameError || Io.Cancelable || Io.UnexpectedError;
pub fn openFile(dir: Dir, io: Io, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File { 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); return io.vtable.fileOpen(io.userdata, dir, sub_path, flags);
} }
@ -149,22 +172,15 @@ pub const MakeError = error{
PathAlreadyExists, PathAlreadyExists,
SymLinkLoop, SymLinkLoop,
LinkQuotaExceeded, LinkQuotaExceeded,
NameTooLong,
FileNotFound, FileNotFound,
SystemResources, SystemResources,
NoSpaceLeft, NoSpaceLeft,
NotDir, NotDir,
ReadOnlyFileSystem, ReadOnlyFileSystem,
/// Windows-only; file paths provided by the user must be valid WTF-8.
/// https://simonsapin.github.io/wtf-8/
InvalidWtf8,
BadPathName,
NoDevice, NoDevice,
/// On Windows, `\\server` or `\\server\share` was not found. /// On Windows, `\\server` or `\\server\share` was not found.
NetworkNotFound, NetworkNotFound,
/// File system cannot encode the requested file name bytes. } || PathNameError || Io.Cancelable || Io.UnexpectedError;
InvalidFileName,
} || Io.Cancelable || Io.UnexpectedError;
/// Creates a single directory with a relative or absolute path. /// Creates a single directory with a relative or absolute path.
/// ///
@ -225,7 +241,7 @@ pub fn makePathStatus(dir: Dir, io: Io, sub_path: []const u8) MakePathError!Make
// could cause an infinite loop // could cause an infinite loop
check_dir: { check_dir: {
// workaround for windows, see https://github.com/ziglang/zig/issues/16738 // workaround for windows, see https://github.com/ziglang/zig/issues/16738
const fstat = statPath(dir, io, component.path) catch |stat_err| switch (stat_err) { const fstat = statPath(dir, io, component.path, .{}) catch |stat_err| switch (stat_err) {
error.IsDir => break :check_dir, error.IsDir => break :check_dir,
else => |e| return e, else => |e| return e,
}; };
@ -251,6 +267,10 @@ pub fn stat(dir: Dir, io: Io) StatError!Stat {
pub const StatPathError = File.OpenError || File.StatError; pub const StatPathError = File.OpenError || File.StatError;
pub const StatPathOptions = struct {
follow_symlinks: bool = true,
};
/// Returns metadata for a file inside the directory. /// Returns metadata for a file inside the directory.
/// ///
/// On Windows, this requires three syscalls. On other operating systems, it /// On Windows, this requires three syscalls. On other operating systems, it
@ -263,6 +283,6 @@ pub const StatPathError = File.OpenError || File.StatError;
/// * On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). /// * 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 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. /// * On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
pub fn statPath(dir: Dir, io: Io, sub_path: []const u8) StatPathError!Stat { pub fn statPath(dir: Dir, io: Io, sub_path: []const u8, options: StatPathOptions) StatPathError!Stat {
return io.vtable.dirStatPath(io.userdata, dir, sub_path); return io.vtable.dirStatPath(io.userdata, dir, sub_path, options);
} }

View file

@ -81,7 +81,63 @@ pub fn stat(file: File, io: Io) StatError!Stat {
pub const OpenFlags = std.fs.File.OpenFlags; pub const OpenFlags = std.fs.File.OpenFlags;
pub const CreateFlags = std.fs.File.CreateFlags; pub const CreateFlags = std.fs.File.CreateFlags;
pub const OpenError = std.fs.File.OpenError || Io.Cancelable; pub const OpenError = error{
SharingViolation,
PipeBusy,
NoDevice,
/// On Windows, `\\server` or `\\server\share` was not found.
NetworkNotFound,
ProcessNotFound,
/// On Windows, antivirus software is enabled by default. It can be
/// disabled, but Windows Update sometimes ignores the user's preference
/// and re-enables it. When enabled, antivirus software on Windows
/// intercepts file system operations and makes them significantly slower
/// in addition to possibly failing with this error code.
AntivirusInterference,
/// In WASI, this error may occur when the file descriptor does
/// not hold the required rights to open a new resource relative to it.
AccessDenied,
PermissionDenied,
SymLinkLoop,
ProcessFdQuotaExceeded,
SystemFdQuotaExceeded,
/// Either:
/// * One of the path components does not exist.
/// * Cwd was used, but cwd has been deleted.
/// * The path associated with the open directory handle has been deleted.
/// * On macOS, multiple processes or threads raced to create the same file
/// with `O.EXCL` set to `false`.
FileNotFound,
/// The path exceeded `max_path_bytes` bytes.
/// Insufficient kernel memory was available, or
/// the named file is a FIFO and per-user hard limit on
/// memory allocation for pipes has been reached.
SystemResources,
/// The file is too large to be opened. This error is unreachable
/// for 64-bit targets, as well as when opening directories.
FileTooBig,
/// The path refers to directory but the `DIRECTORY` flag was not provided.
IsDir,
/// A new path cannot be created because the device has no room for the new file.
/// This error is only reachable when the `CREAT` flag is provided.
NoSpaceLeft,
/// A component used as a directory in the path was not, in fact, a directory, or
/// `DIRECTORY` was specified and the path was not a directory.
NotDir,
/// The path already exists and the `CREAT` and `EXCL` flags were provided.
PathAlreadyExists,
DeviceBusy,
FileLocksNotSupported,
/// One of these three things:
/// * pathname refers to an executable image which is currently being
/// executed and write access was requested.
/// * pathname refers to a file that is currently in use as a swap
/// file, and the O_TRUNC flag was specified.
/// * pathname refers to a file that is currently being read by the
/// kernel (e.g., for module/firmware loading), and write access was
/// requested.
FileBusy,
} || Io.Dir.PathNameError || Io.Cancelable || Io.UnexpectedError;
pub fn close(file: File, io: Io) void { pub fn close(file: File, io: Io) void {
return io.vtable.fileClose(io.userdata, file); return io.vtable.fileClose(io.userdata, file);

View file

@ -166,14 +166,23 @@ pub fn io(pool: *Pool) Io {
else => dirMakePosix, else => dirMakePosix,
}, },
.dirStat = dirStat, .dirStat = dirStat,
.dirStatPath = dirStatPath, .dirStatPath = switch (builtin.os.tag) {
.linux => dirStatPathLinux,
.windows => @panic("TODO"),
.wasi => @panic("TODO"),
else => dirStatPathPosix,
},
.fileStat = switch (builtin.os.tag) { .fileStat = switch (builtin.os.tag) {
.linux => fileStatLinux, .linux => fileStatLinux,
.windows => fileStatWindows, .windows => fileStatWindows,
.wasi => fileStatWasi, .wasi => fileStatWasi,
else => fileStatPosix, else => fileStatPosix,
}, },
.createFile = createFile, .createFile = switch (builtin.os.tag) {
.windows => @panic("TODO"),
.wasi => @panic("TODO"),
else => createFilePosix,
},
.fileOpen = fileOpen, .fileOpen = fileOpen,
.fileClose = fileClose, .fileClose = fileClose,
.pwrite = pwrite, .pwrite = pwrite,
@ -765,8 +774,10 @@ fn conditionWake(userdata: ?*anyopaque, cond: *Io.Condition, wake: Io.Condition.
fn dirMakePosix(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)); const pool: *Pool = @ptrCast(@alignCast(userdata));
var path_buffer: [posix.PATH_MAX]u8 = undefined; var path_buffer: [posix.PATH_MAX]u8 = undefined;
const sub_path_posix = try toPosixPath(sub_path, &path_buffer); const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
while (true) { while (true) {
try pool.checkCancel(); try pool.checkCancel();
switch (posix.errno(posix.system.mkdirat(dir.handle, sub_path_posix, mode))) { switch (posix.errno(posix.system.mkdirat(dir.handle, sub_path_posix, mode))) {
@ -788,7 +799,7 @@ fn dirMakePosix(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode:
.ROFS => return error.ReadOnlyFileSystem, .ROFS => return error.ReadOnlyFileSystem,
// dragonfly: when dir_fd is unlinked from filesystem // dragonfly: when dir_fd is unlinked from filesystem
.NOTCONN => return error.FileNotFound, .NOTCONN => return error.FileNotFound,
.ILSEQ => return error.InvalidFileName, .ILSEQ => return error.BadPathName,
else => |err| return posix.unexpectedErrno(err), else => |err| return posix.unexpectedErrno(err),
} }
} }
@ -802,13 +813,82 @@ fn dirStat(userdata: ?*anyopaque, dir: Io.Dir) Io.Dir.StatError!Io.Dir.Stat {
@panic("TODO"); @panic("TODO");
} }
fn dirStatPath(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8) Io.Dir.StatError!Io.File.Stat { fn dirStatPathLinux(
userdata: ?*anyopaque,
dir: Io.Dir,
sub_path: []const u8,
options: Io.Dir.StatPathOptions,
) Io.Dir.StatPathError!Io.File.Stat {
const pool: *Pool = @ptrCast(@alignCast(userdata)); const pool: *Pool = @ptrCast(@alignCast(userdata));
try pool.checkCancel(); const linux = std.os.linux;
_ = dir; var path_buffer: [posix.PATH_MAX]u8 = undefined;
_ = sub_path; const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
@panic("TODO");
const flags: u32 = linux.AT.NO_AUTOMOUNT |
@as(u32, if (!options.follow_symlinks) linux.AT.SYMLINK_NOFOLLOW else 0);
while (true) {
try pool.checkCancel();
var statx = std.mem.zeroes(linux.Statx);
const rc = linux.statx(
dir.handle,
sub_path_posix,
flags,
linux.STATX_TYPE | linux.STATX_MODE | linux.STATX_ATIME | linux.STATX_MTIME | linux.STATX_CTIME,
&statx,
);
switch (linux.E.init(rc)) {
.SUCCESS => return statFromLinux(&statx),
.INTR => continue,
.ACCES => return error.AccessDenied,
.BADF => |err| return errnoBug(err),
.FAULT => |err| return errnoBug(err),
.INVAL => |err| return errnoBug(err),
.LOOP => return error.SymLinkLoop,
.NAMETOOLONG => |err| return errnoBug(err), // Handled by pathToPosix() above.
.NOENT => return error.FileNotFound,
.NOTDIR => return error.NotDir,
.NOMEM => return error.SystemResources,
else => |err| return posix.unexpectedErrno(err),
}
}
}
fn dirStatPathPosix(
userdata: ?*anyopaque,
dir: Io.Dir,
sub_path: []const u8,
options: Io.Dir.StatPathOptions,
) Io.Dir.StatPathError!Io.File.Stat {
const pool: *Pool = @ptrCast(@alignCast(userdata));
var path_buffer: [posix.PATH_MAX]u8 = undefined;
const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
const flags: u32 = if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0;
const fstatat_sym = if (posix.lfs64_abi) posix.system.fstatat64 else posix.system.fstatat;
while (true) {
try pool.checkCancel();
var stat = std.mem.zeroes(posix.Stat);
switch (posix.errno(fstatat_sym(dir.handle, sub_path_posix, &stat, flags))) {
.SUCCESS => return statFromPosix(stat),
.INTR => continue,
.INVAL => |err| return errnoBug(err),
.BADF => |err| return errnoBug(err), // Always a race condition.
.NOMEM => return error.SystemResources,
.ACCES => return error.AccessDenied,
.PERM => return error.PermissionDenied,
.FAULT => |err| return errnoBug(err),
.NAMETOOLONG => return error.NameTooLong,
.LOOP => return error.SymLinkLoop,
.NOENT => return error.FileNotFound,
.NOTDIR => return error.FileNotFound,
.ILSEQ => return error.BadPathName,
else => |err| return posix.unexpectedErrno(err),
}
}
} }
fn fileStatPosix(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File.Stat { fn fileStatPosix(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File.Stat {
@ -885,17 +965,127 @@ fn fileStatWasi(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File.
} }
} }
fn createFile( const have_flock = @TypeOf(posix.system.flock) != void;
fn createFilePosix(
userdata: ?*anyopaque, userdata: ?*anyopaque,
dir: Io.Dir, dir: Io.Dir,
sub_path: []const u8, sub_path: []const u8,
flags: Io.File.CreateFlags, flags: Io.File.CreateFlags,
) Io.File.OpenError!Io.File { ) Io.File.OpenError!Io.File {
const pool: *Pool = @ptrCast(@alignCast(userdata)); const pool: *Pool = @ptrCast(@alignCast(userdata));
try pool.checkCancel();
const fs_dir: std.fs.Dir = .{ .fd = dir.handle }; var path_buffer: [posix.PATH_MAX]u8 = undefined;
const fs_file = try fs_dir.createFile(sub_path, flags); const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
return .{ .handle = fs_file.handle };
var os_flags: posix.O = .{
.ACCMODE = if (flags.read) .RDWR else .WRONLY,
.CREAT = true,
.TRUNC = flags.truncate,
.EXCL = flags.exclusive,
};
if (@hasField(posix.O, "LARGEFILE")) os_flags.LARGEFILE = true;
if (@hasField(posix.O, "CLOEXEC")) os_flags.CLOEXEC = true;
// Use the O locking flags if the os supports them to acquire the lock
// atomically. Note that the NONBLOCK flag is removed after the openat()
// call is successful.
const has_flock_open_flags = @hasField(posix.O, "EXLOCK");
if (has_flock_open_flags) switch (flags.lock) {
.none => {},
.shared => {
os_flags.SHLOCK = true;
os_flags.NONBLOCK = flags.lock_nonblocking;
},
.exclusive => {
os_flags.EXLOCK = true;
os_flags.NONBLOCK = flags.lock_nonblocking;
},
};
const openat_sym = if (posix.lfs64_abi) posix.system.openat64 else posix.system.openat;
const fd: posix.fd_t = while (true) {
try pool.checkCancel();
const rc = openat_sym(dir.handle, sub_path_posix, os_flags, flags.mode);
switch (posix.errno(rc)) {
.SUCCESS => break @intCast(rc),
.INTR => continue,
.FAULT => |err| return errnoBug(err),
.INVAL => return error.BadPathName,
.BADF => |err| return errnoBug(err),
.ACCES => return error.AccessDenied,
.FBIG => return error.FileTooBig,
.OVERFLOW => return error.FileTooBig,
.ISDIR => return error.IsDir,
.LOOP => return error.SymLinkLoop,
.MFILE => return error.ProcessFdQuotaExceeded,
.NAMETOOLONG => return error.NameTooLong,
.NFILE => return error.SystemFdQuotaExceeded,
.NODEV => return error.NoDevice,
.NOENT => return error.FileNotFound,
.SRCH => return error.ProcessNotFound,
.NOMEM => return error.SystemResources,
.NOSPC => return error.NoSpaceLeft,
.NOTDIR => return error.NotDir,
.PERM => return error.PermissionDenied,
.EXIST => return error.PathAlreadyExists,
.BUSY => return error.DeviceBusy,
.OPNOTSUPP => return error.FileLocksNotSupported,
//.AGAIN => return error.WouldBlock,
.TXTBSY => return error.FileBusy,
.NXIO => return error.NoDevice,
.ILSEQ => return error.BadPathName,
else => |err| return posix.unexpectedErrno(err),
}
};
errdefer posix.close(fd);
if (have_flock and !has_flock_open_flags and flags.lock != .none) {
const lock_nonblocking: i32 = if (flags.lock_nonblocking) posix.LOCK.NB else 0;
const lock_flags = switch (flags.lock) {
.none => unreachable,
.shared => posix.LOCK.SH | lock_nonblocking,
.exclusive => posix.LOCK.EX | lock_nonblocking,
};
while (true) {
try pool.checkCancel();
switch (posix.errno(posix.system.flock(fd, lock_flags))) {
.SUCCESS => break,
.INTR => continue,
.BADF => |err| return errnoBug(err),
.INVAL => |err| return errnoBug(err), // invalid parameters
.NOLCK => return error.SystemResources,
//.AGAIN => return error.WouldBlock,
.OPNOTSUPP => return error.FileLocksNotSupported,
else => |err| return posix.unexpectedErrno(err),
}
}
}
if (has_flock_open_flags and flags.lock_nonblocking) {
var fl_flags: usize = while (true) {
try pool.checkCancel();
switch (posix.errno(posix.system.fcntl(fd, posix.F.GETFL, 0))) {
.SUCCESS => break,
.INTR => continue,
else => |err| return posix.unexpectedErrno(err),
}
};
fl_flags &= ~@as(usize, 1 << @bitOffsetOf(posix.O, "NONBLOCK"));
while (true) {
try pool.checkCancel();
switch (posix.errno(posix.fcntl(fd, posix.F.SETFL, fl_flags))) {
.SUCCESS => break,
.INTR => continue,
else => |err| return posix.unexpectedErrno(err),
}
}
}
return .{ .handle = fd };
} }
fn fileOpen( fn fileOpen(
@ -2293,8 +2483,8 @@ fn timestampToPosix(nanoseconds: i96) std.posix.timespec {
}; };
} }
fn toPosixPath(file_path: []const u8, buffer: *[posix.PATH_MAX]u8) error{ NameTooLong, InvalidFileName }![:0]u8 { fn pathToPosix(file_path: []const u8, buffer: *[posix.PATH_MAX]u8) Io.Dir.PathNameError![:0]u8 {
if (std.mem.containsAtLeastScalar2(u8, file_path, 0, 1)) return error.InvalidFileName; if (std.mem.containsAtLeastScalar2(u8, file_path, 0, 1)) return error.BadPathName;
// >= rather than > to make room for the null byte // >= rather than > to make room for the null byte
if (file_path.len >= buffer.len) return error.NameTooLong; if (file_path.len >= buffer.len) return error.NameTooLong;
@memcpy(buffer[0..file_path.len], file_path); @memcpy(buffer[0..file_path.len], file_path);

View file

@ -11,6 +11,8 @@ const native_endian = @import("builtin").target.cpu.arch.endian();
const tmpDir = std.testing.tmpDir; const tmpDir = std.testing.tmpDir;
test "write a file, read it, then delete it" { test "write a file, read it, then delete it" {
const io = std.testing.io;
var tmp = tmpDir(.{}); var tmp = tmpDir(.{});
defer tmp.cleanup(); defer tmp.cleanup();
@ -45,7 +47,7 @@ test "write a file, read it, then delete it" {
try expectEqual(expected_file_size, file_size); try expectEqual(expected_file_size, file_size);
var file_buffer: [1024]u8 = undefined; var file_buffer: [1024]u8 = undefined;
var file_reader = file.reader(&file_buffer); var file_reader = file.reader(io, &file_buffer);
const contents = try file_reader.interface.allocRemaining(std.testing.allocator, .limited(2 * 1024)); const contents = try file_reader.interface.allocRemaining(std.testing.allocator, .limited(2 * 1024));
defer std.testing.allocator.free(contents); defer std.testing.allocator.free(contents);
@ -114,10 +116,10 @@ test "updateTimes" {
const stat_old = try file.stat(); const stat_old = try file.stat();
// Set atime and mtime to 5s before // Set atime and mtime to 5s before
try file.updateTimes( try file.updateTimes(
stat_old.atime - 5 * std.time.ns_per_s, stat_old.atime.subDuration(.fromSeconds(5)),
stat_old.mtime - 5 * std.time.ns_per_s, stat_old.mtime.subDuration(.fromSeconds(5)),
); );
const stat_new = try file.stat(); const stat_new = try file.stat();
try expect(stat_new.atime < stat_old.atime); try expect(stat_new.atime.nanoseconds < stat_old.atime.nanoseconds);
try expect(stat_new.mtime < stat_old.mtime); try expect(stat_new.mtime.nanoseconds < stat_old.mtime.nanoseconds);
} }

View file

@ -318,10 +318,13 @@ pub fn getName(self: Thread, buffer_ptr: *[max_name_len:0]u8) GetNameError!?[]co
var buf: [32]u8 = undefined; var buf: [32]u8 = undefined;
const path = try std.fmt.bufPrint(&buf, "/proc/self/task/{d}/comm", .{self.getHandle()}); const path = try std.fmt.bufPrint(&buf, "/proc/self/task/{d}/comm", .{self.getHandle()});
var threaded: std.Io.Threaded = .init_single_threaded;
const io = threaded.io();
const file = try std.fs.cwd().openFile(path, .{}); const file = try std.fs.cwd().openFile(path, .{});
defer file.close(); defer file.close();
var file_reader = file.readerStreaming(&.{}); var file_reader = file.readerStreaming(io, &.{});
const data_len = file_reader.interface.readSliceShort(buffer_ptr[0 .. max_name_len + 1]) catch |err| switch (err) { const data_len = file_reader.interface.readSliceShort(buffer_ptr[0 .. max_name_len + 1]) catch |err| switch (err) {
error.ReadFailed => return file_reader.err.?, error.ReadFailed => return file_reader.err.?,
}; };

View file

@ -123,14 +123,9 @@ const SingleThreadedImpl = struct {
fn wait(self: *Impl, mutex: *Mutex, timeout: ?u64) error{Timeout}!void { fn wait(self: *Impl, mutex: *Mutex, timeout: ?u64) error{Timeout}!void {
_ = self; _ = self;
_ = mutex; _ = mutex;
// There are no other threads to wake us up. // There are no other threads to wake us up.
// So if we wait without a timeout we would never wake up. // So if we wait without a timeout we would never wake up.
const timeout_ns = timeout orelse { assert(timeout != null); // Deadlock detected.
unreachable; // deadlock detected
};
std.Thread.sleep(timeout_ns);
return error.Timeout; return error.Timeout;
} }
@ -323,6 +318,8 @@ test "wait and signal" {
return error.SkipZigTest; return error.SkipZigTest;
} }
const io = testing.io;
const num_threads = 4; const num_threads = 4;
const MultiWait = struct { const MultiWait = struct {
@ -348,7 +345,7 @@ test "wait and signal" {
} }
while (true) { while (true) {
std.Thread.sleep(100 * std.time.ns_per_ms); try std.Io.Clock.Duration.sleep(.{ .clock = .awake, .raw = .fromMilliseconds(100) }, io);
multi_wait.mutex.lock(); multi_wait.mutex.lock();
defer multi_wait.mutex.unlock(); defer multi_wait.mutex.unlock();
@ -368,6 +365,8 @@ test signal {
return error.SkipZigTest; return error.SkipZigTest;
} }
const io = testing.io;
const num_threads = 4; const num_threads = 4;
const SignalTest = struct { const SignalTest = struct {
@ -405,7 +404,7 @@ test signal {
} }
while (true) { while (true) {
std.Thread.sleep(10 * std.time.ns_per_ms); try std.Io.Clock.Duration.sleep(.{ .clock = .awake, .raw = .fromMilliseconds(10) }, io);
signal_test.mutex.lock(); signal_test.mutex.lock();
defer signal_test.mutex.unlock(); defer signal_test.mutex.unlock();

View file

@ -441,7 +441,7 @@ pub fn resolveInPlace(base: Uri, new_len: usize, aux_buf: *[]u8) ResolveInPlaceE
}; };
} }
fn validateHost(bytes: []const u8) []const u8 { fn validateHost(bytes: []const u8) HostName.ValidateError![]const u8 {
try HostName.validate(bytes); try HostName.validate(bytes);
return bytes; return bytes;
} }

View file

@ -137,6 +137,7 @@ const ElfDynLibError = error{
ElfStringSectionNotFound, ElfStringSectionNotFound,
ElfSymSectionNotFound, ElfSymSectionNotFound,
ElfHashTableNotFound, ElfHashTableNotFound,
Canceled,
} || posix.OpenError || posix.MMapError; } || posix.OpenError || posix.MMapError;
pub const ElfDynLib = struct { pub const ElfDynLib = struct {

View file

@ -36,10 +36,6 @@ const IteratorError = error{
AccessDenied, AccessDenied,
PermissionDenied, PermissionDenied,
SystemResources, SystemResources,
/// WASI-only. The path of an entry could not be encoded as valid UTF-8.
/// WASI is unable to handle paths that cannot be encoded as well-formed UTF-8.
/// https://github.com/WebAssembly/wasi-filesystem/issues/17#issuecomment-1430639353
InvalidUtf8,
} || posix.UnexpectedError; } || posix.UnexpectedError;
pub const Iterator = switch (native_os) { pub const Iterator = switch (native_os) {
@ -553,7 +549,6 @@ pub const Iterator = switch (native_os) {
.INVAL => unreachable, .INVAL => unreachable,
.NOENT => return error.DirNotFound, // The directory being iterated was deleted during iteration. .NOENT => return error.DirNotFound, // The directory being iterated was deleted during iteration.
.NOTCAPABLE => return error.AccessDenied, .NOTCAPABLE => return error.AccessDenied,
.ILSEQ => return error.InvalidUtf8, // An entry's name cannot be encoded as UTF-8.
else => |err| return posix.unexpectedErrno(err), else => |err| return posix.unexpectedErrno(err),
} }
if (bufused == 0) return null; if (bufused == 0) return null;
@ -844,28 +839,7 @@ pub fn walk(self: Dir, allocator: Allocator) Allocator.Error!Walker {
}; };
} }
pub const OpenError = error{ pub const OpenError = Io.Dir.OpenError;
FileNotFound,
NotDir,
AccessDenied,
PermissionDenied,
SymLinkLoop,
ProcessFdQuotaExceeded,
NameTooLong,
SystemFdQuotaExceeded,
NoDevice,
SystemResources,
/// WASI-only; file paths must be valid UTF-8.
InvalidUtf8,
/// Windows-only; file paths provided by the user must be valid WTF-8.
/// https://wtf-8.codeberg.page/
InvalidWtf8,
BadPathName,
DeviceBusy,
/// On Windows, `\\server` or `\\server\share` was not found.
NetworkNotFound,
ProcessNotFound,
} || posix.UnexpectedError;
pub fn close(self: *Dir) void { pub fn close(self: *Dir) void {
posix.close(self.fd); posix.close(self.fd);
@ -1311,7 +1285,7 @@ pub fn makeOpenPath(self: Dir, sub_path: []const u8, open_dir_options: OpenOptio
}; };
} }
pub const RealPathError = posix.RealPathError; pub const RealPathError = posix.RealPathError || error{Canceled};
/// This function returns the canonicalized absolute pathname of /// This function returns the canonicalized absolute pathname of
/// `pathname` relative to this `Dir`. If `pathname` is absolute, ignores this /// `pathname` relative to this `Dir`. If `pathname` is absolute, ignores this
@ -1369,7 +1343,6 @@ pub fn realpathZ(self: Dir, pathname: [*:0]const u8, out_buffer: []u8) RealPathE
error.FileLocksNotSupported => return error.Unexpected, error.FileLocksNotSupported => return error.Unexpected,
error.FileBusy => return error.Unexpected, error.FileBusy => return error.Unexpected,
error.WouldBlock => return error.Unexpected, error.WouldBlock => return error.Unexpected,
error.InvalidUtf8 => unreachable, // WASI-only
else => |e| return e, else => |e| return e,
}; };
defer posix.close(fd); defer posix.close(fd);
@ -1639,6 +1612,10 @@ fn openDirFlagsZ(self: Dir, sub_path_c: [*:0]const u8, flags: posix.O) OpenError
error.FileLocksNotSupported => unreachable, // locking folders is not supported error.FileLocksNotSupported => unreachable, // locking folders is not supported
error.WouldBlock => unreachable, // can't happen for directories error.WouldBlock => unreachable, // can't happen for directories
error.FileBusy => unreachable, // can't happen for directories error.FileBusy => unreachable, // can't happen for directories
error.SharingViolation => unreachable, // can't happen for directories
error.PipeBusy => unreachable, // can't happen for directories
error.AntivirusInterference => unreachable, // can't happen for directories
error.ProcessNotFound => unreachable, // can't happen for directories
else => |e| return e, else => |e| return e,
}; };
return Dir{ .fd = fd }; return Dir{ .fd = fd };
@ -2095,24 +2072,21 @@ pub const DeleteTreeError = error{
FileBusy, FileBusy,
DeviceBusy, DeviceBusy,
ProcessNotFound, ProcessNotFound,
/// One of the path components was not a directory. /// One of the path components was not a directory.
/// This error is unreachable if `sub_path` does not contain a path separator. /// This error is unreachable if `sub_path` does not contain a path separator.
NotDir, NotDir,
/// WASI-only; file paths must be valid UTF-8. /// WASI-only; file paths must be valid UTF-8.
InvalidUtf8, InvalidUtf8,
/// Windows-only; file paths provided by the user must be valid WTF-8. /// Windows-only; file paths provided by the user must be valid WTF-8.
/// https://wtf-8.codeberg.page/ /// https://wtf-8.codeberg.page/
InvalidWtf8, InvalidWtf8,
/// On Windows, file paths cannot contain these characters: /// On Windows, file paths cannot contain these characters:
/// '/', '*', '?', '"', '<', '>', '|' /// '/', '*', '?', '"', '<', '>', '|'
BadPathName, BadPathName,
/// On Windows, `\\server` or `\\server\share` was not found. /// On Windows, `\\server` or `\\server\share` was not found.
NetworkNotFound, NetworkNotFound,
Canceled,
} || posix.UnexpectedError; } || posix.UnexpectedError;
/// Whether `sub_path` describes a symlink, file, or directory, this function /// Whether `sub_path` describes a symlink, file, or directory, this function
@ -2169,17 +2143,15 @@ pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void {
error.PermissionDenied, error.PermissionDenied,
error.SymLinkLoop, error.SymLinkLoop,
error.ProcessFdQuotaExceeded, error.ProcessFdQuotaExceeded,
error.ProcessNotFound,
error.NameTooLong, error.NameTooLong,
error.SystemFdQuotaExceeded, error.SystemFdQuotaExceeded,
error.NoDevice, error.NoDevice,
error.SystemResources, error.SystemResources,
error.Unexpected, error.Unexpected,
error.InvalidUtf8,
error.InvalidWtf8,
error.BadPathName, error.BadPathName,
error.NetworkNotFound, error.NetworkNotFound,
error.DeviceBusy, error.DeviceBusy,
error.Canceled,
=> |e| return e, => |e| return e,
}; };
stack.appendAssumeCapacity(.{ stack.appendAssumeCapacity(.{
@ -2266,18 +2238,16 @@ pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void {
error.AccessDenied, error.AccessDenied,
error.PermissionDenied, error.PermissionDenied,
error.SymLinkLoop, error.SymLinkLoop,
error.ProcessNotFound,
error.ProcessFdQuotaExceeded, error.ProcessFdQuotaExceeded,
error.NameTooLong, error.NameTooLong,
error.SystemFdQuotaExceeded, error.SystemFdQuotaExceeded,
error.NoDevice, error.NoDevice,
error.SystemResources, error.SystemResources,
error.Unexpected, error.Unexpected,
error.InvalidUtf8,
error.InvalidWtf8,
error.BadPathName, error.BadPathName,
error.NetworkNotFound, error.NetworkNotFound,
error.DeviceBusy, error.DeviceBusy,
error.Canceled,
=> |e| return e, => |e| return e,
}; };
} else { } else {
@ -2374,18 +2344,16 @@ fn deleteTreeMinStackSizeWithKindHint(self: Dir, sub_path: []const u8, kind_hint
error.AccessDenied, error.AccessDenied,
error.PermissionDenied, error.PermissionDenied,
error.SymLinkLoop, error.SymLinkLoop,
error.ProcessNotFound,
error.ProcessFdQuotaExceeded, error.ProcessFdQuotaExceeded,
error.NameTooLong, error.NameTooLong,
error.SystemFdQuotaExceeded, error.SystemFdQuotaExceeded,
error.NoDevice, error.NoDevice,
error.SystemResources, error.SystemResources,
error.Unexpected, error.Unexpected,
error.InvalidUtf8,
error.InvalidWtf8,
error.BadPathName, error.BadPathName,
error.NetworkNotFound, error.NetworkNotFound,
error.DeviceBusy, error.DeviceBusy,
error.Canceled,
=> |e| return e, => |e| return e,
}; };
if (cleanup_dir_parent) |*d| d.close(); if (cleanup_dir_parent) |*d| d.close();
@ -2476,17 +2444,15 @@ fn deleteTreeOpenInitialSubpath(self: Dir, sub_path: []const u8, kind_hint: File
error.PermissionDenied, error.PermissionDenied,
error.SymLinkLoop, error.SymLinkLoop,
error.ProcessFdQuotaExceeded, error.ProcessFdQuotaExceeded,
error.ProcessNotFound,
error.NameTooLong, error.NameTooLong,
error.SystemFdQuotaExceeded, error.SystemFdQuotaExceeded,
error.NoDevice, error.NoDevice,
error.SystemResources, error.SystemResources,
error.Unexpected, error.Unexpected,
error.InvalidUtf8,
error.InvalidWtf8,
error.BadPathName, error.BadPathName,
error.DeviceBusy, error.DeviceBusy,
error.NetworkNotFound, error.NetworkNotFound,
error.Canceled,
=> |e| return e, => |e| return e,
}; };
} else { } else {
@ -2589,7 +2555,7 @@ pub const CopyFileOptions = struct {
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 || error{InvalidFileName};
/// Atomically creates a new file at `dest_path` within `dest_dir` with the /// Atomically creates a new file at `dest_path` within `dest_dir` with the
/// same contents as `source_path` within `source_dir`, overwriting any already /// same contents as `source_path` within `source_dir`, overwriting any already
@ -2690,33 +2656,9 @@ pub fn statFile(self: Dir, sub_path: []const u8) StatFileError!Stat {
const st = try std.os.fstatat_wasi(self.fd, sub_path, .{ .SYMLINK_FOLLOW = true }); const st = try std.os.fstatat_wasi(self.fd, sub_path, .{ .SYMLINK_FOLLOW = true });
return Stat.fromWasi(st); return Stat.fromWasi(st);
} }
if (native_os == .linux) { var threaded: Io.Threaded = .init_single_threaded;
const sub_path_c = try posix.toPosixPath(sub_path); const io = threaded.io();
var stx = std.mem.zeroes(linux.Statx); return Io.Dir.statPath(.{ .handle = self.fd }, io, sub_path, .{});
const rc = linux.statx(
self.fd,
&sub_path_c,
linux.AT.NO_AUTOMOUNT,
linux.STATX_TYPE | linux.STATX_MODE | linux.STATX_ATIME | linux.STATX_MTIME | linux.STATX_CTIME,
&stx,
);
return switch (linux.E.init(rc)) {
.SUCCESS => Stat.fromLinux(stx),
.ACCES => error.AccessDenied,
.BADF => unreachable,
.FAULT => unreachable,
.INVAL => unreachable,
.LOOP => error.SymLinkLoop,
.NAMETOOLONG => unreachable, // Handled by posix.toPosixPath() above.
.NOENT, .NOTDIR => error.FileNotFound,
.NOMEM => error.SystemResources,
else => |err| posix.unexpectedErrno(err),
};
}
const st = try posix.fstatat(self.fd, sub_path, 0);
return Stat.fromPosix(st);
} }
pub const ChmodError = File.ChmodError; pub const ChmodError = File.ChmodError;

View file

@ -38,33 +38,8 @@ pub const default_mode = switch (builtin.os.tag) {
else => 0o666, else => 0o666,
}; };
pub const OpenError = error{ /// Deprecated in favor of `Io.File.OpenError`.
SharingViolation, pub const OpenError = Io.File.OpenError || error{WouldBlock};
PathAlreadyExists,
FileNotFound,
AccessDenied,
PipeBusy,
NoDevice,
NameTooLong,
/// WASI-only; file paths must be valid UTF-8.
InvalidUtf8,
/// Windows-only; file paths provided by the user must be valid WTF-8.
/// https://wtf-8.codeberg.page/
InvalidWtf8,
/// On Windows, file paths cannot contain these characters:
/// '/', '*', '?', '"', '<', '>', '|'
BadPathName,
Unexpected,
/// On Windows, `\\server` or `\\server\share` was not found.
NetworkNotFound,
ProcessNotFound,
/// On Windows, antivirus software is enabled by default. It can be
/// disabled, but Windows Update sometimes ignores the user's preference
/// and re-enables it. When enabled, antivirus software on Windows
/// intercepts file system operations and makes them significantly slower
/// in addition to possibly failing with this error code.
AntivirusInterference,
} || posix.OpenError || posix.FlockError;
pub const OpenMode = enum { pub const OpenMode = enum {
read_only, read_only,

View file

@ -1542,81 +1542,7 @@ pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) PWriteError!usiz
} }
} }
pub const OpenError = error{ pub const OpenError = std.Io.File.OpenError || error{WouldBlock};
/// In WASI, this error may occur when the file descriptor does
/// not hold the required rights to open a new resource relative to it.
AccessDenied,
PermissionDenied,
SymLinkLoop,
ProcessFdQuotaExceeded,
SystemFdQuotaExceeded,
NoDevice,
/// Either:
/// * One of the path components does not exist.
/// * Cwd was used, but cwd has been deleted.
/// * The path associated with the open directory handle has been deleted.
/// * On macOS, multiple processes or threads raced to create the same file
/// with `O.EXCL` set to `false`.
FileNotFound,
/// The path exceeded `max_path_bytes` bytes.
NameTooLong,
/// Insufficient kernel memory was available, or
/// the named file is a FIFO and per-user hard limit on
/// memory allocation for pipes has been reached.
SystemResources,
/// The file is too large to be opened. This error is unreachable
/// for 64-bit targets, as well as when opening directories.
FileTooBig,
/// The path refers to directory but the `DIRECTORY` flag was not provided.
IsDir,
/// A new path cannot be created because the device has no room for the new file.
/// This error is only reachable when the `CREAT` flag is provided.
NoSpaceLeft,
/// A component used as a directory in the path was not, in fact, a directory, or
/// `DIRECTORY` was specified and the path was not a directory.
NotDir,
/// The path already exists and the `CREAT` and `EXCL` flags were provided.
PathAlreadyExists,
DeviceBusy,
/// The underlying filesystem does not support file locks
FileLocksNotSupported,
/// Path contains characters that are disallowed by the underlying filesystem.
BadPathName,
/// WASI-only; file paths must be valid UTF-8.
InvalidUtf8,
/// Windows-only; file paths provided by the user must be valid WTF-8.
/// https://wtf-8.codeberg.page/
InvalidWtf8,
/// On Windows, `\\server` or `\\server\share` was not found.
NetworkNotFound,
/// This error occurs in Linux if the process to be open was not found.
ProcessNotFound,
/// One of these three things:
/// * pathname refers to an executable image which is currently being
/// executed and write access was requested.
/// * pathname refers to a file that is currently in use as a swap
/// file, and the O_TRUNC flag was specified.
/// * pathname refers to a file that is currently being read by the
/// kernel (e.g., for module/firmware loading), and write access was
/// requested.
FileBusy,
WouldBlock,
} || UnexpectedError;
/// Open and possibly create a file. Keeps trying if it gets interrupted. /// Open and possibly create a file. Keeps trying if it gets interrupted.
/// On Windows, `file_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On Windows, `file_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).

View file

@ -766,27 +766,23 @@ test glibcVerFromLinkName {
fn glibcVerFromRPath(io: Io, rpath: []const u8) !std.SemanticVersion { fn glibcVerFromRPath(io: Io, rpath: []const u8) !std.SemanticVersion {
var dir = fs.cwd().openDir(rpath, .{}) catch |err| switch (err) { var dir = fs.cwd().openDir(rpath, .{}) catch |err| switch (err) {
error.NameTooLong => unreachable, error.NameTooLong => return error.Unexpected,
error.InvalidUtf8 => unreachable, // WASI only error.BadPathName => return error.Unexpected,
error.InvalidWtf8 => unreachable, // Windows-only error.DeviceBusy => return error.Unexpected,
error.BadPathName => unreachable, error.NetworkNotFound => return error.Unexpected, // Windows-only
error.DeviceBusy => unreachable,
error.NetworkNotFound => unreachable, // Windows-only
error.FileNotFound, error.FileNotFound => return error.GLibCNotFound,
error.NotDir, error.NotDir => return error.GLibCNotFound,
error.AccessDenied, error.AccessDenied => return error.GLibCNotFound,
error.PermissionDenied, error.PermissionDenied => return error.GLibCNotFound,
error.NoDevice, error.NoDevice => return error.GLibCNotFound,
=> return error.GLibCNotFound,
error.ProcessNotFound, error.ProcessFdQuotaExceeded => |e| return e,
error.ProcessFdQuotaExceeded, error.SystemFdQuotaExceeded => |e| return e,
error.SystemFdQuotaExceeded, error.SystemResources => |e| return e,
error.SystemResources, error.SymLinkLoop => |e| return e,
error.SymLinkLoop, error.Unexpected => |e| return e,
error.Unexpected, error.Canceled => |e| return e,
=> |e| return e,
}; };
defer dir.close(); defer dir.close();
@ -799,38 +795,34 @@ fn glibcVerFromRPath(io: Io, rpath: []const u8) !std.SemanticVersion {
// that start with "GLIBC_2.". // that start with "GLIBC_2.".
const glibc_so_basename = "libc.so.6"; const glibc_so_basename = "libc.so.6";
var file = dir.openFile(glibc_so_basename, .{}) catch |err| switch (err) { var file = dir.openFile(glibc_so_basename, .{}) catch |err| switch (err) {
error.NameTooLong => unreachable, error.NameTooLong => return error.Unexpected,
error.InvalidUtf8 => unreachable, // WASI only error.BadPathName => return error.Unexpected,
error.InvalidWtf8 => unreachable, // Windows only error.PipeBusy => return error.Unexpected, // Windows-only
error.BadPathName => unreachable, // Windows only error.SharingViolation => return error.Unexpected, // Windows-only
error.PipeBusy => unreachable, // Windows-only error.NetworkNotFound => return error.Unexpected, // Windows-only
error.SharingViolation => unreachable, // Windows-only error.AntivirusInterference => return error.Unexpected, // Windows-only
error.NetworkNotFound => unreachable, // Windows-only error.FileLocksNotSupported => return error.Unexpected, // No lock requested.
error.AntivirusInterference => unreachable, // Windows-only error.NoSpaceLeft => return error.Unexpected, // read-only
error.FileLocksNotSupported => unreachable, // No lock requested. error.PathAlreadyExists => return error.Unexpected, // read-only
error.NoSpaceLeft => unreachable, // read-only error.DeviceBusy => return error.Unexpected, // read-only
error.PathAlreadyExists => unreachable, // read-only error.FileBusy => return error.Unexpected, // read-only
error.DeviceBusy => unreachable, // read-only error.NoDevice => return error.Unexpected, // not asking for a special device
error.FileBusy => unreachable, // read-only
error.WouldBlock => unreachable, // not using O_NONBLOCK
error.NoDevice => unreachable, // not asking for a special device
error.AccessDenied,
error.PermissionDenied,
error.FileNotFound,
error.NotDir,
error.IsDir,
=> return error.GLibCNotFound,
error.FileTooBig => return error.Unexpected, error.FileTooBig => return error.Unexpected,
error.WouldBlock => return error.Unexpected, // not opened in non-blocking
error.ProcessNotFound, error.AccessDenied => return error.GLibCNotFound,
error.ProcessFdQuotaExceeded, error.PermissionDenied => return error.GLibCNotFound,
error.SystemFdQuotaExceeded, error.FileNotFound => return error.GLibCNotFound,
error.SystemResources, error.NotDir => return error.GLibCNotFound,
error.SymLinkLoop, error.IsDir => return error.GLibCNotFound,
error.Unexpected,
=> |e| return e, error.ProcessNotFound => |e| return e,
error.ProcessFdQuotaExceeded => |e| return e,
error.SystemFdQuotaExceeded => |e| return e,
error.SystemResources => |e| return e,
error.SymLinkLoop => |e| return e,
error.Unexpected => |e| return e,
error.Canceled => |e| return e,
}; };
defer file.close(); defer file.close();
@ -1016,12 +1008,9 @@ fn detectAbiAndDynamicLinker(io: Io, cpu: Target.Cpu, os: Target.Os, query: Targ
error.NameTooLong => return error.Unexpected, error.NameTooLong => return error.Unexpected,
error.PathAlreadyExists => return error.Unexpected, error.PathAlreadyExists => return error.Unexpected,
error.SharingViolation => return error.Unexpected, error.SharingViolation => return error.Unexpected,
error.InvalidUtf8 => return error.Unexpected, // WASI only
error.InvalidWtf8 => return error.Unexpected, // Windows only
error.BadPathName => return error.Unexpected, error.BadPathName => return error.Unexpected,
error.PipeBusy => return error.Unexpected, error.PipeBusy => return error.Unexpected,
error.FileLocksNotSupported => return error.Unexpected, error.FileLocksNotSupported => return error.Unexpected,
error.WouldBlock => return error.Unexpected,
error.FileBusy => return error.Unexpected, // opened without write permissions error.FileBusy => return error.Unexpected, // opened without write permissions
error.AntivirusInterference => return error.Unexpected, // Windows-only error error.AntivirusInterference => return error.Unexpected, // Windows-only error