authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-10-09 00:19:44-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-10-29 06:20:49-07:00
log8a1e6c8c394058a25cb21f60ad414f28cd37db22
treeef6a86087599855c24d5172eb7b225d515b4dccc
parent750b1431bf34b238bd991b2ee3b50e81ff20b667

std.Io: implement dirStatPath


14 files changed, 409 insertions(+), 294 deletions(-)

lib/std/Build/Cache.zig+9-9
...@@ -1305,7 +1305,7 @@ fn hashFile(file: fs.File, bin_digest: *[Hasher.mac_length]u8) fs.File.PReadErro...@@ -1305,7 +1305,7 @@ fn hashFile(file: fs.File, bin_digest: *[Hasher.mac_length]u8) fs.File.PReadErro
1305}1305}
13061306
1307// Create/Write a file, close it, then grab its stat.mtime timestamp.1307// Create/Write a file, close it, then grab its stat.mtime timestamp.
1308fn testGetCurrentFileTimestamp(dir: fs.Dir) !i128 {1308fn testGetCurrentFileTimestamp(dir: fs.Dir) !Io.Timestamp {
1309 const test_out_file = "test-filetimestamp.tmp";1309 const test_out_file = "test-filetimestamp.tmp";
13101310
1311 var file = try dir.createFile(test_out_file, .{1311 var file = try dir.createFile(test_out_file, .{
...@@ -1333,8 +1333,8 @@ test "cache file and then recall it" {...@@ -1333,8 +1333,8 @@ test "cache file and then recall it" {
13331333
1334 // Wait for file timestamps to tick1334 // Wait for file timestamps to tick
1335 const initial_time = try testGetCurrentFileTimestamp(tmp.dir);1335 const initial_time = try testGetCurrentFileTimestamp(tmp.dir);
1336 while ((try testGetCurrentFileTimestamp(tmp.dir)) == initial_time) {1336 while ((try testGetCurrentFileTimestamp(tmp.dir)).nanoseconds == initial_time.nanoseconds) {
1337 try std.Io.Duration.sleep(.fromNanoseconds(1), io);1337 try std.Io.Clock.Duration.sleep(.{ .clock = .boot, .raw = .fromNanoseconds(1) }, io);
1338 }1338 }
13391339
1340 var digest1: HexDigest = undefined;1340 var digest1: HexDigest = undefined;
...@@ -1399,8 +1399,8 @@ test "check that changing a file makes cache fail" {...@@ -1399,8 +1399,8 @@ test "check that changing a file makes cache fail" {
13991399
1400 // Wait for file timestamps to tick1400 // Wait for file timestamps to tick
1401 const initial_time = try testGetCurrentFileTimestamp(tmp.dir);1401 const initial_time = try testGetCurrentFileTimestamp(tmp.dir);
1402 while ((try testGetCurrentFileTimestamp(tmp.dir)) == initial_time) {1402 while ((try testGetCurrentFileTimestamp(tmp.dir)).nanoseconds == initial_time.nanoseconds) {
1403 try std.Io.Duration.sleep(.fromNanoseconds(1), io);1403 try std.Io.Clock.Duration.sleep(.{ .clock = .boot, .raw = .fromNanoseconds(1) }, io);
1404 }1404 }
14051405
1406 var digest1: HexDigest = undefined;1406 var digest1: HexDigest = undefined;
...@@ -1517,8 +1517,8 @@ test "Manifest with files added after initial hash work" {...@@ -1517,8 +1517,8 @@ test "Manifest with files added after initial hash work" {
15171517
1518 // Wait for file timestamps to tick1518 // Wait for file timestamps to tick
1519 const initial_time = try testGetCurrentFileTimestamp(tmp.dir);1519 const initial_time = try testGetCurrentFileTimestamp(tmp.dir);
1520 while ((try testGetCurrentFileTimestamp(tmp.dir)) == initial_time) {1520 while ((try testGetCurrentFileTimestamp(tmp.dir)).nanoseconds == initial_time.nanoseconds) {
1521 try std.Io.Duration.sleep(.fromNanoseconds(1), io);1521 try std.Io.Clock.Duration.sleep(.{ .clock = .boot, .raw = .fromNanoseconds(1) }, io);
1522 }1522 }
15231523
1524 var digest1: HexDigest = undefined;1524 var digest1: HexDigest = undefined;
...@@ -1568,8 +1568,8 @@ test "Manifest with files added after initial hash work" {...@@ -1568,8 +1568,8 @@ test "Manifest with files added after initial hash work" {
15681568
1569 // Wait for file timestamps to tick1569 // Wait for file timestamps to tick
1570 const initial_time2 = try testGetCurrentFileTimestamp(tmp.dir);1570 const initial_time2 = try testGetCurrentFileTimestamp(tmp.dir);
1571 while ((try testGetCurrentFileTimestamp(tmp.dir)) == initial_time2) {1571 while ((try testGetCurrentFileTimestamp(tmp.dir)).nanoseconds == initial_time2.nanoseconds) {
1572 try std.Io.Duration.sleep(.fromNanoseconds(1), io);1572 try std.Io.Clock.Duration.sleep(.{ .clock = .boot, .raw = .fromNanoseconds(1) }, io);
1573 }1573 }
15741574
1575 {1575 {
lib/std/Io.zig+23-11
...@@ -654,20 +654,20 @@ pub const VTable = struct {...@@ -654,20 +654,20 @@ pub const VTable = struct {
654 conditionWait: *const fn (?*anyopaque, cond: *Condition, mutex: *Mutex) Cancelable!void,654 conditionWait: *const fn (?*anyopaque, cond: *Condition, mutex: *Mutex) Cancelable!void,
655 conditionWake: *const fn (?*anyopaque, cond: *Condition, wake: Condition.Wake) void,655 conditionWake: *const fn (?*anyopaque, cond: *Condition, wake: Condition.Wake) void,
656656
657 dirMake: *const fn (?*anyopaque, dir: Dir, sub_path: []const u8, mode: Dir.Mode) Dir.MakeError!void,657 dirMake: *const fn (?*anyopaque, Dir, sub_path: []const u8, mode: Dir.Mode) Dir.MakeError!void,
658 dirStat: *const fn (?*anyopaque, dir: Dir) Dir.StatError!Dir.Stat,658 dirStat: *const fn (?*anyopaque, Dir) Dir.StatError!Dir.Stat,
659 dirStatPath: *const fn (?*anyopaque, dir: Dir, sub_path: []const u8) Dir.StatError!File.Stat,659 dirStatPath: *const fn (?*anyopaque, Dir, sub_path: []const u8, Dir.StatPathOptions) Dir.StatPathError!File.Stat,
660 fileStat: *const fn (?*anyopaque, file: File) File.StatError!File.Stat,660 fileStat: *const fn (?*anyopaque, File) File.StatError!File.Stat,
661 createFile: *const fn (?*anyopaque, dir: Dir, sub_path: []const u8, flags: File.CreateFlags) File.OpenError!File,661 createFile: *const fn (?*anyopaque, Dir, sub_path: []const u8, File.CreateFlags) File.OpenError!File,
662 fileOpen: *const fn (?*anyopaque, dir: Dir, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File,662 fileOpen: *const fn (?*anyopaque, Dir, sub_path: []const u8, File.OpenFlags) File.OpenError!File,
663 fileClose: *const fn (?*anyopaque, File) void,663 fileClose: *const fn (?*anyopaque, File) void,
664 pwrite: *const fn (?*anyopaque, file: File, buffer: []const u8, offset: std.posix.off_t) File.PWriteError!usize,664 pwrite: *const fn (?*anyopaque, File, buffer: []const u8, offset: std.posix.off_t) File.PWriteError!usize,
665 /// Returns 0 on end of stream.665 /// Returns 0 on end of stream.
666 fileReadStreaming: *const fn (?*anyopaque, file: File, data: [][]u8) File.ReadStreamingError!usize,666 fileReadStreaming: *const fn (?*anyopaque, File, data: [][]u8) File.ReadStreamingError!usize,
667 /// Returns 0 on end of stream.667 /// Returns 0 on end of stream.
668 fileReadPositional: *const fn (?*anyopaque, file: File, data: [][]u8, offset: u64) File.ReadPositionalError!usize,668 fileReadPositional: *const fn (?*anyopaque, File, data: [][]u8, offset: u64) File.ReadPositionalError!usize,
669 fileSeekBy: *const fn (?*anyopaque, file: File, offset: i64) File.SeekError!void,669 fileSeekBy: *const fn (?*anyopaque, File, offset: i64) File.SeekError!void,
670 fileSeekTo: *const fn (?*anyopaque, file: File, offset: u64) File.SeekError!void,670 fileSeekTo: *const fn (?*anyopaque, File, offset: u64) File.SeekError!void,
671671
672 now: *const fn (?*anyopaque, Clock) Clock.Error!Timestamp,672 now: *const fn (?*anyopaque, Clock) Clock.Error!Timestamp,
673 sleep: *const fn (?*anyopaque, Timeout) SleepError!void,673 sleep: *const fn (?*anyopaque, Timeout) SleepError!void,
...@@ -795,6 +795,14 @@ pub const Clock = enum {...@@ -795,6 +795,14 @@ pub const Clock = enum {
795 };795 };
796 }796 }
797797
798 pub fn subDuration(from: Clock.Timestamp, duration: Clock.Duration) Clock.Timestamp {
799 assert(from.clock == duration.clock);
800 return .{
801 .raw = from.raw.subDuration(duration.raw),
802 .clock = from.clock,
803 };
804 }
805
798 pub fn fromNow(io: Io, duration: Clock.Duration) Error!Clock.Timestamp {806 pub fn fromNow(io: Io, duration: Clock.Duration) Error!Clock.Timestamp {
799 return .{807 return .{
800 .clock = duration.clock,808 .clock = duration.clock,
...@@ -855,6 +863,10 @@ pub const Timestamp = struct {...@@ -855,6 +863,10 @@ pub const Timestamp = struct {
855 return .{ .nanoseconds = from.nanoseconds + duration.nanoseconds };863 return .{ .nanoseconds = from.nanoseconds + duration.nanoseconds };
856 }864 }
857865
866 pub fn subDuration(from: Timestamp, duration: Duration) Timestamp {
867 return .{ .nanoseconds = from.nanoseconds - duration.nanoseconds };
868 }
869
858 pub fn withClock(t: Timestamp, clock: Clock) Clock.Timestamp {870 pub fn withClock(t: Timestamp, clock: Clock) Clock.Timestamp {
859 return .{ .nanoseconds = t.nanoseconds, .clock = clock };871 return .{ .nanoseconds = t.nanoseconds, .clock = clock };
860 }872 }
lib/std/Io/Dir.zig+31-11
...@@ -15,6 +15,29 @@ pub fn cwd() Dir {...@@ -15,6 +15,29 @@ pub fn cwd() Dir {
1515
16pub const Handle = std.posix.fd_t;16pub const Handle = std.posix.fd_t;
1717
18pub const PathNameError = error{
19 NameTooLong,
20 /// File system cannot encode the requested file name bytes.
21 /// Could be due to invalid WTF-8 on Windows, invalid UTF-8 on WASI,
22 /// invalid characters on Windows, etc. Filesystem and operating specific.
23 BadPathName,
24};
25
26pub const OpenError = error{
27 FileNotFound,
28 NotDir,
29 AccessDenied,
30 PermissionDenied,
31 SymLinkLoop,
32 ProcessFdQuotaExceeded,
33 SystemFdQuotaExceeded,
34 NoDevice,
35 SystemResources,
36 DeviceBusy,
37 /// On Windows, `\\server` or `\\server\share` was not found.
38 NetworkNotFound,
39} || PathNameError || Io.Cancelable || Io.UnexpectedError;
40
18pub fn openFile(dir: Dir, io: Io, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File {41pub fn openFile(dir: Dir, io: Io, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File {
19 return io.vtable.fileOpen(io.userdata, dir, sub_path, flags);42 return io.vtable.fileOpen(io.userdata, dir, sub_path, flags);
20}43}
...@@ -149,22 +172,15 @@ pub const MakeError = error{...@@ -149,22 +172,15 @@ pub const MakeError = error{
149 PathAlreadyExists,172 PathAlreadyExists,
150 SymLinkLoop,173 SymLinkLoop,
151 LinkQuotaExceeded,174 LinkQuotaExceeded,
152 NameTooLong,
153 FileNotFound,175 FileNotFound,
154 SystemResources,176 SystemResources,
155 NoSpaceLeft,177 NoSpaceLeft,
156 NotDir,178 NotDir,
157 ReadOnlyFileSystem,179 ReadOnlyFileSystem,
158 /// Windows-only; file paths provided by the user must be valid WTF-8.
159 /// https://simonsapin.github.io/wtf-8/
160 InvalidWtf8,
161 BadPathName,
162 NoDevice,180 NoDevice,
163 /// On Windows, `\\server` or `\\server\share` was not found.181 /// On Windows, `\\server` or `\\server\share` was not found.
164 NetworkNotFound,182 NetworkNotFound,
165 /// File system cannot encode the requested file name bytes.183} || PathNameError || Io.Cancelable || Io.UnexpectedError;
166 InvalidFileName,
167} || Io.Cancelable || Io.UnexpectedError;
168184
169/// Creates a single directory with a relative or absolute path.185/// Creates a single directory with a relative or absolute path.
170///186///
...@@ -225,7 +241,7 @@ pub fn makePathStatus(dir: Dir, io: Io, sub_path: []const u8) MakePathError!Make...@@ -225,7 +241,7 @@ pub fn makePathStatus(dir: Dir, io: Io, sub_path: []const u8) MakePathError!Make
225 // could cause an infinite loop241 // could cause an infinite loop
226 check_dir: {242 check_dir: {
227 // workaround for windows, see https://github.com/ziglang/zig/issues/16738243 // workaround for windows, see https://github.com/ziglang/zig/issues/16738
228 const fstat = statPath(dir, io, component.path) catch |stat_err| switch (stat_err) {244 const fstat = statPath(dir, io, component.path, .{}) catch |stat_err| switch (stat_err) {
229 error.IsDir => break :check_dir,245 error.IsDir => break :check_dir,
230 else => |e| return e,246 else => |e| return e,
231 };247 };
...@@ -251,6 +267,10 @@ pub fn stat(dir: Dir, io: Io) StatError!Stat {...@@ -251,6 +267,10 @@ pub fn stat(dir: Dir, io: Io) StatError!Stat {
251267
252pub const StatPathError = File.OpenError || File.StatError;268pub const StatPathError = File.OpenError || File.StatError;
253269
270pub const StatPathOptions = struct {
271 follow_symlinks: bool = true,
272};
273
254/// Returns metadata for a file inside the directory.274/// Returns metadata for a file inside the directory.
255///275///
256/// On Windows, this requires three syscalls. On other operating systems, it276/// On Windows, this requires three syscalls. On other operating systems, it
...@@ -263,6 +283,6 @@ pub const StatPathError = File.OpenError || File.StatError;...@@ -263,6 +283,6 @@ pub const StatPathError = File.OpenError || File.StatError;
263/// * On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).283/// * On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
264/// * On WASI, `sub_path` should be encoded as valid UTF-8.284/// * On WASI, `sub_path` should be encoded as valid UTF-8.
265/// * On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.285/// * On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
266pub fn statPath(dir: Dir, io: Io, sub_path: []const u8) StatPathError!Stat {286pub fn statPath(dir: Dir, io: Io, sub_path: []const u8, options: StatPathOptions) StatPathError!Stat {
267 return io.vtable.dirStatPath(io.userdata, dir, sub_path);287 return io.vtable.dirStatPath(io.userdata, dir, sub_path, options);
268}288}
lib/std/Io/File.zig+57-1
...@@ -81,7 +81,63 @@ pub fn stat(file: File, io: Io) StatError!Stat {...@@ -81,7 +81,63 @@ pub fn stat(file: File, io: Io) StatError!Stat {
81pub const OpenFlags = std.fs.File.OpenFlags;81pub const OpenFlags = std.fs.File.OpenFlags;
82pub const CreateFlags = std.fs.File.CreateFlags;82pub const CreateFlags = std.fs.File.CreateFlags;
8383
84pub const OpenError = std.fs.File.OpenError || Io.Cancelable;84pub const OpenError = error{
85 SharingViolation,
86 PipeBusy,
87 NoDevice,
88 /// On Windows, `\\server` or `\\server\share` was not found.
89 NetworkNotFound,
90 ProcessNotFound,
91 /// On Windows, antivirus software is enabled by default. It can be
92 /// disabled, but Windows Update sometimes ignores the user's preference
93 /// and re-enables it. When enabled, antivirus software on Windows
94 /// intercepts file system operations and makes them significantly slower
95 /// in addition to possibly failing with this error code.
96 AntivirusInterference,
97 /// In WASI, this error may occur when the file descriptor does
98 /// not hold the required rights to open a new resource relative to it.
99 AccessDenied,
100 PermissionDenied,
101 SymLinkLoop,
102 ProcessFdQuotaExceeded,
103 SystemFdQuotaExceeded,
104 /// Either:
105 /// * One of the path components does not exist.
106 /// * Cwd was used, but cwd has been deleted.
107 /// * The path associated with the open directory handle has been deleted.
108 /// * On macOS, multiple processes or threads raced to create the same file
109 /// with `O.EXCL` set to `false`.
110 FileNotFound,
111 /// The path exceeded `max_path_bytes` bytes.
112 /// Insufficient kernel memory was available, or
113 /// the named file is a FIFO and per-user hard limit on
114 /// memory allocation for pipes has been reached.
115 SystemResources,
116 /// The file is too large to be opened. This error is unreachable
117 /// for 64-bit targets, as well as when opening directories.
118 FileTooBig,
119 /// The path refers to directory but the `DIRECTORY` flag was not provided.
120 IsDir,
121 /// A new path cannot be created because the device has no room for the new file.
122 /// This error is only reachable when the `CREAT` flag is provided.
123 NoSpaceLeft,
124 /// A component used as a directory in the path was not, in fact, a directory, or
125 /// `DIRECTORY` was specified and the path was not a directory.
126 NotDir,
127 /// The path already exists and the `CREAT` and `EXCL` flags were provided.
128 PathAlreadyExists,
129 DeviceBusy,
130 FileLocksNotSupported,
131 /// One of these three things:
132 /// * pathname refers to an executable image which is currently being
133 /// executed and write access was requested.
134 /// * pathname refers to a file that is currently in use as a swap
135 /// file, and the O_TRUNC flag was specified.
136 /// * pathname refers to a file that is currently being read by the
137 /// kernel (e.g., for module/firmware loading), and write access was
138 /// requested.
139 FileBusy,
140} || Io.Dir.PathNameError || Io.Cancelable || Io.UnexpectedError;
85141
86pub fn close(file: File, io: Io) void {142pub fn close(file: File, io: Io) void {
87 return io.vtable.fileClose(io.userdata, file);143 return io.vtable.fileClose(io.userdata, file);
lib/std/Io/Threaded.zig+206-16
...@@ -166,14 +166,23 @@ pub fn io(pool: *Pool) Io {...@@ -166,14 +166,23 @@ pub fn io(pool: *Pool) Io {
166 else => dirMakePosix,166 else => dirMakePosix,
167 },167 },
168 .dirStat = dirStat,168 .dirStat = dirStat,
169 .dirStatPath = dirStatPath,169 .dirStatPath = switch (builtin.os.tag) {
170 .linux => dirStatPathLinux,
171 .windows => @panic("TODO"),
172 .wasi => @panic("TODO"),
173 else => dirStatPathPosix,
174 },
170 .fileStat = switch (builtin.os.tag) {175 .fileStat = switch (builtin.os.tag) {
171 .linux => fileStatLinux,176 .linux => fileStatLinux,
172 .windows => fileStatWindows,177 .windows => fileStatWindows,
173 .wasi => fileStatWasi,178 .wasi => fileStatWasi,
174 else => fileStatPosix,179 else => fileStatPosix,
175 },180 },
176 .createFile = createFile,181 .createFile = switch (builtin.os.tag) {
182 .windows => @panic("TODO"),
183 .wasi => @panic("TODO"),
184 else => createFilePosix,
185 },
177 .fileOpen = fileOpen,186 .fileOpen = fileOpen,
178 .fileClose = fileClose,187 .fileClose = fileClose,
179 .pwrite = pwrite,188 .pwrite = pwrite,
...@@ -765,8 +774,10 @@ fn conditionWake(userdata: ?*anyopaque, cond: *Io.Condition, wake: Io.Condition....@@ -765,8 +774,10 @@ fn conditionWake(userdata: ?*anyopaque, cond: *Io.Condition, wake: Io.Condition.
765774
766fn dirMakePosix(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode: Io.Dir.Mode) Io.Dir.MakeError!void {775fn dirMakePosix(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode: Io.Dir.Mode) Io.Dir.MakeError!void {
767 const pool: *Pool = @ptrCast(@alignCast(userdata));776 const pool: *Pool = @ptrCast(@alignCast(userdata));
777
768 var path_buffer: [posix.PATH_MAX]u8 = undefined;778 var path_buffer: [posix.PATH_MAX]u8 = undefined;
769 const sub_path_posix = try toPosixPath(sub_path, &path_buffer);779 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
780
770 while (true) {781 while (true) {
771 try pool.checkCancel();782 try pool.checkCancel();
772 switch (posix.errno(posix.system.mkdirat(dir.handle, sub_path_posix, mode))) {783 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:...@@ -788,7 +799,7 @@ fn dirMakePosix(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode:
788 .ROFS => return error.ReadOnlyFileSystem,799 .ROFS => return error.ReadOnlyFileSystem,
789 // dragonfly: when dir_fd is unlinked from filesystem800 // dragonfly: when dir_fd is unlinked from filesystem
790 .NOTCONN => return error.FileNotFound,801 .NOTCONN => return error.FileNotFound,
791 .ILSEQ => return error.InvalidFileName,802 .ILSEQ => return error.BadPathName,
792 else => |err| return posix.unexpectedErrno(err),803 else => |err| return posix.unexpectedErrno(err),
793 }804 }
794 }805 }
...@@ -802,13 +813,82 @@ fn dirStat(userdata: ?*anyopaque, dir: Io.Dir) Io.Dir.StatError!Io.Dir.Stat {...@@ -802,13 +813,82 @@ fn dirStat(userdata: ?*anyopaque, dir: Io.Dir) Io.Dir.StatError!Io.Dir.Stat {
802 @panic("TODO");813 @panic("TODO");
803}814}
804815
805fn dirStatPath(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8) Io.Dir.StatError!Io.File.Stat {816fn dirStatPathLinux(
817 userdata: ?*anyopaque,
818 dir: Io.Dir,
819 sub_path: []const u8,
820 options: Io.Dir.StatPathOptions,
821) Io.Dir.StatPathError!Io.File.Stat {
806 const pool: *Pool = @ptrCast(@alignCast(userdata));822 const pool: *Pool = @ptrCast(@alignCast(userdata));
807 try pool.checkCancel();823 const linux = std.os.linux;
808824
809 _ = dir;825 var path_buffer: [posix.PATH_MAX]u8 = undefined;
810 _ = sub_path;826 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
811 @panic("TODO");827
828 const flags: u32 = linux.AT.NO_AUTOMOUNT |
829 @as(u32, if (!options.follow_symlinks) linux.AT.SYMLINK_NOFOLLOW else 0);
830
831 while (true) {
832 try pool.checkCancel();
833 var statx = std.mem.zeroes(linux.Statx);
834 const rc = linux.statx(
835 dir.handle,
836 sub_path_posix,
837 flags,
838 linux.STATX_TYPE | linux.STATX_MODE | linux.STATX_ATIME | linux.STATX_MTIME | linux.STATX_CTIME,
839 &statx,
840 );
841 switch (linux.E.init(rc)) {
842 .SUCCESS => return statFromLinux(&statx),
843 .INTR => continue,
844 .ACCES => return error.AccessDenied,
845 .BADF => |err| return errnoBug(err),
846 .FAULT => |err| return errnoBug(err),
847 .INVAL => |err| return errnoBug(err),
848 .LOOP => return error.SymLinkLoop,
849 .NAMETOOLONG => |err| return errnoBug(err), // Handled by pathToPosix() above.
850 .NOENT => return error.FileNotFound,
851 .NOTDIR => return error.NotDir,
852 .NOMEM => return error.SystemResources,
853 else => |err| return posix.unexpectedErrno(err),
854 }
855 }
856}
857
858fn dirStatPathPosix(
859 userdata: ?*anyopaque,
860 dir: Io.Dir,
861 sub_path: []const u8,
862 options: Io.Dir.StatPathOptions,
863) Io.Dir.StatPathError!Io.File.Stat {
864 const pool: *Pool = @ptrCast(@alignCast(userdata));
865
866 var path_buffer: [posix.PATH_MAX]u8 = undefined;
867 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
868
869 const flags: u32 = if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0;
870 const fstatat_sym = if (posix.lfs64_abi) posix.system.fstatat64 else posix.system.fstatat;
871
872 while (true) {
873 try pool.checkCancel();
874 var stat = std.mem.zeroes(posix.Stat);
875 switch (posix.errno(fstatat_sym(dir.handle, sub_path_posix, &stat, flags))) {
876 .SUCCESS => return statFromPosix(stat),
877 .INTR => continue,
878 .INVAL => |err| return errnoBug(err),
879 .BADF => |err| return errnoBug(err), // Always a race condition.
880 .NOMEM => return error.SystemResources,
881 .ACCES => return error.AccessDenied,
882 .PERM => return error.PermissionDenied,
883 .FAULT => |err| return errnoBug(err),
884 .NAMETOOLONG => return error.NameTooLong,
885 .LOOP => return error.SymLinkLoop,
886 .NOENT => return error.FileNotFound,
887 .NOTDIR => return error.FileNotFound,
888 .ILSEQ => return error.BadPathName,
889 else => |err| return posix.unexpectedErrno(err),
890 }
891 }
812}892}
813893
814fn fileStatPosix(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File.Stat {894fn 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....@@ -885,17 +965,127 @@ fn fileStatWasi(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File.
885 }965 }
886}966}
887967
888fn createFile(968const have_flock = @TypeOf(posix.system.flock) != void;
969
970fn createFilePosix(
889 userdata: ?*anyopaque,971 userdata: ?*anyopaque,
890 dir: Io.Dir,972 dir: Io.Dir,
891 sub_path: []const u8,973 sub_path: []const u8,
892 flags: Io.File.CreateFlags,974 flags: Io.File.CreateFlags,
893) Io.File.OpenError!Io.File {975) Io.File.OpenError!Io.File {
894 const pool: *Pool = @ptrCast(@alignCast(userdata));976 const pool: *Pool = @ptrCast(@alignCast(userdata));
895 try pool.checkCancel();977
896 const fs_dir: std.fs.Dir = .{ .fd = dir.handle };978 var path_buffer: [posix.PATH_MAX]u8 = undefined;
897 const fs_file = try fs_dir.createFile(sub_path, flags);979 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
898 return .{ .handle = fs_file.handle };980
981 var os_flags: posix.O = .{
982 .ACCMODE = if (flags.read) .RDWR else .WRONLY,
983 .CREAT = true,
984 .TRUNC = flags.truncate,
985 .EXCL = flags.exclusive,
986 };
987 if (@hasField(posix.O, "LARGEFILE")) os_flags.LARGEFILE = true;
988 if (@hasField(posix.O, "CLOEXEC")) os_flags.CLOEXEC = true;
989
990 // Use the O locking flags if the os supports them to acquire the lock
991 // atomically. Note that the NONBLOCK flag is removed after the openat()
992 // call is successful.
993 const has_flock_open_flags = @hasField(posix.O, "EXLOCK");
994 if (has_flock_open_flags) switch (flags.lock) {
995 .none => {},
996 .shared => {
997 os_flags.SHLOCK = true;
998 os_flags.NONBLOCK = flags.lock_nonblocking;
999 },
1000 .exclusive => {
1001 os_flags.EXLOCK = true;
1002 os_flags.NONBLOCK = flags.lock_nonblocking;
1003 },
1004 };
1005
1006 const openat_sym = if (posix.lfs64_abi) posix.system.openat64 else posix.system.openat;
1007
1008 const fd: posix.fd_t = while (true) {
1009 try pool.checkCancel();
1010 const rc = openat_sym(dir.handle, sub_path_posix, os_flags, flags.mode);
1011 switch (posix.errno(rc)) {
1012 .SUCCESS => break @intCast(rc),
1013 .INTR => continue,
1014
1015 .FAULT => |err| return errnoBug(err),
1016 .INVAL => return error.BadPathName,
1017 .BADF => |err| return errnoBug(err),
1018 .ACCES => return error.AccessDenied,
1019 .FBIG => return error.FileTooBig,
1020 .OVERFLOW => return error.FileTooBig,
1021 .ISDIR => return error.IsDir,
1022 .LOOP => return error.SymLinkLoop,
1023 .MFILE => return error.ProcessFdQuotaExceeded,
1024 .NAMETOOLONG => return error.NameTooLong,
1025 .NFILE => return error.SystemFdQuotaExceeded,
1026 .NODEV => return error.NoDevice,
1027 .NOENT => return error.FileNotFound,
1028 .SRCH => return error.ProcessNotFound,
1029 .NOMEM => return error.SystemResources,
1030 .NOSPC => return error.NoSpaceLeft,
1031 .NOTDIR => return error.NotDir,
1032 .PERM => return error.PermissionDenied,
1033 .EXIST => return error.PathAlreadyExists,
1034 .BUSY => return error.DeviceBusy,
1035 .OPNOTSUPP => return error.FileLocksNotSupported,
1036 //.AGAIN => return error.WouldBlock,
1037 .TXTBSY => return error.FileBusy,
1038 .NXIO => return error.NoDevice,
1039 .ILSEQ => return error.BadPathName,
1040 else => |err| return posix.unexpectedErrno(err),
1041 }
1042 };
1043 errdefer posix.close(fd);
1044
1045 if (have_flock and !has_flock_open_flags and flags.lock != .none) {
1046 const lock_nonblocking: i32 = if (flags.lock_nonblocking) posix.LOCK.NB else 0;
1047 const lock_flags = switch (flags.lock) {
1048 .none => unreachable,
1049 .shared => posix.LOCK.SH | lock_nonblocking,
1050 .exclusive => posix.LOCK.EX | lock_nonblocking,
1051 };
1052 while (true) {
1053 try pool.checkCancel();
1054 switch (posix.errno(posix.system.flock(fd, lock_flags))) {
1055 .SUCCESS => break,
1056 .INTR => continue,
1057
1058 .BADF => |err| return errnoBug(err),
1059 .INVAL => |err| return errnoBug(err), // invalid parameters
1060 .NOLCK => return error.SystemResources,
1061 //.AGAIN => return error.WouldBlock,
1062 .OPNOTSUPP => return error.FileLocksNotSupported,
1063 else => |err| return posix.unexpectedErrno(err),
1064 }
1065 }
1066 }
1067
1068 if (has_flock_open_flags and flags.lock_nonblocking) {
1069 var fl_flags: usize = while (true) {
1070 try pool.checkCancel();
1071 switch (posix.errno(posix.system.fcntl(fd, posix.F.GETFL, 0))) {
1072 .SUCCESS => break,
1073 .INTR => continue,
1074 else => |err| return posix.unexpectedErrno(err),
1075 }
1076 };
1077 fl_flags &= ~@as(usize, 1 << @bitOffsetOf(posix.O, "NONBLOCK"));
1078 while (true) {
1079 try pool.checkCancel();
1080 switch (posix.errno(posix.fcntl(fd, posix.F.SETFL, fl_flags))) {
1081 .SUCCESS => break,
1082 .INTR => continue,
1083 else => |err| return posix.unexpectedErrno(err),
1084 }
1085 }
1086 }
1087
1088 return .{ .handle = fd };
899}1089}
9001090
901fn fileOpen(1091fn fileOpen(
...@@ -2293,8 +2483,8 @@ fn timestampToPosix(nanoseconds: i96) std.posix.timespec {...@@ -2293,8 +2483,8 @@ fn timestampToPosix(nanoseconds: i96) std.posix.timespec {
2293 };2483 };
2294}2484}
22952485
2296fn toPosixPath(file_path: []const u8, buffer: *[posix.PATH_MAX]u8) error{ NameTooLong, InvalidFileName }![:0]u8 {2486fn pathToPosix(file_path: []const u8, buffer: *[posix.PATH_MAX]u8) Io.Dir.PathNameError![:0]u8 {
2297 if (std.mem.containsAtLeastScalar2(u8, file_path, 0, 1)) return error.InvalidFileName;2487 if (std.mem.containsAtLeastScalar2(u8, file_path, 0, 1)) return error.BadPathName;
2298 // >= rather than > to make room for the null byte2488 // >= rather than > to make room for the null byte
2299 if (file_path.len >= buffer.len) return error.NameTooLong;2489 if (file_path.len >= buffer.len) return error.NameTooLong;
2300 @memcpy(buffer[0..file_path.len], file_path);2490 @memcpy(buffer[0..file_path.len], file_path);
lib/std/Io/test.zig+7-5
...@@ -11,6 +11,8 @@ const native_endian = @import("builtin").target.cpu.arch.endian();...@@ -11,6 +11,8 @@ const native_endian = @import("builtin").target.cpu.arch.endian();
11const tmpDir = std.testing.tmpDir;11const tmpDir = std.testing.tmpDir;
1212
13test "write a file, read it, then delete it" {13test "write a file, read it, then delete it" {
14 const io = std.testing.io;
15
14 var tmp = tmpDir(.{});16 var tmp = tmpDir(.{});
15 defer tmp.cleanup();17 defer tmp.cleanup();
1618
...@@ -45,7 +47,7 @@ test "write a file, read it, then delete it" {...@@ -45,7 +47,7 @@ test "write a file, read it, then delete it" {
45 try expectEqual(expected_file_size, file_size);47 try expectEqual(expected_file_size, file_size);
4648
47 var file_buffer: [1024]u8 = undefined;49 var file_buffer: [1024]u8 = undefined;
48 var file_reader = file.reader(&file_buffer);50 var file_reader = file.reader(io, &file_buffer);
49 const contents = try file_reader.interface.allocRemaining(std.testing.allocator, .limited(2 * 1024));51 const contents = try file_reader.interface.allocRemaining(std.testing.allocator, .limited(2 * 1024));
50 defer std.testing.allocator.free(contents);52 defer std.testing.allocator.free(contents);
5153
...@@ -114,10 +116,10 @@ test "updateTimes" {...@@ -114,10 +116,10 @@ test "updateTimes" {
114 const stat_old = try file.stat();116 const stat_old = try file.stat();
115 // Set atime and mtime to 5s before117 // Set atime and mtime to 5s before
116 try file.updateTimes(118 try file.updateTimes(
117 stat_old.atime - 5 * std.time.ns_per_s,119 stat_old.atime.subDuration(.fromSeconds(5)),
118 stat_old.mtime - 5 * std.time.ns_per_s,120 stat_old.mtime.subDuration(.fromSeconds(5)),
119 );121 );
120 const stat_new = try file.stat();122 const stat_new = try file.stat();
121 try expect(stat_new.atime < stat_old.atime);123 try expect(stat_new.atime.nanoseconds < stat_old.atime.nanoseconds);
122 try expect(stat_new.mtime < stat_old.mtime);124 try expect(stat_new.mtime.nanoseconds < stat_old.mtime.nanoseconds);
123}125}
lib/std/Thread.zig+4-1
...@@ -318,10 +318,13 @@ pub fn getName(self: Thread, buffer_ptr: *[max_name_len:0]u8) GetNameError!?[]co...@@ -318,10 +318,13 @@ pub fn getName(self: Thread, buffer_ptr: *[max_name_len:0]u8) GetNameError!?[]co
318 var buf: [32]u8 = undefined;318 var buf: [32]u8 = undefined;
319 const path = try std.fmt.bufPrint(&buf, "/proc/self/task/{d}/comm", .{self.getHandle()});319 const path = try std.fmt.bufPrint(&buf, "/proc/self/task/{d}/comm", .{self.getHandle()});
320320
321 var threaded: std.Io.Threaded = .init_single_threaded;
322 const io = threaded.io();
323
321 const file = try std.fs.cwd().openFile(path, .{});324 const file = try std.fs.cwd().openFile(path, .{});
322 defer file.close();325 defer file.close();
323326
324 var file_reader = file.readerStreaming(&.{});327 var file_reader = file.readerStreaming(io, &.{});
325 const data_len = file_reader.interface.readSliceShort(buffer_ptr[0 .. max_name_len + 1]) catch |err| switch (err) {328 const data_len = file_reader.interface.readSliceShort(buffer_ptr[0 .. max_name_len + 1]) catch |err| switch (err) {
326 error.ReadFailed => return file_reader.err.?,329 error.ReadFailed => return file_reader.err.?,
327 };330 };
lib/std/Thread/Condition.zig+7-8
...@@ -123,14 +123,9 @@ const SingleThreadedImpl = struct {...@@ -123,14 +123,9 @@ const SingleThreadedImpl = struct {
123 fn wait(self: *Impl, mutex: *Mutex, timeout: ?u64) error{Timeout}!void {123 fn wait(self: *Impl, mutex: *Mutex, timeout: ?u64) error{Timeout}!void {
124 _ = self;124 _ = self;
125 _ = mutex;125 _ = mutex;
126
127 // There are no other threads to wake us up.126 // There are no other threads to wake us up.
128 // So if we wait without a timeout we would never wake up.127 // So if we wait without a timeout we would never wake up.
129 const timeout_ns = timeout orelse {128 assert(timeout != null); // Deadlock detected.
130 unreachable; // deadlock detected
131 };
132
133 std.Thread.sleep(timeout_ns);
134 return error.Timeout;129 return error.Timeout;
135 }130 }
136131
...@@ -323,6 +318,8 @@ test "wait and signal" {...@@ -323,6 +318,8 @@ test "wait and signal" {
323 return error.SkipZigTest;318 return error.SkipZigTest;
324 }319 }
325320
321 const io = testing.io;
322
326 const num_threads = 4;323 const num_threads = 4;
327324
328 const MultiWait = struct {325 const MultiWait = struct {
...@@ -348,7 +345,7 @@ test "wait and signal" {...@@ -348,7 +345,7 @@ test "wait and signal" {
348 }345 }
349346
350 while (true) {347 while (true) {
351 std.Thread.sleep(100 * std.time.ns_per_ms);348 try std.Io.Clock.Duration.sleep(.{ .clock = .awake, .raw = .fromMilliseconds(100) }, io);
352349
353 multi_wait.mutex.lock();350 multi_wait.mutex.lock();
354 defer multi_wait.mutex.unlock();351 defer multi_wait.mutex.unlock();
...@@ -368,6 +365,8 @@ test signal {...@@ -368,6 +365,8 @@ test signal {
368 return error.SkipZigTest;365 return error.SkipZigTest;
369 }366 }
370367
368 const io = testing.io;
369
371 const num_threads = 4;370 const num_threads = 4;
372371
373 const SignalTest = struct {372 const SignalTest = struct {
...@@ -405,7 +404,7 @@ test signal {...@@ -405,7 +404,7 @@ test signal {
405 }404 }
406405
407 while (true) {406 while (true) {
408 std.Thread.sleep(10 * std.time.ns_per_ms);407 try std.Io.Clock.Duration.sleep(.{ .clock = .awake, .raw = .fromMilliseconds(10) }, io);
409408
410 signal_test.mutex.lock();409 signal_test.mutex.lock();
411 defer signal_test.mutex.unlock();410 defer signal_test.mutex.unlock();
lib/std/Uri.zig+1-1
...@@ -441,7 +441,7 @@ pub fn resolveInPlace(base: Uri, new_len: usize, aux_buf: *[]u8) ResolveInPlaceE...@@ -441,7 +441,7 @@ pub fn resolveInPlace(base: Uri, new_len: usize, aux_buf: *[]u8) ResolveInPlaceE
441 };441 };
442}442}
443443
444fn validateHost(bytes: []const u8) []const u8 {444fn validateHost(bytes: []const u8) HostName.ValidateError![]const u8 {
445 try HostName.validate(bytes);445 try HostName.validate(bytes);
446 return bytes;446 return bytes;
447}447}
lib/std/dynamic_library.zig+1
...@@ -137,6 +137,7 @@ const ElfDynLibError = error{...@@ -137,6 +137,7 @@ const ElfDynLibError = error{
137 ElfStringSectionNotFound,137 ElfStringSectionNotFound,
138 ElfSymSectionNotFound,138 ElfSymSectionNotFound,
139 ElfHashTableNotFound,139 ElfHashTableNotFound,
140 Canceled,
140} || posix.OpenError || posix.MMapError;141} || posix.OpenError || posix.MMapError;
141142
142pub const ElfDynLib = struct {143pub const ElfDynLib = struct {
lib/std/fs/Dir.zig+16-74
...@@ -36,10 +36,6 @@ const IteratorError = error{...@@ -36,10 +36,6 @@ const IteratorError = error{
36 AccessDenied,36 AccessDenied,
37 PermissionDenied,37 PermissionDenied,
38 SystemResources,38 SystemResources,
39 /// WASI-only. The path of an entry could not be encoded as valid UTF-8.
40 /// WASI is unable to handle paths that cannot be encoded as well-formed UTF-8.
41 /// https://github.com/WebAssembly/wasi-filesystem/issues/17#issuecomment-1430639353
42 InvalidUtf8,
43} || posix.UnexpectedError;39} || posix.UnexpectedError;
4440
45pub const Iterator = switch (native_os) {41pub const Iterator = switch (native_os) {
...@@ -553,7 +549,6 @@ pub const Iterator = switch (native_os) {...@@ -553,7 +549,6 @@ pub const Iterator = switch (native_os) {
553 .INVAL => unreachable,549 .INVAL => unreachable,
554 .NOENT => return error.DirNotFound, // The directory being iterated was deleted during iteration.550 .NOENT => return error.DirNotFound, // The directory being iterated was deleted during iteration.
555 .NOTCAPABLE => return error.AccessDenied,551 .NOTCAPABLE => return error.AccessDenied,
556 .ILSEQ => return error.InvalidUtf8, // An entry's name cannot be encoded as UTF-8.
557 else => |err| return posix.unexpectedErrno(err),552 else => |err| return posix.unexpectedErrno(err),
558 }553 }
559 if (bufused == 0) return null;554 if (bufused == 0) return null;
...@@ -844,28 +839,7 @@ pub fn walk(self: Dir, allocator: Allocator) Allocator.Error!Walker {...@@ -844,28 +839,7 @@ pub fn walk(self: Dir, allocator: Allocator) Allocator.Error!Walker {
844 };839 };
845}840}
846841
847pub const OpenError = error{842pub const OpenError = Io.Dir.OpenError;
848 FileNotFound,
849 NotDir,
850 AccessDenied,
851 PermissionDenied,
852 SymLinkLoop,
853 ProcessFdQuotaExceeded,
854 NameTooLong,
855 SystemFdQuotaExceeded,
856 NoDevice,
857 SystemResources,
858 /// WASI-only; file paths must be valid UTF-8.
859 InvalidUtf8,
860 /// Windows-only; file paths provided by the user must be valid WTF-8.
861 /// https://wtf-8.codeberg.page/
862 InvalidWtf8,
863 BadPathName,
864 DeviceBusy,
865 /// On Windows, `\\server` or `\\server\share` was not found.
866 NetworkNotFound,
867 ProcessNotFound,
868} || posix.UnexpectedError;
869843
870pub fn close(self: *Dir) void {844pub fn close(self: *Dir) void {
871 posix.close(self.fd);845 posix.close(self.fd);
...@@ -1311,7 +1285,7 @@ pub fn makeOpenPath(self: Dir, sub_path: []const u8, open_dir_options: OpenOptio...@@ -1311,7 +1285,7 @@ pub fn makeOpenPath(self: Dir, sub_path: []const u8, open_dir_options: OpenOptio
1311 };1285 };
1312}1286}
13131287
1314pub const RealPathError = posix.RealPathError;1288pub const RealPathError = posix.RealPathError || error{Canceled};
13151289
1316/// This function returns the canonicalized absolute pathname of1290/// This function returns the canonicalized absolute pathname of
1317/// `pathname` relative to this `Dir`. If `pathname` is absolute, ignores this1291/// `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...@@ -1369,7 +1343,6 @@ pub fn realpathZ(self: Dir, pathname: [*:0]const u8, out_buffer: []u8) RealPathE
1369 error.FileLocksNotSupported => return error.Unexpected,1343 error.FileLocksNotSupported => return error.Unexpected,
1370 error.FileBusy => return error.Unexpected,1344 error.FileBusy => return error.Unexpected,
1371 error.WouldBlock => return error.Unexpected,1345 error.WouldBlock => return error.Unexpected,
1372 error.InvalidUtf8 => unreachable, // WASI-only
1373 else => |e| return e,1346 else => |e| return e,
1374 };1347 };
1375 defer posix.close(fd);1348 defer posix.close(fd);
...@@ -1639,6 +1612,10 @@ fn openDirFlagsZ(self: Dir, sub_path_c: [*:0]const u8, flags: posix.O) OpenError...@@ -1639,6 +1612,10 @@ fn openDirFlagsZ(self: Dir, sub_path_c: [*:0]const u8, flags: posix.O) OpenError
1639 error.FileLocksNotSupported => unreachable, // locking folders is not supported1612 error.FileLocksNotSupported => unreachable, // locking folders is not supported
1640 error.WouldBlock => unreachable, // can't happen for directories1613 error.WouldBlock => unreachable, // can't happen for directories
1641 error.FileBusy => unreachable, // can't happen for directories1614 error.FileBusy => unreachable, // can't happen for directories
1615 error.SharingViolation => unreachable, // can't happen for directories
1616 error.PipeBusy => unreachable, // can't happen for directories
1617 error.AntivirusInterference => unreachable, // can't happen for directories
1618 error.ProcessNotFound => unreachable, // can't happen for directories
1642 else => |e| return e,1619 else => |e| return e,
1643 };1620 };
1644 return Dir{ .fd = fd };1621 return Dir{ .fd = fd };
...@@ -2095,24 +2072,21 @@ pub const DeleteTreeError = error{...@@ -2095,24 +2072,21 @@ pub const DeleteTreeError = error{
2095 FileBusy,2072 FileBusy,
2096 DeviceBusy,2073 DeviceBusy,
2097 ProcessNotFound,2074 ProcessNotFound,
2098
2099 /// One of the path components was not a directory.2075 /// One of the path components was not a directory.
2100 /// This error is unreachable if `sub_path` does not contain a path separator.2076 /// This error is unreachable if `sub_path` does not contain a path separator.
2101 NotDir,2077 NotDir,
2102
2103 /// WASI-only; file paths must be valid UTF-8.2078 /// WASI-only; file paths must be valid UTF-8.
2104 InvalidUtf8,2079 InvalidUtf8,
2105
2106 /// Windows-only; file paths provided by the user must be valid WTF-8.2080 /// Windows-only; file paths provided by the user must be valid WTF-8.
2107 /// https://wtf-8.codeberg.page/2081 /// https://wtf-8.codeberg.page/
2108 InvalidWtf8,2082 InvalidWtf8,
2109
2110 /// On Windows, file paths cannot contain these characters:2083 /// On Windows, file paths cannot contain these characters:
2111 /// '/', '*', '?', '"', '<', '>', '|'2084 /// '/', '*', '?', '"', '<', '>', '|'
2112 BadPathName,2085 BadPathName,
2113
2114 /// On Windows, `\\server` or `\\server\share` was not found.2086 /// On Windows, `\\server` or `\\server\share` was not found.
2115 NetworkNotFound,2087 NetworkNotFound,
2088
2089 Canceled,
2116} || posix.UnexpectedError;2090} || posix.UnexpectedError;
21172091
2118/// Whether `sub_path` describes a symlink, file, or directory, this function2092/// 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 {...@@ -2169,17 +2143,15 @@ pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void {
2169 error.PermissionDenied,2143 error.PermissionDenied,
2170 error.SymLinkLoop,2144 error.SymLinkLoop,
2171 error.ProcessFdQuotaExceeded,2145 error.ProcessFdQuotaExceeded,
2172 error.ProcessNotFound,
2173 error.NameTooLong,2146 error.NameTooLong,
2174 error.SystemFdQuotaExceeded,2147 error.SystemFdQuotaExceeded,
2175 error.NoDevice,2148 error.NoDevice,
2176 error.SystemResources,2149 error.SystemResources,
2177 error.Unexpected,2150 error.Unexpected,
2178 error.InvalidUtf8,
2179 error.InvalidWtf8,
2180 error.BadPathName,2151 error.BadPathName,
2181 error.NetworkNotFound,2152 error.NetworkNotFound,
2182 error.DeviceBusy,2153 error.DeviceBusy,
2154 error.Canceled,
2183 => |e| return e,2155 => |e| return e,
2184 };2156 };
2185 stack.appendAssumeCapacity(.{2157 stack.appendAssumeCapacity(.{
...@@ -2266,18 +2238,16 @@ pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void {...@@ -2266,18 +2238,16 @@ pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void {
2266 error.AccessDenied,2238 error.AccessDenied,
2267 error.PermissionDenied,2239 error.PermissionDenied,
2268 error.SymLinkLoop,2240 error.SymLinkLoop,
2269 error.ProcessNotFound,
2270 error.ProcessFdQuotaExceeded,2241 error.ProcessFdQuotaExceeded,
2271 error.NameTooLong,2242 error.NameTooLong,
2272 error.SystemFdQuotaExceeded,2243 error.SystemFdQuotaExceeded,
2273 error.NoDevice,2244 error.NoDevice,
2274 error.SystemResources,2245 error.SystemResources,
2275 error.Unexpected,2246 error.Unexpected,
2276 error.InvalidUtf8,
2277 error.InvalidWtf8,
2278 error.BadPathName,2247 error.BadPathName,
2279 error.NetworkNotFound,2248 error.NetworkNotFound,
2280 error.DeviceBusy,2249 error.DeviceBusy,
2250 error.Canceled,
2281 => |e| return e,2251 => |e| return e,
2282 };2252 };
2283 } else {2253 } else {
...@@ -2374,18 +2344,16 @@ fn deleteTreeMinStackSizeWithKindHint(self: Dir, sub_path: []const u8, kind_hint...@@ -2374,18 +2344,16 @@ fn deleteTreeMinStackSizeWithKindHint(self: Dir, sub_path: []const u8, kind_hint
2374 error.AccessDenied,2344 error.AccessDenied,
2375 error.PermissionDenied,2345 error.PermissionDenied,
2376 error.SymLinkLoop,2346 error.SymLinkLoop,
2377 error.ProcessNotFound,
2378 error.ProcessFdQuotaExceeded,2347 error.ProcessFdQuotaExceeded,
2379 error.NameTooLong,2348 error.NameTooLong,
2380 error.SystemFdQuotaExceeded,2349 error.SystemFdQuotaExceeded,
2381 error.NoDevice,2350 error.NoDevice,
2382 error.SystemResources,2351 error.SystemResources,
2383 error.Unexpected,2352 error.Unexpected,
2384 error.InvalidUtf8,
2385 error.InvalidWtf8,
2386 error.BadPathName,2353 error.BadPathName,
2387 error.NetworkNotFound,2354 error.NetworkNotFound,
2388 error.DeviceBusy,2355 error.DeviceBusy,
2356 error.Canceled,
2389 => |e| return e,2357 => |e| return e,
2390 };2358 };
2391 if (cleanup_dir_parent) |*d| d.close();2359 if (cleanup_dir_parent) |*d| d.close();
...@@ -2476,17 +2444,15 @@ fn deleteTreeOpenInitialSubpath(self: Dir, sub_path: []const u8, kind_hint: File...@@ -2476,17 +2444,15 @@ fn deleteTreeOpenInitialSubpath(self: Dir, sub_path: []const u8, kind_hint: File
2476 error.PermissionDenied,2444 error.PermissionDenied,
2477 error.SymLinkLoop,2445 error.SymLinkLoop,
2478 error.ProcessFdQuotaExceeded,2446 error.ProcessFdQuotaExceeded,
2479 error.ProcessNotFound,
2480 error.NameTooLong,2447 error.NameTooLong,
2481 error.SystemFdQuotaExceeded,2448 error.SystemFdQuotaExceeded,
2482 error.NoDevice,2449 error.NoDevice,
2483 error.SystemResources,2450 error.SystemResources,
2484 error.Unexpected,2451 error.Unexpected,
2485 error.InvalidUtf8,
2486 error.InvalidWtf8,
2487 error.BadPathName,2452 error.BadPathName,
2488 error.DeviceBusy,2453 error.DeviceBusy,
2489 error.NetworkNotFound,2454 error.NetworkNotFound,
2455 error.Canceled,
2490 => |e| return e,2456 => |e| return e,
2491 };2457 };
2492 } else {2458 } else {
...@@ -2589,7 +2555,7 @@ pub const CopyFileOptions = struct {...@@ -2589,7 +2555,7 @@ pub const CopyFileOptions = struct {
25892555
2590pub const CopyFileError = File.OpenError || File.StatError ||2556pub const CopyFileError = File.OpenError || File.StatError ||
2591 AtomicFile.InitError || AtomicFile.FinishError ||2557 AtomicFile.InitError || AtomicFile.FinishError ||
2592 File.ReadError || File.WriteError;2558 File.ReadError || File.WriteError || error{InvalidFileName};
25932559
2594/// Atomically creates a new file at `dest_path` within `dest_dir` with the2560/// Atomically creates a new file at `dest_path` within `dest_dir` with the
2595/// same contents as `source_path` within `source_dir`, overwriting any already2561/// 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 {...@@ -2690,33 +2656,9 @@ pub fn statFile(self: Dir, sub_path: []const u8) StatFileError!Stat {
2690 const st = try std.os.fstatat_wasi(self.fd, sub_path, .{ .SYMLINK_FOLLOW = true });2656 const st = try std.os.fstatat_wasi(self.fd, sub_path, .{ .SYMLINK_FOLLOW = true });
2691 return Stat.fromWasi(st);2657 return Stat.fromWasi(st);
2692 }2658 }
2693 if (native_os == .linux) {2659 var threaded: Io.Threaded = .init_single_threaded;
2694 const sub_path_c = try posix.toPosixPath(sub_path);2660 const io = threaded.io();
2695 var stx = std.mem.zeroes(linux.Statx);2661 return Io.Dir.statPath(.{ .handle = self.fd }, io, sub_path, .{});
2696
2697 const rc = linux.statx(
2698 self.fd,
2699 &sub_path_c,
2700 linux.AT.NO_AUTOMOUNT,
2701 linux.STATX_TYPE | linux.STATX_MODE | linux.STATX_ATIME | linux.STATX_MTIME | linux.STATX_CTIME,
2702 &stx,
2703 );
2704
2705 return switch (linux.E.init(rc)) {
2706 .SUCCESS => Stat.fromLinux(stx),
2707 .ACCES => error.AccessDenied,
2708 .BADF => unreachable,
2709 .FAULT => unreachable,
2710 .INVAL => unreachable,
2711 .LOOP => error.SymLinkLoop,
2712 .NAMETOOLONG => unreachable, // Handled by posix.toPosixPath() above.
2713 .NOENT, .NOTDIR => error.FileNotFound,
2714 .NOMEM => error.SystemResources,
2715 else => |err| posix.unexpectedErrno(err),
2716 };
2717 }
2718 const st = try posix.fstatat(self.fd, sub_path, 0);
2719 return Stat.fromPosix(st);
2720}2662}
27212663
2722pub const ChmodError = File.ChmodError;2664pub const ChmodError = File.ChmodError;
lib/std/fs/File.zig+2-27
...@@ -38,33 +38,8 @@ pub const default_mode = switch (builtin.os.tag) {...@@ -38,33 +38,8 @@ pub const default_mode = switch (builtin.os.tag) {
38 else => 0o666,38 else => 0o666,
39};39};
4040
41pub const OpenError = error{41/// Deprecated in favor of `Io.File.OpenError`.
42 SharingViolation,42pub const OpenError = Io.File.OpenError || error{WouldBlock};
43 PathAlreadyExists,
44 FileNotFound,
45 AccessDenied,
46 PipeBusy,
47 NoDevice,
48 NameTooLong,
49 /// WASI-only; file paths must be valid UTF-8.
50 InvalidUtf8,
51 /// Windows-only; file paths provided by the user must be valid WTF-8.
52 /// https://wtf-8.codeberg.page/
53 InvalidWtf8,
54 /// On Windows, file paths cannot contain these characters:
55 /// '/', '*', '?', '"', '<', '>', '|'
56 BadPathName,
57 Unexpected,
58 /// On Windows, `\\server` or `\\server\share` was not found.
59 NetworkNotFound,
60 ProcessNotFound,
61 /// On Windows, antivirus software is enabled by default. It can be
62 /// disabled, but Windows Update sometimes ignores the user's preference
63 /// and re-enables it. When enabled, antivirus software on Windows
64 /// intercepts file system operations and makes them significantly slower
65 /// in addition to possibly failing with this error code.
66 AntivirusInterference,
67} || posix.OpenError || posix.FlockError;
6843
69pub const OpenMode = enum {44pub const OpenMode = enum {
70 read_only,45 read_only,
lib/std/posix.zig+1-75
...@@ -1542,81 +1542,7 @@ pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) PWriteError!usiz...@@ -1542,81 +1542,7 @@ pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) PWriteError!usiz
1542 }1542 }
1543}1543}
15441544
1545pub const OpenError = error{1545pub const OpenError = std.Io.File.OpenError || error{WouldBlock};
1546 /// In WASI, this error may occur when the file descriptor does
1547 /// not hold the required rights to open a new resource relative to it.
1548 AccessDenied,
1549 PermissionDenied,
1550 SymLinkLoop,
1551 ProcessFdQuotaExceeded,
1552 SystemFdQuotaExceeded,
1553 NoDevice,
1554 /// Either:
1555 /// * One of the path components does not exist.
1556 /// * Cwd was used, but cwd has been deleted.
1557 /// * The path associated with the open directory handle has been deleted.
1558 /// * On macOS, multiple processes or threads raced to create the same file
1559 /// with `O.EXCL` set to `false`.
1560 FileNotFound,
1561
1562 /// The path exceeded `max_path_bytes` bytes.
1563 NameTooLong,
1564
1565 /// Insufficient kernel memory was available, or
1566 /// the named file is a FIFO and per-user hard limit on
1567 /// memory allocation for pipes has been reached.
1568 SystemResources,
1569
1570 /// The file is too large to be opened. This error is unreachable
1571 /// for 64-bit targets, as well as when opening directories.
1572 FileTooBig,
1573
1574 /// The path refers to directory but the `DIRECTORY` flag was not provided.
1575 IsDir,
1576
1577 /// A new path cannot be created because the device has no room for the new file.
1578 /// This error is only reachable when the `CREAT` flag is provided.
1579 NoSpaceLeft,
1580
1581 /// A component used as a directory in the path was not, in fact, a directory, or
1582 /// `DIRECTORY` was specified and the path was not a directory.
1583 NotDir,
1584
1585 /// The path already exists and the `CREAT` and `EXCL` flags were provided.
1586 PathAlreadyExists,
1587 DeviceBusy,
1588
1589 /// The underlying filesystem does not support file locks
1590 FileLocksNotSupported,
1591
1592 /// Path contains characters that are disallowed by the underlying filesystem.
1593 BadPathName,
1594
1595 /// WASI-only; file paths must be valid UTF-8.
1596 InvalidUtf8,
1597
1598 /// Windows-only; file paths provided by the user must be valid WTF-8.
1599 /// https://wtf-8.codeberg.page/
1600 InvalidWtf8,
1601
1602 /// On Windows, `\\server` or `\\server\share` was not found.
1603 NetworkNotFound,
1604
1605 /// This error occurs in Linux if the process to be open was not found.
1606 ProcessNotFound,
1607
1608 /// One of these three things:
1609 /// * pathname refers to an executable image which is currently being
1610 /// executed and write access was requested.
1611 /// * pathname refers to a file that is currently in use as a swap
1612 /// file, and the O_TRUNC flag was specified.
1613 /// * pathname refers to a file that is currently being read by the
1614 /// kernel (e.g., for module/firmware loading), and write access was
1615 /// requested.
1616 FileBusy,
1617
1618 WouldBlock,
1619} || UnexpectedError;
16201546
1621/// Open and possibly create a file. Keeps trying if it gets interrupted.1547/// Open and possibly create a file. Keeps trying if it gets interrupted.
1622/// On Windows, `file_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).1548/// On Windows, `file_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
lib/std/zig/system.zig+44-55
...@@ -766,27 +766,23 @@ test glibcVerFromLinkName {...@@ -766,27 +766,23 @@ test glibcVerFromLinkName {
766766
767fn glibcVerFromRPath(io: Io, rpath: []const u8) !std.SemanticVersion {767fn glibcVerFromRPath(io: Io, rpath: []const u8) !std.SemanticVersion {
768 var dir = fs.cwd().openDir(rpath, .{}) catch |err| switch (err) {768 var dir = fs.cwd().openDir(rpath, .{}) catch |err| switch (err) {
769 error.NameTooLong => unreachable,769 error.NameTooLong => return error.Unexpected,
770 error.InvalidUtf8 => unreachable, // WASI only770 error.BadPathName => return error.Unexpected,
771 error.InvalidWtf8 => unreachable, // Windows-only771 error.DeviceBusy => return error.Unexpected,
772 error.BadPathName => unreachable,772 error.NetworkNotFound => return error.Unexpected, // Windows-only
773 error.DeviceBusy => unreachable,773
774 error.NetworkNotFound => unreachable, // Windows-only774 error.FileNotFound => return error.GLibCNotFound,
775775 error.NotDir => return error.GLibCNotFound,
776 error.FileNotFound,776 error.AccessDenied => return error.GLibCNotFound,
777 error.NotDir,777 error.PermissionDenied => return error.GLibCNotFound,
778 error.AccessDenied,778 error.NoDevice => return error.GLibCNotFound,
779 error.PermissionDenied,779
780 error.NoDevice,780 error.ProcessFdQuotaExceeded => |e| return e,
781 => return error.GLibCNotFound,781 error.SystemFdQuotaExceeded => |e| return e,
782782 error.SystemResources => |e| return e,
783 error.ProcessNotFound,783 error.SymLinkLoop => |e| return e,
784 error.ProcessFdQuotaExceeded,784 error.Unexpected => |e| return e,
785 error.SystemFdQuotaExceeded,785 error.Canceled => |e| return e,
786 error.SystemResources,
787 error.SymLinkLoop,
788 error.Unexpected,
789 => |e| return e,
790 };786 };
791 defer dir.close();787 defer dir.close();
792788
...@@ -799,38 +795,34 @@ fn glibcVerFromRPath(io: Io, rpath: []const u8) !std.SemanticVersion {...@@ -799,38 +795,34 @@ fn glibcVerFromRPath(io: Io, rpath: []const u8) !std.SemanticVersion {
799 // that start with "GLIBC_2.".795 // that start with "GLIBC_2.".
800 const glibc_so_basename = "libc.so.6";796 const glibc_so_basename = "libc.so.6";
801 var file = dir.openFile(glibc_so_basename, .{}) catch |err| switch (err) {797 var file = dir.openFile(glibc_so_basename, .{}) catch |err| switch (err) {
802 error.NameTooLong => unreachable,798 error.NameTooLong => return error.Unexpected,
803 error.InvalidUtf8 => unreachable, // WASI only799 error.BadPathName => return error.Unexpected,
804 error.InvalidWtf8 => unreachable, // Windows only800 error.PipeBusy => return error.Unexpected, // Windows-only
805 error.BadPathName => unreachable, // Windows only801 error.SharingViolation => return error.Unexpected, // Windows-only
806 error.PipeBusy => unreachable, // Windows-only802 error.NetworkNotFound => return error.Unexpected, // Windows-only
807 error.SharingViolation => unreachable, // Windows-only803 error.AntivirusInterference => return error.Unexpected, // Windows-only
808 error.NetworkNotFound => unreachable, // Windows-only804 error.FileLocksNotSupported => return error.Unexpected, // No lock requested.
809 error.AntivirusInterference => unreachable, // Windows-only805 error.NoSpaceLeft => return error.Unexpected, // read-only
810 error.FileLocksNotSupported => unreachable, // No lock requested.806 error.PathAlreadyExists => return error.Unexpected, // read-only
811 error.NoSpaceLeft => unreachable, // read-only807 error.DeviceBusy => return error.Unexpected, // read-only
812 error.PathAlreadyExists => unreachable, // read-only808 error.FileBusy => return error.Unexpected, // read-only
813 error.DeviceBusy => unreachable, // read-only809 error.NoDevice => return error.Unexpected, // not asking for a special device
814 error.FileBusy => unreachable, // read-only
815 error.WouldBlock => unreachable, // not using O_NONBLOCK
816 error.NoDevice => unreachable, // not asking for a special device
817
818 error.AccessDenied,
819 error.PermissionDenied,
820 error.FileNotFound,
821 error.NotDir,
822 error.IsDir,
823 => return error.GLibCNotFound,
824
825 error.FileTooBig => return error.Unexpected,810 error.FileTooBig => return error.Unexpected,
826811 error.WouldBlock => return error.Unexpected, // not opened in non-blocking
827 error.ProcessNotFound,812
828 error.ProcessFdQuotaExceeded,813 error.AccessDenied => return error.GLibCNotFound,
829 error.SystemFdQuotaExceeded,814 error.PermissionDenied => return error.GLibCNotFound,
830 error.SystemResources,815 error.FileNotFound => return error.GLibCNotFound,
831 error.SymLinkLoop,816 error.NotDir => return error.GLibCNotFound,
832 error.Unexpected,817 error.IsDir => return error.GLibCNotFound,
833 => |e| return e,818
819 error.ProcessNotFound => |e| return e,
820 error.ProcessFdQuotaExceeded => |e| return e,
821 error.SystemFdQuotaExceeded => |e| return e,
822 error.SystemResources => |e| return e,
823 error.SymLinkLoop => |e| return e,
824 error.Unexpected => |e| return e,
825 error.Canceled => |e| return e,
834 };826 };
835 defer file.close();827 defer file.close();
836828
...@@ -1016,12 +1008,9 @@ fn detectAbiAndDynamicLinker(io: Io, cpu: Target.Cpu, os: Target.Os, query: Targ...@@ -1016,12 +1008,9 @@ fn detectAbiAndDynamicLinker(io: Io, cpu: Target.Cpu, os: Target.Os, query: Targ
1016 error.NameTooLong => return error.Unexpected,1008 error.NameTooLong => return error.Unexpected,
1017 error.PathAlreadyExists => return error.Unexpected,1009 error.PathAlreadyExists => return error.Unexpected,
1018 error.SharingViolation => return error.Unexpected,1010 error.SharingViolation => return error.Unexpected,
1019 error.InvalidUtf8 => return error.Unexpected, // WASI only
1020 error.InvalidWtf8 => return error.Unexpected, // Windows only
1021 error.BadPathName => return error.Unexpected,1011 error.BadPathName => return error.Unexpected,
1022 error.PipeBusy => return error.Unexpected,1012 error.PipeBusy => return error.Unexpected,
1023 error.FileLocksNotSupported => return error.Unexpected,1013 error.FileLocksNotSupported => return error.Unexpected,
1024 error.WouldBlock => return error.Unexpected,
1025 error.FileBusy => return error.Unexpected, // opened without write permissions1014 error.FileBusy => return error.Unexpected, // opened without write permissions
1026 error.AntivirusInterference => return error.Unexpected, // Windows-only error1015 error.AntivirusInterference => return error.Unexpected, // Windows-only error
10271016