Commit 8a1e6c8c39
Changed files (14)
lib/std/Build/Cache.zig
@@ -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.
-fn testGetCurrentFileTimestamp(dir: fs.Dir) !i128 {
+fn testGetCurrentFileTimestamp(dir: fs.Dir) !Io.Timestamp {
const test_out_file = "test-filetimestamp.tmp";
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
const initial_time = try testGetCurrentFileTimestamp(tmp.dir);
- while ((try testGetCurrentFileTimestamp(tmp.dir)) == initial_time) {
- try std.Io.Duration.sleep(.fromNanoseconds(1), io);
+ while ((try testGetCurrentFileTimestamp(tmp.dir)).nanoseconds == initial_time.nanoseconds) {
+ try std.Io.Clock.Duration.sleep(.{ .clock = .boot, .raw = .fromNanoseconds(1) }, io);
}
var digest1: HexDigest = undefined;
@@ -1399,8 +1399,8 @@ test "check that changing a file makes cache fail" {
// Wait for file timestamps to tick
const initial_time = try testGetCurrentFileTimestamp(tmp.dir);
- while ((try testGetCurrentFileTimestamp(tmp.dir)) == initial_time) {
- try std.Io.Duration.sleep(.fromNanoseconds(1), io);
+ while ((try testGetCurrentFileTimestamp(tmp.dir)).nanoseconds == initial_time.nanoseconds) {
+ try std.Io.Clock.Duration.sleep(.{ .clock = .boot, .raw = .fromNanoseconds(1) }, io);
}
var digest1: HexDigest = undefined;
@@ -1517,8 +1517,8 @@ test "Manifest with files added after initial hash work" {
// Wait for file timestamps to tick
const initial_time = try testGetCurrentFileTimestamp(tmp.dir);
- while ((try testGetCurrentFileTimestamp(tmp.dir)) == initial_time) {
- try std.Io.Duration.sleep(.fromNanoseconds(1), io);
+ while ((try testGetCurrentFileTimestamp(tmp.dir)).nanoseconds == initial_time.nanoseconds) {
+ try std.Io.Clock.Duration.sleep(.{ .clock = .boot, .raw = .fromNanoseconds(1) }, io);
}
var digest1: HexDigest = undefined;
@@ -1568,8 +1568,8 @@ test "Manifest with files added after initial hash work" {
// Wait for file timestamps to tick
const initial_time2 = try testGetCurrentFileTimestamp(tmp.dir);
- while ((try testGetCurrentFileTimestamp(tmp.dir)) == initial_time2) {
- try std.Io.Duration.sleep(.fromNanoseconds(1), io);
+ while ((try testGetCurrentFileTimestamp(tmp.dir)).nanoseconds == initial_time2.nanoseconds) {
+ try std.Io.Clock.Duration.sleep(.{ .clock = .boot, .raw = .fromNanoseconds(1) }, io);
}
{
lib/std/fs/Dir.zig
@@ -36,10 +36,6 @@ const IteratorError = error{
AccessDenied,
PermissionDenied,
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;
pub const Iterator = switch (native_os) {
@@ -553,7 +549,6 @@ pub const Iterator = switch (native_os) {
.INVAL => unreachable,
.NOENT => return error.DirNotFound, // The directory being iterated was deleted during iteration.
.NOTCAPABLE => return error.AccessDenied,
- .ILSEQ => return error.InvalidUtf8, // An entry's name cannot be encoded as UTF-8.
else => |err| return posix.unexpectedErrno(err),
}
if (bufused == 0) return null;
@@ -844,28 +839,7 @@ pub fn walk(self: Dir, allocator: Allocator) Allocator.Error!Walker {
};
}
-pub const OpenError = error{
- 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 const OpenError = Io.Dir.OpenError;
pub fn close(self: *Dir) void {
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
/// `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.FileBusy => return error.Unexpected,
error.WouldBlock => return error.Unexpected,
- error.InvalidUtf8 => unreachable, // WASI-only
else => |e| return e,
};
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.WouldBlock => 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,
};
return Dir{ .fd = fd };
@@ -2095,24 +2072,21 @@ pub const DeleteTreeError = error{
FileBusy,
DeviceBusy,
ProcessNotFound,
-
/// One of the path components was not a directory.
/// This error is unreachable if `sub_path` does not contain a path separator.
NotDir,
-
/// 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,
-
/// On Windows, `\\server` or `\\server\share` was not found.
NetworkNotFound,
+
+ Canceled,
} || posix.UnexpectedError;
/// 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.SymLinkLoop,
error.ProcessFdQuotaExceeded,
- error.ProcessNotFound,
error.NameTooLong,
error.SystemFdQuotaExceeded,
error.NoDevice,
error.SystemResources,
error.Unexpected,
- error.InvalidUtf8,
- error.InvalidWtf8,
error.BadPathName,
error.NetworkNotFound,
error.DeviceBusy,
+ error.Canceled,
=> |e| return e,
};
stack.appendAssumeCapacity(.{
@@ -2266,18 +2238,16 @@ pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void {
error.AccessDenied,
error.PermissionDenied,
error.SymLinkLoop,
- error.ProcessNotFound,
error.ProcessFdQuotaExceeded,
error.NameTooLong,
error.SystemFdQuotaExceeded,
error.NoDevice,
error.SystemResources,
error.Unexpected,
- error.InvalidUtf8,
- error.InvalidWtf8,
error.BadPathName,
error.NetworkNotFound,
error.DeviceBusy,
+ error.Canceled,
=> |e| return e,
};
} else {
@@ -2374,18 +2344,16 @@ fn deleteTreeMinStackSizeWithKindHint(self: Dir, sub_path: []const u8, kind_hint
error.AccessDenied,
error.PermissionDenied,
error.SymLinkLoop,
- error.ProcessNotFound,
error.ProcessFdQuotaExceeded,
error.NameTooLong,
error.SystemFdQuotaExceeded,
error.NoDevice,
error.SystemResources,
error.Unexpected,
- error.InvalidUtf8,
- error.InvalidWtf8,
error.BadPathName,
error.NetworkNotFound,
error.DeviceBusy,
+ error.Canceled,
=> |e| return e,
};
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.SymLinkLoop,
error.ProcessFdQuotaExceeded,
- error.ProcessNotFound,
error.NameTooLong,
error.SystemFdQuotaExceeded,
error.NoDevice,
error.SystemResources,
error.Unexpected,
- error.InvalidUtf8,
- error.InvalidWtf8,
error.BadPathName,
error.DeviceBusy,
error.NetworkNotFound,
+ error.Canceled,
=> |e| return e,
};
} else {
@@ -2589,7 +2555,7 @@ pub const CopyFileOptions = struct {
pub const CopyFileError = File.OpenError || File.StatError ||
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
/// 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 });
return Stat.fromWasi(st);
}
- if (native_os == .linux) {
- const sub_path_c = try posix.toPosixPath(sub_path);
- var stx = std.mem.zeroes(linux.Statx);
-
- 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);
+ var threaded: Io.Threaded = .init_single_threaded;
+ const io = threaded.io();
+ return Io.Dir.statPath(.{ .handle = self.fd }, io, sub_path, .{});
}
pub const ChmodError = File.ChmodError;
lib/std/fs/File.zig
@@ -38,33 +38,8 @@ pub const default_mode = switch (builtin.os.tag) {
else => 0o666,
};
-pub const OpenError = error{
- SharingViolation,
- 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;
+/// Deprecated in favor of `Io.File.OpenError`.
+pub const OpenError = Io.File.OpenError || error{WouldBlock};
pub const OpenMode = enum {
read_only,
lib/std/Io/Dir.zig
@@ -15,6 +15,29 @@ pub fn cwd() Dir {
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 {
return io.vtable.fileOpen(io.userdata, dir, sub_path, flags);
}
@@ -149,22 +172,15 @@ pub const MakeError = error{
PathAlreadyExists,
SymLinkLoop,
LinkQuotaExceeded,
- NameTooLong,
FileNotFound,
SystemResources,
NoSpaceLeft,
NotDir,
ReadOnlyFileSystem,
- /// Windows-only; file paths provided by the user must be valid WTF-8.
- /// https://simonsapin.github.io/wtf-8/
- InvalidWtf8,
- BadPathName,
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;
+} || PathNameError || Io.Cancelable || Io.UnexpectedError;
/// 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
check_dir: {
// 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,
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 StatPathOptions = struct {
+ follow_symlinks: bool = true,
+};
+
/// Returns metadata for a file inside the directory.
///
/// 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 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.
-pub fn statPath(dir: Dir, io: Io, sub_path: []const u8) StatPathError!Stat {
- return io.vtable.dirStatPath(io.userdata, dir, sub_path);
+pub fn statPath(dir: Dir, io: Io, sub_path: []const u8, options: StatPathOptions) StatPathError!Stat {
+ return io.vtable.dirStatPath(io.userdata, dir, sub_path, options);
}
lib/std/Io/File.zig
@@ -81,7 +81,63 @@ pub fn stat(file: File, io: Io) StatError!Stat {
pub const OpenFlags = std.fs.File.OpenFlags;
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 {
return io.vtable.fileClose(io.userdata, file);
lib/std/Io/test.zig
@@ -11,6 +11,8 @@ const native_endian = @import("builtin").target.cpu.arch.endian();
const tmpDir = std.testing.tmpDir;
test "write a file, read it, then delete it" {
+ const io = std.testing.io;
+
var tmp = tmpDir(.{});
defer tmp.cleanup();
@@ -45,7 +47,7 @@ test "write a file, read it, then delete it" {
try expectEqual(expected_file_size, file_size);
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));
defer std.testing.allocator.free(contents);
@@ -114,10 +116,10 @@ test "updateTimes" {
const stat_old = try file.stat();
// Set atime and mtime to 5s before
try file.updateTimes(
- stat_old.atime - 5 * std.time.ns_per_s,
- stat_old.mtime - 5 * std.time.ns_per_s,
+ stat_old.atime.subDuration(.fromSeconds(5)),
+ stat_old.mtime.subDuration(.fromSeconds(5)),
);
const stat_new = try file.stat();
- try expect(stat_new.atime < stat_old.atime);
- try expect(stat_new.mtime < stat_old.mtime);
+ try expect(stat_new.atime.nanoseconds < stat_old.atime.nanoseconds);
+ try expect(stat_new.mtime.nanoseconds < stat_old.mtime.nanoseconds);
}
lib/std/Io/Threaded.zig
@@ -166,14 +166,23 @@ pub fn io(pool: *Pool) Io {
else => dirMakePosix,
},
.dirStat = dirStat,
- .dirStatPath = dirStatPath,
+ .dirStatPath = switch (builtin.os.tag) {
+ .linux => dirStatPathLinux,
+ .windows => @panic("TODO"),
+ .wasi => @panic("TODO"),
+ else => dirStatPathPosix,
+ },
.fileStat = switch (builtin.os.tag) {
.linux => fileStatLinux,
.windows => fileStatWindows,
.wasi => fileStatWasi,
else => fileStatPosix,
},
- .createFile = createFile,
+ .createFile = switch (builtin.os.tag) {
+ .windows => @panic("TODO"),
+ .wasi => @panic("TODO"),
+ else => createFilePosix,
+ },
.fileOpen = fileOpen,
.fileClose = fileClose,
.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 {
const pool: *Pool = @ptrCast(@alignCast(userdata));
+
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) {
try pool.checkCancel();
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,
// dragonfly: when dir_fd is unlinked from filesystem
.NOTCONN => return error.FileNotFound,
- .ILSEQ => return error.InvalidFileName,
+ .ILSEQ => return error.BadPathName,
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");
}
-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));
- try pool.checkCancel();
+ const linux = std.os.linux;
- _ = dir;
- _ = sub_path;
- @panic("TODO");
+ var path_buffer: [posix.PATH_MAX]u8 = undefined;
+ const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
+
+ 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 {
@@ -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,
dir: Io.Dir,
sub_path: []const u8,
flags: Io.File.CreateFlags,
) Io.File.OpenError!Io.File {
const pool: *Pool = @ptrCast(@alignCast(userdata));
- try pool.checkCancel();
- const fs_dir: std.fs.Dir = .{ .fd = dir.handle };
- const fs_file = try fs_dir.createFile(sub_path, flags);
- return .{ .handle = fs_file.handle };
+
+ var path_buffer: [posix.PATH_MAX]u8 = undefined;
+ const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
+
+ 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(
@@ -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 {
- if (std.mem.containsAtLeastScalar2(u8, file_path, 0, 1)) return error.InvalidFileName;
+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.BadPathName;
// >= 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);
lib/std/Thread/Condition.zig
@@ -123,14 +123,9 @@ const SingleThreadedImpl = struct {
fn wait(self: *Impl, mutex: *Mutex, timeout: ?u64) error{Timeout}!void {
_ = self;
_ = mutex;
-
// There are no other threads to wake us up.
// So if we wait without a timeout we would never wake up.
- const timeout_ns = timeout orelse {
- unreachable; // deadlock detected
- };
-
- std.Thread.sleep(timeout_ns);
+ assert(timeout != null); // Deadlock detected.
return error.Timeout;
}
@@ -323,6 +318,8 @@ test "wait and signal" {
return error.SkipZigTest;
}
+ const io = testing.io;
+
const num_threads = 4;
const MultiWait = struct {
@@ -348,7 +345,7 @@ test "wait and signal" {
}
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();
defer multi_wait.mutex.unlock();
@@ -368,6 +365,8 @@ test signal {
return error.SkipZigTest;
}
+ const io = testing.io;
+
const num_threads = 4;
const SignalTest = struct {
@@ -405,7 +404,7 @@ test signal {
}
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();
defer signal_test.mutex.unlock();
lib/std/zig/system.zig
@@ -766,27 +766,23 @@ test glibcVerFromLinkName {
fn glibcVerFromRPath(io: Io, rpath: []const u8) !std.SemanticVersion {
var dir = fs.cwd().openDir(rpath, .{}) catch |err| switch (err) {
- error.NameTooLong => unreachable,
- error.InvalidUtf8 => unreachable, // WASI only
- error.InvalidWtf8 => unreachable, // Windows-only
- error.BadPathName => unreachable,
- error.DeviceBusy => unreachable,
- error.NetworkNotFound => unreachable, // Windows-only
-
- error.FileNotFound,
- error.NotDir,
- error.AccessDenied,
- error.PermissionDenied,
- error.NoDevice,
- => return error.GLibCNotFound,
-
- error.ProcessNotFound,
- error.ProcessFdQuotaExceeded,
- error.SystemFdQuotaExceeded,
- error.SystemResources,
- error.SymLinkLoop,
- error.Unexpected,
- => |e| return e,
+ error.NameTooLong => return error.Unexpected,
+ error.BadPathName => return error.Unexpected,
+ error.DeviceBusy => return error.Unexpected,
+ error.NetworkNotFound => return error.Unexpected, // Windows-only
+
+ error.FileNotFound => return error.GLibCNotFound,
+ error.NotDir => return error.GLibCNotFound,
+ error.AccessDenied => return error.GLibCNotFound,
+ error.PermissionDenied => return error.GLibCNotFound,
+ error.NoDevice => return error.GLibCNotFound,
+
+ 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 dir.close();
@@ -799,38 +795,34 @@ fn glibcVerFromRPath(io: Io, rpath: []const u8) !std.SemanticVersion {
// that start with "GLIBC_2.".
const glibc_so_basename = "libc.so.6";
var file = dir.openFile(glibc_so_basename, .{}) catch |err| switch (err) {
- error.NameTooLong => unreachable,
- error.InvalidUtf8 => unreachable, // WASI only
- error.InvalidWtf8 => unreachable, // Windows only
- error.BadPathName => unreachable, // Windows only
- error.PipeBusy => unreachable, // Windows-only
- error.SharingViolation => unreachable, // Windows-only
- error.NetworkNotFound => unreachable, // Windows-only
- error.AntivirusInterference => unreachable, // Windows-only
- error.FileLocksNotSupported => unreachable, // No lock requested.
- error.NoSpaceLeft => unreachable, // read-only
- error.PathAlreadyExists => unreachable, // read-only
- error.DeviceBusy => unreachable, // read-only
- 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.NameTooLong => return error.Unexpected,
+ error.BadPathName => return error.Unexpected,
+ error.PipeBusy => return error.Unexpected, // Windows-only
+ error.SharingViolation => return error.Unexpected, // Windows-only
+ error.NetworkNotFound => return error.Unexpected, // Windows-only
+ error.AntivirusInterference => return error.Unexpected, // Windows-only
+ error.FileLocksNotSupported => return error.Unexpected, // No lock requested.
+ error.NoSpaceLeft => return error.Unexpected, // read-only
+ error.PathAlreadyExists => return error.Unexpected, // read-only
+ error.DeviceBusy => return error.Unexpected, // read-only
+ error.FileBusy => return error.Unexpected, // read-only
+ error.NoDevice => return error.Unexpected, // not asking for a special device
error.FileTooBig => return error.Unexpected,
-
- error.ProcessNotFound,
- error.ProcessFdQuotaExceeded,
- error.SystemFdQuotaExceeded,
- error.SystemResources,
- error.SymLinkLoop,
- error.Unexpected,
- => |e| return e,
+ error.WouldBlock => return error.Unexpected, // not opened in non-blocking
+
+ error.AccessDenied => return error.GLibCNotFound,
+ error.PermissionDenied => return error.GLibCNotFound,
+ error.FileNotFound => return error.GLibCNotFound,
+ error.NotDir => return error.GLibCNotFound,
+ error.IsDir => return error.GLibCNotFound,
+
+ 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();
@@ -1016,12 +1008,9 @@ fn detectAbiAndDynamicLinker(io: Io, cpu: Target.Cpu, os: Target.Os, query: Targ
error.NameTooLong => return error.Unexpected,
error.PathAlreadyExists => 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.PipeBusy => return error.Unexpected,
error.FileLocksNotSupported => return error.Unexpected,
- error.WouldBlock => return error.Unexpected,
error.FileBusy => return error.Unexpected, // opened without write permissions
error.AntivirusInterference => return error.Unexpected, // Windows-only error
lib/std/dynamic_library.zig
@@ -137,6 +137,7 @@ const ElfDynLibError = error{
ElfStringSectionNotFound,
ElfSymSectionNotFound,
ElfHashTableNotFound,
+ Canceled,
} || posix.OpenError || posix.MMapError;
pub const ElfDynLib = struct {
lib/std/Io.zig
@@ -654,20 +654,20 @@ pub const VTable = struct {
conditionWait: *const fn (?*anyopaque, cond: *Condition, mutex: *Mutex) Cancelable!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,
- dirStat: *const fn (?*anyopaque, dir: Dir) Dir.StatError!Dir.Stat,
- dirStatPath: *const fn (?*anyopaque, dir: Dir, sub_path: []const u8) Dir.StatError!File.Stat,
- fileStat: *const fn (?*anyopaque, file: File) File.StatError!File.Stat,
- createFile: *const fn (?*anyopaque, dir: Dir, sub_path: []const u8, flags: File.CreateFlags) File.OpenError!File,
- fileOpen: *const fn (?*anyopaque, dir: Dir, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File,
+ dirMake: *const fn (?*anyopaque, Dir, sub_path: []const u8, mode: Dir.Mode) Dir.MakeError!void,
+ dirStat: *const fn (?*anyopaque, Dir) Dir.StatError!Dir.Stat,
+ dirStatPath: *const fn (?*anyopaque, Dir, sub_path: []const u8, Dir.StatPathOptions) Dir.StatPathError!File.Stat,
+ fileStat: *const fn (?*anyopaque, File) File.StatError!File.Stat,
+ createFile: *const fn (?*anyopaque, Dir, sub_path: []const u8, File.CreateFlags) File.OpenError!File,
+ fileOpen: *const fn (?*anyopaque, Dir, sub_path: []const u8, File.OpenFlags) File.OpenError!File,
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.
- 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.
- fileReadPositional: *const fn (?*anyopaque, file: File, data: [][]u8, offset: u64) File.ReadPositionalError!usize,
- fileSeekBy: *const fn (?*anyopaque, file: File, offset: i64) File.SeekError!void,
- fileSeekTo: *const fn (?*anyopaque, file: File, offset: u64) File.SeekError!void,
+ fileReadPositional: *const fn (?*anyopaque, File, data: [][]u8, offset: u64) File.ReadPositionalError!usize,
+ fileSeekBy: *const fn (?*anyopaque, File, offset: i64) File.SeekError!void,
+ fileSeekTo: *const fn (?*anyopaque, File, offset: u64) File.SeekError!void,
now: *const fn (?*anyopaque, Clock) Clock.Error!Timestamp,
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 {
return .{
.clock = duration.clock,
@@ -855,6 +863,10 @@ pub const Timestamp = struct {
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 {
return .{ .nanoseconds = t.nanoseconds, .clock = clock };
}
lib/std/posix.zig
@@ -1542,81 +1542,7 @@ pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) PWriteError!usiz
}
}
-pub const OpenError = error{
- /// 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;
+pub const OpenError = std.Io.File.OpenError || error{WouldBlock};
/// 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/).
lib/std/Thread.zig
@@ -318,10 +318,13 @@ pub fn getName(self: Thread, buffer_ptr: *[max_name_len:0]u8) GetNameError!?[]co
var buf: [32]u8 = undefined;
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, .{});
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) {
error.ReadFailed => return file_reader.err.?,
};
lib/std/Uri.zig
@@ -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);
return bytes;
}