authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-11-23 01:44:07-05:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2023-11-23 01:44:07-05:00
logf4e426a06c59922de80a572f8346f2797726b392
treef979587285f09c8b857e4a3e5dfe8aaacded7f52
parent6fc20b3b092a5717caa288c107370292580f0e2c
parent0a536a7c9020c891001be053a4c0b354961cc46f
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #18076 from ziglang/revert-iterable-dir

std.fs: Absorb `IterableDir` into `Dir`

24 files changed, 4404 insertions(+), 4445 deletions(-)

CMakeLists.txt+3-1
...@@ -247,7 +247,9 @@ set(ZIG_STAGE2_SOURCES...@@ -247,7 +247,9 @@ set(ZIG_STAGE2_SOURCES
247 "${CMAKE_SOURCE_DIR}/lib/std/fmt/errol/lookup.zig"247 "${CMAKE_SOURCE_DIR}/lib/std/fmt/errol/lookup.zig"
248 "${CMAKE_SOURCE_DIR}/lib/std/fmt/parse_float.zig"248 "${CMAKE_SOURCE_DIR}/lib/std/fmt/parse_float.zig"
249 "${CMAKE_SOURCE_DIR}/lib/std/fs.zig"249 "${CMAKE_SOURCE_DIR}/lib/std/fs.zig"
250 "${CMAKE_SOURCE_DIR}/lib/std/fs/file.zig"250 "${CMAKE_SOURCE_DIR}/lib/std/fs/AtomicFile.zig"
251 "${CMAKE_SOURCE_DIR}/lib/std/fs/Dir.zig"
252 "${CMAKE_SOURCE_DIR}/lib/std/fs/File.zig"
251 "${CMAKE_SOURCE_DIR}/lib/std/fs/get_app_data_dir.zig"253 "${CMAKE_SOURCE_DIR}/lib/std/fs/get_app_data_dir.zig"
252 "${CMAKE_SOURCE_DIR}/lib/std/fs/path.zig"254 "${CMAKE_SOURCE_DIR}/lib/std/fs/path.zig"
253 "${CMAKE_SOURCE_DIR}/lib/std/hash.zig"255 "${CMAKE_SOURCE_DIR}/lib/std/hash.zig"
lib/std/Build/Step/InstallDir.zig+1-1
...@@ -69,7 +69,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {...@@ -69,7 +69,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
69 const dest_prefix = dest_builder.getInstallPath(self.options.install_dir, self.options.install_subdir);69 const dest_prefix = dest_builder.getInstallPath(self.options.install_dir, self.options.install_subdir);
70 const src_builder = self.step.owner;70 const src_builder = self.step.owner;
71 const src_dir_path = self.options.source_dir.getPath2(src_builder, step);71 const src_dir_path = self.options.source_dir.getPath2(src_builder, step);
72 var src_dir = src_builder.build_root.handle.openIterableDir(src_dir_path, .{}) catch |err| {72 var src_dir = src_builder.build_root.handle.openDir(src_dir_path, .{ .iterate = true }) catch |err| {
73 return step.fail("unable to open source directory '{}{s}': {s}", .{73 return step.fail("unable to open source directory '{}{s}': {s}", .{
74 src_builder.build_root, src_dir_path, @errorName(err),74 src_builder.build_root, src_dir_path, @errorName(err),
75 });75 });
lib/std/child_process.zig+2-1
...@@ -976,7 +976,8 @@ fn windowsCreateProcessPathExt(...@@ -976,7 +976,8 @@ fn windowsCreateProcessPathExt(
976 defer dir_buf.shrinkRetainingCapacity(dir_path_len);976 defer dir_buf.shrinkRetainingCapacity(dir_path_len);
977 const dir_path_z = dir_buf.items[0 .. dir_buf.items.len - 1 :0];977 const dir_path_z = dir_buf.items[0 .. dir_buf.items.len - 1 :0];
978 const prefixed_path = try windows.wToPrefixedFileW(null, dir_path_z);978 const prefixed_path = try windows.wToPrefixedFileW(null, dir_path_z);
979 break :dir fs.cwd().openDirW(prefixed_path.span().ptr, .{}, true) catch return error.FileNotFound;979 break :dir fs.cwd().openDirW(prefixed_path.span().ptr, .{ .iterate = true }) catch
980 return error.FileNotFound;
980 };981 };
981 defer dir.close();982 defer dir.close();
982983
lib/std/crypto/Certificate/Bundle.zig+4-4
...@@ -160,7 +160,7 @@ pub fn addCertsFromDirPath(...@@ -160,7 +160,7 @@ pub fn addCertsFromDirPath(
160 dir: fs.Dir,160 dir: fs.Dir,
161 sub_dir_path: []const u8,161 sub_dir_path: []const u8,
162) AddCertsFromDirPathError!void {162) AddCertsFromDirPathError!void {
163 var iterable_dir = try dir.openIterableDir(sub_dir_path, .{});163 var iterable_dir = try dir.openDir(sub_dir_path, .{ .iterate = true });
164 defer iterable_dir.close();164 defer iterable_dir.close();
165 return addCertsFromDir(cb, gpa, iterable_dir);165 return addCertsFromDir(cb, gpa, iterable_dir);
166}166}
...@@ -171,14 +171,14 @@ pub fn addCertsFromDirPathAbsolute(...@@ -171,14 +171,14 @@ pub fn addCertsFromDirPathAbsolute(
171 abs_dir_path: []const u8,171 abs_dir_path: []const u8,
172) AddCertsFromDirPathError!void {172) AddCertsFromDirPathError!void {
173 assert(fs.path.isAbsolute(abs_dir_path));173 assert(fs.path.isAbsolute(abs_dir_path));
174 var iterable_dir = try fs.openIterableDirAbsolute(abs_dir_path, .{});174 var iterable_dir = try fs.openDirAbsolute(abs_dir_path, .{ .iterate = true });
175 defer iterable_dir.close();175 defer iterable_dir.close();
176 return addCertsFromDir(cb, gpa, iterable_dir);176 return addCertsFromDir(cb, gpa, iterable_dir);
177}177}
178178
179pub const AddCertsFromDirError = AddCertsFromFilePathError;179pub const AddCertsFromDirError = AddCertsFromFilePathError;
180180
181pub fn addCertsFromDir(cb: *Bundle, gpa: Allocator, iterable_dir: fs.IterableDir) AddCertsFromDirError!void {181pub fn addCertsFromDir(cb: *Bundle, gpa: Allocator, iterable_dir: fs.Dir) AddCertsFromDirError!void {
182 var it = iterable_dir.iterate();182 var it = iterable_dir.iterate();
183 while (try it.next()) |entry| {183 while (try it.next()) |entry| {
184 switch (entry.kind) {184 switch (entry.kind) {
...@@ -186,7 +186,7 @@ pub fn addCertsFromDir(cb: *Bundle, gpa: Allocator, iterable_dir: fs.IterableDir...@@ -186,7 +186,7 @@ pub fn addCertsFromDir(cb: *Bundle, gpa: Allocator, iterable_dir: fs.IterableDir
186 else => continue,186 else => continue,
187 }187 }
188188
189 try addCertsFromFilePath(cb, gpa, iterable_dir.dir, entry.name);189 try addCertsFromFilePath(cb, gpa, iterable_dir, entry.name);
190 }190 }
191}191}
192192
lib/std/fs.zig+38-2671
...@@ -7,17 +7,19 @@ const base64 = std.base64;...@@ -7,17 +7,19 @@ const base64 = std.base64;
7const crypto = std.crypto;7const crypto = std.crypto;
8const Allocator = std.mem.Allocator;8const Allocator = std.mem.Allocator;
9const assert = std.debug.assert;9const assert = std.debug.assert;
10const math = std.math;
1110
12const is_darwin = builtin.os.tag.isDarwin();11const is_darwin = builtin.os.tag.isDarwin();
1312
13pub const AtomicFile = @import("fs/AtomicFile.zig");
14pub const Dir = @import("fs/Dir.zig");
15pub const File = @import("fs/File.zig");
16pub const path = @import("fs/path.zig");
17
14pub const has_executable_bit = switch (builtin.os.tag) {18pub const has_executable_bit = switch (builtin.os.tag) {
15 .windows, .wasi => false,19 .windows, .wasi => false,
16 else => true,20 else => true,
17};21};
1822
19pub const path = @import("fs/path.zig");
20pub const File = @import("fs/file.zig").File;
21pub const wasi = @import("fs/wasi.zig");23pub const wasi = @import("fs/wasi.zig");
2224
23// TODO audit these APIs with respect to Dir and absolute paths25// TODO audit these APIs with respect to Dir and absolute paths
...@@ -92,6 +94,7 @@ pub const need_async_thread = std.io.is_async and switch (builtin.os.tag) {...@@ -92,6 +94,7 @@ pub const need_async_thread = std.io.is_async and switch (builtin.os.tag) {
92};94};
9395
94/// TODO remove the allocator requirement from this API96/// TODO remove the allocator requirement from this API
97/// TODO move to Dir
95pub fn atomicSymLink(allocator: Allocator, existing_path: []const u8, new_path: []const u8) !void {98pub fn atomicSymLink(allocator: Allocator, existing_path: []const u8, new_path: []const u8) !void {
96 if (cwd().symLink(existing_path, new_path, .{})) {99 if (cwd().symLink(existing_path, new_path, .{})) {
97 return;100 return;
...@@ -102,7 +105,7 @@ pub fn atomicSymLink(allocator: Allocator, existing_path: []const u8, new_path:...@@ -102,7 +105,7 @@ pub fn atomicSymLink(allocator: Allocator, existing_path: []const u8, new_path:
102105
103 const dirname = path.dirname(new_path) orelse ".";106 const dirname = path.dirname(new_path) orelse ".";
104107
105 var rand_buf: [AtomicFile.RANDOM_BYTES]u8 = undefined;108 var rand_buf: [AtomicFile.random_bytes_len]u8 = undefined;
106 const tmp_path = try allocator.alloc(u8, dirname.len + 1 + base64_encoder.calcSize(rand_buf.len));109 const tmp_path = try allocator.alloc(u8, dirname.len + 1 + base64_encoder.calcSize(rand_buf.len));
107 defer allocator.free(tmp_path);110 defer allocator.free(tmp_path);
108 @memcpy(tmp_path[0..dirname.len], dirname);111 @memcpy(tmp_path[0..dirname.len], dirname);
...@@ -120,24 +123,14 @@ pub fn atomicSymLink(allocator: Allocator, existing_path: []const u8, new_path:...@@ -120,24 +123,14 @@ pub fn atomicSymLink(allocator: Allocator, existing_path: []const u8, new_path:
120 }123 }
121}124}
122125
123pub const PrevStatus = enum {
124 stale,
125 fresh,
126};
127
128pub const CopyFileOptions = struct {
129 /// When this is `null` the mode is copied from the source file.
130 override_mode: ?File.Mode = null,
131};
132
133/// Same as `Dir.updateFile`, except asserts that both `source_path` and `dest_path`126/// Same as `Dir.updateFile`, except asserts that both `source_path` and `dest_path`
134/// are absolute. See `Dir.updateFile` for a function that operates on both127/// are absolute. See `Dir.updateFile` for a function that operates on both
135/// absolute and relative paths.128/// absolute and relative paths.
136pub fn updateFileAbsolute(129pub fn updateFileAbsolute(
137 source_path: []const u8,130 source_path: []const u8,
138 dest_path: []const u8,131 dest_path: []const u8,
139 args: CopyFileOptions,132 args: Dir.CopyFileOptions,
140) !PrevStatus {133) !Dir.PrevStatus {
141 assert(path.isAbsolute(source_path));134 assert(path.isAbsolute(source_path));
142 assert(path.isAbsolute(dest_path));135 assert(path.isAbsolute(dest_path));
143 const my_cwd = cwd();136 const my_cwd = cwd();
...@@ -147,112 +140,35 @@ pub fn updateFileAbsolute(...@@ -147,112 +140,35 @@ pub fn updateFileAbsolute(
147/// Same as `Dir.copyFile`, except asserts that both `source_path` and `dest_path`140/// Same as `Dir.copyFile`, except asserts that both `source_path` and `dest_path`
148/// are absolute. See `Dir.copyFile` for a function that operates on both141/// are absolute. See `Dir.copyFile` for a function that operates on both
149/// absolute and relative paths.142/// absolute and relative paths.
150pub fn copyFileAbsolute(source_path: []const u8, dest_path: []const u8, args: CopyFileOptions) !void {143pub fn copyFileAbsolute(
144 source_path: []const u8,
145 dest_path: []const u8,
146 args: Dir.CopyFileOptions,
147) !void {
151 assert(path.isAbsolute(source_path));148 assert(path.isAbsolute(source_path));
152 assert(path.isAbsolute(dest_path));149 assert(path.isAbsolute(dest_path));
153 const my_cwd = cwd();150 const my_cwd = cwd();
154 return Dir.copyFile(my_cwd, source_path, my_cwd, dest_path, args);151 return Dir.copyFile(my_cwd, source_path, my_cwd, dest_path, args);
155}152}
156153
157pub const AtomicFile = struct {
158 file: File,
159 // TODO either replace this with rand_buf or use []u16 on Windows
160 tmp_path_buf: [TMP_PATH_LEN:0]u8,
161 dest_basename: []const u8,
162 file_open: bool,
163 file_exists: bool,
164 close_dir_on_deinit: bool,
165 dir: Dir,
166
167 const InitError = File.OpenError;
168
169 const RANDOM_BYTES = 12;
170 const TMP_PATH_LEN = base64_encoder.calcSize(RANDOM_BYTES);
171
172 /// Note that the `Dir.atomicFile` API may be more handy than this lower-level function.
173 pub fn init(
174 dest_basename: []const u8,
175 mode: File.Mode,
176 dir: Dir,
177 close_dir_on_deinit: bool,
178 ) InitError!AtomicFile {
179 var rand_buf: [RANDOM_BYTES]u8 = undefined;
180 var tmp_path_buf: [TMP_PATH_LEN:0]u8 = undefined;
181
182 while (true) {
183 crypto.random.bytes(rand_buf[0..]);
184 const tmp_path = base64_encoder.encode(&tmp_path_buf, &rand_buf);
185 tmp_path_buf[tmp_path.len] = 0;
186
187 const file = dir.createFile(
188 tmp_path,
189 .{ .mode = mode, .exclusive = true },
190 ) catch |err| switch (err) {
191 error.PathAlreadyExists => continue,
192 else => |e| return e,
193 };
194
195 return AtomicFile{
196 .file = file,
197 .tmp_path_buf = tmp_path_buf,
198 .dest_basename = dest_basename,
199 .file_open = true,
200 .file_exists = true,
201 .close_dir_on_deinit = close_dir_on_deinit,
202 .dir = dir,
203 };
204 }
205 }
206
207 /// Always call deinit, even after a successful finish().
208 pub fn deinit(self: *AtomicFile) void {
209 if (self.file_open) {
210 self.file.close();
211 self.file_open = false;
212 }
213 if (self.file_exists) {
214 self.dir.deleteFile(&self.tmp_path_buf) catch {};
215 self.file_exists = false;
216 }
217 if (self.close_dir_on_deinit) {
218 self.dir.close();
219 }
220 self.* = undefined;
221 }
222
223 pub const FinishError = std.os.RenameError;
224
225 pub fn finish(self: *AtomicFile) FinishError!void {
226 assert(self.file_exists);
227 if (self.file_open) {
228 self.file.close();
229 self.file_open = false;
230 }
231 try os.renameat(self.dir.fd, self.tmp_path_buf[0..], self.dir.fd, self.dest_basename);
232 self.file_exists = false;
233 }
234};
235
236const default_new_dir_mode = 0o755;
237
238/// Create a new directory, based on an absolute path.154/// Create a new directory, based on an absolute path.
239/// Asserts that the path is absolute. See `Dir.makeDir` for a function that operates155/// Asserts that the path is absolute. See `Dir.makeDir` for a function that operates
240/// on both absolute and relative paths.156/// on both absolute and relative paths.
241pub fn makeDirAbsolute(absolute_path: []const u8) !void {157pub fn makeDirAbsolute(absolute_path: []const u8) !void {
242 assert(path.isAbsolute(absolute_path));158 assert(path.isAbsolute(absolute_path));
243 return os.mkdir(absolute_path, default_new_dir_mode);159 return os.mkdir(absolute_path, Dir.default_mode);
244}160}
245161
246/// Same as `makeDirAbsolute` except the parameter is a null-terminated UTF-8-encoded string.162/// Same as `makeDirAbsolute` except the parameter is a null-terminated UTF-8-encoded string.
247pub fn makeDirAbsoluteZ(absolute_path_z: [*:0]const u8) !void {163pub fn makeDirAbsoluteZ(absolute_path_z: [*:0]const u8) !void {
248 assert(path.isAbsoluteZ(absolute_path_z));164 assert(path.isAbsoluteZ(absolute_path_z));
249 return os.mkdirZ(absolute_path_z, default_new_dir_mode);165 return os.mkdirZ(absolute_path_z, Dir.default_mode);
250}166}
251167
252/// Same as `makeDirAbsolute` except the parameter is a null-terminated WTF-16-encoded string.168/// Same as `makeDirAbsolute` except the parameter is a null-terminated WTF-16-encoded string.
253pub fn makeDirAbsoluteW(absolute_path_w: [*:0]const u16) !void {169pub fn makeDirAbsoluteW(absolute_path_w: [*:0]const u16) !void {
254 assert(path.isAbsoluteWindowsW(absolute_path_w));170 assert(path.isAbsoluteWindowsW(absolute_path_w));
255 return os.mkdirW(absolute_path_w, default_new_dir_mode);171 return os.mkdirW(absolute_path_w, Dir.default_mode);
256}172}
257173
258/// Same as `Dir.deleteDir` except the path is absolute.174/// Same as `Dir.deleteDir` except the path is absolute.
...@@ -310,2486 +226,6 @@ pub fn renameW(old_dir: Dir, old_sub_path_w: []const u16, new_dir: Dir, new_sub_...@@ -310,2486 +226,6 @@ pub fn renameW(old_dir: Dir, old_sub_path_w: []const u16, new_dir: Dir, new_sub_
310 return os.renameatW(old_dir.fd, old_sub_path_w, new_dir.fd, new_sub_path_w);226 return os.renameatW(old_dir.fd, old_sub_path_w, new_dir.fd, new_sub_path_w);
311}227}
312228
313/// A directory that can be iterated. It is *NOT* legal to initialize this with a regular `Dir`
314/// that has been opened without iteration permission.
315pub const IterableDir = struct {
316 dir: Dir,
317
318 pub const Entry = struct {
319 name: []const u8,
320 kind: Kind,
321
322 pub const Kind = File.Kind;
323 };
324
325 const IteratorError = error{ AccessDenied, SystemResources } || os.UnexpectedError;
326
327 pub const Iterator = switch (builtin.os.tag) {
328 .macos, .ios, .freebsd, .netbsd, .dragonfly, .openbsd, .solaris, .illumos => struct {
329 dir: Dir,
330 seek: i64,
331 buf: [1024]u8, // TODO align(@alignOf(os.system.dirent)),
332 index: usize,
333 end_index: usize,
334 first_iter: bool,
335
336 const Self = @This();
337
338 pub const Error = IteratorError;
339
340 /// Memory such as file names referenced in this returned entry becomes invalid
341 /// with subsequent calls to `next`, as well as when this `Dir` is deinitialized.
342 pub fn next(self: *Self) Error!?Entry {
343 switch (builtin.os.tag) {
344 .macos, .ios => return self.nextDarwin(),
345 .freebsd, .netbsd, .dragonfly, .openbsd => return self.nextBsd(),
346 .solaris, .illumos => return self.nextSolaris(),
347 else => @compileError("unimplemented"),
348 }
349 }
350
351 fn nextDarwin(self: *Self) !?Entry {
352 start_over: while (true) {
353 if (self.index >= self.end_index) {
354 if (self.first_iter) {
355 std.os.lseek_SET(self.dir.fd, 0) catch unreachable; // EBADF here likely means that the Dir was not opened with iteration permissions
356 self.first_iter = false;
357 }
358 const rc = os.system.__getdirentries64(
359 self.dir.fd,
360 &self.buf,
361 self.buf.len,
362 &self.seek,
363 );
364 if (rc == 0) return null;
365 if (rc < 0) {
366 switch (os.errno(rc)) {
367 .BADF => unreachable, // Dir is invalid or was opened without iteration ability
368 .FAULT => unreachable,
369 .NOTDIR => unreachable,
370 .INVAL => unreachable,
371 else => |err| return os.unexpectedErrno(err),
372 }
373 }
374 self.index = 0;
375 self.end_index = @as(usize, @intCast(rc));
376 }
377 const darwin_entry = @as(*align(1) os.system.dirent, @ptrCast(&self.buf[self.index]));
378 const next_index = self.index + darwin_entry.reclen();
379 self.index = next_index;
380
381 const name = @as([*]u8, @ptrCast(&darwin_entry.d_name))[0..darwin_entry.d_namlen];
382
383 if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..") or (darwin_entry.d_ino == 0)) {
384 continue :start_over;
385 }
386
387 const entry_kind: Entry.Kind = switch (darwin_entry.d_type) {
388 os.DT.BLK => .block_device,
389 os.DT.CHR => .character_device,
390 os.DT.DIR => .directory,
391 os.DT.FIFO => .named_pipe,
392 os.DT.LNK => .sym_link,
393 os.DT.REG => .file,
394 os.DT.SOCK => .unix_domain_socket,
395 os.DT.WHT => .whiteout,
396 else => .unknown,
397 };
398 return Entry{
399 .name = name,
400 .kind = entry_kind,
401 };
402 }
403 }
404
405 fn nextSolaris(self: *Self) !?Entry {
406 start_over: while (true) {
407 if (self.index >= self.end_index) {
408 if (self.first_iter) {
409 std.os.lseek_SET(self.dir.fd, 0) catch unreachable; // EBADF here likely means that the Dir was not opened with iteration permissions
410 self.first_iter = false;
411 }
412 const rc = os.system.getdents(self.dir.fd, &self.buf, self.buf.len);
413 switch (os.errno(rc)) {
414 .SUCCESS => {},
415 .BADF => unreachable, // Dir is invalid or was opened without iteration ability
416 .FAULT => unreachable,
417 .NOTDIR => unreachable,
418 .INVAL => unreachable,
419 else => |err| return os.unexpectedErrno(err),
420 }
421 if (rc == 0) return null;
422 self.index = 0;
423 self.end_index = @as(usize, @intCast(rc));
424 }
425 const entry = @as(*align(1) os.system.dirent, @ptrCast(&self.buf[self.index]));
426 const next_index = self.index + entry.reclen();
427 self.index = next_index;
428
429 const name = mem.sliceTo(@as([*:0]u8, @ptrCast(&entry.d_name)), 0);
430 if (mem.eql(u8, name, ".") or mem.eql(u8, name, ".."))
431 continue :start_over;
432
433 // Solaris dirent doesn't expose d_type, so we have to call stat to get it.
434 const stat_info = os.fstatat(
435 self.dir.fd,
436 name,
437 os.AT.SYMLINK_NOFOLLOW,
438 ) catch |err| switch (err) {
439 error.NameTooLong => unreachable,
440 error.SymLinkLoop => unreachable,
441 error.FileNotFound => unreachable, // lost the race
442 else => |e| return e,
443 };
444 const entry_kind: Entry.Kind = switch (stat_info.mode & os.S.IFMT) {
445 os.S.IFIFO => .named_pipe,
446 os.S.IFCHR => .character_device,
447 os.S.IFDIR => .directory,
448 os.S.IFBLK => .block_device,
449 os.S.IFREG => .file,
450 os.S.IFLNK => .sym_link,
451 os.S.IFSOCK => .unix_domain_socket,
452 os.S.IFDOOR => .door,
453 os.S.IFPORT => .event_port,
454 else => .unknown,
455 };
456 return Entry{
457 .name = name,
458 .kind = entry_kind,
459 };
460 }
461 }
462
463 fn nextBsd(self: *Self) !?Entry {
464 start_over: while (true) {
465 if (self.index >= self.end_index) {
466 if (self.first_iter) {
467 std.os.lseek_SET(self.dir.fd, 0) catch unreachable; // EBADF here likely means that the Dir was not opened with iteration permissions
468 self.first_iter = false;
469 }
470 const rc = if (builtin.os.tag == .netbsd)
471 os.system.__getdents30(self.dir.fd, &self.buf, self.buf.len)
472 else
473 os.system.getdents(self.dir.fd, &self.buf, self.buf.len);
474 switch (os.errno(rc)) {
475 .SUCCESS => {},
476 .BADF => unreachable, // Dir is invalid or was opened without iteration ability
477 .FAULT => unreachable,
478 .NOTDIR => unreachable,
479 .INVAL => unreachable,
480 // Introduced in freebsd 13.2: directory unlinked but still open.
481 // To be consistent, iteration ends if the directory being iterated is deleted during iteration.
482 .NOENT => return null,
483 else => |err| return os.unexpectedErrno(err),
484 }
485 if (rc == 0) return null;
486 self.index = 0;
487 self.end_index = @as(usize, @intCast(rc));
488 }
489 const bsd_entry = @as(*align(1) os.system.dirent, @ptrCast(&self.buf[self.index]));
490 const next_index = self.index + bsd_entry.reclen();
491 self.index = next_index;
492
493 const name = @as([*]u8, @ptrCast(&bsd_entry.d_name))[0..bsd_entry.d_namlen];
494
495 const skip_zero_fileno = switch (builtin.os.tag) {
496 // d_fileno=0 is used to mark invalid entries or deleted files.
497 .openbsd, .netbsd => true,
498 else => false,
499 };
500 if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..") or
501 (skip_zero_fileno and bsd_entry.d_fileno == 0))
502 {
503 continue :start_over;
504 }
505
506 const entry_kind: Entry.Kind = switch (bsd_entry.d_type) {
507 os.DT.BLK => .block_device,
508 os.DT.CHR => .character_device,
509 os.DT.DIR => .directory,
510 os.DT.FIFO => .named_pipe,
511 os.DT.LNK => .sym_link,
512 os.DT.REG => .file,
513 os.DT.SOCK => .unix_domain_socket,
514 os.DT.WHT => .whiteout,
515 else => .unknown,
516 };
517 return Entry{
518 .name = name,
519 .kind = entry_kind,
520 };
521 }
522 }
523
524 pub fn reset(self: *Self) void {
525 self.index = 0;
526 self.end_index = 0;
527 self.first_iter = true;
528 }
529 },
530 .haiku => struct {
531 dir: Dir,
532 buf: [1024]u8, // TODO align(@alignOf(os.dirent64)),
533 index: usize,
534 end_index: usize,
535 first_iter: bool,
536
537 const Self = @This();
538
539 pub const Error = IteratorError;
540
541 /// Memory such as file names referenced in this returned entry becomes invalid
542 /// with subsequent calls to `next`, as well as when this `Dir` is deinitialized.
543 pub fn next(self: *Self) Error!?Entry {
544 start_over: while (true) {
545 // TODO: find a better max
546 const HAIKU_MAX_COUNT = 10000;
547 if (self.index >= self.end_index) {
548 if (self.first_iter) {
549 std.os.lseek_SET(self.dir.fd, 0) catch unreachable; // EBADF here likely means that the Dir was not opened with iteration permissions
550 self.first_iter = false;
551 }
552 const rc = os.system._kern_read_dir(
553 self.dir.fd,
554 &self.buf,
555 self.buf.len,
556 HAIKU_MAX_COUNT,
557 );
558 if (rc == 0) return null;
559 if (rc < 0) {
560 switch (os.errno(rc)) {
561 .BADF => unreachable, // Dir is invalid or was opened without iteration ability
562 .FAULT => unreachable,
563 .NOTDIR => unreachable,
564 .INVAL => unreachable,
565 else => |err| return os.unexpectedErrno(err),
566 }
567 }
568 self.index = 0;
569 self.end_index = @as(usize, @intCast(rc));
570 }
571 const haiku_entry = @as(*align(1) os.system.dirent, @ptrCast(&self.buf[self.index]));
572 const next_index = self.index + haiku_entry.reclen();
573 self.index = next_index;
574 const name = mem.sliceTo(@as([*:0]u8, @ptrCast(&haiku_entry.d_name)), 0);
575
576 if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..") or (haiku_entry.d_ino == 0)) {
577 continue :start_over;
578 }
579
580 var stat_info: os.Stat = undefined;
581 const rc = os.system._kern_read_stat(
582 self.dir.fd,
583 &haiku_entry.d_name,
584 false,
585 &stat_info,
586 0,
587 );
588 if (rc != 0) {
589 switch (os.errno(rc)) {
590 .SUCCESS => {},
591 .BADF => unreachable, // Dir is invalid or was opened without iteration ability
592 .FAULT => unreachable,
593 .NOTDIR => unreachable,
594 .INVAL => unreachable,
595 else => |err| return os.unexpectedErrno(err),
596 }
597 }
598 const statmode = stat_info.mode & os.S.IFMT;
599
600 const entry_kind: Entry.Kind = switch (statmode) {
601 os.S.IFDIR => .directory,
602 os.S.IFBLK => .block_device,
603 os.S.IFCHR => .character_device,
604 os.S.IFLNK => .sym_link,
605 os.S.IFREG => .file,
606 os.S.IFIFO => .named_pipe,
607 else => .unknown,
608 };
609
610 return Entry{
611 .name = name,
612 .kind = entry_kind,
613 };
614 }
615 }
616
617 pub fn reset(self: *Self) void {
618 self.index = 0;
619 self.end_index = 0;
620 self.first_iter = true;
621 }
622 },
623 .linux => struct {
624 dir: Dir,
625 // The if guard is solely there to prevent compile errors from missing `linux.dirent64`
626 // definition when compiling for other OSes. It doesn't do anything when compiling for Linux.
627 buf: [1024]u8 align(if (builtin.os.tag != .linux) 1 else @alignOf(linux.dirent64)),
628 index: usize,
629 end_index: usize,
630 first_iter: bool,
631
632 const Self = @This();
633 const linux = os.linux;
634
635 pub const Error = IteratorError;
636
637 /// Memory such as file names referenced in this returned entry becomes invalid
638 /// with subsequent calls to `next`, as well as when this `Dir` is deinitialized.
639 pub fn next(self: *Self) Error!?Entry {
640 return self.nextLinux() catch |err| switch (err) {
641 // To be consistent across platforms, iteration ends if the directory being iterated is deleted during iteration.
642 // This matches the behavior of non-Linux UNIX platforms.
643 error.DirNotFound => null,
644 else => |e| return e,
645 };
646 }
647
648 pub const ErrorLinux = error{DirNotFound} || IteratorError;
649
650 /// Implementation of `next` that can return `error.DirNotFound` if the directory being
651 /// iterated was deleted during iteration (this error is Linux specific).
652 pub fn nextLinux(self: *Self) ErrorLinux!?Entry {
653 start_over: while (true) {
654 if (self.index >= self.end_index) {
655 if (self.first_iter) {
656 std.os.lseek_SET(self.dir.fd, 0) catch unreachable; // EBADF here likely means that the Dir was not opened with iteration permissions
657 self.first_iter = false;
658 }
659 const rc = linux.getdents64(self.dir.fd, &self.buf, self.buf.len);
660 switch (linux.getErrno(rc)) {
661 .SUCCESS => {},
662 .BADF => unreachable, // Dir is invalid or was opened without iteration ability
663 .FAULT => unreachable,
664 .NOTDIR => unreachable,
665 .NOENT => return error.DirNotFound, // The directory being iterated was deleted during iteration.
666 .INVAL => return error.Unexpected, // Linux may in some cases return EINVAL when reading /proc/$PID/net.
667 .ACCES => return error.AccessDenied, // Do not have permission to iterate this directory.
668 else => |err| return os.unexpectedErrno(err),
669 }
670 if (rc == 0) return null;
671 self.index = 0;
672 self.end_index = rc;
673 }
674 const linux_entry = @as(*align(1) linux.dirent64, @ptrCast(&self.buf[self.index]));
675 const next_index = self.index + linux_entry.reclen();
676 self.index = next_index;
677
678 const name = mem.sliceTo(@as([*:0]u8, @ptrCast(&linux_entry.d_name)), 0);
679
680 // skip . and .. entries
681 if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) {
682 continue :start_over;
683 }
684
685 const entry_kind: Entry.Kind = switch (linux_entry.d_type) {
686 linux.DT.BLK => .block_device,
687 linux.DT.CHR => .character_device,
688 linux.DT.DIR => .directory,
689 linux.DT.FIFO => .named_pipe,
690 linux.DT.LNK => .sym_link,
691 linux.DT.REG => .file,
692 linux.DT.SOCK => .unix_domain_socket,
693 else => .unknown,
694 };
695 return Entry{
696 .name = name,
697 .kind = entry_kind,
698 };
699 }
700 }
701
702 pub fn reset(self: *Self) void {
703 self.index = 0;
704 self.end_index = 0;
705 self.first_iter = true;
706 }
707 },
708 .windows => struct {
709 dir: Dir,
710 buf: [1024]u8 align(@alignOf(os.windows.FILE_BOTH_DIR_INFORMATION)),
711 index: usize,
712 end_index: usize,
713 first_iter: bool,
714 name_data: [MAX_NAME_BYTES]u8,
715
716 const Self = @This();
717
718 pub const Error = IteratorError;
719
720 /// Memory such as file names referenced in this returned entry becomes invalid
721 /// with subsequent calls to `next`, as well as when this `Dir` is deinitialized.
722 pub fn next(self: *Self) Error!?Entry {
723 while (true) {
724 const w = os.windows;
725 if (self.index >= self.end_index) {
726 var io: w.IO_STATUS_BLOCK = undefined;
727 const rc = w.ntdll.NtQueryDirectoryFile(
728 self.dir.fd,
729 null,
730 null,
731 null,
732 &io,
733 &self.buf,
734 self.buf.len,
735 .FileBothDirectoryInformation,
736 w.FALSE,
737 null,
738 if (self.first_iter) @as(w.BOOLEAN, w.TRUE) else @as(w.BOOLEAN, w.FALSE),
739 );
740 self.first_iter = false;
741 if (io.Information == 0) return null;
742 self.index = 0;
743 self.end_index = io.Information;
744 switch (rc) {
745 .SUCCESS => {},
746 .ACCESS_DENIED => return error.AccessDenied, // Double-check that the Dir was opened with iteration ability
747
748 else => return w.unexpectedStatus(rc),
749 }
750 }
751
752 // While the official api docs guarantee FILE_BOTH_DIR_INFORMATION to be aligned properly
753 // this may not always be the case (e.g. due to faulty VM/Sandboxing tools)
754 const dir_info: *align(2) w.FILE_BOTH_DIR_INFORMATION = @ptrCast(@alignCast(&self.buf[self.index]));
755 if (dir_info.NextEntryOffset != 0) {
756 self.index += dir_info.NextEntryOffset;
757 } else {
758 self.index = self.buf.len;
759 }
760
761 const name_utf16le = @as([*]u16, @ptrCast(&dir_info.FileName))[0 .. dir_info.FileNameLength / 2];
762
763 if (mem.eql(u16, name_utf16le, &[_]u16{'.'}) or mem.eql(u16, name_utf16le, &[_]u16{ '.', '.' }))
764 continue;
765 // Trust that Windows gives us valid UTF-16LE
766 const name_utf8_len = std.unicode.utf16leToUtf8(self.name_data[0..], name_utf16le) catch unreachable;
767 const name_utf8 = self.name_data[0..name_utf8_len];
768 const kind: Entry.Kind = blk: {
769 const attrs = dir_info.FileAttributes;
770 if (attrs & w.FILE_ATTRIBUTE_DIRECTORY != 0) break :blk .directory;
771 if (attrs & w.FILE_ATTRIBUTE_REPARSE_POINT != 0) break :blk .sym_link;
772 break :blk .file;
773 };
774 return Entry{
775 .name = name_utf8,
776 .kind = kind,
777 };
778 }
779 }
780
781 pub fn reset(self: *Self) void {
782 self.index = 0;
783 self.end_index = 0;
784 self.first_iter = true;
785 }
786 },
787 .wasi => struct {
788 dir: Dir,
789 buf: [1024]u8, // TODO align(@alignOf(os.wasi.dirent_t)),
790 cookie: u64,
791 index: usize,
792 end_index: usize,
793
794 const Self = @This();
795
796 pub const Error = IteratorError;
797
798 /// Memory such as file names referenced in this returned entry becomes invalid
799 /// with subsequent calls to `next`, as well as when this `Dir` is deinitialized.
800 pub fn next(self: *Self) Error!?Entry {
801 return self.nextWasi() catch |err| switch (err) {
802 // To be consistent across platforms, iteration ends if the directory being iterated is deleted during iteration.
803 // This matches the behavior of non-Linux UNIX platforms.
804 error.DirNotFound => null,
805 else => |e| return e,
806 };
807 }
808
809 pub const ErrorWasi = error{DirNotFound} || IteratorError;
810
811 /// Implementation of `next` that can return platform-dependent errors depending on the host platform.
812 /// When the host platform is Linux, `error.DirNotFound` can be returned if the directory being
813 /// iterated was deleted during iteration.
814 pub fn nextWasi(self: *Self) ErrorWasi!?Entry {
815 // We intentinally use fd_readdir even when linked with libc,
816 // since its implementation is exactly the same as below,
817 // and we avoid the code complexity here.
818 const w = os.wasi;
819 start_over: while (true) {
820 // According to the WASI spec, the last entry might be truncated,
821 // so we need to check if the left buffer contains the whole dirent.
822 if (self.end_index - self.index < @sizeOf(w.dirent_t)) {
823 var bufused: usize = undefined;
824 switch (w.fd_readdir(self.dir.fd, &self.buf, self.buf.len, self.cookie, &bufused)) {
825 .SUCCESS => {},
826 .BADF => unreachable, // Dir is invalid or was opened without iteration ability
827 .FAULT => unreachable,
828 .NOTDIR => unreachable,
829 .INVAL => unreachable,
830 .NOENT => return error.DirNotFound, // The directory being iterated was deleted during iteration.
831 .NOTCAPABLE => return error.AccessDenied,
832 else => |err| return os.unexpectedErrno(err),
833 }
834 if (bufused == 0) return null;
835 self.index = 0;
836 self.end_index = bufused;
837 }
838 const entry = @as(*align(1) w.dirent_t, @ptrCast(&self.buf[self.index]));
839 const entry_size = @sizeOf(w.dirent_t);
840 const name_index = self.index + entry_size;
841 if (name_index + entry.d_namlen > self.end_index) {
842 // This case, the name is truncated, so we need to call readdir to store the entire name.
843 self.end_index = self.index; // Force fd_readdir in the next loop.
844 continue :start_over;
845 }
846 const name = self.buf[name_index .. name_index + entry.d_namlen];
847
848 const next_index = name_index + entry.d_namlen;
849 self.index = next_index;
850 self.cookie = entry.d_next;
851
852 // skip . and .. entries
853 if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) {
854 continue :start_over;
855 }
856
857 const entry_kind: Entry.Kind = switch (entry.d_type) {
858 .BLOCK_DEVICE => .block_device,
859 .CHARACTER_DEVICE => .character_device,
860 .DIRECTORY => .directory,
861 .SYMBOLIC_LINK => .sym_link,
862 .REGULAR_FILE => .file,
863 .SOCKET_STREAM, .SOCKET_DGRAM => .unix_domain_socket,
864 else => .unknown,
865 };
866 return Entry{
867 .name = name,
868 .kind = entry_kind,
869 };
870 }
871 }
872
873 pub fn reset(self: *Self) void {
874 self.index = 0;
875 self.end_index = 0;
876 self.cookie = os.wasi.DIRCOOKIE_START;
877 }
878 },
879 else => @compileError("unimplemented"),
880 };
881
882 pub fn iterate(self: IterableDir) Iterator {
883 return self.iterateImpl(true);
884 }
885
886 /// Like `iterate`, but will not reset the directory cursor before the first
887 /// iteration. This should only be used in cases where it is known that the
888 /// `IterableDir` has not had its cursor modified yet (e.g. it was just opened).
889 pub fn iterateAssumeFirstIteration(self: IterableDir) Iterator {
890 return self.iterateImpl(false);
891 }
892
893 fn iterateImpl(self: IterableDir, first_iter_start_value: bool) Iterator {
894 switch (builtin.os.tag) {
895 .macos,
896 .ios,
897 .freebsd,
898 .netbsd,
899 .dragonfly,
900 .openbsd,
901 .solaris,
902 .illumos,
903 => return Iterator{
904 .dir = self.dir,
905 .seek = 0,
906 .index = 0,
907 .end_index = 0,
908 .buf = undefined,
909 .first_iter = first_iter_start_value,
910 },
911 .linux, .haiku => return Iterator{
912 .dir = self.dir,
913 .index = 0,
914 .end_index = 0,
915 .buf = undefined,
916 .first_iter = first_iter_start_value,
917 },
918 .windows => return Iterator{
919 .dir = self.dir,
920 .index = 0,
921 .end_index = 0,
922 .first_iter = first_iter_start_value,
923 .buf = undefined,
924 .name_data = undefined,
925 },
926 .wasi => return Iterator{
927 .dir = self.dir,
928 .cookie = os.wasi.DIRCOOKIE_START,
929 .index = 0,
930 .end_index = 0,
931 .buf = undefined,
932 },
933 else => @compileError("unimplemented"),
934 }
935 }
936
937 pub const Walker = struct {
938 stack: std.ArrayList(StackItem),
939 name_buffer: std.ArrayList(u8),
940
941 pub const WalkerEntry = struct {
942 /// The containing directory. This can be used to operate directly on `basename`
943 /// rather than `path`, avoiding `error.NameTooLong` for deeply nested paths.
944 /// The directory remains open until `next` or `deinit` is called.
945 dir: Dir,
946 basename: []const u8,
947 path: []const u8,
948 kind: IterableDir.Entry.Kind,
949 };
950
951 const StackItem = struct {
952 iter: IterableDir.Iterator,
953 dirname_len: usize,
954 };
955
956 /// After each call to this function, and on deinit(), the memory returned
957 /// from this function becomes invalid. A copy must be made in order to keep
958 /// a reference to the path.
959 pub fn next(self: *Walker) !?WalkerEntry {
960 while (self.stack.items.len != 0) {
961 // `top` and `containing` become invalid after appending to `self.stack`
962 var top = &self.stack.items[self.stack.items.len - 1];
963 var containing = top;
964 var dirname_len = top.dirname_len;
965 if (top.iter.next() catch |err| {
966 // If we get an error, then we want the user to be able to continue
967 // walking if they want, which means that we need to pop the directory
968 // that errored from the stack. Otherwise, all future `next` calls would
969 // likely just fail with the same error.
970 var item = self.stack.pop();
971 if (self.stack.items.len != 0) {
972 item.iter.dir.close();
973 }
974 return err;
975 }) |base| {
976 self.name_buffer.shrinkRetainingCapacity(dirname_len);
977 if (self.name_buffer.items.len != 0) {
978 try self.name_buffer.append(path.sep);
979 dirname_len += 1;
980 }
981 try self.name_buffer.appendSlice(base.name);
982 if (base.kind == .directory) {
983 var new_dir = top.iter.dir.openIterableDir(base.name, .{}) catch |err| switch (err) {
984 error.NameTooLong => unreachable, // no path sep in base.name
985 else => |e| return e,
986 };
987 {
988 errdefer new_dir.close();
989 try self.stack.append(StackItem{
990 .iter = new_dir.iterateAssumeFirstIteration(),
991 .dirname_len = self.name_buffer.items.len,
992 });
993 top = &self.stack.items[self.stack.items.len - 1];
994 containing = &self.stack.items[self.stack.items.len - 2];
995 }
996 }
997 return WalkerEntry{
998 .dir = containing.iter.dir,
999 .basename = self.name_buffer.items[dirname_len..],
1000 .path = self.name_buffer.items,
1001 .kind = base.kind,
1002 };
1003 } else {
1004 var item = self.stack.pop();
1005 if (self.stack.items.len != 0) {
1006 item.iter.dir.close();
1007 }
1008 }
1009 }
1010 return null;
1011 }
1012
1013 pub fn deinit(self: *Walker) void {
1014 // Close any remaining directories except the initial one (which is always at index 0)
1015 if (self.stack.items.len > 1) {
1016 for (self.stack.items[1..]) |*item| {
1017 item.iter.dir.close();
1018 }
1019 }
1020 self.stack.deinit();
1021 self.name_buffer.deinit();
1022 }
1023 };
1024
1025 /// Recursively iterates over a directory.
1026 /// Must call `Walker.deinit` when done.
1027 /// The order of returned file system entries is undefined.
1028 /// `self` will not be closed after walking it.
1029 pub fn walk(self: IterableDir, allocator: Allocator) !Walker {
1030 var name_buffer = std.ArrayList(u8).init(allocator);
1031 errdefer name_buffer.deinit();
1032
1033 var stack = std.ArrayList(Walker.StackItem).init(allocator);
1034 errdefer stack.deinit();
1035
1036 try stack.append(Walker.StackItem{
1037 .iter = self.iterate(),
1038 .dirname_len = 0,
1039 });
1040
1041 return Walker{
1042 .stack = stack,
1043 .name_buffer = name_buffer,
1044 };
1045 }
1046
1047 pub fn close(self: *IterableDir) void {
1048 self.dir.close();
1049 self.* = undefined;
1050 }
1051
1052 pub const ChmodError = File.ChmodError;
1053
1054 /// Changes the mode of the directory.
1055 /// The process must have the correct privileges in order to do this
1056 /// successfully, or must have the effective user ID matching the owner
1057 /// of the directory.
1058 pub fn chmod(self: IterableDir, new_mode: File.Mode) ChmodError!void {
1059 const file: File = .{
1060 .handle = self.dir.fd,
1061 .capable_io_mode = .blocking,
1062 };
1063 try file.chmod(new_mode);
1064 }
1065
1066 /// Changes the owner and group of the directory.
1067 /// The process must have the correct privileges in order to do this
1068 /// successfully. The group may be changed by the owner of the directory to
1069 /// any group of which the owner is a member. If the
1070 /// owner or group is specified as `null`, the ID is not changed.
1071 pub fn chown(self: IterableDir, owner: ?File.Uid, group: ?File.Gid) ChownError!void {
1072 const file: File = .{
1073 .handle = self.dir.fd,
1074 .capable_io_mode = .blocking,
1075 };
1076 try file.chown(owner, group);
1077 }
1078
1079 pub const ChownError = File.ChownError;
1080};
1081
1082pub const Dir = struct {
1083 fd: os.fd_t,
1084
1085 pub const iterate = @compileError("only 'IterableDir' can be iterated; 'IterableDir' can be obtained with 'openIterableDir'");
1086 pub const walk = @compileError("only 'IterableDir' can be walked; 'IterableDir' can be obtained with 'openIterableDir'");
1087 pub const chmod = @compileError("only 'IterableDir' can have its mode changed; 'IterableDir' can be obtained with 'openIterableDir'");
1088 pub const chown = @compileError("only 'IterableDir' can have its owner changed; 'IterableDir' can be obtained with 'openIterableDir'");
1089
1090 pub const OpenError = error{
1091 FileNotFound,
1092 NotDir,
1093 InvalidHandle,
1094 AccessDenied,
1095 SymLinkLoop,
1096 ProcessFdQuotaExceeded,
1097 NameTooLong,
1098 SystemFdQuotaExceeded,
1099 NoDevice,
1100 SystemResources,
1101 InvalidUtf8,
1102 BadPathName,
1103 DeviceBusy,
1104 /// On Windows, `\\server` or `\\server\share` was not found.
1105 NetworkNotFound,
1106 } || os.UnexpectedError;
1107
1108 pub fn close(self: *Dir) void {
1109 if (need_async_thread) {
1110 std.event.Loop.instance.?.close(self.fd);
1111 } else {
1112 os.close(self.fd);
1113 }
1114 self.* = undefined;
1115 }
1116
1117 /// Opens a file for reading or writing, without attempting to create a new file.
1118 /// To create a new file, see `createFile`.
1119 /// Call `File.close` to release the resource.
1120 /// Asserts that the path parameter has no null bytes.
1121 pub fn openFile(self: Dir, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File {
1122 if (builtin.os.tag == .windows) {
1123 const path_w = try os.windows.sliceToPrefixedFileW(self.fd, sub_path);
1124 return self.openFileW(path_w.span(), flags);
1125 }
1126 if (builtin.os.tag == .wasi and !builtin.link_libc) {
1127 return self.openFileWasi(sub_path, flags);
1128 }
1129 const path_c = try os.toPosixPath(sub_path);
1130 return self.openFileZ(&path_c, flags);
1131 }
1132
1133 /// Same as `openFile` but WASI only.
1134 pub fn openFileWasi(self: Dir, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File {
1135 const w = os.wasi;
1136 var fdflags: w.fdflags_t = 0x0;
1137 var base: w.rights_t = 0x0;
1138 if (flags.isRead()) {
1139 base |= w.RIGHT.FD_READ | w.RIGHT.FD_TELL | w.RIGHT.FD_SEEK | w.RIGHT.FD_FILESTAT_GET;
1140 }
1141 if (flags.isWrite()) {
1142 fdflags |= w.FDFLAG.APPEND;
1143 base |= w.RIGHT.FD_WRITE |
1144 w.RIGHT.FD_TELL |
1145 w.RIGHT.FD_SEEK |
1146 w.RIGHT.FD_DATASYNC |
1147 w.RIGHT.FD_FDSTAT_SET_FLAGS |
1148 w.RIGHT.FD_SYNC |
1149 w.RIGHT.FD_ALLOCATE |
1150 w.RIGHT.FD_ADVISE |
1151 w.RIGHT.FD_FILESTAT_SET_TIMES |
1152 w.RIGHT.FD_FILESTAT_SET_SIZE;
1153 }
1154 const fd = try os.openatWasi(self.fd, sub_path, 0x0, 0x0, fdflags, base, 0x0);
1155 return File{ .handle = fd };
1156 }
1157
1158 /// Same as `openFile` but the path parameter is null-terminated.
1159 pub fn openFileZ(self: Dir, sub_path: [*:0]const u8, flags: File.OpenFlags) File.OpenError!File {
1160 if (builtin.os.tag == .windows) {
1161 const path_w = try os.windows.cStrToPrefixedFileW(self.fd, sub_path);
1162 return self.openFileW(path_w.span(), flags);
1163 }
1164
1165 var os_flags: u32 = 0;
1166 if (@hasDecl(os.O, "CLOEXEC")) os_flags = os.O.CLOEXEC;
1167
1168 // Use the O locking flags if the os supports them to acquire the lock
1169 // atomically.
1170 const has_flock_open_flags = @hasDecl(os.O, "EXLOCK");
1171 if (has_flock_open_flags) {
1172 // Note that the O.NONBLOCK flag is removed after the openat() call
1173 // is successful.
1174 const nonblocking_lock_flag: u32 = if (flags.lock_nonblocking)
1175 os.O.NONBLOCK
1176 else
1177 0;
1178 os_flags |= switch (flags.lock) {
1179 .none => @as(u32, 0),
1180 .shared => os.O.SHLOCK | nonblocking_lock_flag,
1181 .exclusive => os.O.EXLOCK | nonblocking_lock_flag,
1182 };
1183 }
1184 if (@hasDecl(os.O, "LARGEFILE")) {
1185 os_flags |= os.O.LARGEFILE;
1186 }
1187 if (@hasDecl(os.O, "NOCTTY") and !flags.allow_ctty) {
1188 os_flags |= os.O.NOCTTY;
1189 }
1190 os_flags |= switch (flags.mode) {
1191 .read_only => @as(u32, os.O.RDONLY),
1192 .write_only => @as(u32, os.O.WRONLY),
1193 .read_write => @as(u32, os.O.RDWR),
1194 };
1195 const fd = if (flags.intended_io_mode != .blocking)
1196 try std.event.Loop.instance.?.openatZ(self.fd, sub_path, os_flags, 0)
1197 else
1198 try os.openatZ(self.fd, sub_path, os_flags, 0);
1199 errdefer os.close(fd);
1200
1201 // WASI doesn't have os.flock so we intetinally check OS prior to the inner if block
1202 // since it is not compiltime-known and we need to avoid undefined symbol in Wasm.
1203 if (@hasDecl(os.system, "LOCK") and builtin.target.os.tag != .wasi) {
1204 if (!has_flock_open_flags and flags.lock != .none) {
1205 // TODO: integrate async I/O
1206 const lock_nonblocking = if (flags.lock_nonblocking) os.LOCK.NB else @as(i32, 0);
1207 try os.flock(fd, switch (flags.lock) {
1208 .none => unreachable,
1209 .shared => os.LOCK.SH | lock_nonblocking,
1210 .exclusive => os.LOCK.EX | lock_nonblocking,
1211 });
1212 }
1213 }
1214
1215 if (has_flock_open_flags and flags.lock_nonblocking) {
1216 var fl_flags = os.fcntl(fd, os.F.GETFL, 0) catch |err| switch (err) {
1217 error.FileBusy => unreachable,
1218 error.Locked => unreachable,
1219 error.PermissionDenied => unreachable,
1220 error.DeadLock => unreachable,
1221 error.LockedRegionLimitExceeded => unreachable,
1222 else => |e| return e,
1223 };
1224 fl_flags &= ~@as(usize, os.O.NONBLOCK);
1225 _ = os.fcntl(fd, os.F.SETFL, fl_flags) catch |err| switch (err) {
1226 error.FileBusy => unreachable,
1227 error.Locked => unreachable,
1228 error.PermissionDenied => unreachable,
1229 error.DeadLock => unreachable,
1230 error.LockedRegionLimitExceeded => unreachable,
1231 else => |e| return e,
1232 };
1233 }
1234
1235 return File{
1236 .handle = fd,
1237 .capable_io_mode = .blocking,
1238 .intended_io_mode = flags.intended_io_mode,
1239 };
1240 }
1241
1242 /// Same as `openFile` but Windows-only and the path parameter is
1243 /// [WTF-16](https://simonsapin.github.io/wtf-8/#potentially-ill-formed-utf-16) encoded.
1244 pub fn openFileW(self: Dir, sub_path_w: []const u16, flags: File.OpenFlags) File.OpenError!File {
1245 const w = os.windows;
1246 const file: File = .{
1247 .handle = try w.OpenFile(sub_path_w, .{
1248 .dir = self.fd,
1249 .access_mask = w.SYNCHRONIZE |
1250 (if (flags.isRead()) @as(u32, w.GENERIC_READ) else 0) |
1251 (if (flags.isWrite()) @as(u32, w.GENERIC_WRITE) else 0),
1252 .creation = w.FILE_OPEN,
1253 .io_mode = flags.intended_io_mode,
1254 }),
1255 .capable_io_mode = std.io.default_mode,
1256 .intended_io_mode = flags.intended_io_mode,
1257 };
1258 errdefer file.close();
1259 var io: w.IO_STATUS_BLOCK = undefined;
1260 const range_off: w.LARGE_INTEGER = 0;
1261 const range_len: w.LARGE_INTEGER = 1;
1262 const exclusive = switch (flags.lock) {
1263 .none => return file,
1264 .shared => false,
1265 .exclusive => true,
1266 };
1267 try w.LockFile(
1268 file.handle,
1269 null,
1270 null,
1271 null,
1272 &io,
1273 &range_off,
1274 &range_len,
1275 null,
1276 @intFromBool(flags.lock_nonblocking),
1277 @intFromBool(exclusive),
1278 );
1279 return file;
1280 }
1281
1282 /// Creates, opens, or overwrites a file with write access.
1283 /// Call `File.close` on the result when done.
1284 /// Asserts that the path parameter has no null bytes.
1285 pub fn createFile(self: Dir, sub_path: []const u8, flags: File.CreateFlags) File.OpenError!File {
1286 if (builtin.os.tag == .windows) {
1287 const path_w = try os.windows.sliceToPrefixedFileW(self.fd, sub_path);
1288 return self.createFileW(path_w.span(), flags);
1289 }
1290 if (builtin.os.tag == .wasi and !builtin.link_libc) {
1291 return self.createFileWasi(sub_path, flags);
1292 }
1293 const path_c = try os.toPosixPath(sub_path);
1294 return self.createFileZ(&path_c, flags);
1295 }
1296
1297 /// Same as `createFile` but WASI only.
1298 pub fn createFileWasi(self: Dir, sub_path: []const u8, flags: File.CreateFlags) File.OpenError!File {
1299 const w = os.wasi;
1300 var oflags = w.O.CREAT;
1301 var base: w.rights_t = w.RIGHT.FD_WRITE |
1302 w.RIGHT.FD_DATASYNC |
1303 w.RIGHT.FD_SEEK |
1304 w.RIGHT.FD_TELL |
1305 w.RIGHT.FD_FDSTAT_SET_FLAGS |
1306 w.RIGHT.FD_SYNC |
1307 w.RIGHT.FD_ALLOCATE |
1308 w.RIGHT.FD_ADVISE |
1309 w.RIGHT.FD_FILESTAT_SET_TIMES |
1310 w.RIGHT.FD_FILESTAT_SET_SIZE |
1311 w.RIGHT.FD_FILESTAT_GET;
1312 if (flags.read) {
1313 base |= w.RIGHT.FD_READ;
1314 }
1315 if (flags.truncate) {
1316 oflags |= w.O.TRUNC;
1317 }
1318 if (flags.exclusive) {
1319 oflags |= w.O.EXCL;
1320 }
1321 const fd = try os.openatWasi(self.fd, sub_path, 0x0, oflags, 0x0, base, 0x0);
1322 return File{ .handle = fd };
1323 }
1324
1325 /// Same as `createFile` but the path parameter is null-terminated.
1326 pub fn createFileZ(self: Dir, sub_path_c: [*:0]const u8, flags: File.CreateFlags) File.OpenError!File {
1327 if (builtin.os.tag == .windows) {
1328 const path_w = try os.windows.cStrToPrefixedFileW(self.fd, sub_path_c);
1329 return self.createFileW(path_w.span(), flags);
1330 }
1331
1332 // Use the O locking flags if the os supports them to acquire the lock
1333 // atomically.
1334 const has_flock_open_flags = @hasDecl(os.O, "EXLOCK");
1335 // Note that the O.NONBLOCK flag is removed after the openat() call
1336 // is successful.
1337 const nonblocking_lock_flag: u32 = if (has_flock_open_flags and flags.lock_nonblocking)
1338 os.O.NONBLOCK
1339 else
1340 0;
1341 const lock_flag: u32 = if (has_flock_open_flags) switch (flags.lock) {
1342 .none => @as(u32, 0),
1343 .shared => os.O.SHLOCK | nonblocking_lock_flag,
1344 .exclusive => os.O.EXLOCK | nonblocking_lock_flag,
1345 } else 0;
1346
1347 const O_LARGEFILE = if (@hasDecl(os.O, "LARGEFILE")) os.O.LARGEFILE else 0;
1348 const os_flags = lock_flag | O_LARGEFILE | os.O.CREAT | os.O.CLOEXEC |
1349 (if (flags.truncate) @as(u32, os.O.TRUNC) else 0) |
1350 (if (flags.read) @as(u32, os.O.RDWR) else os.O.WRONLY) |
1351 (if (flags.exclusive) @as(u32, os.O.EXCL) else 0);
1352 const fd = if (flags.intended_io_mode != .blocking)
1353 try std.event.Loop.instance.?.openatZ(self.fd, sub_path_c, os_flags, flags.mode)
1354 else
1355 try os.openatZ(self.fd, sub_path_c, os_flags, flags.mode);
1356 errdefer os.close(fd);
1357
1358 // WASI doesn't have os.flock so we intetinally check OS prior to the inner if block
1359 // since it is not compiltime-known and we need to avoid undefined symbol in Wasm.
1360 if (builtin.target.os.tag != .wasi) {
1361 if (!has_flock_open_flags and flags.lock != .none) {
1362 // TODO: integrate async I/O
1363 const lock_nonblocking = if (flags.lock_nonblocking) os.LOCK.NB else @as(i32, 0);
1364 try os.flock(fd, switch (flags.lock) {
1365 .none => unreachable,
1366 .shared => os.LOCK.SH | lock_nonblocking,
1367 .exclusive => os.LOCK.EX | lock_nonblocking,
1368 });
1369 }
1370 }
1371
1372 if (has_flock_open_flags and flags.lock_nonblocking) {
1373 var fl_flags = os.fcntl(fd, os.F.GETFL, 0) catch |err| switch (err) {
1374 error.FileBusy => unreachable,
1375 error.Locked => unreachable,
1376 error.PermissionDenied => unreachable,
1377 error.DeadLock => unreachable,
1378 error.LockedRegionLimitExceeded => unreachable,
1379 else => |e| return e,
1380 };
1381 fl_flags &= ~@as(usize, os.O.NONBLOCK);
1382 _ = os.fcntl(fd, os.F.SETFL, fl_flags) catch |err| switch (err) {
1383 error.FileBusy => unreachable,
1384 error.Locked => unreachable,
1385 error.PermissionDenied => unreachable,
1386 error.DeadLock => unreachable,
1387 error.LockedRegionLimitExceeded => unreachable,
1388 else => |e| return e,
1389 };
1390 }
1391
1392 return File{
1393 .handle = fd,
1394 .capable_io_mode = .blocking,
1395 .intended_io_mode = flags.intended_io_mode,
1396 };
1397 }
1398
1399 /// Same as `createFile` but Windows-only and the path parameter is
1400 /// [WTF-16](https://simonsapin.github.io/wtf-8/#potentially-ill-formed-utf-16) encoded.
1401 pub fn createFileW(self: Dir, sub_path_w: []const u16, flags: File.CreateFlags) File.OpenError!File {
1402 const w = os.windows;
1403 const read_flag = if (flags.read) @as(u32, w.GENERIC_READ) else 0;
1404 const file: File = .{
1405 .handle = try os.windows.OpenFile(sub_path_w, .{
1406 .dir = self.fd,
1407 .access_mask = w.SYNCHRONIZE | w.GENERIC_WRITE | read_flag,
1408 .creation = if (flags.exclusive)
1409 @as(u32, w.FILE_CREATE)
1410 else if (flags.truncate)
1411 @as(u32, w.FILE_OVERWRITE_IF)
1412 else
1413 @as(u32, w.FILE_OPEN_IF),
1414 .io_mode = flags.intended_io_mode,
1415 }),
1416 .capable_io_mode = std.io.default_mode,
1417 .intended_io_mode = flags.intended_io_mode,
1418 };
1419 errdefer file.close();
1420 var io: w.IO_STATUS_BLOCK = undefined;
1421 const range_off: w.LARGE_INTEGER = 0;
1422 const range_len: w.LARGE_INTEGER = 1;
1423 const exclusive = switch (flags.lock) {
1424 .none => return file,
1425 .shared => false,
1426 .exclusive => true,
1427 };
1428 try w.LockFile(
1429 file.handle,
1430 null,
1431 null,
1432 null,
1433 &io,
1434 &range_off,
1435 &range_len,
1436 null,
1437 @intFromBool(flags.lock_nonblocking),
1438 @intFromBool(exclusive),
1439 );
1440 return file;
1441 }
1442
1443 /// Creates a single directory with a relative or absolute path.
1444 /// To create multiple directories to make an entire path, see `makePath`.
1445 /// To operate on only absolute paths, see `makeDirAbsolute`.
1446 pub fn makeDir(self: Dir, sub_path: []const u8) !void {
1447 try os.mkdirat(self.fd, sub_path, default_new_dir_mode);
1448 }
1449
1450 /// Creates a single directory with a relative or absolute null-terminated UTF-8-encoded path.
1451 /// To create multiple directories to make an entire path, see `makePath`.
1452 /// To operate on only absolute paths, see `makeDirAbsoluteZ`.
1453 pub fn makeDirZ(self: Dir, sub_path: [*:0]const u8) !void {
1454 try os.mkdiratZ(self.fd, sub_path, default_new_dir_mode);
1455 }
1456
1457 /// Creates a single directory with a relative or absolute null-terminated WTF-16-encoded path.
1458 /// To create multiple directories to make an entire path, see `makePath`.
1459 /// To operate on only absolute paths, see `makeDirAbsoluteW`.
1460 pub fn makeDirW(self: Dir, sub_path: [*:0]const u16) !void {
1461 try os.mkdiratW(self.fd, sub_path, default_new_dir_mode);
1462 }
1463
1464 /// Calls makeDir iteratively to make an entire path
1465 /// (i.e. creating any parent directories that do not exist).
1466 /// Returns success if the path already exists and is a directory.
1467 /// This function is not atomic, and if it returns an error, the file system may
1468 /// have been modified regardless.
1469 pub fn makePath(self: Dir, sub_path: []const u8) !void {
1470 var it = try path.componentIterator(sub_path);
1471 var component = it.last() orelse return;
1472 while (true) {
1473 self.makeDir(component.path) catch |err| switch (err) {
1474 error.PathAlreadyExists => {
1475 // TODO stat the file and return an error if it's not a directory
1476 // this is important because otherwise a dangling symlink
1477 // could cause an infinite loop
1478 },
1479 error.FileNotFound => |e| {
1480 component = it.previous() orelse return e;
1481 continue;
1482 },
1483 else => |e| return e,
1484 };
1485 component = it.next() orelse return;
1486 }
1487 }
1488
1489 /// Calls makeOpenDirAccessMaskW iteratively to make an entire path
1490 /// (i.e. creating any parent directories that do not exist).
1491 /// Opens the dir if the path already exists and is a directory.
1492 /// This function is not atomic, and if it returns an error, the file system may
1493 /// have been modified regardless.
1494 fn makeOpenPathAccessMaskW(self: Dir, sub_path: []const u8, access_mask: u32, no_follow: bool) OpenError!Dir {
1495 const w = os.windows;
1496 var it = try path.componentIterator(sub_path);
1497 // If there are no components in the path, then create a dummy component with the full path.
1498 var component = it.last() orelse path.NativeUtf8ComponentIterator.Component{
1499 .name = "",
1500 .path = sub_path,
1501 };
1502
1503 while (true) {
1504 const sub_path_w = try w.sliceToPrefixedFileW(self.fd, component.path);
1505 const is_last = it.peekNext() == null;
1506 var result = self.makeOpenDirAccessMaskW(sub_path_w.span().ptr, access_mask, .{
1507 .no_follow = no_follow,
1508 .create_disposition = if (is_last) w.FILE_OPEN_IF else w.FILE_CREATE,
1509 }) catch |err| switch (err) {
1510 error.FileNotFound => |e| {
1511 component = it.previous() orelse return e;
1512 continue;
1513 },
1514 else => |e| return e,
1515 };
1516
1517 component = it.next() orelse return result;
1518 // Don't leak the intermediate file handles
1519 result.close();
1520 }
1521 }
1522
1523 /// This function performs `makePath`, followed by `openDir`.
1524 /// If supported by the OS, this operation is atomic. It is not atomic on
1525 /// all operating systems.
1526 /// On Windows, this function performs `makeOpenPathAccessMaskW`.
1527 pub fn makeOpenPath(self: Dir, sub_path: []const u8, open_dir_options: OpenDirOptions) !Dir {
1528 return switch (builtin.os.tag) {
1529 .windows => {
1530 const w = os.windows;
1531 const base_flags = w.STANDARD_RIGHTS_READ | w.FILE_READ_ATTRIBUTES | w.FILE_READ_EA |
1532 w.SYNCHRONIZE | w.FILE_TRAVERSE;
1533
1534 return self.makeOpenPathAccessMaskW(sub_path, base_flags, open_dir_options.no_follow);
1535 },
1536 else => {
1537 return self.openDir(sub_path, open_dir_options) catch |err| switch (err) {
1538 error.FileNotFound => {
1539 try self.makePath(sub_path);
1540 return self.openDir(sub_path, open_dir_options);
1541 },
1542 else => |e| return e,
1543 };
1544 },
1545 };
1546 }
1547
1548 /// This function performs `makePath`, followed by `openIterableDir`.
1549 /// If supported by the OS, this operation is atomic. It is not atomic on
1550 /// all operating systems.
1551 pub fn makeOpenPathIterable(self: Dir, sub_path: []const u8, open_dir_options: OpenDirOptions) !IterableDir {
1552 return switch (builtin.os.tag) {
1553 .windows => {
1554 const w = os.windows;
1555 const base_flags = w.STANDARD_RIGHTS_READ | w.FILE_READ_ATTRIBUTES | w.FILE_READ_EA |
1556 w.SYNCHRONIZE | w.FILE_TRAVERSE | w.FILE_LIST_DIRECTORY;
1557
1558 return IterableDir{
1559 .dir = try self.makeOpenPathAccessMaskW(sub_path, base_flags, open_dir_options.no_follow),
1560 };
1561 },
1562 else => {
1563 return self.openIterableDir(sub_path, open_dir_options) catch |err| switch (err) {
1564 error.FileNotFound => {
1565 try self.makePath(sub_path);
1566 return self.openIterableDir(sub_path, open_dir_options);
1567 },
1568 else => |e| return e,
1569 };
1570 },
1571 };
1572 }
1573
1574 /// This function returns the canonicalized absolute pathname of
1575 /// `pathname` relative to this `Dir`. If `pathname` is absolute, ignores this
1576 /// `Dir` handle and returns the canonicalized absolute pathname of `pathname`
1577 /// argument.
1578 /// This function is not universally supported by all platforms.
1579 /// Currently supported hosts are: Linux, macOS, and Windows.
1580 /// See also `Dir.realpathZ`, `Dir.realpathW`, and `Dir.realpathAlloc`.
1581 pub fn realpath(self: Dir, pathname: []const u8, out_buffer: []u8) ![]u8 {
1582 if (builtin.os.tag == .wasi) {
1583 @compileError("realpath is not available on WASI");
1584 }
1585 if (builtin.os.tag == .windows) {
1586 const pathname_w = try os.windows.sliceToPrefixedFileW(self.fd, pathname);
1587 return self.realpathW(pathname_w.span(), out_buffer);
1588 }
1589 const pathname_c = try os.toPosixPath(pathname);
1590 return self.realpathZ(&pathname_c, out_buffer);
1591 }
1592
1593 /// Same as `Dir.realpath` except `pathname` is null-terminated.
1594 /// See also `Dir.realpath`, `realpathZ`.
1595 pub fn realpathZ(self: Dir, pathname: [*:0]const u8, out_buffer: []u8) ![]u8 {
1596 if (builtin.os.tag == .windows) {
1597 const pathname_w = try os.windows.cStrToPrefixedFileW(self.fd, pathname);
1598 return self.realpathW(pathname_w.span(), out_buffer);
1599 }
1600
1601 const flags = if (builtin.os.tag == .linux) os.O.PATH | os.O.NONBLOCK | os.O.CLOEXEC else os.O.NONBLOCK | os.O.CLOEXEC;
1602 const fd = os.openatZ(self.fd, pathname, flags, 0) catch |err| switch (err) {
1603 error.FileLocksNotSupported => unreachable,
1604 else => |e| return e,
1605 };
1606 defer os.close(fd);
1607
1608 // Use of MAX_PATH_BYTES here is valid as the realpath function does not
1609 // have a variant that takes an arbitrary-size buffer.
1610 // TODO(#4812): Consider reimplementing realpath or using the POSIX.1-2008
1611 // NULL out parameter (GNU's canonicalize_file_name) to handle overelong
1612 // paths. musl supports passing NULL but restricts the output to PATH_MAX
1613 // anyway.
1614 var buffer: [MAX_PATH_BYTES]u8 = undefined;
1615 const out_path = try os.getFdPath(fd, &buffer);
1616
1617 if (out_path.len > out_buffer.len) {
1618 return error.NameTooLong;
1619 }
1620
1621 const result = out_buffer[0..out_path.len];
1622 @memcpy(result, out_path);
1623 return result;
1624 }
1625
1626 /// Windows-only. Same as `Dir.realpath` except `pathname` is WTF16 encoded.
1627 /// See also `Dir.realpath`, `realpathW`.
1628 pub fn realpathW(self: Dir, pathname: []const u16, out_buffer: []u8) ![]u8 {
1629 const w = os.windows;
1630
1631 const access_mask = w.GENERIC_READ | w.SYNCHRONIZE;
1632 const share_access = w.FILE_SHARE_READ;
1633 const creation = w.FILE_OPEN;
1634 const h_file = blk: {
1635 const res = w.OpenFile(pathname, .{
1636 .dir = self.fd,
1637 .access_mask = access_mask,
1638 .share_access = share_access,
1639 .creation = creation,
1640 .io_mode = .blocking,
1641 .filter = .any,
1642 }) catch |err| switch (err) {
1643 error.WouldBlock => unreachable,
1644 else => |e| return e,
1645 };
1646 break :blk res;
1647 };
1648 defer w.CloseHandle(h_file);
1649
1650 // Use of MAX_PATH_BYTES here is valid as the realpath function does not
1651 // have a variant that takes an arbitrary-size buffer.
1652 // TODO(#4812): Consider reimplementing realpath or using the POSIX.1-2008
1653 // NULL out parameter (GNU's canonicalize_file_name) to handle overelong
1654 // paths. musl supports passing NULL but restricts the output to PATH_MAX
1655 // anyway.
1656 var buffer: [MAX_PATH_BYTES]u8 = undefined;
1657 const out_path = try os.getFdPath(h_file, &buffer);
1658
1659 if (out_path.len > out_buffer.len) {
1660 return error.NameTooLong;
1661 }
1662
1663 const result = out_buffer[0..out_path.len];
1664 @memcpy(result, out_path);
1665 return result;
1666 }
1667
1668 /// Same as `Dir.realpath` except caller must free the returned memory.
1669 /// See also `Dir.realpath`.
1670 pub fn realpathAlloc(self: Dir, allocator: Allocator, pathname: []const u8) ![]u8 {
1671 // Use of MAX_PATH_BYTES here is valid as the realpath function does not
1672 // have a variant that takes an arbitrary-size buffer.
1673 // TODO(#4812): Consider reimplementing realpath or using the POSIX.1-2008
1674 // NULL out parameter (GNU's canonicalize_file_name) to handle overelong
1675 // paths. musl supports passing NULL but restricts the output to PATH_MAX
1676 // anyway.
1677 var buf: [MAX_PATH_BYTES]u8 = undefined;
1678 return allocator.dupe(u8, try self.realpath(pathname, buf[0..]));
1679 }
1680
1681 /// Changes the current working directory to the open directory handle.
1682 /// This modifies global state and can have surprising effects in multi-
1683 /// threaded applications. Most applications and especially libraries should
1684 /// not call this function as a general rule, however it can have use cases
1685 /// in, for example, implementing a shell, or child process execution.
1686 /// Not all targets support this. For example, WASI does not have the concept
1687 /// of a current working directory.
1688 pub fn setAsCwd(self: Dir) !void {
1689 if (builtin.os.tag == .wasi) {
1690 @compileError("changing cwd is not currently possible in WASI");
1691 }
1692 if (builtin.os.tag == .windows) {
1693 var dir_path_buffer: [os.windows.PATH_MAX_WIDE]u16 = undefined;
1694 const dir_path = try os.windows.GetFinalPathNameByHandle(self.fd, .{}, &dir_path_buffer);
1695 if (builtin.link_libc) {
1696 return os.chdirW(dir_path);
1697 }
1698 return os.windows.SetCurrentDirectory(dir_path);
1699 }
1700 try os.fchdir(self.fd);
1701 }
1702
1703 pub const OpenDirOptions = struct {
1704 /// `true` means the opened directory can be used as the `Dir` parameter
1705 /// for functions which operate based on an open directory handle. When `false`,
1706 /// such operations are Illegal Behavior.
1707 access_sub_paths: bool = true,
1708
1709 /// `true` means it won't dereference the symlinks.
1710 no_follow: bool = false,
1711 };
1712
1713 /// Opens a directory at the given path. The directory is a system resource that remains
1714 /// open until `close` is called on the result.
1715 ///
1716 /// Asserts that the path parameter has no null bytes.
1717 pub fn openDir(self: Dir, sub_path: []const u8, args: OpenDirOptions) OpenError!Dir {
1718 if (builtin.os.tag == .windows) {
1719 const sub_path_w = try os.windows.sliceToPrefixedFileW(self.fd, sub_path);
1720 return self.openDirW(sub_path_w.span().ptr, args, false);
1721 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
1722 return self.openDirWasi(sub_path, args);
1723 } else {
1724 const sub_path_c = try os.toPosixPath(sub_path);
1725 return self.openDirZ(&sub_path_c, args, false);
1726 }
1727 }
1728
1729 /// Opens an iterable directory at the given path. The directory is a system resource that remains
1730 /// open until `close` is called on the result.
1731 ///
1732 /// Asserts that the path parameter has no null bytes.
1733 pub fn openIterableDir(self: Dir, sub_path: []const u8, args: OpenDirOptions) OpenError!IterableDir {
1734 if (builtin.os.tag == .windows) {
1735 const sub_path_w = try os.windows.sliceToPrefixedFileW(self.fd, sub_path);
1736 return IterableDir{ .dir = try self.openDirW(sub_path_w.span().ptr, args, true) };
1737 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
1738 return IterableDir{ .dir = try self.openDirWasi(sub_path, args) };
1739 } else {
1740 const sub_path_c = try os.toPosixPath(sub_path);
1741 return IterableDir{ .dir = try self.openDirZ(&sub_path_c, args, true) };
1742 }
1743 }
1744
1745 /// Same as `openDir` except only WASI.
1746 pub fn openDirWasi(self: Dir, sub_path: []const u8, args: OpenDirOptions) OpenError!Dir {
1747 const w = os.wasi;
1748 var base: w.rights_t = w.RIGHT.FD_FILESTAT_GET | w.RIGHT.FD_FDSTAT_SET_FLAGS | w.RIGHT.FD_FILESTAT_SET_TIMES;
1749 if (args.access_sub_paths) {
1750 base |= w.RIGHT.FD_READDIR |
1751 w.RIGHT.PATH_CREATE_DIRECTORY |
1752 w.RIGHT.PATH_CREATE_FILE |
1753 w.RIGHT.PATH_LINK_SOURCE |
1754 w.RIGHT.PATH_LINK_TARGET |
1755 w.RIGHT.PATH_OPEN |
1756 w.RIGHT.PATH_READLINK |
1757 w.RIGHT.PATH_RENAME_SOURCE |
1758 w.RIGHT.PATH_RENAME_TARGET |
1759 w.RIGHT.PATH_FILESTAT_GET |
1760 w.RIGHT.PATH_FILESTAT_SET_SIZE |
1761 w.RIGHT.PATH_FILESTAT_SET_TIMES |
1762 w.RIGHT.PATH_SYMLINK |
1763 w.RIGHT.PATH_REMOVE_DIRECTORY |
1764 w.RIGHT.PATH_UNLINK_FILE;
1765 }
1766 const symlink_flags: w.lookupflags_t = if (args.no_follow) 0x0 else w.LOOKUP_SYMLINK_FOLLOW;
1767 // TODO do we really need all the rights here?
1768 const inheriting: w.rights_t = w.RIGHT.ALL ^ w.RIGHT.SOCK_SHUTDOWN;
1769
1770 const result = os.openatWasi(
1771 self.fd,
1772 sub_path,
1773 symlink_flags,
1774 w.O.DIRECTORY,
1775 0x0,
1776 base,
1777 inheriting,
1778 );
1779 const fd = result catch |err| switch (err) {
1780 error.FileTooBig => unreachable, // can't happen for directories
1781 error.IsDir => unreachable, // we're providing O.DIRECTORY
1782 error.NoSpaceLeft => unreachable, // not providing O.CREAT
1783 error.PathAlreadyExists => unreachable, // not providing O.CREAT
1784 error.FileLocksNotSupported => unreachable, // locking folders is not supported
1785 error.WouldBlock => unreachable, // can't happen for directories
1786 error.FileBusy => unreachable, // can't happen for directories
1787 else => |e| return e,
1788 };
1789 return Dir{ .fd = fd };
1790 }
1791
1792 /// Same as `openDir` except the parameter is null-terminated.
1793 pub fn openDirZ(self: Dir, sub_path_c: [*:0]const u8, args: OpenDirOptions, iterable: bool) OpenError!Dir {
1794 if (builtin.os.tag == .windows) {
1795 const sub_path_w = try os.windows.cStrToPrefixedFileW(self.fd, sub_path_c);
1796 return self.openDirW(sub_path_w.span().ptr, args, iterable);
1797 }
1798 const symlink_flags: u32 = if (args.no_follow) os.O.NOFOLLOW else 0x0;
1799 if (!iterable) {
1800 const O_PATH = if (@hasDecl(os.O, "PATH")) os.O.PATH else 0;
1801 return self.openDirFlagsZ(sub_path_c, os.O.DIRECTORY | os.O.RDONLY | os.O.CLOEXEC | O_PATH | symlink_flags);
1802 } else {
1803 return self.openDirFlagsZ(sub_path_c, os.O.DIRECTORY | os.O.RDONLY | os.O.CLOEXEC | symlink_flags);
1804 }
1805 }
1806
1807 /// Same as `openDir` except the path parameter is WTF-16 encoded, NT-prefixed.
1808 /// This function asserts the target OS is Windows.
1809 pub fn openDirW(self: Dir, sub_path_w: [*:0]const u16, args: OpenDirOptions, iterable: bool) OpenError!Dir {
1810 const w = os.windows;
1811 // TODO remove some of these flags if args.access_sub_paths is false
1812 const base_flags = w.STANDARD_RIGHTS_READ | w.FILE_READ_ATTRIBUTES | w.FILE_READ_EA |
1813 w.SYNCHRONIZE | w.FILE_TRAVERSE;
1814 const flags: u32 = if (iterable) base_flags | w.FILE_LIST_DIRECTORY else base_flags;
1815 const dir = try self.makeOpenDirAccessMaskW(sub_path_w, flags, .{
1816 .no_follow = args.no_follow,
1817 .create_disposition = w.FILE_OPEN,
1818 });
1819 return dir;
1820 }
1821
1822 /// `flags` must contain `os.O.DIRECTORY`.
1823 fn openDirFlagsZ(self: Dir, sub_path_c: [*:0]const u8, flags: u32) OpenError!Dir {
1824 const result = if (need_async_thread)
1825 std.event.Loop.instance.?.openatZ(self.fd, sub_path_c, flags, 0)
1826 else
1827 os.openatZ(self.fd, sub_path_c, flags, 0);
1828 const fd = result catch |err| switch (err) {
1829 error.FileTooBig => unreachable, // can't happen for directories
1830 error.IsDir => unreachable, // we're providing O.DIRECTORY
1831 error.NoSpaceLeft => unreachable, // not providing O.CREAT
1832 error.PathAlreadyExists => unreachable, // not providing O.CREAT
1833 error.FileLocksNotSupported => unreachable, // locking folders is not supported
1834 error.WouldBlock => unreachable, // can't happen for directories
1835 error.FileBusy => unreachable, // can't happen for directories
1836 else => |e| return e,
1837 };
1838 return Dir{ .fd = fd };
1839 }
1840
1841 const MakeOpenDirAccessMaskWOptions = struct {
1842 no_follow: bool,
1843 create_disposition: u32,
1844 };
1845
1846 fn makeOpenDirAccessMaskW(self: Dir, sub_path_w: [*:0]const u16, access_mask: u32, flags: MakeOpenDirAccessMaskWOptions) OpenError!Dir {
1847 const w = os.windows;
1848
1849 var result = Dir{
1850 .fd = undefined,
1851 };
1852
1853 const path_len_bytes = @as(u16, @intCast(mem.sliceTo(sub_path_w, 0).len * 2));
1854 var nt_name = w.UNICODE_STRING{
1855 .Length = path_len_bytes,
1856 .MaximumLength = path_len_bytes,
1857 .Buffer = @constCast(sub_path_w),
1858 };
1859 var attr = w.OBJECT_ATTRIBUTES{
1860 .Length = @sizeOf(w.OBJECT_ATTRIBUTES),
1861 .RootDirectory = if (path.isAbsoluteWindowsW(sub_path_w)) null else self.fd,
1862 .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
1863 .ObjectName = &nt_name,
1864 .SecurityDescriptor = null,
1865 .SecurityQualityOfService = null,
1866 };
1867 const open_reparse_point: w.DWORD = if (flags.no_follow) w.FILE_OPEN_REPARSE_POINT else 0x0;
1868 var io: w.IO_STATUS_BLOCK = undefined;
1869 const rc = w.ntdll.NtCreateFile(
1870 &result.fd,
1871 access_mask,
1872 &attr,
1873 &io,
1874 null,
1875 w.FILE_ATTRIBUTE_NORMAL,
1876 w.FILE_SHARE_READ | w.FILE_SHARE_WRITE,
1877 flags.create_disposition,
1878 w.FILE_DIRECTORY_FILE | w.FILE_SYNCHRONOUS_IO_NONALERT | w.FILE_OPEN_FOR_BACKUP_INTENT | open_reparse_point,
1879 null,
1880 0,
1881 );
1882
1883 switch (rc) {
1884 .SUCCESS => return result,
1885 .OBJECT_NAME_INVALID => return error.BadPathName,
1886 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
1887 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
1888 .NOT_A_DIRECTORY => return error.NotDir,
1889 // This can happen if the directory has 'List folder contents' permission set to 'Deny'
1890 // and the directory is trying to be opened for iteration.
1891 .ACCESS_DENIED => return error.AccessDenied,
1892 .INVALID_PARAMETER => unreachable,
1893 else => return w.unexpectedStatus(rc),
1894 }
1895 }
1896
1897 pub const DeleteFileError = os.UnlinkError;
1898
1899 /// Delete a file name and possibly the file it refers to, based on an open directory handle.
1900 /// Asserts that the path parameter has no null bytes.
1901 pub fn deleteFile(self: Dir, sub_path: []const u8) DeleteFileError!void {
1902 if (builtin.os.tag == .windows) {
1903 const sub_path_w = try os.windows.sliceToPrefixedFileW(self.fd, sub_path);
1904 return self.deleteFileW(sub_path_w.span());
1905 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
1906 os.unlinkat(self.fd, sub_path, 0) catch |err| switch (err) {
1907 error.DirNotEmpty => unreachable, // not passing AT.REMOVEDIR
1908 else => |e| return e,
1909 };
1910 } else {
1911 const sub_path_c = try os.toPosixPath(sub_path);
1912 return self.deleteFileZ(&sub_path_c);
1913 }
1914 }
1915
1916 /// Same as `deleteFile` except the parameter is null-terminated.
1917 pub fn deleteFileZ(self: Dir, sub_path_c: [*:0]const u8) DeleteFileError!void {
1918 os.unlinkatZ(self.fd, sub_path_c, 0) catch |err| switch (err) {
1919 error.DirNotEmpty => unreachable, // not passing AT.REMOVEDIR
1920 error.AccessDenied => |e| switch (builtin.os.tag) {
1921 // non-Linux POSIX systems return EPERM when trying to delete a directory, so
1922 // we need to handle that case specifically and translate the error
1923 .macos, .ios, .freebsd, .netbsd, .dragonfly, .openbsd, .solaris, .illumos => {
1924 // Don't follow symlinks to match unlinkat (which acts on symlinks rather than follows them)
1925 const fstat = os.fstatatZ(self.fd, sub_path_c, os.AT.SYMLINK_NOFOLLOW) catch return e;
1926 const is_dir = fstat.mode & os.S.IFMT == os.S.IFDIR;
1927 return if (is_dir) error.IsDir else e;
1928 },
1929 else => return e,
1930 },
1931 else => |e| return e,
1932 };
1933 }
1934
1935 /// Same as `deleteFile` except the parameter is WTF-16 encoded.
1936 pub fn deleteFileW(self: Dir, sub_path_w: []const u16) DeleteFileError!void {
1937 os.unlinkatW(self.fd, sub_path_w, 0) catch |err| switch (err) {
1938 error.DirNotEmpty => unreachable, // not passing AT.REMOVEDIR
1939 else => |e| return e,
1940 };
1941 }
1942
1943 pub const DeleteDirError = error{
1944 DirNotEmpty,
1945 FileNotFound,
1946 AccessDenied,
1947 FileBusy,
1948 FileSystem,
1949 SymLinkLoop,
1950 NameTooLong,
1951 NotDir,
1952 SystemResources,
1953 ReadOnlyFileSystem,
1954 InvalidUtf8,
1955 BadPathName,
1956 /// On Windows, `\\server` or `\\server\share` was not found.
1957 NetworkNotFound,
1958 Unexpected,
1959 };
1960
1961 /// Returns `error.DirNotEmpty` if the directory is not empty.
1962 /// To delete a directory recursively, see `deleteTree`.
1963 /// Asserts that the path parameter has no null bytes.
1964 pub fn deleteDir(self: Dir, sub_path: []const u8) DeleteDirError!void {
1965 if (builtin.os.tag == .windows) {
1966 const sub_path_w = try os.windows.sliceToPrefixedFileW(self.fd, sub_path);
1967 return self.deleteDirW(sub_path_w.span());
1968 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
1969 os.unlinkat(self.fd, sub_path, os.AT.REMOVEDIR) catch |err| switch (err) {
1970 error.IsDir => unreachable, // not possible since we pass AT.REMOVEDIR
1971 else => |e| return e,
1972 };
1973 } else {
1974 const sub_path_c = try os.toPosixPath(sub_path);
1975 return self.deleteDirZ(&sub_path_c);
1976 }
1977 }
1978
1979 /// Same as `deleteDir` except the parameter is null-terminated.
1980 pub fn deleteDirZ(self: Dir, sub_path_c: [*:0]const u8) DeleteDirError!void {
1981 os.unlinkatZ(self.fd, sub_path_c, os.AT.REMOVEDIR) catch |err| switch (err) {
1982 error.IsDir => unreachable, // not possible since we pass AT.REMOVEDIR
1983 else => |e| return e,
1984 };
1985 }
1986
1987 /// Same as `deleteDir` except the parameter is UTF16LE, NT prefixed.
1988 /// This function is Windows-only.
1989 pub fn deleteDirW(self: Dir, sub_path_w: []const u16) DeleteDirError!void {
1990 os.unlinkatW(self.fd, sub_path_w, os.AT.REMOVEDIR) catch |err| switch (err) {
1991 error.IsDir => unreachable, // not possible since we pass AT.REMOVEDIR
1992 else => |e| return e,
1993 };
1994 }
1995
1996 pub const RenameError = os.RenameError;
1997
1998 /// Change the name or location of a file or directory.
1999 /// If new_sub_path already exists, it will be replaced.
2000 /// Renaming a file over an existing directory or a directory
2001 /// over an existing file will fail with `error.IsDir` or `error.NotDir`
2002 pub fn rename(self: Dir, old_sub_path: []const u8, new_sub_path: []const u8) RenameError!void {
2003 return os.renameat(self.fd, old_sub_path, self.fd, new_sub_path);
2004 }
2005
2006 /// Same as `rename` except the parameters are null-terminated.
2007 pub fn renameZ(self: Dir, old_sub_path_z: [*:0]const u8, new_sub_path_z: [*:0]const u8) RenameError!void {
2008 return os.renameatZ(self.fd, old_sub_path_z, self.fd, new_sub_path_z);
2009 }
2010
2011 /// Same as `rename` except the parameters are UTF16LE, NT prefixed.
2012 /// This function is Windows-only.
2013 pub fn renameW(self: Dir, old_sub_path_w: []const u16, new_sub_path_w: []const u16) RenameError!void {
2014 return os.renameatW(self.fd, old_sub_path_w, self.fd, new_sub_path_w);
2015 }
2016
2017 /// Creates a symbolic link named `sym_link_path` which contains the string `target_path`.
2018 /// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent
2019 /// one; the latter case is known as a dangling link.
2020 /// If `sym_link_path` exists, it will not be overwritten.
2021 pub fn symLink(
2022 self: Dir,
2023 target_path: []const u8,
2024 sym_link_path: []const u8,
2025 flags: SymLinkFlags,
2026 ) !void {
2027 if (builtin.os.tag == .wasi and !builtin.link_libc) {
2028 return self.symLinkWasi(target_path, sym_link_path, flags);
2029 }
2030 if (builtin.os.tag == .windows) {
2031 // Target path does not use sliceToPrefixedFileW because certain paths
2032 // are handled differently when creating a symlink than they would be
2033 // when converting to an NT namespaced path. CreateSymbolicLink in
2034 // symLinkW will handle the necessary conversion.
2035 var target_path_w: os.windows.PathSpace = undefined;
2036 target_path_w.len = try std.unicode.utf8ToUtf16Le(&target_path_w.data, target_path);
2037 target_path_w.data[target_path_w.len] = 0;
2038 const sym_link_path_w = try os.windows.sliceToPrefixedFileW(self.fd, sym_link_path);
2039 return self.symLinkW(target_path_w.span(), sym_link_path_w.span(), flags);
2040 }
2041 const target_path_c = try os.toPosixPath(target_path);
2042 const sym_link_path_c = try os.toPosixPath(sym_link_path);
2043 return self.symLinkZ(&target_path_c, &sym_link_path_c, flags);
2044 }
2045
2046 /// WASI-only. Same as `symLink` except targeting WASI.
2047 pub fn symLinkWasi(
2048 self: Dir,
2049 target_path: []const u8,
2050 sym_link_path: []const u8,
2051 _: SymLinkFlags,
2052 ) !void {
2053 return os.symlinkat(target_path, self.fd, sym_link_path);
2054 }
2055
2056 /// Same as `symLink`, except the pathname parameters are null-terminated.
2057 pub fn symLinkZ(
2058 self: Dir,
2059 target_path_c: [*:0]const u8,
2060 sym_link_path_c: [*:0]const u8,
2061 flags: SymLinkFlags,
2062 ) !void {
2063 if (builtin.os.tag == .windows) {
2064 const target_path_w = try os.windows.cStrToPrefixedFileW(self.fd, target_path_c);
2065 const sym_link_path_w = try os.windows.cStrToPrefixedFileW(self.fd, sym_link_path_c);
2066 return self.symLinkW(target_path_w.span(), sym_link_path_w.span(), flags);
2067 }
2068 return os.symlinkatZ(target_path_c, self.fd, sym_link_path_c);
2069 }
2070
2071 /// Windows-only. Same as `symLink` except the pathname parameters
2072 /// are null-terminated, WTF16 encoded.
2073 pub fn symLinkW(
2074 self: Dir,
2075 /// WTF-16, does not need to be NT-prefixed. The NT-prefixing
2076 /// of this path is handled by CreateSymbolicLink.
2077 target_path_w: [:0]const u16,
2078 /// WTF-16, must be NT-prefixed or relative
2079 sym_link_path_w: []const u16,
2080 flags: SymLinkFlags,
2081 ) !void {
2082 return os.windows.CreateSymbolicLink(self.fd, sym_link_path_w, target_path_w, flags.is_directory);
2083 }
2084
2085 pub const ReadLinkError = os.ReadLinkError;
2086
2087 /// Read value of a symbolic link.
2088 /// The return value is a slice of `buffer`, from index `0`.
2089 /// Asserts that the path parameter has no null bytes.
2090 pub fn readLink(self: Dir, sub_path: []const u8, buffer: []u8) ReadLinkError![]u8 {
2091 if (builtin.os.tag == .wasi and !builtin.link_libc) {
2092 return self.readLinkWasi(sub_path, buffer);
2093 }
2094 if (builtin.os.tag == .windows) {
2095 const sub_path_w = try os.windows.sliceToPrefixedFileW(self.fd, sub_path);
2096 return self.readLinkW(sub_path_w.span(), buffer);
2097 }
2098 const sub_path_c = try os.toPosixPath(sub_path);
2099 return self.readLinkZ(&sub_path_c, buffer);
2100 }
2101
2102 /// WASI-only. Same as `readLink` except targeting WASI.
2103 pub fn readLinkWasi(self: Dir, sub_path: []const u8, buffer: []u8) ![]u8 {
2104 return os.readlinkat(self.fd, sub_path, buffer);
2105 }
2106
2107 /// Same as `readLink`, except the `pathname` parameter is null-terminated.
2108 pub fn readLinkZ(self: Dir, sub_path_c: [*:0]const u8, buffer: []u8) ![]u8 {
2109 if (builtin.os.tag == .windows) {
2110 const sub_path_w = try os.windows.cStrToPrefixedFileW(self.fd, sub_path_c);
2111 return self.readLinkW(sub_path_w.span(), buffer);
2112 }
2113 return os.readlinkatZ(self.fd, sub_path_c, buffer);
2114 }
2115
2116 /// Windows-only. Same as `readLink` except the pathname parameter
2117 /// is null-terminated, WTF16 encoded.
2118 pub fn readLinkW(self: Dir, sub_path_w: []const u16, buffer: []u8) ![]u8 {
2119 return os.windows.ReadLink(self.fd, sub_path_w, buffer);
2120 }
2121
2122 /// Read all of file contents using a preallocated buffer.
2123 /// The returned slice has the same pointer as `buffer`. If the length matches `buffer.len`
2124 /// the situation is ambiguous. It could either mean that the entire file was read, and
2125 /// it exactly fits the buffer, or it could mean the buffer was not big enough for the
2126 /// entire file.
2127 pub fn readFile(self: Dir, file_path: []const u8, buffer: []u8) ![]u8 {
2128 var file = try self.openFile(file_path, .{});
2129 defer file.close();
2130
2131 const end_index = try file.readAll(buffer);
2132 return buffer[0..end_index];
2133 }
2134
2135 /// On success, caller owns returned buffer.
2136 /// If the file is larger than `max_bytes`, returns `error.FileTooBig`.
2137 pub fn readFileAlloc(self: Dir, allocator: mem.Allocator, file_path: []const u8, max_bytes: usize) ![]u8 {
2138 return self.readFileAllocOptions(allocator, file_path, max_bytes, null, @alignOf(u8), null);
2139 }
2140
2141 /// On success, caller owns returned buffer.
2142 /// If the file is larger than `max_bytes`, returns `error.FileTooBig`.
2143 /// If `size_hint` is specified the initial buffer size is calculated using
2144 /// that value, otherwise the effective file size is used instead.
2145 /// Allows specifying alignment and a sentinel value.
2146 pub fn readFileAllocOptions(
2147 self: Dir,
2148 allocator: mem.Allocator,
2149 file_path: []const u8,
2150 max_bytes: usize,
2151 size_hint: ?usize,
2152 comptime alignment: u29,
2153 comptime optional_sentinel: ?u8,
2154 ) !(if (optional_sentinel) |s| [:s]align(alignment) u8 else []align(alignment) u8) {
2155 var file = try self.openFile(file_path, .{});
2156 defer file.close();
2157
2158 // If the file size doesn't fit a usize it'll be certainly greater than
2159 // `max_bytes`
2160 const stat_size = size_hint orelse math.cast(usize, try file.getEndPos()) orelse
2161 return error.FileTooBig;
2162
2163 return file.readToEndAllocOptions(allocator, max_bytes, stat_size, alignment, optional_sentinel);
2164 }
2165
2166 pub const DeleteTreeError = error{
2167 InvalidHandle,
2168 AccessDenied,
2169 FileTooBig,
2170 SymLinkLoop,
2171 ProcessFdQuotaExceeded,
2172 NameTooLong,
2173 SystemFdQuotaExceeded,
2174 NoDevice,
2175 SystemResources,
2176 ReadOnlyFileSystem,
2177 FileSystem,
2178 FileBusy,
2179 DeviceBusy,
2180
2181 /// One of the path components was not a directory.
2182 /// This error is unreachable if `sub_path` does not contain a path separator.
2183 NotDir,
2184
2185 /// On Windows, file paths must be valid Unicode.
2186 InvalidUtf8,
2187
2188 /// On Windows, file paths cannot contain these characters:
2189 /// '/', '*', '?', '"', '<', '>', '|'
2190 BadPathName,
2191
2192 /// On Windows, `\\server` or `\\server\share` was not found.
2193 NetworkNotFound,
2194 } || os.UnexpectedError;
2195
2196 /// Whether `full_path` describes a symlink, file, or directory, this function
2197 /// removes it. If it cannot be removed because it is a non-empty directory,
2198 /// this function recursively removes its entries and then tries again.
2199 /// This operation is not atomic on most file systems.
2200 pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void {
2201 var initial_iterable_dir = (try self.deleteTreeOpenInitialSubpath(sub_path, .file)) orelse return;
2202
2203 const StackItem = struct {
2204 name: []const u8,
2205 parent_dir: Dir,
2206 iter: IterableDir.Iterator,
2207
2208 fn closeAll(items: []@This()) void {
2209 for (items) |*item| item.iter.dir.close();
2210 }
2211 };
2212
2213 var stack_buffer: [16]StackItem = undefined;
2214 var stack = std.ArrayListUnmanaged(StackItem).initBuffer(&stack_buffer);
2215 defer StackItem.closeAll(stack.items);
2216
2217 stack.appendAssumeCapacity(.{
2218 .name = sub_path,
2219 .parent_dir = self,
2220 .iter = initial_iterable_dir.iterateAssumeFirstIteration(),
2221 });
2222
2223 process_stack: while (stack.items.len != 0) {
2224 var top = &stack.items[stack.items.len - 1];
2225 while (try top.iter.next()) |entry| {
2226 var treat_as_dir = entry.kind == .directory;
2227 handle_entry: while (true) {
2228 if (treat_as_dir) {
2229 if (stack.unusedCapacitySlice().len >= 1) {
2230 var iterable_dir = top.iter.dir.openIterableDir(entry.name, .{ .no_follow = true }) catch |err| switch (err) {
2231 error.NotDir => {
2232 treat_as_dir = false;
2233 continue :handle_entry;
2234 },
2235 error.FileNotFound => {
2236 // That's fine, we were trying to remove this directory anyway.
2237 break :handle_entry;
2238 },
2239
2240 error.InvalidHandle,
2241 error.AccessDenied,
2242 error.SymLinkLoop,
2243 error.ProcessFdQuotaExceeded,
2244 error.NameTooLong,
2245 error.SystemFdQuotaExceeded,
2246 error.NoDevice,
2247 error.SystemResources,
2248 error.Unexpected,
2249 error.InvalidUtf8,
2250 error.BadPathName,
2251 error.NetworkNotFound,
2252 error.DeviceBusy,
2253 => |e| return e,
2254 };
2255 stack.appendAssumeCapacity(.{
2256 .name = entry.name,
2257 .parent_dir = top.iter.dir,
2258 .iter = iterable_dir.iterateAssumeFirstIteration(),
2259 });
2260 continue :process_stack;
2261 } else {
2262 try top.iter.dir.deleteTreeMinStackSizeWithKindHint(entry.name, entry.kind);
2263 break :handle_entry;
2264 }
2265 } else {
2266 if (top.iter.dir.deleteFile(entry.name)) {
2267 break :handle_entry;
2268 } else |err| switch (err) {
2269 error.FileNotFound => break :handle_entry,
2270
2271 // Impossible because we do not pass any path separators.
2272 error.NotDir => unreachable,
2273
2274 error.IsDir => {
2275 treat_as_dir = true;
2276 continue :handle_entry;
2277 },
2278
2279 error.AccessDenied,
2280 error.InvalidUtf8,
2281 error.SymLinkLoop,
2282 error.NameTooLong,
2283 error.SystemResources,
2284 error.ReadOnlyFileSystem,
2285 error.FileSystem,
2286 error.FileBusy,
2287 error.BadPathName,
2288 error.NetworkNotFound,
2289 error.Unexpected,
2290 => |e| return e,
2291 }
2292 }
2293 }
2294 }
2295
2296 // On Windows, we can't delete until the dir's handle has been closed, so
2297 // close it before we try to delete.
2298 top.iter.dir.close();
2299
2300 // In order to avoid double-closing the directory when cleaning up
2301 // the stack in the case of an error, we save the relevant portions and
2302 // pop the value from the stack.
2303 const parent_dir = top.parent_dir;
2304 const name = top.name;
2305 stack.items.len -= 1;
2306
2307 var need_to_retry: bool = false;
2308 parent_dir.deleteDir(name) catch |err| switch (err) {
2309 error.FileNotFound => {},
2310 error.DirNotEmpty => need_to_retry = true,
2311 else => |e| return e,
2312 };
2313
2314 if (need_to_retry) {
2315 // Since we closed the handle that the previous iterator used, we
2316 // need to re-open the dir and re-create the iterator.
2317 var iterable_dir = iterable_dir: {
2318 var treat_as_dir = true;
2319 handle_entry: while (true) {
2320 if (treat_as_dir) {
2321 break :iterable_dir parent_dir.openIterableDir(name, .{ .no_follow = true }) catch |err| switch (err) {
2322 error.NotDir => {
2323 treat_as_dir = false;
2324 continue :handle_entry;
2325 },
2326 error.FileNotFound => {
2327 // That's fine, we were trying to remove this directory anyway.
2328 continue :process_stack;
2329 },
2330
2331 error.InvalidHandle,
2332 error.AccessDenied,
2333 error.SymLinkLoop,
2334 error.ProcessFdQuotaExceeded,
2335 error.NameTooLong,
2336 error.SystemFdQuotaExceeded,
2337 error.NoDevice,
2338 error.SystemResources,
2339 error.Unexpected,
2340 error.InvalidUtf8,
2341 error.BadPathName,
2342 error.NetworkNotFound,
2343 error.DeviceBusy,
2344 => |e| return e,
2345 };
2346 } else {
2347 if (parent_dir.deleteFile(name)) {
2348 continue :process_stack;
2349 } else |err| switch (err) {
2350 error.FileNotFound => continue :process_stack,
2351
2352 // Impossible because we do not pass any path separators.
2353 error.NotDir => unreachable,
2354
2355 error.IsDir => {
2356 treat_as_dir = true;
2357 continue :handle_entry;
2358 },
2359
2360 error.AccessDenied,
2361 error.InvalidUtf8,
2362 error.SymLinkLoop,
2363 error.NameTooLong,
2364 error.SystemResources,
2365 error.ReadOnlyFileSystem,
2366 error.FileSystem,
2367 error.FileBusy,
2368 error.BadPathName,
2369 error.NetworkNotFound,
2370 error.Unexpected,
2371 => |e| return e,
2372 }
2373 }
2374 }
2375 };
2376 // We know there is room on the stack since we are just re-adding
2377 // the StackItem that we previously popped.
2378 stack.appendAssumeCapacity(.{
2379 .name = name,
2380 .parent_dir = parent_dir,
2381 .iter = iterable_dir.iterateAssumeFirstIteration(),
2382 });
2383 continue :process_stack;
2384 }
2385 }
2386 }
2387
2388 /// Like `deleteTree`, but only keeps one `Iterator` active at a time to minimize the function's stack size.
2389 /// This is slower than `deleteTree` but uses less stack space.
2390 pub fn deleteTreeMinStackSize(self: Dir, sub_path: []const u8) DeleteTreeError!void {
2391 return self.deleteTreeMinStackSizeWithKindHint(sub_path, .file);
2392 }
2393
2394 fn deleteTreeMinStackSizeWithKindHint(self: Dir, sub_path: []const u8, kind_hint: File.Kind) DeleteTreeError!void {
2395 start_over: while (true) {
2396 var iterable_dir = (try self.deleteTreeOpenInitialSubpath(sub_path, kind_hint)) orelse return;
2397 var cleanup_dir_parent: ?IterableDir = null;
2398 defer if (cleanup_dir_parent) |*d| d.close();
2399
2400 var cleanup_dir = true;
2401 defer if (cleanup_dir) iterable_dir.close();
2402
2403 // Valid use of MAX_PATH_BYTES because dir_name_buf will only
2404 // ever store a single path component that was returned from the
2405 // filesystem.
2406 var dir_name_buf: [MAX_PATH_BYTES]u8 = undefined;
2407 var dir_name: []const u8 = sub_path;
2408
2409 // Here we must avoid recursion, in order to provide O(1) memory guarantee of this function.
2410 // Go through each entry and if it is not a directory, delete it. If it is a directory,
2411 // open it, and close the original directory. Repeat. Then start the entire operation over.
2412
2413 scan_dir: while (true) {
2414 var dir_it = iterable_dir.iterateAssumeFirstIteration();
2415 dir_it: while (try dir_it.next()) |entry| {
2416 var treat_as_dir = entry.kind == .directory;
2417 handle_entry: while (true) {
2418 if (treat_as_dir) {
2419 const new_dir = iterable_dir.dir.openIterableDir(entry.name, .{ .no_follow = true }) catch |err| switch (err) {
2420 error.NotDir => {
2421 treat_as_dir = false;
2422 continue :handle_entry;
2423 },
2424 error.FileNotFound => {
2425 // That's fine, we were trying to remove this directory anyway.
2426 continue :dir_it;
2427 },
2428
2429 error.InvalidHandle,
2430 error.AccessDenied,
2431 error.SymLinkLoop,
2432 error.ProcessFdQuotaExceeded,
2433 error.NameTooLong,
2434 error.SystemFdQuotaExceeded,
2435 error.NoDevice,
2436 error.SystemResources,
2437 error.Unexpected,
2438 error.InvalidUtf8,
2439 error.BadPathName,
2440 error.NetworkNotFound,
2441 error.DeviceBusy,
2442 => |e| return e,
2443 };
2444 if (cleanup_dir_parent) |*d| d.close();
2445 cleanup_dir_parent = iterable_dir;
2446 iterable_dir = new_dir;
2447 const result = dir_name_buf[0..entry.name.len];
2448 @memcpy(result, entry.name);
2449 dir_name = result;
2450 continue :scan_dir;
2451 } else {
2452 if (iterable_dir.dir.deleteFile(entry.name)) {
2453 continue :dir_it;
2454 } else |err| switch (err) {
2455 error.FileNotFound => continue :dir_it,
2456
2457 // Impossible because we do not pass any path separators.
2458 error.NotDir => unreachable,
2459
2460 error.IsDir => {
2461 treat_as_dir = true;
2462 continue :handle_entry;
2463 },
2464
2465 error.AccessDenied,
2466 error.InvalidUtf8,
2467 error.SymLinkLoop,
2468 error.NameTooLong,
2469 error.SystemResources,
2470 error.ReadOnlyFileSystem,
2471 error.FileSystem,
2472 error.FileBusy,
2473 error.BadPathName,
2474 error.NetworkNotFound,
2475 error.Unexpected,
2476 => |e| return e,
2477 }
2478 }
2479 }
2480 }
2481 // Reached the end of the directory entries, which means we successfully deleted all of them.
2482 // Now to remove the directory itself.
2483 iterable_dir.close();
2484 cleanup_dir = false;
2485
2486 if (cleanup_dir_parent) |d| {
2487 d.dir.deleteDir(dir_name) catch |err| switch (err) {
2488 // These two things can happen due to file system race conditions.
2489 error.FileNotFound, error.DirNotEmpty => continue :start_over,
2490 else => |e| return e,
2491 };
2492 continue :start_over;
2493 } else {
2494 self.deleteDir(sub_path) catch |err| switch (err) {
2495 error.FileNotFound => return,
2496 error.DirNotEmpty => continue :start_over,
2497 else => |e| return e,
2498 };
2499 return;
2500 }
2501 }
2502 }
2503 }
2504
2505 /// On successful delete, returns null.
2506 fn deleteTreeOpenInitialSubpath(self: Dir, sub_path: []const u8, kind_hint: File.Kind) !?IterableDir {
2507 return iterable_dir: {
2508 // Treat as a file by default
2509 var treat_as_dir = kind_hint == .directory;
2510
2511 handle_entry: while (true) {
2512 if (treat_as_dir) {
2513 break :iterable_dir self.openIterableDir(sub_path, .{ .no_follow = true }) catch |err| switch (err) {
2514 error.NotDir => {
2515 treat_as_dir = false;
2516 continue :handle_entry;
2517 },
2518 error.FileNotFound => {
2519 // That's fine, we were trying to remove this directory anyway.
2520 return null;
2521 },
2522
2523 error.InvalidHandle,
2524 error.AccessDenied,
2525 error.SymLinkLoop,
2526 error.ProcessFdQuotaExceeded,
2527 error.NameTooLong,
2528 error.SystemFdQuotaExceeded,
2529 error.NoDevice,
2530 error.SystemResources,
2531 error.Unexpected,
2532 error.InvalidUtf8,
2533 error.BadPathName,
2534 error.DeviceBusy,
2535 error.NetworkNotFound,
2536 => |e| return e,
2537 };
2538 } else {
2539 if (self.deleteFile(sub_path)) {
2540 return null;
2541 } else |err| switch (err) {
2542 error.FileNotFound => return null,
2543
2544 error.IsDir => {
2545 treat_as_dir = true;
2546 continue :handle_entry;
2547 },
2548
2549 error.AccessDenied,
2550 error.InvalidUtf8,
2551 error.SymLinkLoop,
2552 error.NameTooLong,
2553 error.SystemResources,
2554 error.ReadOnlyFileSystem,
2555 error.NotDir,
2556 error.FileSystem,
2557 error.FileBusy,
2558 error.BadPathName,
2559 error.NetworkNotFound,
2560 error.Unexpected,
2561 => |e| return e,
2562 }
2563 }
2564 }
2565 };
2566 }
2567
2568 pub const WriteFileError = File.WriteError || File.OpenError;
2569
2570 /// Deprecated: use `writeFile2`.
2571 pub fn writeFile(self: Dir, sub_path: []const u8, data: []const u8) WriteFileError!void {
2572 return writeFile2(self, .{
2573 .sub_path = sub_path,
2574 .data = data,
2575 .flags = .{},
2576 });
2577 }
2578
2579 pub const WriteFileOptions = struct {
2580 sub_path: []const u8,
2581 data: []const u8,
2582 flags: File.CreateFlags = .{},
2583 };
2584
2585 /// Writes content to the file system, using the file creation flags provided.
2586 pub fn writeFile2(self: Dir, options: WriteFileOptions) WriteFileError!void {
2587 var file = try self.createFile(options.sub_path, options.flags);
2588 defer file.close();
2589 try file.writeAll(options.data);
2590 }
2591
2592 pub const AccessError = os.AccessError;
2593
2594 /// Test accessing `path`.
2595 /// `path` is UTF-8-encoded.
2596 /// Be careful of Time-Of-Check-Time-Of-Use race conditions when using this function.
2597 /// For example, instead of testing if a file exists and then opening it, just
2598 /// open it and handle the error for file not found.
2599 pub fn access(self: Dir, sub_path: []const u8, flags: File.OpenFlags) AccessError!void {
2600 if (builtin.os.tag == .windows) {
2601 const sub_path_w = os.windows.sliceToPrefixedFileW(self.fd, sub_path) catch |err| switch (err) {
2602 error.AccessDenied => return error.PermissionDenied,
2603 else => |e| return e,
2604 };
2605 return self.accessW(sub_path_w.span().ptr, flags);
2606 }
2607 const path_c = try os.toPosixPath(sub_path);
2608 return self.accessZ(&path_c, flags);
2609 }
2610
2611 /// Same as `access` except the path parameter is null-terminated.
2612 pub fn accessZ(self: Dir, sub_path: [*:0]const u8, flags: File.OpenFlags) AccessError!void {
2613 if (builtin.os.tag == .windows) {
2614 const sub_path_w = os.windows.cStrToPrefixedFileW(self.fd, sub_path) catch |err| switch (err) {
2615 error.AccessDenied => return error.PermissionDenied,
2616 else => |e| return e,
2617 };
2618 return self.accessW(sub_path_w.span().ptr, flags);
2619 }
2620 const os_mode = switch (flags.mode) {
2621 .read_only => @as(u32, os.F_OK),
2622 .write_only => @as(u32, os.W_OK),
2623 .read_write => @as(u32, os.R_OK | os.W_OK),
2624 };
2625 const result = if (need_async_thread and flags.intended_io_mode != .blocking)
2626 std.event.Loop.instance.?.faccessatZ(self.fd, sub_path, os_mode, 0)
2627 else
2628 os.faccessatZ(self.fd, sub_path, os_mode, 0);
2629 return result;
2630 }
2631
2632 /// Same as `access` except asserts the target OS is Windows and the path parameter is
2633 /// * WTF-16 encoded
2634 /// * null-terminated
2635 /// * NtDll prefixed
2636 /// TODO currently this ignores `flags`.
2637 pub fn accessW(self: Dir, sub_path_w: [*:0]const u16, flags: File.OpenFlags) AccessError!void {
2638 _ = flags;
2639 return os.faccessatW(self.fd, sub_path_w, 0, 0);
2640 }
2641
2642 /// Check the file size, mtime, and mode of `source_path` and `dest_path`. If they are equal, does nothing.
2643 /// Otherwise, atomically copies `source_path` to `dest_path`. The destination file gains the mtime,
2644 /// atime, and mode of the source file so that the next call to `updateFile` will not need a copy.
2645 /// Returns the previous status of the file before updating.
2646 /// If any of the directories do not exist for dest_path, they are created.
2647 pub fn updateFile(
2648 source_dir: Dir,
2649 source_path: []const u8,
2650 dest_dir: Dir,
2651 dest_path: []const u8,
2652 options: CopyFileOptions,
2653 ) !PrevStatus {
2654 var src_file = try source_dir.openFile(source_path, .{});
2655 defer src_file.close();
2656
2657 const src_stat = try src_file.stat();
2658 const actual_mode = options.override_mode orelse src_stat.mode;
2659 check_dest_stat: {
2660 const dest_stat = blk: {
2661 var dest_file = dest_dir.openFile(dest_path, .{}) catch |err| switch (err) {
2662 error.FileNotFound => break :check_dest_stat,
2663 else => |e| return e,
2664 };
2665 defer dest_file.close();
2666
2667 break :blk try dest_file.stat();
2668 };
2669
2670 if (src_stat.size == dest_stat.size and
2671 src_stat.mtime == dest_stat.mtime and
2672 actual_mode == dest_stat.mode)
2673 {
2674 return PrevStatus.fresh;
2675 }
2676 }
2677
2678 if (path.dirname(dest_path)) |dirname| {
2679 try dest_dir.makePath(dirname);
2680 }
2681
2682 var atomic_file = try dest_dir.atomicFile(dest_path, .{ .mode = actual_mode });
2683 defer atomic_file.deinit();
2684
2685 try atomic_file.file.writeFileAll(src_file, .{ .in_len = src_stat.size });
2686 try atomic_file.file.updateTimes(src_stat.atime, src_stat.mtime);
2687 try atomic_file.finish();
2688 return PrevStatus.stale;
2689 }
2690
2691 pub const CopyFileError = File.OpenError || File.StatError || AtomicFile.InitError || CopyFileRawError || AtomicFile.FinishError;
2692
2693 /// Guaranteed to be atomic.
2694 /// On Linux, until https://patchwork.kernel.org/patch/9636735/ is merged and readily available,
2695 /// there is a possibility of power loss or application termination leaving temporary files present
2696 /// in the same directory as dest_path.
2697 pub fn copyFile(source_dir: Dir, source_path: []const u8, dest_dir: Dir, dest_path: []const u8, options: CopyFileOptions) CopyFileError!void {
2698 var in_file = try source_dir.openFile(source_path, .{});
2699 defer in_file.close();
2700
2701 var size: ?u64 = null;
2702 const mode = options.override_mode orelse blk: {
2703 const st = try in_file.stat();
2704 size = st.size;
2705 break :blk st.mode;
2706 };
2707
2708 var atomic_file = try dest_dir.atomicFile(dest_path, .{ .mode = mode });
2709 defer atomic_file.deinit();
2710
2711 try copy_file(in_file.handle, atomic_file.file.handle, size);
2712 try atomic_file.finish();
2713 }
2714
2715 pub const AtomicFileOptions = struct {
2716 mode: File.Mode = File.default_mode,
2717 };
2718
2719 /// Directly access the `.file` field, and then call `AtomicFile.finish`
2720 /// to atomically replace `dest_path` with contents.
2721 /// Always call `AtomicFile.deinit` to clean up, regardless of whether `AtomicFile.finish` succeeded.
2722 /// `dest_path` must remain valid until `AtomicFile.deinit` is called.
2723 pub fn atomicFile(self: Dir, dest_path: []const u8, options: AtomicFileOptions) !AtomicFile {
2724 if (path.dirname(dest_path)) |dirname| {
2725 const dir = try self.openDir(dirname, .{});
2726 return AtomicFile.init(path.basename(dest_path), options.mode, dir, true);
2727 } else {
2728 return AtomicFile.init(dest_path, options.mode, self, false);
2729 }
2730 }
2731
2732 pub const Stat = File.Stat;
2733 pub const StatError = File.StatError;
2734
2735 pub fn stat(self: Dir) StatError!Stat {
2736 const file: File = .{
2737 .handle = self.fd,
2738 .capable_io_mode = .blocking,
2739 };
2740 return file.stat();
2741 }
2742
2743 pub const StatFileError = File.OpenError || File.StatError || os.FStatAtError;
2744
2745 /// Returns metadata for a file inside the directory.
2746 ///
2747 /// On Windows, this requires three syscalls. On other operating systems, it
2748 /// only takes one.
2749 ///
2750 /// Symlinks are followed.
2751 ///
2752 /// `sub_path` may be absolute, in which case `self` is ignored.
2753 pub fn statFile(self: Dir, sub_path: []const u8) StatFileError!Stat {
2754 if (builtin.os.tag == .windows) {
2755 var file = try self.openFile(sub_path, .{});
2756 defer file.close();
2757 return file.stat();
2758 }
2759 if (builtin.os.tag == .wasi and !builtin.link_libc) {
2760 const st = try os.fstatatWasi(self.fd, sub_path, os.wasi.LOOKUP_SYMLINK_FOLLOW);
2761 return Stat.fromSystem(st);
2762 }
2763 const st = try os.fstatat(self.fd, sub_path, 0);
2764 return Stat.fromSystem(st);
2765 }
2766
2767 const Permissions = File.Permissions;
2768 pub const SetPermissionsError = File.SetPermissionsError;
2769
2770 /// Sets permissions according to the provided `Permissions` struct.
2771 /// This method is *NOT* available on WASI
2772 pub fn setPermissions(self: Dir, permissions: Permissions) SetPermissionsError!void {
2773 const file: File = .{
2774 .handle = self.fd,
2775 .capable_io_mode = .blocking,
2776 };
2777 try file.setPermissions(permissions);
2778 }
2779
2780 const Metadata = File.Metadata;
2781 pub const MetadataError = File.MetadataError;
2782
2783 /// Returns a `Metadata` struct, representing the permissions on the directory
2784 pub fn metadata(self: Dir) MetadataError!Metadata {
2785 const file: File = .{
2786 .handle = self.fd,
2787 .capable_io_mode = .blocking,
2788 };
2789 return try file.metadata();
2790 }
2791};
2792
2793/// Returns a handle to the current working directory. It is not opened with iteration capability.229/// Returns a handle to the current working directory. It is not opened with iteration capability.
2794/// Closing the returned `Dir` is checked illegal behavior. Iterating over the result is illegal behavior.230/// Closing the returned `Dir` is checked illegal behavior. Iterating over the result is illegal behavior.
2795/// On POSIX targets, this function is comptime-callable.231/// On POSIX targets, this function is comptime-callable.
...@@ -2821,33 +257,12 @@ pub fn openDirAbsolute(absolute_path: []const u8, flags: Dir.OpenDirOptions) Fil...@@ -2821,33 +257,12 @@ pub fn openDirAbsolute(absolute_path: []const u8, flags: Dir.OpenDirOptions) Fil
2821/// Same as `openDirAbsolute` but the path parameter is null-terminated.257/// Same as `openDirAbsolute` but the path parameter is null-terminated.
2822pub fn openDirAbsoluteZ(absolute_path_c: [*:0]const u8, flags: Dir.OpenDirOptions) File.OpenError!Dir {258pub fn openDirAbsoluteZ(absolute_path_c: [*:0]const u8, flags: Dir.OpenDirOptions) File.OpenError!Dir {
2823 assert(path.isAbsoluteZ(absolute_path_c));259 assert(path.isAbsoluteZ(absolute_path_c));
2824 return cwd().openDirZ(absolute_path_c, flags, false);260 return cwd().openDirZ(absolute_path_c, flags);
2825}261}
2826/// Same as `openDirAbsolute` but the path parameter is null-terminated.262/// Same as `openDirAbsolute` but the path parameter is null-terminated.
2827pub fn openDirAbsoluteW(absolute_path_c: [*:0]const u16, flags: Dir.OpenDirOptions) File.OpenError!Dir {263pub fn openDirAbsoluteW(absolute_path_c: [*:0]const u16, flags: Dir.OpenDirOptions) File.OpenError!Dir {
2828 assert(path.isAbsoluteWindowsW(absolute_path_c));264 assert(path.isAbsoluteWindowsW(absolute_path_c));
2829 return cwd().openDirW(absolute_path_c, flags, false);265 return cwd().openDirW(absolute_path_c, flags);
2830}
2831
2832/// Opens a directory at the given path. The directory is a system resource that remains
2833/// open until `close` is called on the result.
2834/// See `openIterableDirAbsoluteZ` for a function that accepts a null-terminated path.
2835///
2836/// Asserts that the path parameter has no null bytes.
2837pub fn openIterableDirAbsolute(absolute_path: []const u8, flags: Dir.OpenDirOptions) File.OpenError!IterableDir {
2838 assert(path.isAbsolute(absolute_path));
2839 return cwd().openIterableDir(absolute_path, flags);
2840}
2841
2842/// Same as `openIterableDirAbsolute` but the path parameter is null-terminated.
2843pub fn openIterableDirAbsoluteZ(absolute_path_c: [*:0]const u8, flags: Dir.OpenDirOptions) File.OpenError!IterableDir {
2844 assert(path.isAbsoluteZ(absolute_path_c));
2845 return IterableDir{ .dir = try cwd().openDirZ(absolute_path_c, flags, true) };
2846}
2847/// Same as `openIterableDirAbsolute` but the path parameter is null-terminated.
2848pub fn openIterableDirAbsoluteW(absolute_path_c: [*:0]const u16, flags: Dir.OpenDirOptions) File.OpenError!IterableDir {
2849 assert(path.isAbsoluteWindowsW(absolute_path_c));
2850 return IterableDir{ .dir = try cwd().openDirW(absolute_path_c, flags, true) };
2851}266}
2852267
2853/// Opens a file for reading or writing, without attempting to create a new file, based on an absolute path.268/// Opens a file for reading or writing, without attempting to create a new file, based on an absolute path.
...@@ -2976,20 +391,16 @@ pub fn readLinkAbsoluteZ(pathname_c: [*:0]const u8, buffer: *[MAX_PATH_BYTES]u8)...@@ -2976,20 +391,16 @@ pub fn readLinkAbsoluteZ(pathname_c: [*:0]const u8, buffer: *[MAX_PATH_BYTES]u8)
2976 return os.readlinkZ(pathname_c, buffer);391 return os.readlinkZ(pathname_c, buffer);
2977}392}
2978393
2979/// Use with `Dir.symLink` and `symLinkAbsolute` to specify whether the symlink
2980/// will point to a file or a directory. This value is ignored on all hosts
2981/// except Windows where creating symlinks to different resource types, requires
2982/// different flags. By default, `symLinkAbsolute` is assumed to point to a file.
2983pub const SymLinkFlags = struct {
2984 is_directory: bool = false,
2985};
2986
2987/// Creates a symbolic link named `sym_link_path` which contains the string `target_path`.394/// Creates a symbolic link named `sym_link_path` which contains the string `target_path`.
2988/// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent395/// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent
2989/// one; the latter case is known as a dangling link.396/// one; the latter case is known as a dangling link.
2990/// If `sym_link_path` exists, it will not be overwritten.397/// If `sym_link_path` exists, it will not be overwritten.
2991/// See also `symLinkAbsoluteZ` and `symLinkAbsoluteW`.398/// See also `symLinkAbsoluteZ` and `symLinkAbsoluteW`.
2992pub fn symLinkAbsolute(target_path: []const u8, sym_link_path: []const u8, flags: SymLinkFlags) !void {399pub fn symLinkAbsolute(
400 target_path: []const u8,
401 sym_link_path: []const u8,
402 flags: Dir.SymLinkFlags,
403) !void {
2993 assert(path.isAbsolute(target_path));404 assert(path.isAbsolute(target_path));
2994 assert(path.isAbsolute(sym_link_path));405 assert(path.isAbsolute(sym_link_path));
2995 if (builtin.os.tag == .windows) {406 if (builtin.os.tag == .windows) {
...@@ -3004,7 +415,11 @@ pub fn symLinkAbsolute(target_path: []const u8, sym_link_path: []const u8, flags...@@ -3004,7 +415,11 @@ pub fn symLinkAbsolute(target_path: []const u8, sym_link_path: []const u8, flags
3004/// Note that this function will by default try creating a symbolic link to a file. If you would415/// Note that this function will by default try creating a symbolic link to a file. If you would
3005/// like to create a symbolic link to a directory, specify this with `SymLinkFlags{ .is_directory = true }`.416/// like to create a symbolic link to a directory, specify this with `SymLinkFlags{ .is_directory = true }`.
3006/// See also `symLinkAbsolute`, `symLinkAbsoluteZ`.417/// See also `symLinkAbsolute`, `symLinkAbsoluteZ`.
3007pub fn symLinkAbsoluteW(target_path_w: []const u16, sym_link_path_w: []const u16, flags: SymLinkFlags) !void {418pub fn symLinkAbsoluteW(
419 target_path_w: []const u16,
420 sym_link_path_w: []const u16,
421 flags: Dir.SymLinkFlags,
422) !void {
3008 assert(path.isAbsoluteWindowsWTF16(target_path_w));423 assert(path.isAbsoluteWindowsWTF16(target_path_w));
3009 assert(path.isAbsoluteWindowsWTF16(sym_link_path_w));424 assert(path.isAbsoluteWindowsWTF16(sym_link_path_w));
3010 return os.windows.CreateSymbolicLink(null, sym_link_path_w, target_path_w, flags.is_directory);425 return os.windows.CreateSymbolicLink(null, sym_link_path_w, target_path_w, flags.is_directory);
...@@ -3012,7 +427,11 @@ pub fn symLinkAbsoluteW(target_path_w: []const u16, sym_link_path_w: []const u16...@@ -3012,7 +427,11 @@ pub fn symLinkAbsoluteW(target_path_w: []const u16, sym_link_path_w: []const u16
3012427
3013/// Same as `symLinkAbsolute` except the parameters are null-terminated pointers.428/// Same as `symLinkAbsolute` except the parameters are null-terminated pointers.
3014/// See also `symLinkAbsolute`.429/// See also `symLinkAbsolute`.
3015pub fn symLinkAbsoluteZ(target_path_c: [*:0]const u8, sym_link_path_c: [*:0]const u8, flags: SymLinkFlags) !void {430pub fn symLinkAbsoluteZ(
431 target_path_c: [*:0]const u8,
432 sym_link_path_c: [*:0]const u8,
433 flags: Dir.SymLinkFlags,
434) !void {
3016 assert(path.isAbsoluteZ(target_path_c));435 assert(path.isAbsoluteZ(target_path_c));
3017 assert(path.isAbsoluteZ(sym_link_path_c));436 assert(path.isAbsoluteZ(sym_link_path_c));
3018 if (builtin.os.tag == .windows) {437 if (builtin.os.tag == .windows) {
...@@ -3209,59 +628,6 @@ pub fn realpathAlloc(allocator: Allocator, pathname: []const u8) ![]u8 {...@@ -3209,59 +628,6 @@ pub fn realpathAlloc(allocator: Allocator, pathname: []const u8) ![]u8 {
3209 return allocator.dupe(u8, try os.realpath(pathname, &buf));628 return allocator.dupe(u8, try os.realpath(pathname, &buf));
3210}629}
3211630
3212const CopyFileRawError = error{SystemResources} || os.CopyFileRangeError || os.SendFileError;
3213
3214// Transfer all the data between two file descriptors in the most efficient way.
3215// The copy starts at offset 0, the initial offsets are preserved.
3216// No metadata is transferred over.
3217fn copy_file(fd_in: os.fd_t, fd_out: os.fd_t, maybe_size: ?u64) CopyFileRawError!void {
3218 if (comptime builtin.target.isDarwin()) {
3219 const rc = os.system.fcopyfile(fd_in, fd_out, null, os.system.COPYFILE_DATA);
3220 switch (os.errno(rc)) {
3221 .SUCCESS => return,
3222 .INVAL => unreachable,
3223 .NOMEM => return error.SystemResources,
3224 // The source file is not a directory, symbolic link, or regular file.
3225 // Try with the fallback path before giving up.
3226 .OPNOTSUPP => {},
3227 else => |err| return os.unexpectedErrno(err),
3228 }
3229 }
3230
3231 if (builtin.os.tag == .linux) {
3232 // Try copy_file_range first as that works at the FS level and is the
3233 // most efficient method (if available).
3234 var offset: u64 = 0;
3235 cfr_loop: while (true) {
3236 // The kernel checks the u64 value `offset+count` for overflow, use
3237 // a 32 bit value so that the syscall won't return EINVAL except for
3238 // impossibly large files (> 2^64-1 - 2^32-1).
3239 const amt = try os.copy_file_range(fd_in, offset, fd_out, offset, math.maxInt(u32), 0);
3240 // Terminate as soon as we have copied size bytes or no bytes
3241 if (maybe_size) |s| {
3242 if (s == amt) break :cfr_loop;
3243 }
3244 if (amt == 0) break :cfr_loop;
3245 offset += amt;
3246 }
3247 return;
3248 }
3249
3250 // Sendfile is a zero-copy mechanism iff the OS supports it, otherwise the
3251 // fallback code will copy the contents chunk by chunk.
3252 const empty_iovec = [0]os.iovec_const{};
3253 var offset: u64 = 0;
3254 sendfile_loop: while (true) {
3255 const amt = try os.sendfile(fd_out, fd_in, offset, 0, &empty_iovec, &empty_iovec, 0);
3256 // Terminate as soon as we have copied size bytes or no bytes
3257 if (maybe_size) |s| {
3258 if (s == amt) break :sendfile_loop;
3259 }
3260 if (amt == 0) break :sendfile_loop;
3261 offset += amt;
3262 }
3263}
3264
3265test {631test {
3266 if (builtin.os.tag != .wasi) {632 if (builtin.os.tag != .wasi) {
3267 _ = &makeDirAbsolute;633 _ = &makeDirAbsolute;
...@@ -3269,10 +635,11 @@ test {...@@ -3269,10 +635,11 @@ test {
3269 _ = &copyFileAbsolute;635 _ = &copyFileAbsolute;
3270 _ = &updateFileAbsolute;636 _ = &updateFileAbsolute;
3271 }637 }
3272 _ = &Dir.copyFile;638 _ = &AtomicFile;
639 _ = &Dir;
640 _ = &File;
641 _ = &path;
3273 _ = @import("fs/test.zig");642 _ = @import("fs/test.zig");
3274 _ = @import("fs/path.zig");
3275 _ = @import("fs/file.zig");
3276 _ = @import("fs/get_app_data_dir.zig");643 _ = @import("fs/get_app_data_dir.zig");
3277 _ = @import("fs/watch.zig");644 _ = @import("fs/watch.zig");
3278}645}
lib/std/fs/AtomicFile.zig created+85
...@@ -0,0 +1,85 @@
1file: File,
2// TODO either replace this with rand_buf or use []u16 on Windows
3tmp_path_buf: [tmp_path_len:0]u8,
4dest_basename: []const u8,
5file_open: bool,
6file_exists: bool,
7close_dir_on_deinit: bool,
8dir: Dir,
9
10pub const InitError = File.OpenError;
11
12pub const random_bytes_len = 12;
13const tmp_path_len = fs.base64_encoder.calcSize(random_bytes_len);
14
15/// Note that the `Dir.atomicFile` API may be more handy than this lower-level function.
16pub fn init(
17 dest_basename: []const u8,
18 mode: File.Mode,
19 dir: Dir,
20 close_dir_on_deinit: bool,
21) InitError!AtomicFile {
22 var rand_buf: [random_bytes_len]u8 = undefined;
23 var tmp_path_buf: [tmp_path_len:0]u8 = undefined;
24
25 while (true) {
26 std.crypto.random.bytes(rand_buf[0..]);
27 const tmp_path = fs.base64_encoder.encode(&tmp_path_buf, &rand_buf);
28 tmp_path_buf[tmp_path.len] = 0;
29
30 const file = dir.createFile(
31 tmp_path,
32 .{ .mode = mode, .exclusive = true },
33 ) catch |err| switch (err) {
34 error.PathAlreadyExists => continue,
35 else => |e| return e,
36 };
37
38 return AtomicFile{
39 .file = file,
40 .tmp_path_buf = tmp_path_buf,
41 .dest_basename = dest_basename,
42 .file_open = true,
43 .file_exists = true,
44 .close_dir_on_deinit = close_dir_on_deinit,
45 .dir = dir,
46 };
47 }
48}
49
50/// Always call deinit, even after a successful finish().
51pub fn deinit(self: *AtomicFile) void {
52 if (self.file_open) {
53 self.file.close();
54 self.file_open = false;
55 }
56 if (self.file_exists) {
57 self.dir.deleteFile(&self.tmp_path_buf) catch {};
58 self.file_exists = false;
59 }
60 if (self.close_dir_on_deinit) {
61 self.dir.close();
62 }
63 self.* = undefined;
64}
65
66pub const FinishError = posix.RenameError;
67
68pub fn finish(self: *AtomicFile) FinishError!void {
69 assert(self.file_exists);
70 if (self.file_open) {
71 self.file.close();
72 self.file_open = false;
73 }
74 try posix.renameat(self.dir.fd, self.tmp_path_buf[0..], self.dir.fd, self.dest_basename);
75 self.file_exists = false;
76}
77
78const AtomicFile = @This();
79const std = @import("../std.zig");
80const File = std.fs.File;
81const Dir = std.fs.Dir;
82const fs = std.fs;
83const assert = std.debug.assert;
84// https://github.com/ziglang/zig/issues/5019
85const posix = std.os;
lib/std/fs/Dir.zig created+2534
...@@ -0,0 +1,2534 @@
1fd: posix.fd_t,
2
3pub const default_mode = 0o755;
4
5pub const Entry = struct {
6 name: []const u8,
7 kind: Kind,
8
9 pub const Kind = File.Kind;
10};
11
12const IteratorError = error{ AccessDenied, SystemResources } || posix.UnexpectedError;
13
14pub const Iterator = switch (builtin.os.tag) {
15 .macos, .ios, .freebsd, .netbsd, .dragonfly, .openbsd, .solaris, .illumos => struct {
16 dir: Dir,
17 seek: i64,
18 buf: [1024]u8, // TODO align(@alignOf(posix.system.dirent)),
19 index: usize,
20 end_index: usize,
21 first_iter: bool,
22
23 const Self = @This();
24
25 pub const Error = IteratorError;
26
27 /// Memory such as file names referenced in this returned entry becomes invalid
28 /// with subsequent calls to `next`, as well as when this `Dir` is deinitialized.
29 pub fn next(self: *Self) Error!?Entry {
30 switch (builtin.os.tag) {
31 .macos, .ios => return self.nextDarwin(),
32 .freebsd, .netbsd, .dragonfly, .openbsd => return self.nextBsd(),
33 .solaris, .illumos => return self.nextSolaris(),
34 else => @compileError("unimplemented"),
35 }
36 }
37
38 fn nextDarwin(self: *Self) !?Entry {
39 start_over: while (true) {
40 if (self.index >= self.end_index) {
41 if (self.first_iter) {
42 posix.lseek_SET(self.dir.fd, 0) catch unreachable; // EBADF here likely means that the Dir was not opened with iteration permissions
43 self.first_iter = false;
44 }
45 const rc = posix.system.__getdirentries64(
46 self.dir.fd,
47 &self.buf,
48 self.buf.len,
49 &self.seek,
50 );
51 if (rc == 0) return null;
52 if (rc < 0) {
53 switch (posix.errno(rc)) {
54 .BADF => unreachable, // Dir is invalid or was opened without iteration ability
55 .FAULT => unreachable,
56 .NOTDIR => unreachable,
57 .INVAL => unreachable,
58 else => |err| return posix.unexpectedErrno(err),
59 }
60 }
61 self.index = 0;
62 self.end_index = @as(usize, @intCast(rc));
63 }
64 const darwin_entry = @as(*align(1) posix.system.dirent, @ptrCast(&self.buf[self.index]));
65 const next_index = self.index + darwin_entry.reclen();
66 self.index = next_index;
67
68 const name = @as([*]u8, @ptrCast(&darwin_entry.d_name))[0..darwin_entry.d_namlen];
69
70 if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..") or (darwin_entry.d_ino == 0)) {
71 continue :start_over;
72 }
73
74 const entry_kind: Entry.Kind = switch (darwin_entry.d_type) {
75 posix.DT.BLK => .block_device,
76 posix.DT.CHR => .character_device,
77 posix.DT.DIR => .directory,
78 posix.DT.FIFO => .named_pipe,
79 posix.DT.LNK => .sym_link,
80 posix.DT.REG => .file,
81 posix.DT.SOCK => .unix_domain_socket,
82 posix.DT.WHT => .whiteout,
83 else => .unknown,
84 };
85 return Entry{
86 .name = name,
87 .kind = entry_kind,
88 };
89 }
90 }
91
92 fn nextSolaris(self: *Self) !?Entry {
93 start_over: while (true) {
94 if (self.index >= self.end_index) {
95 if (self.first_iter) {
96 posix.lseek_SET(self.dir.fd, 0) catch unreachable; // EBADF here likely means that the Dir was not opened with iteration permissions
97 self.first_iter = false;
98 }
99 const rc = posix.system.getdents(self.dir.fd, &self.buf, self.buf.len);
100 switch (posix.errno(rc)) {
101 .SUCCESS => {},
102 .BADF => unreachable, // Dir is invalid or was opened without iteration ability
103 .FAULT => unreachable,
104 .NOTDIR => unreachable,
105 .INVAL => unreachable,
106 else => |err| return posix.unexpectedErrno(err),
107 }
108 if (rc == 0) return null;
109 self.index = 0;
110 self.end_index = @as(usize, @intCast(rc));
111 }
112 const entry = @as(*align(1) posix.system.dirent, @ptrCast(&self.buf[self.index]));
113 const next_index = self.index + entry.reclen();
114 self.index = next_index;
115
116 const name = mem.sliceTo(@as([*:0]u8, @ptrCast(&entry.d_name)), 0);
117 if (mem.eql(u8, name, ".") or mem.eql(u8, name, ".."))
118 continue :start_over;
119
120 // Solaris dirent doesn't expose d_type, so we have to call stat to get it.
121 const stat_info = posix.fstatat(
122 self.dir.fd,
123 name,
124 posix.AT.SYMLINK_NOFOLLOW,
125 ) catch |err| switch (err) {
126 error.NameTooLong => unreachable,
127 error.SymLinkLoop => unreachable,
128 error.FileNotFound => unreachable, // lost the race
129 else => |e| return e,
130 };
131 const entry_kind: Entry.Kind = switch (stat_info.mode & posix.S.IFMT) {
132 posix.S.IFIFO => .named_pipe,
133 posix.S.IFCHR => .character_device,
134 posix.S.IFDIR => .directory,
135 posix.S.IFBLK => .block_device,
136 posix.S.IFREG => .file,
137 posix.S.IFLNK => .sym_link,
138 posix.S.IFSOCK => .unix_domain_socket,
139 posix.S.IFDOOR => .door,
140 posix.S.IFPORT => .event_port,
141 else => .unknown,
142 };
143 return Entry{
144 .name = name,
145 .kind = entry_kind,
146 };
147 }
148 }
149
150 fn nextBsd(self: *Self) !?Entry {
151 start_over: while (true) {
152 if (self.index >= self.end_index) {
153 if (self.first_iter) {
154 posix.lseek_SET(self.dir.fd, 0) catch unreachable; // EBADF here likely means that the Dir was not opened with iteration permissions
155 self.first_iter = false;
156 }
157 const rc = if (builtin.os.tag == .netbsd)
158 posix.system.__getdents30(self.dir.fd, &self.buf, self.buf.len)
159 else
160 posix.system.getdents(self.dir.fd, &self.buf, self.buf.len);
161 switch (posix.errno(rc)) {
162 .SUCCESS => {},
163 .BADF => unreachable, // Dir is invalid or was opened without iteration ability
164 .FAULT => unreachable,
165 .NOTDIR => unreachable,
166 .INVAL => unreachable,
167 // Introduced in freebsd 13.2: directory unlinked but still open.
168 // To be consistent, iteration ends if the directory being iterated is deleted during iteration.
169 .NOENT => return null,
170 else => |err| return posix.unexpectedErrno(err),
171 }
172 if (rc == 0) return null;
173 self.index = 0;
174 self.end_index = @as(usize, @intCast(rc));
175 }
176 const bsd_entry = @as(*align(1) posix.system.dirent, @ptrCast(&self.buf[self.index]));
177 const next_index = self.index + bsd_entry.reclen();
178 self.index = next_index;
179
180 const name = @as([*]u8, @ptrCast(&bsd_entry.d_name))[0..bsd_entry.d_namlen];
181
182 const skip_zero_fileno = switch (builtin.os.tag) {
183 // d_fileno=0 is used to mark invalid entries or deleted files.
184 .openbsd, .netbsd => true,
185 else => false,
186 };
187 if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..") or
188 (skip_zero_fileno and bsd_entry.d_fileno == 0))
189 {
190 continue :start_over;
191 }
192
193 const entry_kind: Entry.Kind = switch (bsd_entry.d_type) {
194 posix.DT.BLK => .block_device,
195 posix.DT.CHR => .character_device,
196 posix.DT.DIR => .directory,
197 posix.DT.FIFO => .named_pipe,
198 posix.DT.LNK => .sym_link,
199 posix.DT.REG => .file,
200 posix.DT.SOCK => .unix_domain_socket,
201 posix.DT.WHT => .whiteout,
202 else => .unknown,
203 };
204 return Entry{
205 .name = name,
206 .kind = entry_kind,
207 };
208 }
209 }
210
211 pub fn reset(self: *Self) void {
212 self.index = 0;
213 self.end_index = 0;
214 self.first_iter = true;
215 }
216 },
217 .haiku => struct {
218 dir: Dir,
219 buf: [1024]u8, // TODO align(@alignOf(posix.dirent64)),
220 index: usize,
221 end_index: usize,
222 first_iter: bool,
223
224 const Self = @This();
225
226 pub const Error = IteratorError;
227
228 /// Memory such as file names referenced in this returned entry becomes invalid
229 /// with subsequent calls to `next`, as well as when this `Dir` is deinitialized.
230 pub fn next(self: *Self) Error!?Entry {
231 start_over: while (true) {
232 // TODO: find a better max
233 const HAIKU_MAX_COUNT = 10000;
234 if (self.index >= self.end_index) {
235 if (self.first_iter) {
236 posix.lseek_SET(self.dir.fd, 0) catch unreachable; // EBADF here likely means that the Dir was not opened with iteration permissions
237 self.first_iter = false;
238 }
239 const rc = posix.system._kern_read_dir(
240 self.dir.fd,
241 &self.buf,
242 self.buf.len,
243 HAIKU_MAX_COUNT,
244 );
245 if (rc == 0) return null;
246 if (rc < 0) {
247 switch (posix.errno(rc)) {
248 .BADF => unreachable, // Dir is invalid or was opened without iteration ability
249 .FAULT => unreachable,
250 .NOTDIR => unreachable,
251 .INVAL => unreachable,
252 else => |err| return posix.unexpectedErrno(err),
253 }
254 }
255 self.index = 0;
256 self.end_index = @as(usize, @intCast(rc));
257 }
258 const haiku_entry = @as(*align(1) posix.system.dirent, @ptrCast(&self.buf[self.index]));
259 const next_index = self.index + haiku_entry.reclen();
260 self.index = next_index;
261 const name = mem.sliceTo(@as([*:0]u8, @ptrCast(&haiku_entry.d_name)), 0);
262
263 if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..") or (haiku_entry.d_ino == 0)) {
264 continue :start_over;
265 }
266
267 var stat_info: posix.Stat = undefined;
268 const rc = posix.system._kern_read_stat(
269 self.dir.fd,
270 &haiku_entry.d_name,
271 false,
272 &stat_info,
273 0,
274 );
275 if (rc != 0) {
276 switch (posix.errno(rc)) {
277 .SUCCESS => {},
278 .BADF => unreachable, // Dir is invalid or was opened without iteration ability
279 .FAULT => unreachable,
280 .NOTDIR => unreachable,
281 .INVAL => unreachable,
282 else => |err| return posix.unexpectedErrno(err),
283 }
284 }
285 const statmode = stat_info.mode & posix.S.IFMT;
286
287 const entry_kind: Entry.Kind = switch (statmode) {
288 posix.S.IFDIR => .directory,
289 posix.S.IFBLK => .block_device,
290 posix.S.IFCHR => .character_device,
291 posix.S.IFLNK => .sym_link,
292 posix.S.IFREG => .file,
293 posix.S.IFIFO => .named_pipe,
294 else => .unknown,
295 };
296
297 return Entry{
298 .name = name,
299 .kind = entry_kind,
300 };
301 }
302 }
303
304 pub fn reset(self: *Self) void {
305 self.index = 0;
306 self.end_index = 0;
307 self.first_iter = true;
308 }
309 },
310 .linux => struct {
311 dir: Dir,
312 // The if guard is solely there to prevent compile errors from missing `linux.dirent64`
313 // definition when compiling for other OSes. It doesn't do anything when compiling for Linux.
314 buf: [1024]u8 align(if (builtin.os.tag != .linux) 1 else @alignOf(linux.dirent64)),
315 index: usize,
316 end_index: usize,
317 first_iter: bool,
318
319 const Self = @This();
320 const linux = std.os.linux;
321
322 pub const Error = IteratorError;
323
324 /// Memory such as file names referenced in this returned entry becomes invalid
325 /// with subsequent calls to `next`, as well as when this `Dir` is deinitialized.
326 pub fn next(self: *Self) Error!?Entry {
327 return self.nextLinux() catch |err| switch (err) {
328 // To be consistent across platforms, iteration ends if the directory being iterated is deleted during iteration.
329 // This matches the behavior of non-Linux UNIX platforms.
330 error.DirNotFound => null,
331 else => |e| return e,
332 };
333 }
334
335 pub const ErrorLinux = error{DirNotFound} || IteratorError;
336
337 /// Implementation of `next` that can return `error.DirNotFound` if the directory being
338 /// iterated was deleted during iteration (this error is Linux specific).
339 pub fn nextLinux(self: *Self) ErrorLinux!?Entry {
340 start_over: while (true) {
341 if (self.index >= self.end_index) {
342 if (self.first_iter) {
343 posix.lseek_SET(self.dir.fd, 0) catch unreachable; // EBADF here likely means that the Dir was not opened with iteration permissions
344 self.first_iter = false;
345 }
346 const rc = linux.getdents64(self.dir.fd, &self.buf, self.buf.len);
347 switch (linux.getErrno(rc)) {
348 .SUCCESS => {},
349 .BADF => unreachable, // Dir is invalid or was opened without iteration ability
350 .FAULT => unreachable,
351 .NOTDIR => unreachable,
352 .NOENT => return error.DirNotFound, // The directory being iterated was deleted during iteration.
353 .INVAL => return error.Unexpected, // Linux may in some cases return EINVAL when reading /proc/$PID/net.
354 .ACCES => return error.AccessDenied, // Do not have permission to iterate this directory.
355 else => |err| return posix.unexpectedErrno(err),
356 }
357 if (rc == 0) return null;
358 self.index = 0;
359 self.end_index = rc;
360 }
361 const linux_entry = @as(*align(1) linux.dirent64, @ptrCast(&self.buf[self.index]));
362 const next_index = self.index + linux_entry.reclen();
363 self.index = next_index;
364
365 const name = mem.sliceTo(@as([*:0]u8, @ptrCast(&linux_entry.d_name)), 0);
366
367 // skip . and .. entries
368 if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) {
369 continue :start_over;
370 }
371
372 const entry_kind: Entry.Kind = switch (linux_entry.d_type) {
373 linux.DT.BLK => .block_device,
374 linux.DT.CHR => .character_device,
375 linux.DT.DIR => .directory,
376 linux.DT.FIFO => .named_pipe,
377 linux.DT.LNK => .sym_link,
378 linux.DT.REG => .file,
379 linux.DT.SOCK => .unix_domain_socket,
380 else => .unknown,
381 };
382 return Entry{
383 .name = name,
384 .kind = entry_kind,
385 };
386 }
387 }
388
389 pub fn reset(self: *Self) void {
390 self.index = 0;
391 self.end_index = 0;
392 self.first_iter = true;
393 }
394 },
395 .windows => struct {
396 dir: Dir,
397 buf: [1024]u8 align(@alignOf(std.os.windows.FILE_BOTH_DIR_INFORMATION)),
398 index: usize,
399 end_index: usize,
400 first_iter: bool,
401 name_data: [fs.MAX_NAME_BYTES]u8,
402
403 const Self = @This();
404
405 pub const Error = IteratorError;
406
407 /// Memory such as file names referenced in this returned entry becomes invalid
408 /// with subsequent calls to `next`, as well as when this `Dir` is deinitialized.
409 pub fn next(self: *Self) Error!?Entry {
410 while (true) {
411 const w = std.os.windows;
412 if (self.index >= self.end_index) {
413 var io: w.IO_STATUS_BLOCK = undefined;
414 const rc = w.ntdll.NtQueryDirectoryFile(
415 self.dir.fd,
416 null,
417 null,
418 null,
419 &io,
420 &self.buf,
421 self.buf.len,
422 .FileBothDirectoryInformation,
423 w.FALSE,
424 null,
425 if (self.first_iter) @as(w.BOOLEAN, w.TRUE) else @as(w.BOOLEAN, w.FALSE),
426 );
427 self.first_iter = false;
428 if (io.Information == 0) return null;
429 self.index = 0;
430 self.end_index = io.Information;
431 switch (rc) {
432 .SUCCESS => {},
433 .ACCESS_DENIED => return error.AccessDenied, // Double-check that the Dir was opened with iteration ability
434
435 else => return w.unexpectedStatus(rc),
436 }
437 }
438
439 // While the official api docs guarantee FILE_BOTH_DIR_INFORMATION to be aligned properly
440 // this may not always be the case (e.g. due to faulty VM/Sandboxing tools)
441 const dir_info: *align(2) w.FILE_BOTH_DIR_INFORMATION = @ptrCast(@alignCast(&self.buf[self.index]));
442 if (dir_info.NextEntryOffset != 0) {
443 self.index += dir_info.NextEntryOffset;
444 } else {
445 self.index = self.buf.len;
446 }
447
448 const name_utf16le = @as([*]u16, @ptrCast(&dir_info.FileName))[0 .. dir_info.FileNameLength / 2];
449
450 if (mem.eql(u16, name_utf16le, &[_]u16{'.'}) or mem.eql(u16, name_utf16le, &[_]u16{ '.', '.' }))
451 continue;
452 // Trust that Windows gives us valid UTF-16LE
453 const name_utf8_len = std.unicode.utf16leToUtf8(self.name_data[0..], name_utf16le) catch unreachable;
454 const name_utf8 = self.name_data[0..name_utf8_len];
455 const kind: Entry.Kind = blk: {
456 const attrs = dir_info.FileAttributes;
457 if (attrs & w.FILE_ATTRIBUTE_DIRECTORY != 0) break :blk .directory;
458 if (attrs & w.FILE_ATTRIBUTE_REPARSE_POINT != 0) break :blk .sym_link;
459 break :blk .file;
460 };
461 return Entry{
462 .name = name_utf8,
463 .kind = kind,
464 };
465 }
466 }
467
468 pub fn reset(self: *Self) void {
469 self.index = 0;
470 self.end_index = 0;
471 self.first_iter = true;
472 }
473 },
474 .wasi => struct {
475 dir: Dir,
476 buf: [1024]u8, // TODO align(@alignOf(posix.wasi.dirent_t)),
477 cookie: u64,
478 index: usize,
479 end_index: usize,
480
481 const Self = @This();
482
483 pub const Error = IteratorError;
484
485 /// Memory such as file names referenced in this returned entry becomes invalid
486 /// with subsequent calls to `next`, as well as when this `Dir` is deinitialized.
487 pub fn next(self: *Self) Error!?Entry {
488 return self.nextWasi() catch |err| switch (err) {
489 // To be consistent across platforms, iteration ends if the directory being iterated is deleted during iteration.
490 // This matches the behavior of non-Linux UNIX platforms.
491 error.DirNotFound => null,
492 else => |e| return e,
493 };
494 }
495
496 pub const ErrorWasi = error{DirNotFound} || IteratorError;
497
498 /// Implementation of `next` that can return platform-dependent errors depending on the host platform.
499 /// When the host platform is Linux, `error.DirNotFound` can be returned if the directory being
500 /// iterated was deleted during iteration.
501 pub fn nextWasi(self: *Self) ErrorWasi!?Entry {
502 // We intentinally use fd_readdir even when linked with libc,
503 // since its implementation is exactly the same as below,
504 // and we avoid the code complexity here.
505 const w = std.os.wasi;
506 start_over: while (true) {
507 // According to the WASI spec, the last entry might be truncated,
508 // so we need to check if the left buffer contains the whole dirent.
509 if (self.end_index - self.index < @sizeOf(w.dirent_t)) {
510 var bufused: usize = undefined;
511 switch (w.fd_readdir(self.dir.fd, &self.buf, self.buf.len, self.cookie, &bufused)) {
512 .SUCCESS => {},
513 .BADF => unreachable, // Dir is invalid or was opened without iteration ability
514 .FAULT => unreachable,
515 .NOTDIR => unreachable,
516 .INVAL => unreachable,
517 .NOENT => return error.DirNotFound, // The directory being iterated was deleted during iteration.
518 .NOTCAPABLE => return error.AccessDenied,
519 else => |err| return posix.unexpectedErrno(err),
520 }
521 if (bufused == 0) return null;
522 self.index = 0;
523 self.end_index = bufused;
524 }
525 const entry = @as(*align(1) w.dirent_t, @ptrCast(&self.buf[self.index]));
526 const entry_size = @sizeOf(w.dirent_t);
527 const name_index = self.index + entry_size;
528 if (name_index + entry.d_namlen > self.end_index) {
529 // This case, the name is truncated, so we need to call readdir to store the entire name.
530 self.end_index = self.index; // Force fd_readdir in the next loop.
531 continue :start_over;
532 }
533 const name = self.buf[name_index .. name_index + entry.d_namlen];
534
535 const next_index = name_index + entry.d_namlen;
536 self.index = next_index;
537 self.cookie = entry.d_next;
538
539 // skip . and .. entries
540 if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) {
541 continue :start_over;
542 }
543
544 const entry_kind: Entry.Kind = switch (entry.d_type) {
545 .BLOCK_DEVICE => .block_device,
546 .CHARACTER_DEVICE => .character_device,
547 .DIRECTORY => .directory,
548 .SYMBOLIC_LINK => .sym_link,
549 .REGULAR_FILE => .file,
550 .SOCKET_STREAM, .SOCKET_DGRAM => .unix_domain_socket,
551 else => .unknown,
552 };
553 return Entry{
554 .name = name,
555 .kind = entry_kind,
556 };
557 }
558 }
559
560 pub fn reset(self: *Self) void {
561 self.index = 0;
562 self.end_index = 0;
563 self.cookie = std.os.wasi.DIRCOOKIE_START;
564 }
565 },
566 else => @compileError("unimplemented"),
567};
568
569pub fn iterate(self: Dir) Iterator {
570 return self.iterateImpl(true);
571}
572
573/// Like `iterate`, but will not reset the directory cursor before the first
574/// iteration. This should only be used in cases where it is known that the
575/// `Dir` has not had its cursor modified yet (e.g. it was just opened).
576pub fn iterateAssumeFirstIteration(self: Dir) Iterator {
577 return self.iterateImpl(false);
578}
579
580fn iterateImpl(self: Dir, first_iter_start_value: bool) Iterator {
581 switch (builtin.os.tag) {
582 .macos,
583 .ios,
584 .freebsd,
585 .netbsd,
586 .dragonfly,
587 .openbsd,
588 .solaris,
589 .illumos,
590 => return Iterator{
591 .dir = self,
592 .seek = 0,
593 .index = 0,
594 .end_index = 0,
595 .buf = undefined,
596 .first_iter = first_iter_start_value,
597 },
598 .linux, .haiku => return Iterator{
599 .dir = self,
600 .index = 0,
601 .end_index = 0,
602 .buf = undefined,
603 .first_iter = first_iter_start_value,
604 },
605 .windows => return Iterator{
606 .dir = self,
607 .index = 0,
608 .end_index = 0,
609 .first_iter = first_iter_start_value,
610 .buf = undefined,
611 .name_data = undefined,
612 },
613 .wasi => return Iterator{
614 .dir = self,
615 .cookie = std.os.wasi.DIRCOOKIE_START,
616 .index = 0,
617 .end_index = 0,
618 .buf = undefined,
619 },
620 else => @compileError("unimplemented"),
621 }
622}
623
624pub const Walker = struct {
625 stack: std.ArrayList(StackItem),
626 name_buffer: std.ArrayList(u8),
627
628 pub const WalkerEntry = struct {
629 /// The containing directory. This can be used to operate directly on `basename`
630 /// rather than `path`, avoiding `error.NameTooLong` for deeply nested paths.
631 /// The directory remains open until `next` or `deinit` is called.
632 dir: Dir,
633 basename: []const u8,
634 path: []const u8,
635 kind: Dir.Entry.Kind,
636 };
637
638 const StackItem = struct {
639 iter: Dir.Iterator,
640 dirname_len: usize,
641 };
642
643 /// After each call to this function, and on deinit(), the memory returned
644 /// from this function becomes invalid. A copy must be made in order to keep
645 /// a reference to the path.
646 pub fn next(self: *Walker) !?WalkerEntry {
647 while (self.stack.items.len != 0) {
648 // `top` and `containing` become invalid after appending to `self.stack`
649 var top = &self.stack.items[self.stack.items.len - 1];
650 var containing = top;
651 var dirname_len = top.dirname_len;
652 if (top.iter.next() catch |err| {
653 // If we get an error, then we want the user to be able to continue
654 // walking if they want, which means that we need to pop the directory
655 // that errored from the stack. Otherwise, all future `next` calls would
656 // likely just fail with the same error.
657 var item = self.stack.pop();
658 if (self.stack.items.len != 0) {
659 item.iter.dir.close();
660 }
661 return err;
662 }) |base| {
663 self.name_buffer.shrinkRetainingCapacity(dirname_len);
664 if (self.name_buffer.items.len != 0) {
665 try self.name_buffer.append(fs.path.sep);
666 dirname_len += 1;
667 }
668 try self.name_buffer.appendSlice(base.name);
669 if (base.kind == .directory) {
670 var new_dir = top.iter.dir.openDir(base.name, .{ .iterate = true }) catch |err| switch (err) {
671 error.NameTooLong => unreachable, // no path sep in base.name
672 else => |e| return e,
673 };
674 {
675 errdefer new_dir.close();
676 try self.stack.append(StackItem{
677 .iter = new_dir.iterateAssumeFirstIteration(),
678 .dirname_len = self.name_buffer.items.len,
679 });
680 top = &self.stack.items[self.stack.items.len - 1];
681 containing = &self.stack.items[self.stack.items.len - 2];
682 }
683 }
684 return WalkerEntry{
685 .dir = containing.iter.dir,
686 .basename = self.name_buffer.items[dirname_len..],
687 .path = self.name_buffer.items,
688 .kind = base.kind,
689 };
690 } else {
691 var item = self.stack.pop();
692 if (self.stack.items.len != 0) {
693 item.iter.dir.close();
694 }
695 }
696 }
697 return null;
698 }
699
700 pub fn deinit(self: *Walker) void {
701 // Close any remaining directories except the initial one (which is always at index 0)
702 if (self.stack.items.len > 1) {
703 for (self.stack.items[1..]) |*item| {
704 item.iter.dir.close();
705 }
706 }
707 self.stack.deinit();
708 self.name_buffer.deinit();
709 }
710};
711
712/// Recursively iterates over a directory.
713/// `self` must have been opened with `OpenDirOptions{.iterate = true}`.
714/// Must call `Walker.deinit` when done.
715/// The order of returned file system entries is undefined.
716/// `self` will not be closed after walking it.
717pub fn walk(self: Dir, allocator: Allocator) !Walker {
718 var name_buffer = std.ArrayList(u8).init(allocator);
719 errdefer name_buffer.deinit();
720
721 var stack = std.ArrayList(Walker.StackItem).init(allocator);
722 errdefer stack.deinit();
723
724 try stack.append(Walker.StackItem{
725 .iter = self.iterate(),
726 .dirname_len = 0,
727 });
728
729 return Walker{
730 .stack = stack,
731 .name_buffer = name_buffer,
732 };
733}
734
735pub const OpenError = error{
736 FileNotFound,
737 NotDir,
738 InvalidHandle,
739 AccessDenied,
740 SymLinkLoop,
741 ProcessFdQuotaExceeded,
742 NameTooLong,
743 SystemFdQuotaExceeded,
744 NoDevice,
745 SystemResources,
746 InvalidUtf8,
747 BadPathName,
748 DeviceBusy,
749 /// On Windows, `\\server` or `\\server\share` was not found.
750 NetworkNotFound,
751} || posix.UnexpectedError;
752
753pub fn close(self: *Dir) void {
754 if (fs.need_async_thread) {
755 std.event.Loop.instance.?.close(self.fd);
756 } else {
757 posix.close(self.fd);
758 }
759 self.* = undefined;
760}
761
762/// Opens a file for reading or writing, without attempting to create a new file.
763/// To create a new file, see `createFile`.
764/// Call `File.close` to release the resource.
765/// Asserts that the path parameter has no null bytes.
766pub fn openFile(self: Dir, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File {
767 if (builtin.os.tag == .windows) {
768 const path_w = try std.os.windows.sliceToPrefixedFileW(self.fd, sub_path);
769 return self.openFileW(path_w.span(), flags);
770 }
771 if (builtin.os.tag == .wasi and !builtin.link_libc) {
772 return self.openFileWasi(sub_path, flags);
773 }
774 const path_c = try posix.toPosixPath(sub_path);
775 return self.openFileZ(&path_c, flags);
776}
777
778/// Same as `openFile` but WASI only.
779pub fn openFileWasi(self: Dir, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File {
780 const w = std.os.wasi;
781 var fdflags: w.fdflags_t = 0x0;
782 var base: w.rights_t = 0x0;
783 if (flags.isRead()) {
784 base |= w.RIGHT.FD_READ | w.RIGHT.FD_TELL | w.RIGHT.FD_SEEK | w.RIGHT.FD_FILESTAT_GET;
785 }
786 if (flags.isWrite()) {
787 fdflags |= w.FDFLAG.APPEND;
788 base |= w.RIGHT.FD_WRITE |
789 w.RIGHT.FD_TELL |
790 w.RIGHT.FD_SEEK |
791 w.RIGHT.FD_DATASYNC |
792 w.RIGHT.FD_FDSTAT_SET_FLAGS |
793 w.RIGHT.FD_SYNC |
794 w.RIGHT.FD_ALLOCATE |
795 w.RIGHT.FD_ADVISE |
796 w.RIGHT.FD_FILESTAT_SET_TIMES |
797 w.RIGHT.FD_FILESTAT_SET_SIZE;
798 }
799 const fd = try posix.openatWasi(self.fd, sub_path, 0x0, 0x0, fdflags, base, 0x0);
800 return File{ .handle = fd };
801}
802
803/// Same as `openFile` but the path parameter is null-terminated.
804pub fn openFileZ(self: Dir, sub_path: [*:0]const u8, flags: File.OpenFlags) File.OpenError!File {
805 if (builtin.os.tag == .windows) {
806 const path_w = try std.os.windows.cStrToPrefixedFileW(self.fd, sub_path);
807 return self.openFileW(path_w.span(), flags);
808 }
809
810 var os_flags: u32 = 0;
811 if (@hasDecl(posix.O, "CLOEXEC")) os_flags = posix.O.CLOEXEC;
812
813 // Use the O locking flags if the os supports them to acquire the lock
814 // atomically.
815 const has_flock_open_flags = @hasDecl(posix.O, "EXLOCK");
816 if (has_flock_open_flags) {
817 // Note that the O.NONBLOCK flag is removed after the openat() call
818 // is successful.
819 const nonblocking_lock_flag: u32 = if (flags.lock_nonblocking)
820 posix.O.NONBLOCK
821 else
822 0;
823 os_flags |= switch (flags.lock) {
824 .none => @as(u32, 0),
825 .shared => posix.O.SHLOCK | nonblocking_lock_flag,
826 .exclusive => posix.O.EXLOCK | nonblocking_lock_flag,
827 };
828 }
829 if (@hasDecl(posix.O, "LARGEFILE")) {
830 os_flags |= posix.O.LARGEFILE;
831 }
832 if (@hasDecl(posix.O, "NOCTTY") and !flags.allow_ctty) {
833 os_flags |= posix.O.NOCTTY;
834 }
835 os_flags |= switch (flags.mode) {
836 .read_only => @as(u32, posix.O.RDONLY),
837 .write_only => @as(u32, posix.O.WRONLY),
838 .read_write => @as(u32, posix.O.RDWR),
839 };
840 const fd = if (flags.intended_io_mode != .blocking)
841 try std.event.Loop.instance.?.openatZ(self.fd, sub_path, os_flags, 0)
842 else
843 try posix.openatZ(self.fd, sub_path, os_flags, 0);
844 errdefer posix.close(fd);
845
846 // WASI doesn't have posix.flock so we intetinally check OS prior to the inner if block
847 // since it is not compiltime-known and we need to avoid undefined symbol in Wasm.
848 if (@hasDecl(posix.system, "LOCK") and builtin.target.os.tag != .wasi) {
849 if (!has_flock_open_flags and flags.lock != .none) {
850 // TODO: integrate async I/O
851 const lock_nonblocking: i32 = if (flags.lock_nonblocking) posix.LOCK.NB else 0;
852 try posix.flock(fd, switch (flags.lock) {
853 .none => unreachable,
854 .shared => posix.LOCK.SH | lock_nonblocking,
855 .exclusive => posix.LOCK.EX | lock_nonblocking,
856 });
857 }
858 }
859
860 if (has_flock_open_flags and flags.lock_nonblocking) {
861 var fl_flags = posix.fcntl(fd, posix.F.GETFL, 0) catch |err| switch (err) {
862 error.FileBusy => unreachable,
863 error.Locked => unreachable,
864 error.PermissionDenied => unreachable,
865 error.DeadLock => unreachable,
866 error.LockedRegionLimitExceeded => unreachable,
867 else => |e| return e,
868 };
869 fl_flags &= ~@as(usize, posix.O.NONBLOCK);
870 _ = posix.fcntl(fd, posix.F.SETFL, fl_flags) catch |err| switch (err) {
871 error.FileBusy => unreachable,
872 error.Locked => unreachable,
873 error.PermissionDenied => unreachable,
874 error.DeadLock => unreachable,
875 error.LockedRegionLimitExceeded => unreachable,
876 else => |e| return e,
877 };
878 }
879
880 return File{
881 .handle = fd,
882 .capable_io_mode = .blocking,
883 .intended_io_mode = flags.intended_io_mode,
884 };
885}
886
887/// Same as `openFile` but Windows-only and the path parameter is
888/// [WTF-16](https://simonsapin.github.io/wtf-8/#potentially-ill-formed-utf-16) encoded.
889pub fn openFileW(self: Dir, sub_path_w: []const u16, flags: File.OpenFlags) File.OpenError!File {
890 const w = std.os.windows;
891 const file: File = .{
892 .handle = try w.OpenFile(sub_path_w, .{
893 .dir = self.fd,
894 .access_mask = w.SYNCHRONIZE |
895 (if (flags.isRead()) @as(u32, w.GENERIC_READ) else 0) |
896 (if (flags.isWrite()) @as(u32, w.GENERIC_WRITE) else 0),
897 .creation = w.FILE_OPEN,
898 .io_mode = flags.intended_io_mode,
899 }),
900 .capable_io_mode = std.io.default_mode,
901 .intended_io_mode = flags.intended_io_mode,
902 };
903 errdefer file.close();
904 var io: w.IO_STATUS_BLOCK = undefined;
905 const range_off: w.LARGE_INTEGER = 0;
906 const range_len: w.LARGE_INTEGER = 1;
907 const exclusive = switch (flags.lock) {
908 .none => return file,
909 .shared => false,
910 .exclusive => true,
911 };
912 try w.LockFile(
913 file.handle,
914 null,
915 null,
916 null,
917 &io,
918 &range_off,
919 &range_len,
920 null,
921 @intFromBool(flags.lock_nonblocking),
922 @intFromBool(exclusive),
923 );
924 return file;
925}
926
927/// Creates, opens, or overwrites a file with write access.
928/// Call `File.close` on the result when done.
929/// Asserts that the path parameter has no null bytes.
930pub fn createFile(self: Dir, sub_path: []const u8, flags: File.CreateFlags) File.OpenError!File {
931 if (builtin.os.tag == .windows) {
932 const path_w = try std.os.windows.sliceToPrefixedFileW(self.fd, sub_path);
933 return self.createFileW(path_w.span(), flags);
934 }
935 if (builtin.os.tag == .wasi and !builtin.link_libc) {
936 return self.createFileWasi(sub_path, flags);
937 }
938 const path_c = try posix.toPosixPath(sub_path);
939 return self.createFileZ(&path_c, flags);
940}
941
942/// Same as `createFile` but WASI only.
943pub fn createFileWasi(self: Dir, sub_path: []const u8, flags: File.CreateFlags) File.OpenError!File {
944 const w = std.os.wasi;
945 var oflags = w.O.CREAT;
946 var base: w.rights_t = w.RIGHT.FD_WRITE |
947 w.RIGHT.FD_DATASYNC |
948 w.RIGHT.FD_SEEK |
949 w.RIGHT.FD_TELL |
950 w.RIGHT.FD_FDSTAT_SET_FLAGS |
951 w.RIGHT.FD_SYNC |
952 w.RIGHT.FD_ALLOCATE |
953 w.RIGHT.FD_ADVISE |
954 w.RIGHT.FD_FILESTAT_SET_TIMES |
955 w.RIGHT.FD_FILESTAT_SET_SIZE |
956 w.RIGHT.FD_FILESTAT_GET;
957 if (flags.read) {
958 base |= w.RIGHT.FD_READ;
959 }
960 if (flags.truncate) {
961 oflags |= w.O.TRUNC;
962 }
963 if (flags.exclusive) {
964 oflags |= w.O.EXCL;
965 }
966 const fd = try posix.openatWasi(self.fd, sub_path, 0x0, oflags, 0x0, base, 0x0);
967 return File{ .handle = fd };
968}
969
970/// Same as `createFile` but the path parameter is null-terminated.
971pub fn createFileZ(self: Dir, sub_path_c: [*:0]const u8, flags: File.CreateFlags) File.OpenError!File {
972 if (builtin.os.tag == .windows) {
973 const path_w = try std.os.windows.cStrToPrefixedFileW(self.fd, sub_path_c);
974 return self.createFileW(path_w.span(), flags);
975 }
976
977 // Use the O locking flags if the os supports them to acquire the lock
978 // atomically.
979 const has_flock_open_flags = @hasDecl(posix.O, "EXLOCK");
980 // Note that the O.NONBLOCK flag is removed after the openat() call
981 // is successful.
982 const nonblocking_lock_flag: u32 = if (has_flock_open_flags and flags.lock_nonblocking)
983 posix.O.NONBLOCK
984 else
985 0;
986 const lock_flag: u32 = if (has_flock_open_flags) switch (flags.lock) {
987 .none => @as(u32, 0),
988 .shared => posix.O.SHLOCK | nonblocking_lock_flag,
989 .exclusive => posix.O.EXLOCK | nonblocking_lock_flag,
990 } else 0;
991
992 const O_LARGEFILE = if (@hasDecl(posix.O, "LARGEFILE")) posix.O.LARGEFILE else 0;
993 const os_flags = lock_flag | O_LARGEFILE | posix.O.CREAT | posix.O.CLOEXEC |
994 (if (flags.truncate) @as(u32, posix.O.TRUNC) else 0) |
995 (if (flags.read) @as(u32, posix.O.RDWR) else posix.O.WRONLY) |
996 (if (flags.exclusive) @as(u32, posix.O.EXCL) else 0);
997 const fd = if (flags.intended_io_mode != .blocking)
998 try std.event.Loop.instance.?.openatZ(self.fd, sub_path_c, os_flags, flags.mode)
999 else
1000 try posix.openatZ(self.fd, sub_path_c, os_flags, flags.mode);
1001 errdefer posix.close(fd);
1002
1003 // WASI doesn't have posix.flock so we intetinally check OS prior to the inner if block
1004 // since it is not compiltime-known and we need to avoid undefined symbol in Wasm.
1005 if (builtin.target.os.tag != .wasi) {
1006 if (!has_flock_open_flags and flags.lock != .none) {
1007 // TODO: integrate async I/O
1008 const lock_nonblocking: i32 = if (flags.lock_nonblocking) posix.LOCK.NB else 0;
1009 try posix.flock(fd, switch (flags.lock) {
1010 .none => unreachable,
1011 .shared => posix.LOCK.SH | lock_nonblocking,
1012 .exclusive => posix.LOCK.EX | lock_nonblocking,
1013 });
1014 }
1015 }
1016
1017 if (has_flock_open_flags and flags.lock_nonblocking) {
1018 var fl_flags = posix.fcntl(fd, posix.F.GETFL, 0) catch |err| switch (err) {
1019 error.FileBusy => unreachable,
1020 error.Locked => unreachable,
1021 error.PermissionDenied => unreachable,
1022 error.DeadLock => unreachable,
1023 error.LockedRegionLimitExceeded => unreachable,
1024 else => |e| return e,
1025 };
1026 fl_flags &= ~@as(usize, posix.O.NONBLOCK);
1027 _ = posix.fcntl(fd, posix.F.SETFL, fl_flags) catch |err| switch (err) {
1028 error.FileBusy => unreachable,
1029 error.Locked => unreachable,
1030 error.PermissionDenied => unreachable,
1031 error.DeadLock => unreachable,
1032 error.LockedRegionLimitExceeded => unreachable,
1033 else => |e| return e,
1034 };
1035 }
1036
1037 return File{
1038 .handle = fd,
1039 .capable_io_mode = .blocking,
1040 .intended_io_mode = flags.intended_io_mode,
1041 };
1042}
1043
1044/// Same as `createFile` but Windows-only and the path parameter is
1045/// [WTF-16](https://simonsapin.github.io/wtf-8/#potentially-ill-formed-utf-16) encoded.
1046pub fn createFileW(self: Dir, sub_path_w: []const u16, flags: File.CreateFlags) File.OpenError!File {
1047 const w = std.os.windows;
1048 const read_flag = if (flags.read) @as(u32, w.GENERIC_READ) else 0;
1049 const file: File = .{
1050 .handle = try w.OpenFile(sub_path_w, .{
1051 .dir = self.fd,
1052 .access_mask = w.SYNCHRONIZE | w.GENERIC_WRITE | read_flag,
1053 .creation = if (flags.exclusive)
1054 @as(u32, w.FILE_CREATE)
1055 else if (flags.truncate)
1056 @as(u32, w.FILE_OVERWRITE_IF)
1057 else
1058 @as(u32, w.FILE_OPEN_IF),
1059 .io_mode = flags.intended_io_mode,
1060 }),
1061 .capable_io_mode = std.io.default_mode,
1062 .intended_io_mode = flags.intended_io_mode,
1063 };
1064 errdefer file.close();
1065 var io: w.IO_STATUS_BLOCK = undefined;
1066 const range_off: w.LARGE_INTEGER = 0;
1067 const range_len: w.LARGE_INTEGER = 1;
1068 const exclusive = switch (flags.lock) {
1069 .none => return file,
1070 .shared => false,
1071 .exclusive => true,
1072 };
1073 try w.LockFile(
1074 file.handle,
1075 null,
1076 null,
1077 null,
1078 &io,
1079 &range_off,
1080 &range_len,
1081 null,
1082 @intFromBool(flags.lock_nonblocking),
1083 @intFromBool(exclusive),
1084 );
1085 return file;
1086}
1087
1088/// Creates a single directory with a relative or absolute path.
1089/// To create multiple directories to make an entire path, see `makePath`.
1090/// To operate on only absolute paths, see `makeDirAbsolute`.
1091pub fn makeDir(self: Dir, sub_path: []const u8) !void {
1092 try posix.mkdirat(self.fd, sub_path, default_mode);
1093}
1094
1095/// Creates a single directory with a relative or absolute null-terminated UTF-8-encoded path.
1096/// To create multiple directories to make an entire path, see `makePath`.
1097/// To operate on only absolute paths, see `makeDirAbsoluteZ`.
1098pub fn makeDirZ(self: Dir, sub_path: [*:0]const u8) !void {
1099 try posix.mkdiratZ(self.fd, sub_path, default_mode);
1100}
1101
1102/// Creates a single directory with a relative or absolute null-terminated WTF-16-encoded path.
1103/// To create multiple directories to make an entire path, see `makePath`.
1104/// To operate on only absolute paths, see `makeDirAbsoluteW`.
1105pub fn makeDirW(self: Dir, sub_path: [*:0]const u16) !void {
1106 try posix.mkdiratW(self.fd, sub_path, default_mode);
1107}
1108
1109/// Calls makeDir iteratively to make an entire path
1110/// (i.e. creating any parent directories that do not exist).
1111/// Returns success if the path already exists and is a directory.
1112/// This function is not atomic, and if it returns an error, the file system may
1113/// have been modified regardless.
1114pub fn makePath(self: Dir, sub_path: []const u8) !void {
1115 var it = try fs.path.componentIterator(sub_path);
1116 var component = it.last() orelse return;
1117 while (true) {
1118 self.makeDir(component.path) catch |err| switch (err) {
1119 error.PathAlreadyExists => {
1120 // TODO stat the file and return an error if it's not a directory
1121 // this is important because otherwise a dangling symlink
1122 // could cause an infinite loop
1123 },
1124 error.FileNotFound => |e| {
1125 component = it.previous() orelse return e;
1126 continue;
1127 },
1128 else => |e| return e,
1129 };
1130 component = it.next() orelse return;
1131 }
1132}
1133
1134/// Calls makeOpenDirAccessMaskW iteratively to make an entire path
1135/// (i.e. creating any parent directories that do not exist).
1136/// Opens the dir if the path already exists and is a directory.
1137/// This function is not atomic, and if it returns an error, the file system may
1138/// have been modified regardless.
1139fn makeOpenPathAccessMaskW(self: Dir, sub_path: []const u8, access_mask: u32, no_follow: bool) OpenError!Dir {
1140 const w = std.os.windows;
1141 var it = try fs.path.componentIterator(sub_path);
1142 // If there are no components in the path, then create a dummy component with the full path.
1143 var component = it.last() orelse fs.path.NativeUtf8ComponentIterator.Component{
1144 .name = "",
1145 .path = sub_path,
1146 };
1147
1148 while (true) {
1149 const sub_path_w = try w.sliceToPrefixedFileW(self.fd, component.path);
1150 const is_last = it.peekNext() == null;
1151 var result = self.makeOpenDirAccessMaskW(sub_path_w.span().ptr, access_mask, .{
1152 .no_follow = no_follow,
1153 .create_disposition = if (is_last) w.FILE_OPEN_IF else w.FILE_CREATE,
1154 }) catch |err| switch (err) {
1155 error.FileNotFound => |e| {
1156 component = it.previous() orelse return e;
1157 continue;
1158 },
1159 else => |e| return e,
1160 };
1161
1162 component = it.next() orelse return result;
1163 // Don't leak the intermediate file handles
1164 result.close();
1165 }
1166}
1167
1168/// This function performs `makePath`, followed by `openDir`.
1169/// If supported by the OS, this operation is atomic. It is not atomic on
1170/// all operating systems.
1171/// On Windows, this function performs `makeOpenPathAccessMaskW`.
1172pub fn makeOpenPath(self: Dir, sub_path: []const u8, open_dir_options: OpenDirOptions) !Dir {
1173 return switch (builtin.os.tag) {
1174 .windows => {
1175 const w = std.os.windows;
1176 const base_flags = w.STANDARD_RIGHTS_READ | w.FILE_READ_ATTRIBUTES | w.FILE_READ_EA |
1177 w.SYNCHRONIZE | w.FILE_TRAVERSE |
1178 (if (open_dir_options.iterate) w.FILE_LIST_DIRECTORY else @as(u32, 0));
1179
1180 return self.makeOpenPathAccessMaskW(sub_path, base_flags, open_dir_options.no_follow);
1181 },
1182 else => {
1183 return self.openDir(sub_path, open_dir_options) catch |err| switch (err) {
1184 error.FileNotFound => {
1185 try self.makePath(sub_path);
1186 return self.openDir(sub_path, open_dir_options);
1187 },
1188 else => |e| return e,
1189 };
1190 },
1191 };
1192}
1193
1194/// This function returns the canonicalized absolute pathname of
1195/// `pathname` relative to this `Dir`. If `pathname` is absolute, ignores this
1196/// `Dir` handle and returns the canonicalized absolute pathname of `pathname`
1197/// argument.
1198/// This function is not universally supported by all platforms.
1199/// Currently supported hosts are: Linux, macOS, and Windows.
1200/// See also `Dir.realpathZ`, `Dir.realpathW`, and `Dir.realpathAlloc`.
1201pub fn realpath(self: Dir, pathname: []const u8, out_buffer: []u8) ![]u8 {
1202 if (builtin.os.tag == .wasi) {
1203 @compileError("realpath is not available on WASI");
1204 }
1205 if (builtin.os.tag == .windows) {
1206 const pathname_w = try std.os.windows.sliceToPrefixedFileW(self.fd, pathname);
1207 return self.realpathW(pathname_w.span(), out_buffer);
1208 }
1209 const pathname_c = try posix.toPosixPath(pathname);
1210 return self.realpathZ(&pathname_c, out_buffer);
1211}
1212
1213/// Same as `Dir.realpath` except `pathname` is null-terminated.
1214/// See also `Dir.realpath`, `realpathZ`.
1215pub fn realpathZ(self: Dir, pathname: [*:0]const u8, out_buffer: []u8) ![]u8 {
1216 if (builtin.os.tag == .windows) {
1217 const pathname_w = try posix.windows.cStrToPrefixedFileW(self.fd, pathname);
1218 return self.realpathW(pathname_w.span(), out_buffer);
1219 }
1220
1221 const flags = if (builtin.os.tag == .linux)
1222 posix.O.PATH | posix.O.NONBLOCK | posix.O.CLOEXEC
1223 else
1224 posix.O.NONBLOCK | posix.O.CLOEXEC;
1225 const fd = posix.openatZ(self.fd, pathname, flags, 0) catch |err| switch (err) {
1226 error.FileLocksNotSupported => unreachable,
1227 else => |e| return e,
1228 };
1229 defer posix.close(fd);
1230
1231 // Use of MAX_PATH_BYTES here is valid as the realpath function does not
1232 // have a variant that takes an arbitrary-size buffer.
1233 // TODO(#4812): Consider reimplementing realpath or using the POSIX.1-2008
1234 // NULL out parameter (GNU's canonicalize_file_name) to handle overelong
1235 // paths. musl supports passing NULL but restricts the output to PATH_MAX
1236 // anyway.
1237 var buffer: [fs.MAX_PATH_BYTES]u8 = undefined;
1238 const out_path = try posix.getFdPath(fd, &buffer);
1239
1240 if (out_path.len > out_buffer.len) {
1241 return error.NameTooLong;
1242 }
1243
1244 const result = out_buffer[0..out_path.len];
1245 @memcpy(result, out_path);
1246 return result;
1247}
1248
1249/// Windows-only. Same as `Dir.realpath` except `pathname` is WTF16 encoded.
1250/// See also `Dir.realpath`, `realpathW`.
1251pub fn realpathW(self: Dir, pathname: []const u16, out_buffer: []u8) ![]u8 {
1252 const w = std.os.windows;
1253
1254 const access_mask = w.GENERIC_READ | w.SYNCHRONIZE;
1255 const share_access = w.FILE_SHARE_READ;
1256 const creation = w.FILE_OPEN;
1257 const h_file = blk: {
1258 const res = w.OpenFile(pathname, .{
1259 .dir = self.fd,
1260 .access_mask = access_mask,
1261 .share_access = share_access,
1262 .creation = creation,
1263 .io_mode = .blocking,
1264 .filter = .any,
1265 }) catch |err| switch (err) {
1266 error.WouldBlock => unreachable,
1267 else => |e| return e,
1268 };
1269 break :blk res;
1270 };
1271 defer w.CloseHandle(h_file);
1272
1273 // Use of MAX_PATH_BYTES here is valid as the realpath function does not
1274 // have a variant that takes an arbitrary-size buffer.
1275 // TODO(#4812): Consider reimplementing realpath or using the POSIX.1-2008
1276 // NULL out parameter (GNU's canonicalize_file_name) to handle overelong
1277 // paths. musl supports passing NULL but restricts the output to PATH_MAX
1278 // anyway.
1279 var buffer: [fs.MAX_PATH_BYTES]u8 = undefined;
1280 const out_path = try posix.getFdPath(h_file, &buffer);
1281
1282 if (out_path.len > out_buffer.len) {
1283 return error.NameTooLong;
1284 }
1285
1286 const result = out_buffer[0..out_path.len];
1287 @memcpy(result, out_path);
1288 return result;
1289}
1290
1291/// Same as `Dir.realpath` except caller must free the returned memory.
1292/// See also `Dir.realpath`.
1293pub fn realpathAlloc(self: Dir, allocator: Allocator, pathname: []const u8) ![]u8 {
1294 // Use of MAX_PATH_BYTES here is valid as the realpath function does not
1295 // have a variant that takes an arbitrary-size buffer.
1296 // TODO(#4812): Consider reimplementing realpath or using the POSIX.1-2008
1297 // NULL out parameter (GNU's canonicalize_file_name) to handle overelong
1298 // paths. musl supports passing NULL but restricts the output to PATH_MAX
1299 // anyway.
1300 var buf: [fs.MAX_PATH_BYTES]u8 = undefined;
1301 return allocator.dupe(u8, try self.realpath(pathname, buf[0..]));
1302}
1303
1304/// Changes the current working directory to the open directory handle.
1305/// This modifies global state and can have surprising effects in multi-
1306/// threaded applications. Most applications and especially libraries should
1307/// not call this function as a general rule, however it can have use cases
1308/// in, for example, implementing a shell, or child process execution.
1309/// Not all targets support this. For example, WASI does not have the concept
1310/// of a current working directory.
1311pub fn setAsCwd(self: Dir) !void {
1312 if (builtin.os.tag == .wasi) {
1313 @compileError("changing cwd is not currently possible in WASI");
1314 }
1315 if (builtin.os.tag == .windows) {
1316 var dir_path_buffer: [std.os.windows.PATH_MAX_WIDE]u16 = undefined;
1317 const dir_path = try std.os.windows.GetFinalPathNameByHandle(self.fd, .{}, &dir_path_buffer);
1318 if (builtin.link_libc) {
1319 return posix.chdirW(dir_path);
1320 }
1321 return std.os.windows.SetCurrentDirectory(dir_path);
1322 }
1323 try posix.fchdir(self.fd);
1324}
1325
1326pub const OpenDirOptions = struct {
1327 /// `true` means the opened directory can be used as the `Dir` parameter
1328 /// for functions which operate based on an open directory handle. When `false`,
1329 /// such operations are Illegal Behavior.
1330 access_sub_paths: bool = true,
1331
1332 /// `true` means the opened directory can be scanned for the files and sub-directories
1333 /// of the result. It means the `iterate` function can be called.
1334 iterate: bool = false,
1335
1336 /// `true` means it won't dereference the symlinks.
1337 no_follow: bool = false,
1338};
1339
1340/// Opens a directory at the given path. The directory is a system resource that remains
1341/// open until `close` is called on the result.
1342/// The directory cannot be iterated unless the `iterate` option is set to `true`.
1343///
1344/// Asserts that the path parameter has no null bytes.
1345pub fn openDir(self: Dir, sub_path: []const u8, args: OpenDirOptions) OpenError!Dir {
1346 if (builtin.os.tag == .windows) {
1347 const sub_path_w = try posix.windows.sliceToPrefixedFileW(self.fd, sub_path);
1348 return self.openDirW(sub_path_w.span().ptr, args);
1349 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
1350 return self.openDirWasi(sub_path, args);
1351 } else {
1352 const sub_path_c = try posix.toPosixPath(sub_path);
1353 return self.openDirZ(&sub_path_c, args);
1354 }
1355}
1356
1357/// Same as `openDir` except only WASI.
1358pub fn openDirWasi(self: Dir, sub_path: []const u8, args: OpenDirOptions) OpenError!Dir {
1359 const w = std.os.wasi;
1360 var base: w.rights_t = w.RIGHT.FD_FILESTAT_GET | w.RIGHT.FD_FDSTAT_SET_FLAGS | w.RIGHT.FD_FILESTAT_SET_TIMES;
1361 if (args.access_sub_paths) {
1362 base |= w.RIGHT.FD_READDIR |
1363 w.RIGHT.PATH_CREATE_DIRECTORY |
1364 w.RIGHT.PATH_CREATE_FILE |
1365 w.RIGHT.PATH_LINK_SOURCE |
1366 w.RIGHT.PATH_LINK_TARGET |
1367 w.RIGHT.PATH_OPEN |
1368 w.RIGHT.PATH_READLINK |
1369 w.RIGHT.PATH_RENAME_SOURCE |
1370 w.RIGHT.PATH_RENAME_TARGET |
1371 w.RIGHT.PATH_FILESTAT_GET |
1372 w.RIGHT.PATH_FILESTAT_SET_SIZE |
1373 w.RIGHT.PATH_FILESTAT_SET_TIMES |
1374 w.RIGHT.PATH_SYMLINK |
1375 w.RIGHT.PATH_REMOVE_DIRECTORY |
1376 w.RIGHT.PATH_UNLINK_FILE;
1377 }
1378 const symlink_flags: w.lookupflags_t = if (args.no_follow) 0x0 else w.LOOKUP_SYMLINK_FOLLOW;
1379 // TODO do we really need all the rights here?
1380 const inheriting: w.rights_t = w.RIGHT.ALL ^ w.RIGHT.SOCK_SHUTDOWN;
1381
1382 const result = posix.openatWasi(
1383 self.fd,
1384 sub_path,
1385 symlink_flags,
1386 w.O.DIRECTORY,
1387 0x0,
1388 base,
1389 inheriting,
1390 );
1391 const fd = result catch |err| switch (err) {
1392 error.FileTooBig => unreachable, // can't happen for directories
1393 error.IsDir => unreachable, // we're providing O.DIRECTORY
1394 error.NoSpaceLeft => unreachable, // not providing O.CREAT
1395 error.PathAlreadyExists => unreachable, // not providing O.CREAT
1396 error.FileLocksNotSupported => unreachable, // locking folders is not supported
1397 error.WouldBlock => unreachable, // can't happen for directories
1398 error.FileBusy => unreachable, // can't happen for directories
1399 else => |e| return e,
1400 };
1401 return Dir{ .fd = fd };
1402}
1403
1404/// Same as `openDir` except the parameter is null-terminated.
1405pub fn openDirZ(self: Dir, sub_path_c: [*:0]const u8, args: OpenDirOptions) OpenError!Dir {
1406 if (builtin.os.tag == .windows) {
1407 const sub_path_w = try std.os.windows.cStrToPrefixedFileW(self.fd, sub_path_c);
1408 return self.openDirW(sub_path_w.span().ptr, args);
1409 }
1410 const symlink_flags: u32 = if (args.no_follow) posix.O.NOFOLLOW else 0x0;
1411 if (!args.iterate) {
1412 const O_PATH = if (@hasDecl(posix.O, "PATH")) posix.O.PATH else 0;
1413 return self.openDirFlagsZ(sub_path_c, posix.O.DIRECTORY | posix.O.RDONLY | posix.O.CLOEXEC | O_PATH | symlink_flags);
1414 } else {
1415 return self.openDirFlagsZ(sub_path_c, posix.O.DIRECTORY | posix.O.RDONLY | posix.O.CLOEXEC | symlink_flags);
1416 }
1417}
1418
1419/// Same as `openDir` except the path parameter is WTF-16 encoded, NT-prefixed.
1420/// This function asserts the target OS is Windows.
1421pub fn openDirW(self: Dir, sub_path_w: [*:0]const u16, args: OpenDirOptions) OpenError!Dir {
1422 const w = std.os.windows;
1423 // TODO remove some of these flags if args.access_sub_paths is false
1424 const base_flags = w.STANDARD_RIGHTS_READ | w.FILE_READ_ATTRIBUTES | w.FILE_READ_EA |
1425 w.SYNCHRONIZE | w.FILE_TRAVERSE;
1426 const flags: u32 = if (args.iterate) base_flags | w.FILE_LIST_DIRECTORY else base_flags;
1427 const dir = try self.makeOpenDirAccessMaskW(sub_path_w, flags, .{
1428 .no_follow = args.no_follow,
1429 .create_disposition = w.FILE_OPEN,
1430 });
1431 return dir;
1432}
1433
1434/// `flags` must contain `posix.O.DIRECTORY`.
1435fn openDirFlagsZ(self: Dir, sub_path_c: [*:0]const u8, flags: u32) OpenError!Dir {
1436 const result = if (fs.need_async_thread)
1437 std.event.Loop.instance.?.openatZ(self.fd, sub_path_c, flags, 0)
1438 else
1439 posix.openatZ(self.fd, sub_path_c, flags, 0);
1440 const fd = result catch |err| switch (err) {
1441 error.FileTooBig => unreachable, // can't happen for directories
1442 error.IsDir => unreachable, // we're providing O.DIRECTORY
1443 error.NoSpaceLeft => unreachable, // not providing O.CREAT
1444 error.PathAlreadyExists => unreachable, // not providing O.CREAT
1445 error.FileLocksNotSupported => unreachable, // locking folders is not supported
1446 error.WouldBlock => unreachable, // can't happen for directories
1447 error.FileBusy => unreachable, // can't happen for directories
1448 else => |e| return e,
1449 };
1450 return Dir{ .fd = fd };
1451}
1452
1453const MakeOpenDirAccessMaskWOptions = struct {
1454 no_follow: bool,
1455 create_disposition: u32,
1456};
1457
1458fn makeOpenDirAccessMaskW(self: Dir, sub_path_w: [*:0]const u16, access_mask: u32, flags: MakeOpenDirAccessMaskWOptions) OpenError!Dir {
1459 const w = std.os.windows;
1460
1461 var result = Dir{
1462 .fd = undefined,
1463 };
1464
1465 const path_len_bytes = @as(u16, @intCast(mem.sliceTo(sub_path_w, 0).len * 2));
1466 var nt_name = w.UNICODE_STRING{
1467 .Length = path_len_bytes,
1468 .MaximumLength = path_len_bytes,
1469 .Buffer = @constCast(sub_path_w),
1470 };
1471 var attr = w.OBJECT_ATTRIBUTES{
1472 .Length = @sizeOf(w.OBJECT_ATTRIBUTES),
1473 .RootDirectory = if (fs.path.isAbsoluteWindowsW(sub_path_w)) null else self.fd,
1474 .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
1475 .ObjectName = &nt_name,
1476 .SecurityDescriptor = null,
1477 .SecurityQualityOfService = null,
1478 };
1479 const open_reparse_point: w.DWORD = if (flags.no_follow) w.FILE_OPEN_REPARSE_POINT else 0x0;
1480 var io: w.IO_STATUS_BLOCK = undefined;
1481 const rc = w.ntdll.NtCreateFile(
1482 &result.fd,
1483 access_mask,
1484 &attr,
1485 &io,
1486 null,
1487 w.FILE_ATTRIBUTE_NORMAL,
1488 w.FILE_SHARE_READ | w.FILE_SHARE_WRITE,
1489 flags.create_disposition,
1490 w.FILE_DIRECTORY_FILE | w.FILE_SYNCHRONOUS_IO_NONALERT | w.FILE_OPEN_FOR_BACKUP_INTENT | open_reparse_point,
1491 null,
1492 0,
1493 );
1494
1495 switch (rc) {
1496 .SUCCESS => return result,
1497 .OBJECT_NAME_INVALID => return error.BadPathName,
1498 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
1499 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
1500 .NOT_A_DIRECTORY => return error.NotDir,
1501 // This can happen if the directory has 'List folder contents' permission set to 'Deny'
1502 // and the directory is trying to be opened for iteration.
1503 .ACCESS_DENIED => return error.AccessDenied,
1504 .INVALID_PARAMETER => unreachable,
1505 else => return w.unexpectedStatus(rc),
1506 }
1507}
1508
1509pub const DeleteFileError = posix.UnlinkError;
1510
1511/// Delete a file name and possibly the file it refers to, based on an open directory handle.
1512/// Asserts that the path parameter has no null bytes.
1513pub fn deleteFile(self: Dir, sub_path: []const u8) DeleteFileError!void {
1514 if (builtin.os.tag == .windows) {
1515 const sub_path_w = try std.os.windows.sliceToPrefixedFileW(self.fd, sub_path);
1516 return self.deleteFileW(sub_path_w.span());
1517 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
1518 posix.unlinkat(self.fd, sub_path, 0) catch |err| switch (err) {
1519 error.DirNotEmpty => unreachable, // not passing AT.REMOVEDIR
1520 else => |e| return e,
1521 };
1522 } else {
1523 const sub_path_c = try posix.toPosixPath(sub_path);
1524 return self.deleteFileZ(&sub_path_c);
1525 }
1526}
1527
1528/// Same as `deleteFile` except the parameter is null-terminated.
1529pub fn deleteFileZ(self: Dir, sub_path_c: [*:0]const u8) DeleteFileError!void {
1530 posix.unlinkatZ(self.fd, sub_path_c, 0) catch |err| switch (err) {
1531 error.DirNotEmpty => unreachable, // not passing AT.REMOVEDIR
1532 error.AccessDenied => |e| switch (builtin.os.tag) {
1533 // non-Linux POSIX systems return EPERM when trying to delete a directory, so
1534 // we need to handle that case specifically and translate the error
1535 .macos, .ios, .freebsd, .netbsd, .dragonfly, .openbsd, .solaris, .illumos => {
1536 // Don't follow symlinks to match unlinkat (which acts on symlinks rather than follows them)
1537 const fstat = posix.fstatatZ(self.fd, sub_path_c, posix.AT.SYMLINK_NOFOLLOW) catch return e;
1538 const is_dir = fstat.mode & posix.S.IFMT == posix.S.IFDIR;
1539 return if (is_dir) error.IsDir else e;
1540 },
1541 else => return e,
1542 },
1543 else => |e| return e,
1544 };
1545}
1546
1547/// Same as `deleteFile` except the parameter is WTF-16 encoded.
1548pub fn deleteFileW(self: Dir, sub_path_w: []const u16) DeleteFileError!void {
1549 posix.unlinkatW(self.fd, sub_path_w, 0) catch |err| switch (err) {
1550 error.DirNotEmpty => unreachable, // not passing AT.REMOVEDIR
1551 else => |e| return e,
1552 };
1553}
1554
1555pub const DeleteDirError = error{
1556 DirNotEmpty,
1557 FileNotFound,
1558 AccessDenied,
1559 FileBusy,
1560 FileSystem,
1561 SymLinkLoop,
1562 NameTooLong,
1563 NotDir,
1564 SystemResources,
1565 ReadOnlyFileSystem,
1566 InvalidUtf8,
1567 BadPathName,
1568 /// On Windows, `\\server` or `\\server\share` was not found.
1569 NetworkNotFound,
1570 Unexpected,
1571};
1572
1573/// Returns `error.DirNotEmpty` if the directory is not empty.
1574/// To delete a directory recursively, see `deleteTree`.
1575/// Asserts that the path parameter has no null bytes.
1576pub fn deleteDir(self: Dir, sub_path: []const u8) DeleteDirError!void {
1577 if (builtin.os.tag == .windows) {
1578 const sub_path_w = try std.os.windows.sliceToPrefixedFileW(self.fd, sub_path);
1579 return self.deleteDirW(sub_path_w.span());
1580 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
1581 posix.unlinkat(self.fd, sub_path, posix.AT.REMOVEDIR) catch |err| switch (err) {
1582 error.IsDir => unreachable, // not possible since we pass AT.REMOVEDIR
1583 else => |e| return e,
1584 };
1585 } else {
1586 const sub_path_c = try posix.toPosixPath(sub_path);
1587 return self.deleteDirZ(&sub_path_c);
1588 }
1589}
1590
1591/// Same as `deleteDir` except the parameter is null-terminated.
1592pub fn deleteDirZ(self: Dir, sub_path_c: [*:0]const u8) DeleteDirError!void {
1593 posix.unlinkatZ(self.fd, sub_path_c, posix.AT.REMOVEDIR) catch |err| switch (err) {
1594 error.IsDir => unreachable, // not possible since we pass AT.REMOVEDIR
1595 else => |e| return e,
1596 };
1597}
1598
1599/// Same as `deleteDir` except the parameter is UTF16LE, NT prefixed.
1600/// This function is Windows-only.
1601pub fn deleteDirW(self: Dir, sub_path_w: []const u16) DeleteDirError!void {
1602 posix.unlinkatW(self.fd, sub_path_w, posix.AT.REMOVEDIR) catch |err| switch (err) {
1603 error.IsDir => unreachable, // not possible since we pass AT.REMOVEDIR
1604 else => |e| return e,
1605 };
1606}
1607
1608pub const RenameError = posix.RenameError;
1609
1610/// Change the name or location of a file or directory.
1611/// If new_sub_path already exists, it will be replaced.
1612/// Renaming a file over an existing directory or a directory
1613/// over an existing file will fail with `error.IsDir` or `error.NotDir`
1614pub fn rename(self: Dir, old_sub_path: []const u8, new_sub_path: []const u8) RenameError!void {
1615 return posix.renameat(self.fd, old_sub_path, self.fd, new_sub_path);
1616}
1617
1618/// Same as `rename` except the parameters are null-terminated.
1619pub fn renameZ(self: Dir, old_sub_path_z: [*:0]const u8, new_sub_path_z: [*:0]const u8) RenameError!void {
1620 return posix.renameatZ(self.fd, old_sub_path_z, self.fd, new_sub_path_z);
1621}
1622
1623/// Same as `rename` except the parameters are UTF16LE, NT prefixed.
1624/// This function is Windows-only.
1625pub fn renameW(self: Dir, old_sub_path_w: []const u16, new_sub_path_w: []const u16) RenameError!void {
1626 return posix.renameatW(self.fd, old_sub_path_w, self.fd, new_sub_path_w);
1627}
1628
1629/// Use with `Dir.symLink` and `symLinkAbsolute` to specify whether the symlink
1630/// will point to a file or a directory. This value is ignored on all hosts
1631/// except Windows where creating symlinks to different resource types, requires
1632/// different flags. By default, `symLinkAbsolute` is assumed to point to a file.
1633pub const SymLinkFlags = struct {
1634 is_directory: bool = false,
1635};
1636
1637/// Creates a symbolic link named `sym_link_path` which contains the string `target_path`.
1638/// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent
1639/// one; the latter case is known as a dangling link.
1640/// If `sym_link_path` exists, it will not be overwritten.
1641pub fn symLink(
1642 self: Dir,
1643 target_path: []const u8,
1644 sym_link_path: []const u8,
1645 flags: SymLinkFlags,
1646) !void {
1647 if (builtin.os.tag == .wasi and !builtin.link_libc) {
1648 return self.symLinkWasi(target_path, sym_link_path, flags);
1649 }
1650 if (builtin.os.tag == .windows) {
1651 // Target path does not use sliceToPrefixedFileW because certain paths
1652 // are handled differently when creating a symlink than they would be
1653 // when converting to an NT namespaced path. CreateSymbolicLink in
1654 // symLinkW will handle the necessary conversion.
1655 var target_path_w: std.os.windows.PathSpace = undefined;
1656 target_path_w.len = try std.unicode.utf8ToUtf16Le(&target_path_w.data, target_path);
1657 target_path_w.data[target_path_w.len] = 0;
1658 const sym_link_path_w = try std.os.windows.sliceToPrefixedFileW(self.fd, sym_link_path);
1659 return self.symLinkW(target_path_w.span(), sym_link_path_w.span(), flags);
1660 }
1661 const target_path_c = try posix.toPosixPath(target_path);
1662 const sym_link_path_c = try posix.toPosixPath(sym_link_path);
1663 return self.symLinkZ(&target_path_c, &sym_link_path_c, flags);
1664}
1665
1666/// WASI-only. Same as `symLink` except targeting WASI.
1667pub fn symLinkWasi(
1668 self: Dir,
1669 target_path: []const u8,
1670 sym_link_path: []const u8,
1671 _: SymLinkFlags,
1672) !void {
1673 return posix.symlinkat(target_path, self.fd, sym_link_path);
1674}
1675
1676/// Same as `symLink`, except the pathname parameters are null-terminated.
1677pub fn symLinkZ(
1678 self: Dir,
1679 target_path_c: [*:0]const u8,
1680 sym_link_path_c: [*:0]const u8,
1681 flags: SymLinkFlags,
1682) !void {
1683 if (builtin.os.tag == .windows) {
1684 const target_path_w = try std.os.windows.cStrToPrefixedFileW(self.fd, target_path_c);
1685 const sym_link_path_w = try std.os.windows.cStrToPrefixedFileW(self.fd, sym_link_path_c);
1686 return self.symLinkW(target_path_w.span(), sym_link_path_w.span(), flags);
1687 }
1688 return posix.symlinkatZ(target_path_c, self.fd, sym_link_path_c);
1689}
1690
1691/// Windows-only. Same as `symLink` except the pathname parameters
1692/// are null-terminated, WTF16 encoded.
1693pub fn symLinkW(
1694 self: Dir,
1695 /// WTF-16, does not need to be NT-prefixed. The NT-prefixing
1696 /// of this path is handled by CreateSymbolicLink.
1697 target_path_w: [:0]const u16,
1698 /// WTF-16, must be NT-prefixed or relative
1699 sym_link_path_w: []const u16,
1700 flags: SymLinkFlags,
1701) !void {
1702 return std.os.windows.CreateSymbolicLink(self.fd, sym_link_path_w, target_path_w, flags.is_directory);
1703}
1704
1705pub const ReadLinkError = posix.ReadLinkError;
1706
1707/// Read value of a symbolic link.
1708/// The return value is a slice of `buffer`, from index `0`.
1709/// Asserts that the path parameter has no null bytes.
1710pub fn readLink(self: Dir, sub_path: []const u8, buffer: []u8) ReadLinkError![]u8 {
1711 if (builtin.os.tag == .wasi and !builtin.link_libc) {
1712 return self.readLinkWasi(sub_path, buffer);
1713 }
1714 if (builtin.os.tag == .windows) {
1715 const sub_path_w = try std.os.windows.sliceToPrefixedFileW(self.fd, sub_path);
1716 return self.readLinkW(sub_path_w.span(), buffer);
1717 }
1718 const sub_path_c = try posix.toPosixPath(sub_path);
1719 return self.readLinkZ(&sub_path_c, buffer);
1720}
1721
1722/// WASI-only. Same as `readLink` except targeting WASI.
1723pub fn readLinkWasi(self: Dir, sub_path: []const u8, buffer: []u8) ![]u8 {
1724 return posix.readlinkat(self.fd, sub_path, buffer);
1725}
1726
1727/// Same as `readLink`, except the `pathname` parameter is null-terminated.
1728pub fn readLinkZ(self: Dir, sub_path_c: [*:0]const u8, buffer: []u8) ![]u8 {
1729 if (builtin.os.tag == .windows) {
1730 const sub_path_w = try std.os.windows.cStrToPrefixedFileW(self.fd, sub_path_c);
1731 return self.readLinkW(sub_path_w.span(), buffer);
1732 }
1733 return posix.readlinkatZ(self.fd, sub_path_c, buffer);
1734}
1735
1736/// Windows-only. Same as `readLink` except the pathname parameter
1737/// is null-terminated, WTF16 encoded.
1738pub fn readLinkW(self: Dir, sub_path_w: []const u16, buffer: []u8) ![]u8 {
1739 return std.os.windows.ReadLink(self.fd, sub_path_w, buffer);
1740}
1741
1742/// Read all of file contents using a preallocated buffer.
1743/// The returned slice has the same pointer as `buffer`. If the length matches `buffer.len`
1744/// the situation is ambiguous. It could either mean that the entire file was read, and
1745/// it exactly fits the buffer, or it could mean the buffer was not big enough for the
1746/// entire file.
1747pub fn readFile(self: Dir, file_path: []const u8, buffer: []u8) ![]u8 {
1748 var file = try self.openFile(file_path, .{});
1749 defer file.close();
1750
1751 const end_index = try file.readAll(buffer);
1752 return buffer[0..end_index];
1753}
1754
1755/// On success, caller owns returned buffer.
1756/// If the file is larger than `max_bytes`, returns `error.FileTooBig`.
1757pub fn readFileAlloc(self: Dir, allocator: mem.Allocator, file_path: []const u8, max_bytes: usize) ![]u8 {
1758 return self.readFileAllocOptions(allocator, file_path, max_bytes, null, @alignOf(u8), null);
1759}
1760
1761/// On success, caller owns returned buffer.
1762/// If the file is larger than `max_bytes`, returns `error.FileTooBig`.
1763/// If `size_hint` is specified the initial buffer size is calculated using
1764/// that value, otherwise the effective file size is used instead.
1765/// Allows specifying alignment and a sentinel value.
1766pub fn readFileAllocOptions(
1767 self: Dir,
1768 allocator: mem.Allocator,
1769 file_path: []const u8,
1770 max_bytes: usize,
1771 size_hint: ?usize,
1772 comptime alignment: u29,
1773 comptime optional_sentinel: ?u8,
1774) !(if (optional_sentinel) |s| [:s]align(alignment) u8 else []align(alignment) u8) {
1775 var file = try self.openFile(file_path, .{});
1776 defer file.close();
1777
1778 // If the file size doesn't fit a usize it'll be certainly greater than
1779 // `max_bytes`
1780 const stat_size = size_hint orelse std.math.cast(usize, try file.getEndPos()) orelse
1781 return error.FileTooBig;
1782
1783 return file.readToEndAllocOptions(allocator, max_bytes, stat_size, alignment, optional_sentinel);
1784}
1785
1786pub const DeleteTreeError = error{
1787 InvalidHandle,
1788 AccessDenied,
1789 FileTooBig,
1790 SymLinkLoop,
1791 ProcessFdQuotaExceeded,
1792 NameTooLong,
1793 SystemFdQuotaExceeded,
1794 NoDevice,
1795 SystemResources,
1796 ReadOnlyFileSystem,
1797 FileSystem,
1798 FileBusy,
1799 DeviceBusy,
1800
1801 /// One of the path components was not a directory.
1802 /// This error is unreachable if `sub_path` does not contain a path separator.
1803 NotDir,
1804
1805 /// On Windows, file paths must be valid Unicode.
1806 InvalidUtf8,
1807
1808 /// On Windows, file paths cannot contain these characters:
1809 /// '/', '*', '?', '"', '<', '>', '|'
1810 BadPathName,
1811
1812 /// On Windows, `\\server` or `\\server\share` was not found.
1813 NetworkNotFound,
1814} || posix.UnexpectedError;
1815
1816/// Whether `full_path` describes a symlink, file, or directory, this function
1817/// removes it. If it cannot be removed because it is a non-empty directory,
1818/// this function recursively removes its entries and then tries again.
1819/// This operation is not atomic on most file systems.
1820pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void {
1821 var initial_iterable_dir = (try self.deleteTreeOpenInitialSubpath(sub_path, .file)) orelse return;
1822
1823 const StackItem = struct {
1824 name: []const u8,
1825 parent_dir: Dir,
1826 iter: Dir.Iterator,
1827
1828 fn closeAll(items: []@This()) void {
1829 for (items) |*item| item.iter.dir.close();
1830 }
1831 };
1832
1833 var stack_buffer: [16]StackItem = undefined;
1834 var stack = std.ArrayListUnmanaged(StackItem).initBuffer(&stack_buffer);
1835 defer StackItem.closeAll(stack.items);
1836
1837 stack.appendAssumeCapacity(.{
1838 .name = sub_path,
1839 .parent_dir = self,
1840 .iter = initial_iterable_dir.iterateAssumeFirstIteration(),
1841 });
1842
1843 process_stack: while (stack.items.len != 0) {
1844 var top = &stack.items[stack.items.len - 1];
1845 while (try top.iter.next()) |entry| {
1846 var treat_as_dir = entry.kind == .directory;
1847 handle_entry: while (true) {
1848 if (treat_as_dir) {
1849 if (stack.unusedCapacitySlice().len >= 1) {
1850 var iterable_dir = top.iter.dir.openDir(entry.name, .{
1851 .no_follow = true,
1852 .iterate = true,
1853 }) catch |err| switch (err) {
1854 error.NotDir => {
1855 treat_as_dir = false;
1856 continue :handle_entry;
1857 },
1858 error.FileNotFound => {
1859 // That's fine, we were trying to remove this directory anyway.
1860 break :handle_entry;
1861 },
1862
1863 error.InvalidHandle,
1864 error.AccessDenied,
1865 error.SymLinkLoop,
1866 error.ProcessFdQuotaExceeded,
1867 error.NameTooLong,
1868 error.SystemFdQuotaExceeded,
1869 error.NoDevice,
1870 error.SystemResources,
1871 error.Unexpected,
1872 error.InvalidUtf8,
1873 error.BadPathName,
1874 error.NetworkNotFound,
1875 error.DeviceBusy,
1876 => |e| return e,
1877 };
1878 stack.appendAssumeCapacity(.{
1879 .name = entry.name,
1880 .parent_dir = top.iter.dir,
1881 .iter = iterable_dir.iterateAssumeFirstIteration(),
1882 });
1883 continue :process_stack;
1884 } else {
1885 try top.iter.dir.deleteTreeMinStackSizeWithKindHint(entry.name, entry.kind);
1886 break :handle_entry;
1887 }
1888 } else {
1889 if (top.iter.dir.deleteFile(entry.name)) {
1890 break :handle_entry;
1891 } else |err| switch (err) {
1892 error.FileNotFound => break :handle_entry,
1893
1894 // Impossible because we do not pass any path separators.
1895 error.NotDir => unreachable,
1896
1897 error.IsDir => {
1898 treat_as_dir = true;
1899 continue :handle_entry;
1900 },
1901
1902 error.AccessDenied,
1903 error.InvalidUtf8,
1904 error.SymLinkLoop,
1905 error.NameTooLong,
1906 error.SystemResources,
1907 error.ReadOnlyFileSystem,
1908 error.FileSystem,
1909 error.FileBusy,
1910 error.BadPathName,
1911 error.NetworkNotFound,
1912 error.Unexpected,
1913 => |e| return e,
1914 }
1915 }
1916 }
1917 }
1918
1919 // On Windows, we can't delete until the dir's handle has been closed, so
1920 // close it before we try to delete.
1921 top.iter.dir.close();
1922
1923 // In order to avoid double-closing the directory when cleaning up
1924 // the stack in the case of an error, we save the relevant portions and
1925 // pop the value from the stack.
1926 const parent_dir = top.parent_dir;
1927 const name = top.name;
1928 stack.items.len -= 1;
1929
1930 var need_to_retry: bool = false;
1931 parent_dir.deleteDir(name) catch |err| switch (err) {
1932 error.FileNotFound => {},
1933 error.DirNotEmpty => need_to_retry = true,
1934 else => |e| return e,
1935 };
1936
1937 if (need_to_retry) {
1938 // Since we closed the handle that the previous iterator used, we
1939 // need to re-open the dir and re-create the iterator.
1940 var iterable_dir = iterable_dir: {
1941 var treat_as_dir = true;
1942 handle_entry: while (true) {
1943 if (treat_as_dir) {
1944 break :iterable_dir parent_dir.openDir(name, .{
1945 .no_follow = true,
1946 .iterate = true,
1947 }) catch |err| switch (err) {
1948 error.NotDir => {
1949 treat_as_dir = false;
1950 continue :handle_entry;
1951 },
1952 error.FileNotFound => {
1953 // That's fine, we were trying to remove this directory anyway.
1954 continue :process_stack;
1955 },
1956
1957 error.InvalidHandle,
1958 error.AccessDenied,
1959 error.SymLinkLoop,
1960 error.ProcessFdQuotaExceeded,
1961 error.NameTooLong,
1962 error.SystemFdQuotaExceeded,
1963 error.NoDevice,
1964 error.SystemResources,
1965 error.Unexpected,
1966 error.InvalidUtf8,
1967 error.BadPathName,
1968 error.NetworkNotFound,
1969 error.DeviceBusy,
1970 => |e| return e,
1971 };
1972 } else {
1973 if (parent_dir.deleteFile(name)) {
1974 continue :process_stack;
1975 } else |err| switch (err) {
1976 error.FileNotFound => continue :process_stack,
1977
1978 // Impossible because we do not pass any path separators.
1979 error.NotDir => unreachable,
1980
1981 error.IsDir => {
1982 treat_as_dir = true;
1983 continue :handle_entry;
1984 },
1985
1986 error.AccessDenied,
1987 error.InvalidUtf8,
1988 error.SymLinkLoop,
1989 error.NameTooLong,
1990 error.SystemResources,
1991 error.ReadOnlyFileSystem,
1992 error.FileSystem,
1993 error.FileBusy,
1994 error.BadPathName,
1995 error.NetworkNotFound,
1996 error.Unexpected,
1997 => |e| return e,
1998 }
1999 }
2000 }
2001 };
2002 // We know there is room on the stack since we are just re-adding
2003 // the StackItem that we previously popped.
2004 stack.appendAssumeCapacity(.{
2005 .name = name,
2006 .parent_dir = parent_dir,
2007 .iter = iterable_dir.iterateAssumeFirstIteration(),
2008 });
2009 continue :process_stack;
2010 }
2011 }
2012}
2013
2014/// Like `deleteTree`, but only keeps one `Iterator` active at a time to minimize the function's stack size.
2015/// This is slower than `deleteTree` but uses less stack space.
2016pub fn deleteTreeMinStackSize(self: Dir, sub_path: []const u8) DeleteTreeError!void {
2017 return self.deleteTreeMinStackSizeWithKindHint(sub_path, .file);
2018}
2019
2020fn deleteTreeMinStackSizeWithKindHint(self: Dir, sub_path: []const u8, kind_hint: File.Kind) DeleteTreeError!void {
2021 start_over: while (true) {
2022 var dir = (try self.deleteTreeOpenInitialSubpath(sub_path, kind_hint)) orelse return;
2023 var cleanup_dir_parent: ?Dir = null;
2024 defer if (cleanup_dir_parent) |*d| d.close();
2025
2026 var cleanup_dir = true;
2027 defer if (cleanup_dir) dir.close();
2028
2029 // Valid use of MAX_PATH_BYTES because dir_name_buf will only
2030 // ever store a single path component that was returned from the
2031 // filesystem.
2032 var dir_name_buf: [fs.MAX_PATH_BYTES]u8 = undefined;
2033 var dir_name: []const u8 = sub_path;
2034
2035 // Here we must avoid recursion, in order to provide O(1) memory guarantee of this function.
2036 // Go through each entry and if it is not a directory, delete it. If it is a directory,
2037 // open it, and close the original directory. Repeat. Then start the entire operation over.
2038
2039 scan_dir: while (true) {
2040 var dir_it = dir.iterateAssumeFirstIteration();
2041 dir_it: while (try dir_it.next()) |entry| {
2042 var treat_as_dir = entry.kind == .directory;
2043 handle_entry: while (true) {
2044 if (treat_as_dir) {
2045 const new_dir = dir.openDir(entry.name, .{
2046 .no_follow = true,
2047 .iterate = true,
2048 }) catch |err| switch (err) {
2049 error.NotDir => {
2050 treat_as_dir = false;
2051 continue :handle_entry;
2052 },
2053 error.FileNotFound => {
2054 // That's fine, we were trying to remove this directory anyway.
2055 continue :dir_it;
2056 },
2057
2058 error.InvalidHandle,
2059 error.AccessDenied,
2060 error.SymLinkLoop,
2061 error.ProcessFdQuotaExceeded,
2062 error.NameTooLong,
2063 error.SystemFdQuotaExceeded,
2064 error.NoDevice,
2065 error.SystemResources,
2066 error.Unexpected,
2067 error.InvalidUtf8,
2068 error.BadPathName,
2069 error.NetworkNotFound,
2070 error.DeviceBusy,
2071 => |e| return e,
2072 };
2073 if (cleanup_dir_parent) |*d| d.close();
2074 cleanup_dir_parent = dir;
2075 dir = new_dir;
2076 const result = dir_name_buf[0..entry.name.len];
2077 @memcpy(result, entry.name);
2078 dir_name = result;
2079 continue :scan_dir;
2080 } else {
2081 if (dir.deleteFile(entry.name)) {
2082 continue :dir_it;
2083 } else |err| switch (err) {
2084 error.FileNotFound => continue :dir_it,
2085
2086 // Impossible because we do not pass any path separators.
2087 error.NotDir => unreachable,
2088
2089 error.IsDir => {
2090 treat_as_dir = true;
2091 continue :handle_entry;
2092 },
2093
2094 error.AccessDenied,
2095 error.InvalidUtf8,
2096 error.SymLinkLoop,
2097 error.NameTooLong,
2098 error.SystemResources,
2099 error.ReadOnlyFileSystem,
2100 error.FileSystem,
2101 error.FileBusy,
2102 error.BadPathName,
2103 error.NetworkNotFound,
2104 error.Unexpected,
2105 => |e| return e,
2106 }
2107 }
2108 }
2109 }
2110 // Reached the end of the directory entries, which means we successfully deleted all of them.
2111 // Now to remove the directory itself.
2112 dir.close();
2113 cleanup_dir = false;
2114
2115 if (cleanup_dir_parent) |d| {
2116 d.deleteDir(dir_name) catch |err| switch (err) {
2117 // These two things can happen due to file system race conditions.
2118 error.FileNotFound, error.DirNotEmpty => continue :start_over,
2119 else => |e| return e,
2120 };
2121 continue :start_over;
2122 } else {
2123 self.deleteDir(sub_path) catch |err| switch (err) {
2124 error.FileNotFound => return,
2125 error.DirNotEmpty => continue :start_over,
2126 else => |e| return e,
2127 };
2128 return;
2129 }
2130 }
2131 }
2132}
2133
2134/// On successful delete, returns null.
2135fn deleteTreeOpenInitialSubpath(self: Dir, sub_path: []const u8, kind_hint: File.Kind) !?Dir {
2136 return iterable_dir: {
2137 // Treat as a file by default
2138 var treat_as_dir = kind_hint == .directory;
2139
2140 handle_entry: while (true) {
2141 if (treat_as_dir) {
2142 break :iterable_dir self.openDir(sub_path, .{
2143 .no_follow = true,
2144 .iterate = true,
2145 }) catch |err| switch (err) {
2146 error.NotDir => {
2147 treat_as_dir = false;
2148 continue :handle_entry;
2149 },
2150 error.FileNotFound => {
2151 // That's fine, we were trying to remove this directory anyway.
2152 return null;
2153 },
2154
2155 error.InvalidHandle,
2156 error.AccessDenied,
2157 error.SymLinkLoop,
2158 error.ProcessFdQuotaExceeded,
2159 error.NameTooLong,
2160 error.SystemFdQuotaExceeded,
2161 error.NoDevice,
2162 error.SystemResources,
2163 error.Unexpected,
2164 error.InvalidUtf8,
2165 error.BadPathName,
2166 error.DeviceBusy,
2167 error.NetworkNotFound,
2168 => |e| return e,
2169 };
2170 } else {
2171 if (self.deleteFile(sub_path)) {
2172 return null;
2173 } else |err| switch (err) {
2174 error.FileNotFound => return null,
2175
2176 error.IsDir => {
2177 treat_as_dir = true;
2178 continue :handle_entry;
2179 },
2180
2181 error.AccessDenied,
2182 error.InvalidUtf8,
2183 error.SymLinkLoop,
2184 error.NameTooLong,
2185 error.SystemResources,
2186 error.ReadOnlyFileSystem,
2187 error.NotDir,
2188 error.FileSystem,
2189 error.FileBusy,
2190 error.BadPathName,
2191 error.NetworkNotFound,
2192 error.Unexpected,
2193 => |e| return e,
2194 }
2195 }
2196 }
2197 };
2198}
2199
2200pub const WriteFileError = File.WriteError || File.OpenError;
2201
2202/// Deprecated: use `writeFile2`.
2203pub fn writeFile(self: Dir, sub_path: []const u8, data: []const u8) WriteFileError!void {
2204 return writeFile2(self, .{
2205 .sub_path = sub_path,
2206 .data = data,
2207 .flags = .{},
2208 });
2209}
2210
2211pub const WriteFileOptions = struct {
2212 sub_path: []const u8,
2213 data: []const u8,
2214 flags: File.CreateFlags = .{},
2215};
2216
2217/// Writes content to the file system, using the file creation flags provided.
2218pub fn writeFile2(self: Dir, options: WriteFileOptions) WriteFileError!void {
2219 var file = try self.createFile(options.sub_path, options.flags);
2220 defer file.close();
2221 try file.writeAll(options.data);
2222}
2223
2224pub const AccessError = posix.AccessError;
2225
2226/// Test accessing `path`.
2227/// `path` is UTF-8-encoded.
2228/// Be careful of Time-Of-Check-Time-Of-Use race conditions when using this function.
2229/// For example, instead of testing if a file exists and then opening it, just
2230/// open it and handle the error for file not found.
2231pub fn access(self: Dir, sub_path: []const u8, flags: File.OpenFlags) AccessError!void {
2232 if (builtin.os.tag == .windows) {
2233 const sub_path_w = std.os.windows.sliceToPrefixedFileW(self.fd, sub_path) catch |err| switch (err) {
2234 error.AccessDenied => return error.PermissionDenied,
2235 else => |e| return e,
2236 };
2237 return self.accessW(sub_path_w.span().ptr, flags);
2238 }
2239 const path_c = try posix.toPosixPath(sub_path);
2240 return self.accessZ(&path_c, flags);
2241}
2242
2243/// Same as `access` except the path parameter is null-terminated.
2244pub fn accessZ(self: Dir, sub_path: [*:0]const u8, flags: File.OpenFlags) AccessError!void {
2245 if (builtin.os.tag == .windows) {
2246 const sub_path_w = std.os.windows.cStrToPrefixedFileW(self.fd, sub_path) catch |err| switch (err) {
2247 error.AccessDenied => return error.PermissionDenied,
2248 else => |e| return e,
2249 };
2250 return self.accessW(sub_path_w.span().ptr, flags);
2251 }
2252 const os_mode = switch (flags.mode) {
2253 .read_only => @as(u32, posix.F_OK),
2254 .write_only => @as(u32, posix.W_OK),
2255 .read_write => @as(u32, posix.R_OK | posix.W_OK),
2256 };
2257 const result = if (fs.need_async_thread and flags.intended_io_mode != .blocking)
2258 std.event.Loop.instance.?.faccessatZ(self.fd, sub_path, os_mode, 0)
2259 else
2260 posix.faccessatZ(self.fd, sub_path, os_mode, 0);
2261 return result;
2262}
2263
2264/// Same as `access` except asserts the target OS is Windows and the path parameter is
2265/// * WTF-16 encoded
2266/// * null-terminated
2267/// * NtDll prefixed
2268/// TODO currently this ignores `flags`.
2269pub fn accessW(self: Dir, sub_path_w: [*:0]const u16, flags: File.OpenFlags) AccessError!void {
2270 _ = flags;
2271 return posix.faccessatW(self.fd, sub_path_w, 0, 0);
2272}
2273
2274pub const CopyFileOptions = struct {
2275 /// When this is `null` the mode is copied from the source file.
2276 override_mode: ?File.Mode = null,
2277};
2278
2279pub const PrevStatus = enum {
2280 stale,
2281 fresh,
2282};
2283
2284/// Check the file size, mtime, and mode of `source_path` and `dest_path`. If they are equal, does nothing.
2285/// Otherwise, atomically copies `source_path` to `dest_path`. The destination file gains the mtime,
2286/// atime, and mode of the source file so that the next call to `updateFile` will not need a copy.
2287/// Returns the previous status of the file before updating.
2288/// If any of the directories do not exist for dest_path, they are created.
2289pub fn updateFile(
2290 source_dir: Dir,
2291 source_path: []const u8,
2292 dest_dir: Dir,
2293 dest_path: []const u8,
2294 options: CopyFileOptions,
2295) !PrevStatus {
2296 var src_file = try source_dir.openFile(source_path, .{});
2297 defer src_file.close();
2298
2299 const src_stat = try src_file.stat();
2300 const actual_mode = options.override_mode orelse src_stat.mode;
2301 check_dest_stat: {
2302 const dest_stat = blk: {
2303 var dest_file = dest_dir.openFile(dest_path, .{}) catch |err| switch (err) {
2304 error.FileNotFound => break :check_dest_stat,
2305 else => |e| return e,
2306 };
2307 defer dest_file.close();
2308
2309 break :blk try dest_file.stat();
2310 };
2311
2312 if (src_stat.size == dest_stat.size and
2313 src_stat.mtime == dest_stat.mtime and
2314 actual_mode == dest_stat.mode)
2315 {
2316 return PrevStatus.fresh;
2317 }
2318 }
2319
2320 if (fs.path.dirname(dest_path)) |dirname| {
2321 try dest_dir.makePath(dirname);
2322 }
2323
2324 var atomic_file = try dest_dir.atomicFile(dest_path, .{ .mode = actual_mode });
2325 defer atomic_file.deinit();
2326
2327 try atomic_file.file.writeFileAll(src_file, .{ .in_len = src_stat.size });
2328 try atomic_file.file.updateTimes(src_stat.atime, src_stat.mtime);
2329 try atomic_file.finish();
2330 return PrevStatus.stale;
2331}
2332
2333pub const CopyFileError = File.OpenError || File.StatError ||
2334 AtomicFile.InitError || CopyFileRawError || AtomicFile.FinishError;
2335
2336/// Guaranteed to be atomic.
2337/// On Linux, until https://patchwork.kernel.org/patch/9636735/ is merged and readily available,
2338/// there is a possibility of power loss or application termination leaving temporary files present
2339/// in the same directory as dest_path.
2340pub fn copyFile(
2341 source_dir: Dir,
2342 source_path: []const u8,
2343 dest_dir: Dir,
2344 dest_path: []const u8,
2345 options: CopyFileOptions,
2346) CopyFileError!void {
2347 var in_file = try source_dir.openFile(source_path, .{});
2348 defer in_file.close();
2349
2350 var size: ?u64 = null;
2351 const mode = options.override_mode orelse blk: {
2352 const st = try in_file.stat();
2353 size = st.size;
2354 break :blk st.mode;
2355 };
2356
2357 var atomic_file = try dest_dir.atomicFile(dest_path, .{ .mode = mode });
2358 defer atomic_file.deinit();
2359
2360 try copy_file(in_file.handle, atomic_file.file.handle, size);
2361 try atomic_file.finish();
2362}
2363
2364const CopyFileRawError = error{SystemResources} || posix.CopyFileRangeError || posix.SendFileError;
2365
2366// Transfer all the data between two file descriptors in the most efficient way.
2367// The copy starts at offset 0, the initial offsets are preserved.
2368// No metadata is transferred over.
2369fn copy_file(fd_in: posix.fd_t, fd_out: posix.fd_t, maybe_size: ?u64) CopyFileRawError!void {
2370 if (comptime builtin.target.isDarwin()) {
2371 const rc = posix.system.fcopyfile(fd_in, fd_out, null, posix.system.COPYFILE_DATA);
2372 switch (posix.errno(rc)) {
2373 .SUCCESS => return,
2374 .INVAL => unreachable,
2375 .NOMEM => return error.SystemResources,
2376 // The source file is not a directory, symbolic link, or regular file.
2377 // Try with the fallback path before giving up.
2378 .OPNOTSUPP => {},
2379 else => |err| return posix.unexpectedErrno(err),
2380 }
2381 }
2382
2383 if (builtin.os.tag == .linux) {
2384 // Try copy_file_range first as that works at the FS level and is the
2385 // most efficient method (if available).
2386 var offset: u64 = 0;
2387 cfr_loop: while (true) {
2388 // The kernel checks the u64 value `offset+count` for overflow, use
2389 // a 32 bit value so that the syscall won't return EINVAL except for
2390 // impossibly large files (> 2^64-1 - 2^32-1).
2391 const amt = try posix.copy_file_range(fd_in, offset, fd_out, offset, std.math.maxInt(u32), 0);
2392 // Terminate as soon as we have copied size bytes or no bytes
2393 if (maybe_size) |s| {
2394 if (s == amt) break :cfr_loop;
2395 }
2396 if (amt == 0) break :cfr_loop;
2397 offset += amt;
2398 }
2399 return;
2400 }
2401
2402 // Sendfile is a zero-copy mechanism iff the OS supports it, otherwise the
2403 // fallback code will copy the contents chunk by chunk.
2404 const empty_iovec = [0]posix.iovec_const{};
2405 var offset: u64 = 0;
2406 sendfile_loop: while (true) {
2407 const amt = try posix.sendfile(fd_out, fd_in, offset, 0, &empty_iovec, &empty_iovec, 0);
2408 // Terminate as soon as we have copied size bytes or no bytes
2409 if (maybe_size) |s| {
2410 if (s == amt) break :sendfile_loop;
2411 }
2412 if (amt == 0) break :sendfile_loop;
2413 offset += amt;
2414 }
2415}
2416
2417pub const AtomicFileOptions = struct {
2418 mode: File.Mode = File.default_mode,
2419};
2420
2421/// Directly access the `.file` field, and then call `AtomicFile.finish`
2422/// to atomically replace `dest_path` with contents.
2423/// Always call `AtomicFile.deinit` to clean up, regardless of whether `AtomicFile.finish` succeeded.
2424/// `dest_path` must remain valid until `AtomicFile.deinit` is called.
2425pub fn atomicFile(self: Dir, dest_path: []const u8, options: AtomicFileOptions) !AtomicFile {
2426 if (fs.path.dirname(dest_path)) |dirname| {
2427 const dir = try self.openDir(dirname, .{});
2428 return AtomicFile.init(fs.path.basename(dest_path), options.mode, dir, true);
2429 } else {
2430 return AtomicFile.init(dest_path, options.mode, self, false);
2431 }
2432}
2433
2434pub const Stat = File.Stat;
2435pub const StatError = File.StatError;
2436
2437pub fn stat(self: Dir) StatError!Stat {
2438 const file: File = .{
2439 .handle = self.fd,
2440 .capable_io_mode = .blocking,
2441 };
2442 return file.stat();
2443}
2444
2445pub const StatFileError = File.OpenError || File.StatError || posix.FStatAtError;
2446
2447/// Returns metadata for a file inside the directory.
2448///
2449/// On Windows, this requires three syscalls. On other operating systems, it
2450/// only takes one.
2451///
2452/// Symlinks are followed.
2453///
2454/// `sub_path` may be absolute, in which case `self` is ignored.
2455pub fn statFile(self: Dir, sub_path: []const u8) StatFileError!Stat {
2456 if (builtin.os.tag == .windows) {
2457 var file = try self.openFile(sub_path, .{});
2458 defer file.close();
2459 return file.stat();
2460 }
2461 if (builtin.os.tag == .wasi and !builtin.link_libc) {
2462 const st = try posix.fstatatWasi(self.fd, sub_path, posix.wasi.LOOKUP_SYMLINK_FOLLOW);
2463 return Stat.fromSystem(st);
2464 }
2465 const st = try posix.fstatat(self.fd, sub_path, 0);
2466 return Stat.fromSystem(st);
2467}
2468
2469pub const ChmodError = File.ChmodError;
2470
2471/// Changes the mode of the directory.
2472/// The process must have the correct privileges in order to do this
2473/// successfully, or must have the effective user ID matching the owner
2474/// of the directory. Additionally, the directory must have been opened
2475/// with `OpenDirOptions{ .iterate = true }`.
2476pub fn chmod(self: Dir, new_mode: File.Mode) ChmodError!void {
2477 const file: File = .{
2478 .handle = self.fd,
2479 .capable_io_mode = .blocking,
2480 };
2481 try file.chmod(new_mode);
2482}
2483
2484/// Changes the owner and group of the directory.
2485/// The process must have the correct privileges in order to do this
2486/// successfully. The group may be changed by the owner of the directory to
2487/// any group of which the owner is a member. Additionally, the directory
2488/// must have been opened with `OpenDirOptions{ .iterate = true }`. If the
2489/// owner or group is specified as `null`, the ID is not changed.
2490pub fn chown(self: Dir, owner: ?File.Uid, group: ?File.Gid) ChownError!void {
2491 const file: File = .{
2492 .handle = self.fd,
2493 .capable_io_mode = .blocking,
2494 };
2495 try file.chown(owner, group);
2496}
2497
2498pub const ChownError = File.ChownError;
2499
2500const Permissions = File.Permissions;
2501pub const SetPermissionsError = File.SetPermissionsError;
2502
2503/// Sets permissions according to the provided `Permissions` struct.
2504/// This method is *NOT* available on WASI
2505pub fn setPermissions(self: Dir, permissions: Permissions) SetPermissionsError!void {
2506 const file: File = .{
2507 .handle = self.fd,
2508 .capable_io_mode = .blocking,
2509 };
2510 try file.setPermissions(permissions);
2511}
2512
2513const Metadata = File.Metadata;
2514pub const MetadataError = File.MetadataError;
2515
2516/// Returns a `Metadata` struct, representing the permissions on the directory
2517pub fn metadata(self: Dir) MetadataError!Metadata {
2518 const file: File = .{
2519 .handle = self.fd,
2520 .capable_io_mode = .blocking,
2521 };
2522 return try file.metadata();
2523}
2524
2525const Dir = @This();
2526const builtin = @import("builtin");
2527const std = @import("../std.zig");
2528const File = std.fs.File;
2529const AtomicFile = std.fs.AtomicFile;
2530// https://github.com/ziglang/zig/issues/5019
2531const posix = std.os;
2532const mem = std.mem;
2533const fs = std.fs;
2534const Allocator = std.mem.Allocator;
lib/std/fs/File.zig created+1624
...@@ -0,0 +1,1624 @@
1/// The OS-specific file descriptor or file handle.
2handle: Handle,
3
4/// On some systems, such as Linux, file system file descriptors are incapable
5/// of non-blocking I/O. This forces us to perform asynchronous I/O on a dedicated thread,
6/// to achieve non-blocking file-system I/O. To do this, `File` must be aware of whether
7/// it is a file system file descriptor, or, more specifically, whether the I/O is always
8/// blocking.
9capable_io_mode: io.ModeOverride = io.default_mode,
10
11/// Furthermore, even when `std.options.io_mode` is async, it is still sometimes desirable
12/// to perform blocking I/O, although not by default. For example, when printing a
13/// stack trace to stderr. This field tracks both by acting as an overriding I/O mode.
14/// When not building in async I/O mode, the type only has the `.blocking` tag, making
15/// it a zero-bit type.
16intended_io_mode: io.ModeOverride = io.default_mode,
17
18pub const Handle = posix.fd_t;
19pub const Mode = posix.mode_t;
20pub const INode = posix.ino_t;
21pub const Uid = posix.uid_t;
22pub const Gid = posix.gid_t;
23
24pub const Kind = enum {
25 block_device,
26 character_device,
27 directory,
28 named_pipe,
29 sym_link,
30 file,
31 unix_domain_socket,
32 whiteout,
33 door,
34 event_port,
35 unknown,
36};
37
38/// This is the default mode given to POSIX operating systems for creating
39/// files. `0o666` is "-rw-rw-rw-" which is counter-intuitive at first,
40/// since most people would expect "-rw-r--r--", for example, when using
41/// the `touch` command, which would correspond to `0o644`. However, POSIX
42/// libc implementations use `0o666` inside `fopen` and then rely on the
43/// process-scoped "umask" setting to adjust this number for file creation.
44pub const default_mode = switch (builtin.os.tag) {
45 .windows => 0,
46 .wasi => 0,
47 else => 0o666,
48};
49
50pub const OpenError = error{
51 SharingViolation,
52 PathAlreadyExists,
53 FileNotFound,
54 AccessDenied,
55 PipeBusy,
56 NameTooLong,
57 /// On Windows, file paths must be valid Unicode.
58 InvalidUtf8,
59 /// On Windows, file paths cannot contain these characters:
60 /// '/', '*', '?', '"', '<', '>', '|'
61 BadPathName,
62 Unexpected,
63 /// On Windows, `\\server` or `\\server\share` was not found.
64 NetworkNotFound,
65} || posix.OpenError || posix.FlockError;
66
67pub const OpenMode = enum {
68 read_only,
69 write_only,
70 read_write,
71};
72
73pub const Lock = enum {
74 none,
75 shared,
76 exclusive,
77};
78
79pub const OpenFlags = struct {
80 mode: OpenMode = .read_only,
81
82 /// Open the file with an advisory lock to coordinate with other processes
83 /// accessing it at the same time. An exclusive lock will prevent other
84 /// processes from acquiring a lock. A shared lock will prevent other
85 /// processes from acquiring a exclusive lock, but does not prevent
86 /// other process from getting their own shared locks.
87 ///
88 /// The lock is advisory, except on Linux in very specific circumstances[1].
89 /// This means that a process that does not respect the locking API can still get access
90 /// to the file, despite the lock.
91 ///
92 /// On these operating systems, the lock is acquired atomically with
93 /// opening the file:
94 /// * Darwin
95 /// * DragonFlyBSD
96 /// * FreeBSD
97 /// * Haiku
98 /// * NetBSD
99 /// * OpenBSD
100 /// On these operating systems, the lock is acquired via a separate syscall
101 /// after opening the file:
102 /// * Linux
103 /// * Windows
104 ///
105 /// [1]: https://www.kernel.org/doc/Documentation/filesystems/mandatory-locking.txt
106 lock: Lock = .none,
107
108 /// Sets whether or not to wait until the file is locked to return. If set to true,
109 /// `error.WouldBlock` will be returned. Otherwise, the file will wait until the file
110 /// is available to proceed.
111 /// In async I/O mode, non-blocking at the OS level is
112 /// determined by `intended_io_mode`, and `true` means `error.WouldBlock` is returned,
113 /// and `false` means `error.WouldBlock` is handled by the event loop.
114 lock_nonblocking: bool = false,
115
116 /// Setting this to `.blocking` prevents `O.NONBLOCK` from being passed even
117 /// if `std.io.is_async`. It allows the use of `nosuspend` when calling functions
118 /// related to opening the file, reading, writing, and locking.
119 intended_io_mode: io.ModeOverride = io.default_mode,
120
121 /// Set this to allow the opened file to automatically become the
122 /// controlling TTY for the current process.
123 allow_ctty: bool = false,
124
125 pub fn isRead(self: OpenFlags) bool {
126 return self.mode != .write_only;
127 }
128
129 pub fn isWrite(self: OpenFlags) bool {
130 return self.mode != .read_only;
131 }
132};
133
134pub const CreateFlags = struct {
135 /// Whether the file will be created with read access.
136 read: bool = false,
137
138 /// If the file already exists, and is a regular file, and the access
139 /// mode allows writing, it will be truncated to length 0.
140 truncate: bool = true,
141
142 /// Ensures that this open call creates the file, otherwise causes
143 /// `error.PathAlreadyExists` to be returned.
144 exclusive: bool = false,
145
146 /// Open the file with an advisory lock to coordinate with other processes
147 /// accessing it at the same time. An exclusive lock will prevent other
148 /// processes from acquiring a lock. A shared lock will prevent other
149 /// processes from acquiring a exclusive lock, but does not prevent
150 /// other process from getting their own shared locks.
151 ///
152 /// The lock is advisory, except on Linux in very specific circumstances[1].
153 /// This means that a process that does not respect the locking API can still get access
154 /// to the file, despite the lock.
155 ///
156 /// On these operating systems, the lock is acquired atomically with
157 /// opening the file:
158 /// * Darwin
159 /// * DragonFlyBSD
160 /// * FreeBSD
161 /// * Haiku
162 /// * NetBSD
163 /// * OpenBSD
164 /// On these operating systems, the lock is acquired via a separate syscall
165 /// after opening the file:
166 /// * Linux
167 /// * Windows
168 ///
169 /// [1]: https://www.kernel.org/doc/Documentation/filesystems/mandatory-locking.txt
170 lock: Lock = .none,
171
172 /// Sets whether or not to wait until the file is locked to return. If set to true,
173 /// `error.WouldBlock` will be returned. Otherwise, the file will wait until the file
174 /// is available to proceed.
175 /// In async I/O mode, non-blocking at the OS level is
176 /// determined by `intended_io_mode`, and `true` means `error.WouldBlock` is returned,
177 /// and `false` means `error.WouldBlock` is handled by the event loop.
178 lock_nonblocking: bool = false,
179
180 /// For POSIX systems this is the file system mode the file will
181 /// be created with. On other systems this is always 0.
182 mode: Mode = default_mode,
183
184 /// Setting this to `.blocking` prevents `O.NONBLOCK` from being passed even
185 /// if `std.io.is_async`. It allows the use of `nosuspend` when calling functions
186 /// related to opening the file, reading, writing, and locking.
187 intended_io_mode: io.ModeOverride = io.default_mode,
188};
189
190/// Upon success, the stream is in an uninitialized state. To continue using it,
191/// you must use the open() function.
192pub fn close(self: File) void {
193 if (is_windows) {
194 windows.CloseHandle(self.handle);
195 } else if (self.capable_io_mode != self.intended_io_mode) {
196 std.event.Loop.instance.?.close(self.handle);
197 } else {
198 posix.close(self.handle);
199 }
200}
201
202pub const SyncError = posix.SyncError;
203
204/// Blocks until all pending file contents and metadata modifications
205/// for the file have been synchronized with the underlying filesystem.
206///
207/// Note that this does not ensure that metadata for the
208/// directory containing the file has also reached disk.
209pub fn sync(self: File) SyncError!void {
210 return posix.fsync(self.handle);
211}
212
213/// Test whether the file refers to a terminal.
214/// See also `supportsAnsiEscapeCodes`.
215pub fn isTty(self: File) bool {
216 return posix.isatty(self.handle);
217}
218
219/// Test whether ANSI escape codes will be treated as such.
220pub fn supportsAnsiEscapeCodes(self: File) bool {
221 if (builtin.os.tag == .windows) {
222 var console_mode: windows.DWORD = 0;
223 if (windows.kernel32.GetConsoleMode(self.handle, &console_mode) != 0) {
224 if (console_mode & windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0) return true;
225 }
226
227 return posix.isCygwinPty(self.handle);
228 }
229 if (builtin.os.tag == .wasi) {
230 // WASI sanitizes stdout when fd is a tty so ANSI escape codes
231 // will not be interpreted as actual cursor commands, and
232 // stderr is always sanitized.
233 return false;
234 }
235 if (self.isTty()) {
236 if (self.handle == posix.STDOUT_FILENO or self.handle == posix.STDERR_FILENO) {
237 if (posix.getenvZ("TERM")) |term| {
238 if (std.mem.eql(u8, term, "dumb"))
239 return false;
240 }
241 }
242 return true;
243 }
244 return false;
245}
246
247pub const SetEndPosError = posix.TruncateError;
248
249/// Shrinks or expands the file.
250/// The file offset after this call is left unchanged.
251pub fn setEndPos(self: File, length: u64) SetEndPosError!void {
252 try posix.ftruncate(self.handle, length);
253}
254
255pub const SeekError = posix.SeekError;
256
257/// Repositions read/write file offset relative to the current offset.
258/// TODO: integrate with async I/O
259pub fn seekBy(self: File, offset: i64) SeekError!void {
260 return posix.lseek_CUR(self.handle, offset);
261}
262
263/// Repositions read/write file offset relative to the end.
264/// TODO: integrate with async I/O
265pub fn seekFromEnd(self: File, offset: i64) SeekError!void {
266 return posix.lseek_END(self.handle, offset);
267}
268
269/// Repositions read/write file offset relative to the beginning.
270/// TODO: integrate with async I/O
271pub fn seekTo(self: File, offset: u64) SeekError!void {
272 return posix.lseek_SET(self.handle, offset);
273}
274
275pub const GetSeekPosError = posix.SeekError || posix.FStatError;
276
277/// TODO: integrate with async I/O
278pub fn getPos(self: File) GetSeekPosError!u64 {
279 return posix.lseek_CUR_get(self.handle);
280}
281
282/// TODO: integrate with async I/O
283pub fn getEndPos(self: File) GetSeekPosError!u64 {
284 if (builtin.os.tag == .windows) {
285 return windows.GetFileSizeEx(self.handle);
286 }
287 return (try self.stat()).size;
288}
289
290pub const ModeError = posix.FStatError;
291
292/// TODO: integrate with async I/O
293pub fn mode(self: File) ModeError!Mode {
294 if (builtin.os.tag == .windows) {
295 return 0;
296 }
297 return (try self.stat()).mode;
298}
299
300pub const Stat = struct {
301 /// A number that the system uses to point to the file metadata. This
302 /// number is not guaranteed to be unique across time, as some file
303 /// systems may reuse an inode after its file has been deleted. Some
304 /// systems may change the inode of a file over time.
305 ///
306 /// On Linux, the inode is a structure that stores the metadata, and
307 /// the inode _number_ is what you see here: the index number of the
308 /// inode.
309 ///
310 /// The FileIndex on Windows is similar. It is a number for a file that
311 /// is unique to each filesystem.
312 inode: INode,
313 size: u64,
314 /// This is available on POSIX systems and is always 0 otherwise.
315 mode: Mode,
316 kind: Kind,
317
318 /// Access time in nanoseconds, relative to UTC 1970-01-01.
319 atime: i128,
320 /// Last modification time in nanoseconds, relative to UTC 1970-01-01.
321 mtime: i128,
322 /// Creation time in nanoseconds, relative to UTC 1970-01-01.
323 ctime: i128,
324
325 pub fn fromSystem(st: posix.system.Stat) Stat {
326 const atime = st.atime();
327 const mtime = st.mtime();
328 const ctime = st.ctime();
329 const kind: Kind = if (builtin.os.tag == .wasi and !builtin.link_libc) switch (st.filetype) {
330 .BLOCK_DEVICE => .block_device,
331 .CHARACTER_DEVICE => .character_device,
332 .DIRECTORY => .directory,
333 .SYMBOLIC_LINK => .sym_link,
334 .REGULAR_FILE => .file,
335 .SOCKET_STREAM, .SOCKET_DGRAM => .unix_domain_socket,
336 else => .unknown,
337 } else blk: {
338 const m = st.mode & posix.S.IFMT;
339 switch (m) {
340 posix.S.IFBLK => break :blk .block_device,
341 posix.S.IFCHR => break :blk .character_device,
342 posix.S.IFDIR => break :blk .directory,
343 posix.S.IFIFO => break :blk .named_pipe,
344 posix.S.IFLNK => break :blk .sym_link,
345 posix.S.IFREG => break :blk .file,
346 posix.S.IFSOCK => break :blk .unix_domain_socket,
347 else => {},
348 }
349 if (builtin.os.tag.isSolarish()) switch (m) {
350 posix.S.IFDOOR => break :blk .door,
351 posix.S.IFPORT => break :blk .event_port,
352 else => {},
353 };
354
355 break :blk .unknown;
356 };
357
358 return Stat{
359 .inode = st.ino,
360 .size = @as(u64, @bitCast(st.size)),
361 .mode = st.mode,
362 .kind = kind,
363 .atime = @as(i128, atime.tv_sec) * std.time.ns_per_s + atime.tv_nsec,
364 .mtime = @as(i128, mtime.tv_sec) * std.time.ns_per_s + mtime.tv_nsec,
365 .ctime = @as(i128, ctime.tv_sec) * std.time.ns_per_s + ctime.tv_nsec,
366 };
367 }
368};
369
370pub const StatError = posix.FStatError;
371
372/// TODO: integrate with async I/O
373pub fn stat(self: File) StatError!Stat {
374 if (builtin.os.tag == .windows) {
375 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
376 var info: windows.FILE_ALL_INFORMATION = undefined;
377 const rc = windows.ntdll.NtQueryInformationFile(self.handle, &io_status_block, &info, @sizeOf(windows.FILE_ALL_INFORMATION), .FileAllInformation);
378 switch (rc) {
379 .SUCCESS => {},
380 // Buffer overflow here indicates that there is more information available than was able to be stored in the buffer
381 // size provided. This is treated as success because the type of variable-length information that this would be relevant for
382 // (name, volume name, etc) we don't care about.
383 .BUFFER_OVERFLOW => {},
384 .INVALID_PARAMETER => unreachable,
385 .ACCESS_DENIED => return error.AccessDenied,
386 else => return windows.unexpectedStatus(rc),
387 }
388 return Stat{
389 .inode = info.InternalInformation.IndexNumber,
390 .size = @as(u64, @bitCast(info.StandardInformation.EndOfFile)),
391 .mode = 0,
392 .kind = if (info.StandardInformation.Directory == 0) .file else .directory,
393 .atime = windows.fromSysTime(info.BasicInformation.LastAccessTime),
394 .mtime = windows.fromSysTime(info.BasicInformation.LastWriteTime),
395 .ctime = windows.fromSysTime(info.BasicInformation.CreationTime),
396 };
397 }
398
399 const st = try posix.fstat(self.handle);
400 return Stat.fromSystem(st);
401}
402
403pub const ChmodError = posix.FChmodError;
404
405/// Changes the mode of the file.
406/// The process must have the correct privileges in order to do this
407/// successfully, or must have the effective user ID matching the owner
408/// of the file.
409pub fn chmod(self: File, new_mode: Mode) ChmodError!void {
410 try posix.fchmod(self.handle, new_mode);
411}
412
413pub const ChownError = posix.FChownError;
414
415/// Changes the owner and group of the file.
416/// The process must have the correct privileges in order to do this
417/// successfully. The group may be changed by the owner of the file to
418/// any group of which the owner is a member. If the owner or group is
419/// specified as `null`, the ID is not changed.
420pub fn chown(self: File, owner: ?Uid, group: ?Gid) ChownError!void {
421 try posix.fchown(self.handle, owner, group);
422}
423
424/// Cross-platform representation of permissions on a file.
425/// The `readonly` and `setReadonly` are the only methods available across all platforms.
426/// Platform-specific functionality is available through the `inner` field.
427pub const Permissions = struct {
428 /// You may use the `inner` field to use platform-specific functionality
429 inner: switch (builtin.os.tag) {
430 .windows => PermissionsWindows,
431 else => PermissionsUnix,
432 },
433
434 const Self = @This();
435
436 /// Returns `true` if permissions represent an unwritable file.
437 /// On Unix, `true` is returned only if no class has write permissions.
438 pub fn readOnly(self: Self) bool {
439 return self.inner.readOnly();
440 }
441
442 /// Sets whether write permissions are provided.
443 /// On Unix, this affects *all* classes. If this is undesired, use `unixSet`.
444 /// This method *DOES NOT* set permissions on the filesystem: use `File.setPermissions(permissions)`
445 pub fn setReadOnly(self: *Self, read_only: bool) void {
446 self.inner.setReadOnly(read_only);
447 }
448};
449
450pub const PermissionsWindows = struct {
451 attributes: windows.DWORD,
452
453 const Self = @This();
454
455 /// Returns `true` if permissions represent an unwritable file.
456 pub fn readOnly(self: Self) bool {
457 return self.attributes & windows.FILE_ATTRIBUTE_READONLY != 0;
458 }
459
460 /// Sets whether write permissions are provided.
461 /// This method *DOES NOT* set permissions on the filesystem: use `File.setPermissions(permissions)`
462 pub fn setReadOnly(self: *Self, read_only: bool) void {
463 if (read_only) {
464 self.attributes |= windows.FILE_ATTRIBUTE_READONLY;
465 } else {
466 self.attributes &= ~@as(windows.DWORD, windows.FILE_ATTRIBUTE_READONLY);
467 }
468 }
469};
470
471pub const PermissionsUnix = struct {
472 mode: Mode,
473
474 const Self = @This();
475
476 /// Returns `true` if permissions represent an unwritable file.
477 /// `true` is returned only if no class has write permissions.
478 pub fn readOnly(self: Self) bool {
479 return self.mode & 0o222 == 0;
480 }
481
482 /// Sets whether write permissions are provided.
483 /// This affects *all* classes. If this is undesired, use `unixSet`.
484 /// This method *DOES NOT* set permissions on the filesystem: use `File.setPermissions(permissions)`
485 pub fn setReadOnly(self: *Self, read_only: bool) void {
486 if (read_only) {
487 self.mode &= ~@as(Mode, 0o222);
488 } else {
489 self.mode |= @as(Mode, 0o222);
490 }
491 }
492
493 pub const Class = enum(u2) {
494 user = 2,
495 group = 1,
496 other = 0,
497 };
498
499 pub const Permission = enum(u3) {
500 read = 0o4,
501 write = 0o2,
502 execute = 0o1,
503 };
504
505 /// Returns `true` if the chosen class has the selected permission.
506 /// This method is only available on Unix platforms.
507 pub fn unixHas(self: Self, class: Class, permission: Permission) bool {
508 const mask = @as(Mode, @intFromEnum(permission)) << @as(u3, @intFromEnum(class)) * 3;
509 return self.mode & mask != 0;
510 }
511
512 /// Sets the permissions for the chosen class. Any permissions set to `null` are left unchanged.
513 /// This method *DOES NOT* set permissions on the filesystem: use `File.setPermissions(permissions)`
514 pub fn unixSet(self: *Self, class: Class, permissions: struct {
515 read: ?bool = null,
516 write: ?bool = null,
517 execute: ?bool = null,
518 }) void {
519 const shift = @as(u3, @intFromEnum(class)) * 3;
520 if (permissions.read) |r| {
521 if (r) {
522 self.mode |= @as(Mode, 0o4) << shift;
523 } else {
524 self.mode &= ~(@as(Mode, 0o4) << shift);
525 }
526 }
527 if (permissions.write) |w| {
528 if (w) {
529 self.mode |= @as(Mode, 0o2) << shift;
530 } else {
531 self.mode &= ~(@as(Mode, 0o2) << shift);
532 }
533 }
534 if (permissions.execute) |x| {
535 if (x) {
536 self.mode |= @as(Mode, 0o1) << shift;
537 } else {
538 self.mode &= ~(@as(Mode, 0o1) << shift);
539 }
540 }
541 }
542
543 /// Returns a `Permissions` struct representing the permissions from the passed mode.
544 pub fn unixNew(new_mode: Mode) Self {
545 return Self{
546 .mode = new_mode,
547 };
548 }
549};
550
551pub const SetPermissionsError = ChmodError;
552
553/// Sets permissions according to the provided `Permissions` struct.
554/// This method is *NOT* available on WASI
555pub fn setPermissions(self: File, permissions: Permissions) SetPermissionsError!void {
556 switch (builtin.os.tag) {
557 .windows => {
558 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
559 var info = windows.FILE_BASIC_INFORMATION{
560 .CreationTime = 0,
561 .LastAccessTime = 0,
562 .LastWriteTime = 0,
563 .ChangeTime = 0,
564 .FileAttributes = permissions.inner.attributes,
565 };
566 const rc = windows.ntdll.NtSetInformationFile(
567 self.handle,
568 &io_status_block,
569 &info,
570 @sizeOf(windows.FILE_BASIC_INFORMATION),
571 .FileBasicInformation,
572 );
573 switch (rc) {
574 .SUCCESS => return,
575 .INVALID_HANDLE => unreachable,
576 .ACCESS_DENIED => return error.AccessDenied,
577 else => return windows.unexpectedStatus(rc),
578 }
579 },
580 .wasi => @compileError("Unsupported OS"), // Wasi filesystem does not *yet* support chmod
581 else => {
582 try self.chmod(permissions.inner.mode);
583 },
584 }
585}
586
587/// Cross-platform representation of file metadata.
588/// Platform-specific functionality is available through the `inner` field.
589pub const Metadata = struct {
590 /// You may use the `inner` field to use platform-specific functionality
591 inner: switch (builtin.os.tag) {
592 .windows => MetadataWindows,
593 .linux => MetadataLinux,
594 else => MetadataUnix,
595 },
596
597 const Self = @This();
598
599 /// Returns the size of the file
600 pub fn size(self: Self) u64 {
601 return self.inner.size();
602 }
603
604 /// Returns a `Permissions` struct, representing the permissions on the file
605 pub fn permissions(self: Self) Permissions {
606 return self.inner.permissions();
607 }
608
609 /// Returns the `Kind` of file.
610 /// On Windows, can only return: `.file`, `.directory`, `.sym_link` or `.unknown`
611 pub fn kind(self: Self) Kind {
612 return self.inner.kind();
613 }
614
615 /// Returns the last time the file was accessed in nanoseconds since UTC 1970-01-01
616 pub fn accessed(self: Self) i128 {
617 return self.inner.accessed();
618 }
619
620 /// Returns the time the file was modified in nanoseconds since UTC 1970-01-01
621 pub fn modified(self: Self) i128 {
622 return self.inner.modified();
623 }
624
625 /// Returns the time the file was created in nanoseconds since UTC 1970-01-01
626 /// On Windows, this cannot return null
627 /// On Linux, this returns null if the filesystem does not support creation times, or if the kernel is older than 4.11
628 /// On Unices, this returns null if the filesystem or OS does not support creation times
629 /// On MacOS, this returns the ctime if the filesystem does not support creation times; this is insanity, and yet another reason to hate on Apple
630 pub fn created(self: Self) ?i128 {
631 return self.inner.created();
632 }
633};
634
635pub const MetadataUnix = struct {
636 stat: posix.Stat,
637
638 const Self = @This();
639
640 /// Returns the size of the file
641 pub fn size(self: Self) u64 {
642 return @as(u64, @intCast(self.stat.size));
643 }
644
645 /// Returns a `Permissions` struct, representing the permissions on the file
646 pub fn permissions(self: Self) Permissions {
647 return Permissions{ .inner = PermissionsUnix{ .mode = self.stat.mode } };
648 }
649
650 /// Returns the `Kind` of the file
651 pub fn kind(self: Self) Kind {
652 if (builtin.os.tag == .wasi and !builtin.link_libc) return switch (self.stat.filetype) {
653 .BLOCK_DEVICE => .block_device,
654 .CHARACTER_DEVICE => .character_device,
655 .DIRECTORY => .directory,
656 .SYMBOLIC_LINK => .sym_link,
657 .REGULAR_FILE => .file,
658 .SOCKET_STREAM, .SOCKET_DGRAM => .unix_domain_socket,
659 else => .unknown,
660 };
661
662 const m = self.stat.mode & posix.S.IFMT;
663
664 switch (m) {
665 posix.S.IFBLK => return .block_device,
666 posix.S.IFCHR => return .character_device,
667 posix.S.IFDIR => return .directory,
668 posix.S.IFIFO => return .named_pipe,
669 posix.S.IFLNK => return .sym_link,
670 posix.S.IFREG => return .file,
671 posix.S.IFSOCK => return .unix_domain_socket,
672 else => {},
673 }
674
675 if (builtin.os.tag.isSolarish()) switch (m) {
676 posix.S.IFDOOR => return .door,
677 posix.S.IFPORT => return .event_port,
678 else => {},
679 };
680
681 return .unknown;
682 }
683
684 /// Returns the last time the file was accessed in nanoseconds since UTC 1970-01-01
685 pub fn accessed(self: Self) i128 {
686 const atime = self.stat.atime();
687 return @as(i128, atime.tv_sec) * std.time.ns_per_s + atime.tv_nsec;
688 }
689
690 /// Returns the last time the file was modified in nanoseconds since UTC 1970-01-01
691 pub fn modified(self: Self) i128 {
692 const mtime = self.stat.mtime();
693 return @as(i128, mtime.tv_sec) * std.time.ns_per_s + mtime.tv_nsec;
694 }
695
696 /// Returns the time the file was created in nanoseconds since UTC 1970-01-01.
697 /// Returns null if this is not supported by the OS or filesystem
698 pub fn created(self: Self) ?i128 {
699 if (!@hasDecl(@TypeOf(self.stat), "birthtime")) return null;
700 const birthtime = self.stat.birthtime();
701
702 // If the filesystem doesn't support this the value *should* be:
703 // On FreeBSD: tv_nsec = 0, tv_sec = -1
704 // On NetBSD and OpenBSD: tv_nsec = 0, tv_sec = 0
705 // On MacOS, it is set to ctime -- we cannot detect this!!
706 switch (builtin.os.tag) {
707 .freebsd => if (birthtime.tv_sec == -1 and birthtime.tv_nsec == 0) return null,
708 .netbsd, .openbsd => if (birthtime.tv_sec == 0 and birthtime.tv_nsec == 0) return null,
709 .macos => {},
710 else => @compileError("Creation time detection not implemented for OS"),
711 }
712
713 return @as(i128, birthtime.tv_sec) * std.time.ns_per_s + birthtime.tv_nsec;
714 }
715};
716
717/// `MetadataUnix`, but using Linux's `statx` syscall.
718/// On Linux versions below 4.11, `statx` will be filled with data from stat.
719pub const MetadataLinux = struct {
720 statx: std.os.linux.Statx,
721
722 const Self = @This();
723
724 /// Returns the size of the file
725 pub fn size(self: Self) u64 {
726 return self.statx.size;
727 }
728
729 /// Returns a `Permissions` struct, representing the permissions on the file
730 pub fn permissions(self: Self) Permissions {
731 return Permissions{ .inner = PermissionsUnix{ .mode = self.statx.mode } };
732 }
733
734 /// Returns the `Kind` of the file
735 pub fn kind(self: Self) Kind {
736 const m = self.statx.mode & posix.S.IFMT;
737
738 switch (m) {
739 posix.S.IFBLK => return .block_device,
740 posix.S.IFCHR => return .character_device,
741 posix.S.IFDIR => return .directory,
742 posix.S.IFIFO => return .named_pipe,
743 posix.S.IFLNK => return .sym_link,
744 posix.S.IFREG => return .file,
745 posix.S.IFSOCK => return .unix_domain_socket,
746 else => {},
747 }
748
749 return .unknown;
750 }
751
752 /// Returns the last time the file was accessed in nanoseconds since UTC 1970-01-01
753 pub fn accessed(self: Self) i128 {
754 return @as(i128, self.statx.atime.tv_sec) * std.time.ns_per_s + self.statx.atime.tv_nsec;
755 }
756
757 /// Returns the last time the file was modified in nanoseconds since UTC 1970-01-01
758 pub fn modified(self: Self) i128 {
759 return @as(i128, self.statx.mtime.tv_sec) * std.time.ns_per_s + self.statx.mtime.tv_nsec;
760 }
761
762 /// Returns the time the file was created in nanoseconds since UTC 1970-01-01.
763 /// Returns null if this is not supported by the filesystem, or on kernels before than version 4.11
764 pub fn created(self: Self) ?i128 {
765 if (self.statx.mask & std.os.linux.STATX_BTIME == 0) return null;
766 return @as(i128, self.statx.btime.tv_sec) * std.time.ns_per_s + self.statx.btime.tv_nsec;
767 }
768};
769
770pub const MetadataWindows = struct {
771 attributes: windows.DWORD,
772 reparse_tag: windows.DWORD,
773 _size: u64,
774 access_time: i128,
775 modified_time: i128,
776 creation_time: i128,
777
778 const Self = @This();
779
780 /// Returns the size of the file
781 pub fn size(self: Self) u64 {
782 return self._size;
783 }
784
785 /// Returns a `Permissions` struct, representing the permissions on the file
786 pub fn permissions(self: Self) Permissions {
787 return Permissions{ .inner = PermissionsWindows{ .attributes = self.attributes } };
788 }
789
790 /// Returns the `Kind` of the file.
791 /// Can only return: `.file`, `.directory`, `.sym_link` or `.unknown`
792 pub fn kind(self: Self) Kind {
793 if (self.attributes & windows.FILE_ATTRIBUTE_REPARSE_POINT != 0) {
794 if (self.reparse_tag & 0x20000000 != 0) {
795 return .sym_link;
796 }
797 } else if (self.attributes & windows.FILE_ATTRIBUTE_DIRECTORY != 0) {
798 return .directory;
799 } else {
800 return .file;
801 }
802 return .unknown;
803 }
804
805 /// Returns the last time the file was accessed in nanoseconds since UTC 1970-01-01
806 pub fn accessed(self: Self) i128 {
807 return self.access_time;
808 }
809
810 /// Returns the time the file was modified in nanoseconds since UTC 1970-01-01
811 pub fn modified(self: Self) i128 {
812 return self.modified_time;
813 }
814
815 /// Returns the time the file was created in nanoseconds since UTC 1970-01-01.
816 /// This never returns null, only returning an optional for compatibility with other OSes
817 pub fn created(self: Self) ?i128 {
818 return self.creation_time;
819 }
820};
821
822pub const MetadataError = posix.FStatError;
823
824pub fn metadata(self: File) MetadataError!Metadata {
825 return Metadata{
826 .inner = switch (builtin.os.tag) {
827 .windows => blk: {
828 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
829 var info: windows.FILE_ALL_INFORMATION = undefined;
830
831 const rc = windows.ntdll.NtQueryInformationFile(self.handle, &io_status_block, &info, @sizeOf(windows.FILE_ALL_INFORMATION), .FileAllInformation);
832 switch (rc) {
833 .SUCCESS => {},
834 // Buffer overflow here indicates that there is more information available than was able to be stored in the buffer
835 // size provided. This is treated as success because the type of variable-length information that this would be relevant for
836 // (name, volume name, etc) we don't care about.
837 .BUFFER_OVERFLOW => {},
838 .INVALID_PARAMETER => unreachable,
839 .ACCESS_DENIED => return error.AccessDenied,
840 else => return windows.unexpectedStatus(rc),
841 }
842
843 const reparse_tag: windows.DWORD = reparse_blk: {
844 if (info.BasicInformation.FileAttributes & windows.FILE_ATTRIBUTE_REPARSE_POINT != 0) {
845 var reparse_buf: [windows.MAXIMUM_REPARSE_DATA_BUFFER_SIZE]u8 = undefined;
846 try windows.DeviceIoControl(self.handle, windows.FSCTL_GET_REPARSE_POINT, null, reparse_buf[0..]);
847 const reparse_struct: *const windows.REPARSE_DATA_BUFFER = @ptrCast(@alignCast(&reparse_buf[0]));
848 break :reparse_blk reparse_struct.ReparseTag;
849 }
850 break :reparse_blk 0;
851 };
852
853 break :blk MetadataWindows{
854 .attributes = info.BasicInformation.FileAttributes,
855 .reparse_tag = reparse_tag,
856 ._size = @as(u64, @bitCast(info.StandardInformation.EndOfFile)),
857 .access_time = windows.fromSysTime(info.BasicInformation.LastAccessTime),
858 .modified_time = windows.fromSysTime(info.BasicInformation.LastWriteTime),
859 .creation_time = windows.fromSysTime(info.BasicInformation.CreationTime),
860 };
861 },
862 .linux => blk: {
863 const l = std.os.linux;
864 var stx = std.mem.zeroes(l.Statx);
865 const rcx = l.statx(self.handle, "\x00", l.AT.EMPTY_PATH, l.STATX_TYPE |
866 l.STATX_MODE | l.STATX_ATIME | l.STATX_MTIME | l.STATX_BTIME, &stx);
867
868 switch (posix.errno(rcx)) {
869 .SUCCESS => {},
870 // NOSYS happens when `statx` is unsupported, which is the case on kernel versions before 4.11
871 // Here, we call `fstat` and fill `stx` with the data we need
872 .NOSYS => {
873 const st = try posix.fstat(self.handle);
874
875 stx.mode = @as(u16, @intCast(st.mode));
876
877 // Hacky conversion from timespec to statx_timestamp
878 stx.atime = std.mem.zeroes(l.statx_timestamp);
879 stx.atime.tv_sec = st.atim.tv_sec;
880 stx.atime.tv_nsec = @as(u32, @intCast(st.atim.tv_nsec)); // Guaranteed to succeed (tv_nsec is always below 10^9)
881
882 stx.mtime = std.mem.zeroes(l.statx_timestamp);
883 stx.mtime.tv_sec = st.mtim.tv_sec;
884 stx.mtime.tv_nsec = @as(u32, @intCast(st.mtim.tv_nsec));
885
886 stx.mask = l.STATX_BASIC_STATS | l.STATX_MTIME;
887 },
888 .BADF => unreachable,
889 .FAULT => unreachable,
890 .NOMEM => return error.SystemResources,
891 else => |err| return posix.unexpectedErrno(err),
892 }
893
894 break :blk MetadataLinux{
895 .statx = stx,
896 };
897 },
898 else => blk: {
899 const st = try posix.fstat(self.handle);
900 break :blk MetadataUnix{
901 .stat = st,
902 };
903 },
904 },
905 };
906}
907
908pub const UpdateTimesError = posix.FutimensError || windows.SetFileTimeError;
909
910/// The underlying file system may have a different granularity than nanoseconds,
911/// and therefore this function cannot guarantee any precision will be stored.
912/// Further, the maximum value is limited by the system ABI. When a value is provided
913/// that exceeds this range, the value is clamped to the maximum.
914/// TODO: integrate with async I/O
915pub fn updateTimes(
916 self: File,
917 /// access timestamp in nanoseconds
918 atime: i128,
919 /// last modification timestamp in nanoseconds
920 mtime: i128,
921) UpdateTimesError!void {
922 if (builtin.os.tag == .windows) {
923 const atime_ft = windows.nanoSecondsToFileTime(atime);
924 const mtime_ft = windows.nanoSecondsToFileTime(mtime);
925 return windows.SetFileTime(self.handle, null, &atime_ft, &mtime_ft);
926 }
927 const times = [2]posix.timespec{
928 posix.timespec{
929 .tv_sec = math.cast(isize, @divFloor(atime, std.time.ns_per_s)) orelse maxInt(isize),
930 .tv_nsec = math.cast(isize, @mod(atime, std.time.ns_per_s)) orelse maxInt(isize),
931 },
932 posix.timespec{
933 .tv_sec = math.cast(isize, @divFloor(mtime, std.time.ns_per_s)) orelse maxInt(isize),
934 .tv_nsec = math.cast(isize, @mod(mtime, std.time.ns_per_s)) orelse maxInt(isize),
935 },
936 };
937 try posix.futimens(self.handle, &times);
938}
939
940/// Reads all the bytes from the current position to the end of the file.
941/// On success, caller owns returned buffer.
942/// If the file is larger than `max_bytes`, returns `error.FileTooBig`.
943pub fn readToEndAlloc(self: File, allocator: Allocator, max_bytes: usize) ![]u8 {
944 return self.readToEndAllocOptions(allocator, max_bytes, null, @alignOf(u8), null);
945}
946
947/// Reads all the bytes from the current position to the end of the file.
948/// On success, caller owns returned buffer.
949/// If the file is larger than `max_bytes`, returns `error.FileTooBig`.
950/// If `size_hint` is specified the initial buffer size is calculated using
951/// that value, otherwise an arbitrary value is used instead.
952/// Allows specifying alignment and a sentinel value.
953pub fn readToEndAllocOptions(
954 self: File,
955 allocator: Allocator,
956 max_bytes: usize,
957 size_hint: ?usize,
958 comptime alignment: u29,
959 comptime optional_sentinel: ?u8,
960) !(if (optional_sentinel) |s| [:s]align(alignment) u8 else []align(alignment) u8) {
961 // If no size hint is provided fall back to the size=0 code path
962 const size = size_hint orelse 0;
963
964 // The file size returned by stat is used as hint to set the buffer
965 // size. If the reported size is zero, as it happens on Linux for files
966 // in /proc, a small buffer is allocated instead.
967 const initial_cap = (if (size > 0) size else 1024) + @intFromBool(optional_sentinel != null);
968 var array_list = try std.ArrayListAligned(u8, alignment).initCapacity(allocator, initial_cap);
969 defer array_list.deinit();
970
971 self.reader().readAllArrayListAligned(alignment, &array_list, max_bytes) catch |err| switch (err) {
972 error.StreamTooLong => return error.FileTooBig,
973 else => |e| return e,
974 };
975
976 if (optional_sentinel) |sentinel| {
977 return try array_list.toOwnedSliceSentinel(sentinel);
978 } else {
979 return try array_list.toOwnedSlice();
980 }
981}
982
983pub const ReadError = posix.ReadError;
984pub const PReadError = posix.PReadError;
985
986pub fn read(self: File, buffer: []u8) ReadError!usize {
987 if (is_windows) {
988 return windows.ReadFile(self.handle, buffer, null, self.intended_io_mode);
989 }
990
991 if (self.intended_io_mode == .blocking) {
992 return posix.read(self.handle, buffer);
993 } else {
994 return std.event.Loop.instance.?.read(self.handle, buffer, self.capable_io_mode != self.intended_io_mode);
995 }
996}
997
998/// Returns the number of bytes read. If the number read is smaller than `buffer.len`, it
999/// means the file reached the end. Reaching the end of a file is not an error condition.
1000pub fn readAll(self: File, buffer: []u8) ReadError!usize {
1001 var index: usize = 0;
1002 while (index != buffer.len) {
1003 const amt = try self.read(buffer[index..]);
1004 if (amt == 0) break;
1005 index += amt;
1006 }
1007 return index;
1008}
1009
1010/// On Windows, this function currently does alter the file pointer.
1011/// https://github.com/ziglang/zig/issues/12783
1012pub fn pread(self: File, buffer: []u8, offset: u64) PReadError!usize {
1013 if (is_windows) {
1014 return windows.ReadFile(self.handle, buffer, offset, self.intended_io_mode);
1015 }
1016
1017 if (self.intended_io_mode == .blocking) {
1018 return posix.pread(self.handle, buffer, offset);
1019 } else {
1020 return std.event.Loop.instance.?.pread(self.handle, buffer, offset, self.capable_io_mode != self.intended_io_mode);
1021 }
1022}
1023
1024/// Returns the number of bytes read. If the number read is smaller than `buffer.len`, it
1025/// means the file reached the end. Reaching the end of a file is not an error condition.
1026/// On Windows, this function currently does alter the file pointer.
1027/// https://github.com/ziglang/zig/issues/12783
1028pub fn preadAll(self: File, buffer: []u8, offset: u64) PReadError!usize {
1029 var index: usize = 0;
1030 while (index != buffer.len) {
1031 const amt = try self.pread(buffer[index..], offset + index);
1032 if (amt == 0) break;
1033 index += amt;
1034 }
1035 return index;
1036}
1037
1038/// See https://github.com/ziglang/zig/issues/7699
1039pub fn readv(self: File, iovecs: []const posix.iovec) ReadError!usize {
1040 if (is_windows) {
1041 // TODO improve this to use ReadFileScatter
1042 if (iovecs.len == 0) return @as(usize, 0);
1043 const first = iovecs[0];
1044 return windows.ReadFile(self.handle, first.iov_base[0..first.iov_len], null, self.intended_io_mode);
1045 }
1046
1047 if (self.intended_io_mode == .blocking) {
1048 return posix.readv(self.handle, iovecs);
1049 } else {
1050 return std.event.Loop.instance.?.readv(self.handle, iovecs, self.capable_io_mode != self.intended_io_mode);
1051 }
1052}
1053
1054/// Returns the number of bytes read. If the number read is smaller than the total bytes
1055/// from all the buffers, it means the file reached the end. Reaching the end of a file
1056/// is not an error condition.
1057///
1058/// The `iovecs` parameter is mutable because:
1059/// * This function needs to mutate the fields in order to handle partial
1060/// reads from the underlying OS layer.
1061/// * The OS layer expects pointer addresses to be inside the application's address space
1062/// even if the length is zero. Meanwhile, in Zig, slices may have undefined pointer
1063/// addresses when the length is zero. So this function modifies the iov_base fields
1064/// when the length is zero.
1065///
1066/// Related open issue: https://github.com/ziglang/zig/issues/7699
1067pub fn readvAll(self: File, iovecs: []posix.iovec) ReadError!usize {
1068 if (iovecs.len == 0) return 0;
1069
1070 // We use the address of this local variable for all zero-length
1071 // vectors so that the OS does not complain that we are giving it
1072 // addresses outside the application's address space.
1073 var garbage: [1]u8 = undefined;
1074 for (iovecs) |*v| {
1075 if (v.iov_len == 0) v.iov_base = &garbage;
1076 }
1077
1078 var i: usize = 0;
1079 var off: usize = 0;
1080 while (true) {
1081 var amt = try self.readv(iovecs[i..]);
1082 var eof = amt == 0;
1083 off += amt;
1084 while (amt >= iovecs[i].iov_len) {
1085 amt -= iovecs[i].iov_len;
1086 i += 1;
1087 if (i >= iovecs.len) return off;
1088 eof = false;
1089 }
1090 if (eof) return off;
1091 iovecs[i].iov_base += amt;
1092 iovecs[i].iov_len -= amt;
1093 }
1094}
1095
1096/// See https://github.com/ziglang/zig/issues/7699
1097/// On Windows, this function currently does alter the file pointer.
1098/// https://github.com/ziglang/zig/issues/12783
1099pub fn preadv(self: File, iovecs: []const posix.iovec, offset: u64) PReadError!usize {
1100 if (is_windows) {
1101 // TODO improve this to use ReadFileScatter
1102 if (iovecs.len == 0) return @as(usize, 0);
1103 const first = iovecs[0];
1104 return windows.ReadFile(self.handle, first.iov_base[0..first.iov_len], offset, self.intended_io_mode);
1105 }
1106
1107 if (self.intended_io_mode == .blocking) {
1108 return posix.preadv(self.handle, iovecs, offset);
1109 } else {
1110 return std.event.Loop.instance.?.preadv(self.handle, iovecs, offset, self.capable_io_mode != self.intended_io_mode);
1111 }
1112}
1113
1114/// Returns the number of bytes read. If the number read is smaller than the total bytes
1115/// from all the buffers, it means the file reached the end. Reaching the end of a file
1116/// is not an error condition.
1117/// The `iovecs` parameter is mutable because this function needs to mutate the fields in
1118/// order to handle partial reads from the underlying OS layer.
1119/// See https://github.com/ziglang/zig/issues/7699
1120/// On Windows, this function currently does alter the file pointer.
1121/// https://github.com/ziglang/zig/issues/12783
1122pub fn preadvAll(self: File, iovecs: []posix.iovec, offset: u64) PReadError!usize {
1123 if (iovecs.len == 0) return 0;
1124
1125 var i: usize = 0;
1126 var off: usize = 0;
1127 while (true) {
1128 var amt = try self.preadv(iovecs[i..], offset + off);
1129 var eof = amt == 0;
1130 off += amt;
1131 while (amt >= iovecs[i].iov_len) {
1132 amt -= iovecs[i].iov_len;
1133 i += 1;
1134 if (i >= iovecs.len) return off;
1135 eof = false;
1136 }
1137 if (eof) return off;
1138 iovecs[i].iov_base += amt;
1139 iovecs[i].iov_len -= amt;
1140 }
1141}
1142
1143pub const WriteError = posix.WriteError;
1144pub const PWriteError = posix.PWriteError;
1145
1146pub fn write(self: File, bytes: []const u8) WriteError!usize {
1147 if (is_windows) {
1148 return windows.WriteFile(self.handle, bytes, null, self.intended_io_mode);
1149 }
1150
1151 if (self.intended_io_mode == .blocking) {
1152 return posix.write(self.handle, bytes);
1153 } else {
1154 return std.event.Loop.instance.?.write(self.handle, bytes, self.capable_io_mode != self.intended_io_mode);
1155 }
1156}
1157
1158pub fn writeAll(self: File, bytes: []const u8) WriteError!void {
1159 var index: usize = 0;
1160 while (index < bytes.len) {
1161 index += try self.write(bytes[index..]);
1162 }
1163}
1164
1165/// On Windows, this function currently does alter the file pointer.
1166/// https://github.com/ziglang/zig/issues/12783
1167pub fn pwrite(self: File, bytes: []const u8, offset: u64) PWriteError!usize {
1168 if (is_windows) {
1169 return windows.WriteFile(self.handle, bytes, offset, self.intended_io_mode);
1170 }
1171
1172 if (self.intended_io_mode == .blocking) {
1173 return posix.pwrite(self.handle, bytes, offset);
1174 } else {
1175 return std.event.Loop.instance.?.pwrite(self.handle, bytes, offset, self.capable_io_mode != self.intended_io_mode);
1176 }
1177}
1178
1179/// On Windows, this function currently does alter the file pointer.
1180/// https://github.com/ziglang/zig/issues/12783
1181pub fn pwriteAll(self: File, bytes: []const u8, offset: u64) PWriteError!void {
1182 var index: usize = 0;
1183 while (index < bytes.len) {
1184 index += try self.pwrite(bytes[index..], offset + index);
1185 }
1186}
1187
1188/// See https://github.com/ziglang/zig/issues/7699
1189/// See equivalent function: `std.net.Stream.writev`.
1190pub fn writev(self: File, iovecs: []const posix.iovec_const) WriteError!usize {
1191 if (is_windows) {
1192 // TODO improve this to use WriteFileScatter
1193 if (iovecs.len == 0) return @as(usize, 0);
1194 const first = iovecs[0];
1195 return windows.WriteFile(self.handle, first.iov_base[0..first.iov_len], null, self.intended_io_mode);
1196 }
1197
1198 if (self.intended_io_mode == .blocking) {
1199 return posix.writev(self.handle, iovecs);
1200 } else {
1201 return std.event.Loop.instance.?.writev(self.handle, iovecs, self.capable_io_mode != self.intended_io_mode);
1202 }
1203}
1204
1205/// The `iovecs` parameter is mutable because:
1206/// * This function needs to mutate the fields in order to handle partial
1207/// writes from the underlying OS layer.
1208/// * The OS layer expects pointer addresses to be inside the application's address space
1209/// even if the length is zero. Meanwhile, in Zig, slices may have undefined pointer
1210/// addresses when the length is zero. So this function modifies the iov_base fields
1211/// when the length is zero.
1212/// See https://github.com/ziglang/zig/issues/7699
1213/// See equivalent function: `std.net.Stream.writevAll`.
1214pub fn writevAll(self: File, iovecs: []posix.iovec_const) WriteError!void {
1215 if (iovecs.len == 0) return;
1216
1217 // We use the address of this local variable for all zero-length
1218 // vectors so that the OS does not complain that we are giving it
1219 // addresses outside the application's address space.
1220 var garbage: [1]u8 = undefined;
1221 for (iovecs) |*v| {
1222 if (v.iov_len == 0) v.iov_base = &garbage;
1223 }
1224
1225 var i: usize = 0;
1226 while (true) {
1227 var amt = try self.writev(iovecs[i..]);
1228 while (amt >= iovecs[i].iov_len) {
1229 amt -= iovecs[i].iov_len;
1230 i += 1;
1231 if (i >= iovecs.len) return;
1232 }
1233 iovecs[i].iov_base += amt;
1234 iovecs[i].iov_len -= amt;
1235 }
1236}
1237
1238/// See https://github.com/ziglang/zig/issues/7699
1239/// On Windows, this function currently does alter the file pointer.
1240/// https://github.com/ziglang/zig/issues/12783
1241pub fn pwritev(self: File, iovecs: []posix.iovec_const, offset: u64) PWriteError!usize {
1242 if (is_windows) {
1243 // TODO improve this to use WriteFileScatter
1244 if (iovecs.len == 0) return @as(usize, 0);
1245 const first = iovecs[0];
1246 return windows.WriteFile(self.handle, first.iov_base[0..first.iov_len], offset, self.intended_io_mode);
1247 }
1248
1249 if (self.intended_io_mode == .blocking) {
1250 return posix.pwritev(self.handle, iovecs, offset);
1251 } else {
1252 return std.event.Loop.instance.?.pwritev(self.handle, iovecs, offset, self.capable_io_mode != self.intended_io_mode);
1253 }
1254}
1255
1256/// The `iovecs` parameter is mutable because this function needs to mutate the fields in
1257/// order to handle partial writes from the underlying OS layer.
1258/// See https://github.com/ziglang/zig/issues/7699
1259/// On Windows, this function currently does alter the file pointer.
1260/// https://github.com/ziglang/zig/issues/12783
1261pub fn pwritevAll(self: File, iovecs: []posix.iovec_const, offset: u64) PWriteError!void {
1262 if (iovecs.len == 0) return;
1263
1264 var i: usize = 0;
1265 var off: u64 = 0;
1266 while (true) {
1267 var amt = try self.pwritev(iovecs[i..], offset + off);
1268 off += amt;
1269 while (amt >= iovecs[i].iov_len) {
1270 amt -= iovecs[i].iov_len;
1271 i += 1;
1272 if (i >= iovecs.len) return;
1273 }
1274 iovecs[i].iov_base += amt;
1275 iovecs[i].iov_len -= amt;
1276 }
1277}
1278
1279pub const CopyRangeError = posix.CopyFileRangeError;
1280
1281pub fn copyRange(in: File, in_offset: u64, out: File, out_offset: u64, len: u64) CopyRangeError!u64 {
1282 const adjusted_len = math.cast(usize, len) orelse maxInt(usize);
1283 const result = try posix.copy_file_range(in.handle, in_offset, out.handle, out_offset, adjusted_len, 0);
1284 return result;
1285}
1286
1287/// Returns the number of bytes copied. If the number read is smaller than `buffer.len`, it
1288/// means the in file reached the end. Reaching the end of a file is not an error condition.
1289pub fn copyRangeAll(in: File, in_offset: u64, out: File, out_offset: u64, len: u64) CopyRangeError!u64 {
1290 var total_bytes_copied: u64 = 0;
1291 var in_off = in_offset;
1292 var out_off = out_offset;
1293 while (total_bytes_copied < len) {
1294 const amt_copied = try copyRange(in, in_off, out, out_off, len - total_bytes_copied);
1295 if (amt_copied == 0) return total_bytes_copied;
1296 total_bytes_copied += amt_copied;
1297 in_off += amt_copied;
1298 out_off += amt_copied;
1299 }
1300 return total_bytes_copied;
1301}
1302
1303pub const WriteFileOptions = struct {
1304 in_offset: u64 = 0,
1305
1306 /// `null` means the entire file. `0` means no bytes from the file.
1307 /// When this is `null`, trailers must be sent in a separate writev() call
1308 /// due to a flaw in the BSD sendfile API. Other operating systems, such as
1309 /// Linux, already do this anyway due to API limitations.
1310 /// If the size of the source file is known, passing the size here will save one syscall.
1311 in_len: ?u64 = null,
1312
1313 headers_and_trailers: []posix.iovec_const = &[0]posix.iovec_const{},
1314
1315 /// The trailer count is inferred from `headers_and_trailers.len - header_count`
1316 header_count: usize = 0,
1317};
1318
1319pub const WriteFileError = ReadError || error{EndOfStream} || WriteError;
1320
1321pub fn writeFileAll(self: File, in_file: File, args: WriteFileOptions) WriteFileError!void {
1322 return self.writeFileAllSendfile(in_file, args) catch |err| switch (err) {
1323 error.Unseekable,
1324 error.FastOpenAlreadyInProgress,
1325 error.MessageTooBig,
1326 error.FileDescriptorNotASocket,
1327 error.NetworkUnreachable,
1328 error.NetworkSubsystemFailed,
1329 => return self.writeFileAllUnseekable(in_file, args),
1330
1331 else => |e| return e,
1332 };
1333}
1334
1335/// Does not try seeking in either of the File parameters.
1336/// See `writeFileAll` as an alternative to calling this.
1337pub fn writeFileAllUnseekable(self: File, in_file: File, args: WriteFileOptions) WriteFileError!void {
1338 const headers = args.headers_and_trailers[0..args.header_count];
1339 const trailers = args.headers_and_trailers[args.header_count..];
1340
1341 try self.writevAll(headers);
1342
1343 try in_file.reader().skipBytes(args.in_offset, .{ .buf_size = 4096 });
1344
1345 var fifo = std.fifo.LinearFifo(u8, .{ .Static = 4096 }).init();
1346 if (args.in_len) |len| {
1347 var stream = std.io.limitedReader(in_file.reader(), len);
1348 try fifo.pump(stream.reader(), self.writer());
1349 } else {
1350 try fifo.pump(in_file.reader(), self.writer());
1351 }
1352
1353 try self.writevAll(trailers);
1354}
1355
1356/// Low level function which can fail for OS-specific reasons.
1357/// See `writeFileAll` as an alternative to calling this.
1358/// TODO integrate with async I/O
1359fn writeFileAllSendfile(self: File, in_file: File, args: WriteFileOptions) posix.SendFileError!void {
1360 const count = blk: {
1361 if (args.in_len) |l| {
1362 if (l == 0) {
1363 return self.writevAll(args.headers_and_trailers);
1364 } else {
1365 break :blk l;
1366 }
1367 } else {
1368 break :blk 0;
1369 }
1370 };
1371 const headers = args.headers_and_trailers[0..args.header_count];
1372 const trailers = args.headers_and_trailers[args.header_count..];
1373 const zero_iovec = &[0]posix.iovec_const{};
1374 // When reading the whole file, we cannot put the trailers in the sendfile() syscall,
1375 // because we have no way to determine whether a partial write is past the end of the file or not.
1376 const trls = if (count == 0) zero_iovec else trailers;
1377 const offset = args.in_offset;
1378 const out_fd = self.handle;
1379 const in_fd = in_file.handle;
1380 const flags = 0;
1381 var amt: usize = 0;
1382 hdrs: {
1383 var i: usize = 0;
1384 while (i < headers.len) {
1385 amt = try posix.sendfile(out_fd, in_fd, offset, count, headers[i..], trls, flags);
1386 while (amt >= headers[i].iov_len) {
1387 amt -= headers[i].iov_len;
1388 i += 1;
1389 if (i >= headers.len) break :hdrs;
1390 }
1391 headers[i].iov_base += amt;
1392 headers[i].iov_len -= amt;
1393 }
1394 }
1395 if (count == 0) {
1396 var off: u64 = amt;
1397 while (true) {
1398 amt = try posix.sendfile(out_fd, in_fd, offset + off, 0, zero_iovec, zero_iovec, flags);
1399 if (amt == 0) break;
1400 off += amt;
1401 }
1402 } else {
1403 var off: u64 = amt;
1404 while (off < count) {
1405 amt = try posix.sendfile(out_fd, in_fd, offset + off, count - off, zero_iovec, trailers, flags);
1406 off += amt;
1407 }
1408 amt = @as(usize, @intCast(off - count));
1409 }
1410 var i: usize = 0;
1411 while (i < trailers.len) {
1412 while (amt >= trailers[i].iov_len) {
1413 amt -= trailers[i].iov_len;
1414 i += 1;
1415 if (i >= trailers.len) return;
1416 }
1417 trailers[i].iov_base += amt;
1418 trailers[i].iov_len -= amt;
1419 amt = try posix.writev(self.handle, trailers[i..]);
1420 }
1421}
1422
1423pub const Reader = io.Reader(File, ReadError, read);
1424
1425pub fn reader(file: File) Reader {
1426 return .{ .context = file };
1427}
1428
1429pub const Writer = io.Writer(File, WriteError, write);
1430
1431pub fn writer(file: File) Writer {
1432 return .{ .context = file };
1433}
1434
1435pub const SeekableStream = io.SeekableStream(
1436 File,
1437 SeekError,
1438 GetSeekPosError,
1439 seekTo,
1440 seekBy,
1441 getPos,
1442 getEndPos,
1443);
1444
1445pub fn seekableStream(file: File) SeekableStream {
1446 return .{ .context = file };
1447}
1448
1449const range_off: windows.LARGE_INTEGER = 0;
1450const range_len: windows.LARGE_INTEGER = 1;
1451
1452pub const LockError = error{
1453 SystemResources,
1454 FileLocksNotSupported,
1455} || posix.UnexpectedError;
1456
1457/// Blocks when an incompatible lock is held by another process.
1458/// A process may hold only one type of lock (shared or exclusive) on
1459/// a file. When a process terminates in any way, the lock is released.
1460///
1461/// Assumes the file is unlocked.
1462///
1463/// TODO: integrate with async I/O
1464pub fn lock(file: File, l: Lock) LockError!void {
1465 if (is_windows) {
1466 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
1467 const exclusive = switch (l) {
1468 .none => return,
1469 .shared => false,
1470 .exclusive => true,
1471 };
1472 return windows.LockFile(
1473 file.handle,
1474 null,
1475 null,
1476 null,
1477 &io_status_block,
1478 &range_off,
1479 &range_len,
1480 null,
1481 windows.FALSE, // non-blocking=false
1482 @intFromBool(exclusive),
1483 ) catch |err| switch (err) {
1484 error.WouldBlock => unreachable, // non-blocking=false
1485 else => |e| return e,
1486 };
1487 } else {
1488 return posix.flock(file.handle, switch (l) {
1489 .none => posix.LOCK.UN,
1490 .shared => posix.LOCK.SH,
1491 .exclusive => posix.LOCK.EX,
1492 }) catch |err| switch (err) {
1493 error.WouldBlock => unreachable, // non-blocking=false
1494 else => |e| return e,
1495 };
1496 }
1497}
1498
1499/// Assumes the file is locked.
1500pub fn unlock(file: File) void {
1501 if (is_windows) {
1502 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
1503 return windows.UnlockFile(
1504 file.handle,
1505 &io_status_block,
1506 &range_off,
1507 &range_len,
1508 null,
1509 ) catch |err| switch (err) {
1510 error.RangeNotLocked => unreachable, // Function assumes unlocked.
1511 error.Unexpected => unreachable, // Resource deallocation must succeed.
1512 };
1513 } else {
1514 return posix.flock(file.handle, posix.LOCK.UN) catch |err| switch (err) {
1515 error.WouldBlock => unreachable, // unlocking can't block
1516 error.SystemResources => unreachable, // We are deallocating resources.
1517 error.FileLocksNotSupported => unreachable, // We already got the lock.
1518 error.Unexpected => unreachable, // Resource deallocation must succeed.
1519 };
1520 }
1521}
1522
1523/// Attempts to obtain a lock, returning `true` if the lock is
1524/// obtained, and `false` if there was an existing incompatible lock held.
1525/// A process may hold only one type of lock (shared or exclusive) on
1526/// a file. When a process terminates in any way, the lock is released.
1527///
1528/// Assumes the file is unlocked.
1529///
1530/// TODO: integrate with async I/O
1531pub fn tryLock(file: File, l: Lock) LockError!bool {
1532 if (is_windows) {
1533 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
1534 const exclusive = switch (l) {
1535 .none => return,
1536 .shared => false,
1537 .exclusive => true,
1538 };
1539 windows.LockFile(
1540 file.handle,
1541 null,
1542 null,
1543 null,
1544 &io_status_block,
1545 &range_off,
1546 &range_len,
1547 null,
1548 windows.TRUE, // non-blocking=true
1549 @intFromBool(exclusive),
1550 ) catch |err| switch (err) {
1551 error.WouldBlock => return false,
1552 else => |e| return e,
1553 };
1554 } else {
1555 posix.flock(file.handle, switch (l) {
1556 .none => posix.LOCK.UN,
1557 .shared => posix.LOCK.SH | posix.LOCK.NB,
1558 .exclusive => posix.LOCK.EX | posix.LOCK.NB,
1559 }) catch |err| switch (err) {
1560 error.WouldBlock => return false,
1561 else => |e| return e,
1562 };
1563 }
1564 return true;
1565}
1566
1567/// Assumes the file is already locked in exclusive mode.
1568/// Atomically modifies the lock to be in shared mode, without releasing it.
1569///
1570/// TODO: integrate with async I/O
1571pub fn downgradeLock(file: File) LockError!void {
1572 if (is_windows) {
1573 // On Windows it works like a semaphore + exclusivity flag. To implement this
1574 // function, we first obtain another lock in shared mode. This changes the
1575 // exclusivity flag, but increments the semaphore to 2. So we follow up with
1576 // an NtUnlockFile which decrements the semaphore but does not modify the
1577 // exclusivity flag.
1578 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
1579 windows.LockFile(
1580 file.handle,
1581 null,
1582 null,
1583 null,
1584 &io_status_block,
1585 &range_off,
1586 &range_len,
1587 null,
1588 windows.TRUE, // non-blocking=true
1589 windows.FALSE, // exclusive=false
1590 ) catch |err| switch (err) {
1591 error.WouldBlock => unreachable, // File was not locked in exclusive mode.
1592 else => |e| return e,
1593 };
1594 return windows.UnlockFile(
1595 file.handle,
1596 &io_status_block,
1597 &range_off,
1598 &range_len,
1599 null,
1600 ) catch |err| switch (err) {
1601 error.RangeNotLocked => unreachable, // File was not locked.
1602 error.Unexpected => unreachable, // Resource deallocation must succeed.
1603 };
1604 } else {
1605 return posix.flock(file.handle, posix.LOCK.SH | posix.LOCK.NB) catch |err| switch (err) {
1606 error.WouldBlock => unreachable, // File was not locked in exclusive mode.
1607 else => |e| return e,
1608 };
1609 }
1610}
1611
1612const File = @This();
1613const std = @import("../std.zig");
1614const builtin = @import("builtin");
1615const Allocator = std.mem.Allocator;
1616// https://github.com/ziglang/zig/issues/5019
1617const posix = std.os;
1618const io = std.io;
1619const math = std.math;
1620const assert = std.debug.assert;
1621const windows = std.os.windows;
1622const Os = std.builtin.Os;
1623const maxInt = std.math.maxInt;
1624const is_windows = builtin.os.tag == .windows;
lib/std/fs/file.zig deleted-1622
...@@ -1,1622 +0,0 @@
1const std = @import("../std.zig");
2const builtin = @import("builtin");
3const os = std.os;
4const io = std.io;
5const mem = std.mem;
6const math = std.math;
7const assert = std.debug.assert;
8const windows = os.windows;
9const Os = std.builtin.Os;
10const maxInt = std.math.maxInt;
11const is_windows = builtin.os.tag == .windows;
12
13pub const File = struct {
14 /// The OS-specific file descriptor or file handle.
15 handle: Handle,
16
17 /// On some systems, such as Linux, file system file descriptors are incapable
18 /// of non-blocking I/O. This forces us to perform asynchronous I/O on a dedicated thread,
19 /// to achieve non-blocking file-system I/O. To do this, `File` must be aware of whether
20 /// it is a file system file descriptor, or, more specifically, whether the I/O is always
21 /// blocking.
22 capable_io_mode: io.ModeOverride = io.default_mode,
23
24 /// Furthermore, even when `std.options.io_mode` is async, it is still sometimes desirable
25 /// to perform blocking I/O, although not by default. For example, when printing a
26 /// stack trace to stderr. This field tracks both by acting as an overriding I/O mode.
27 /// When not building in async I/O mode, the type only has the `.blocking` tag, making
28 /// it a zero-bit type.
29 intended_io_mode: io.ModeOverride = io.default_mode,
30
31 pub const Handle = os.fd_t;
32 pub const Mode = os.mode_t;
33 pub const INode = os.ino_t;
34 pub const Uid = os.uid_t;
35 pub const Gid = os.gid_t;
36
37 pub const Kind = enum {
38 block_device,
39 character_device,
40 directory,
41 named_pipe,
42 sym_link,
43 file,
44 unix_domain_socket,
45 whiteout,
46 door,
47 event_port,
48 unknown,
49 };
50
51 /// This is the default mode given to POSIX operating systems for creating
52 /// files. `0o666` is "-rw-rw-rw-" which is counter-intuitive at first,
53 /// since most people would expect "-rw-r--r--", for example, when using
54 /// the `touch` command, which would correspond to `0o644`. However, POSIX
55 /// libc implementations use `0o666` inside `fopen` and then rely on the
56 /// process-scoped "umask" setting to adjust this number for file creation.
57 pub const default_mode = switch (builtin.os.tag) {
58 .windows => 0,
59 .wasi => 0,
60 else => 0o666,
61 };
62
63 pub const OpenError = error{
64 SharingViolation,
65 PathAlreadyExists,
66 FileNotFound,
67 AccessDenied,
68 PipeBusy,
69 NameTooLong,
70 /// On Windows, file paths must be valid Unicode.
71 InvalidUtf8,
72 /// On Windows, file paths cannot contain these characters:
73 /// '/', '*', '?', '"', '<', '>', '|'
74 BadPathName,
75 Unexpected,
76 /// On Windows, `\\server` or `\\server\share` was not found.
77 NetworkNotFound,
78 } || os.OpenError || os.FlockError;
79
80 pub const OpenMode = enum {
81 read_only,
82 write_only,
83 read_write,
84 };
85
86 pub const Lock = enum {
87 none,
88 shared,
89 exclusive,
90 };
91
92 pub const OpenFlags = struct {
93 mode: OpenMode = .read_only,
94
95 /// Open the file with an advisory lock to coordinate with other processes
96 /// accessing it at the same time. An exclusive lock will prevent other
97 /// processes from acquiring a lock. A shared lock will prevent other
98 /// processes from acquiring a exclusive lock, but does not prevent
99 /// other process from getting their own shared locks.
100 ///
101 /// The lock is advisory, except on Linux in very specific circumstances[1].
102 /// This means that a process that does not respect the locking API can still get access
103 /// to the file, despite the lock.
104 ///
105 /// On these operating systems, the lock is acquired atomically with
106 /// opening the file:
107 /// * Darwin
108 /// * DragonFlyBSD
109 /// * FreeBSD
110 /// * Haiku
111 /// * NetBSD
112 /// * OpenBSD
113 /// On these operating systems, the lock is acquired via a separate syscall
114 /// after opening the file:
115 /// * Linux
116 /// * Windows
117 ///
118 /// [1]: https://www.kernel.org/doc/Documentation/filesystems/mandatory-locking.txt
119 lock: Lock = .none,
120
121 /// Sets whether or not to wait until the file is locked to return. If set to true,
122 /// `error.WouldBlock` will be returned. Otherwise, the file will wait until the file
123 /// is available to proceed.
124 /// In async I/O mode, non-blocking at the OS level is
125 /// determined by `intended_io_mode`, and `true` means `error.WouldBlock` is returned,
126 /// and `false` means `error.WouldBlock` is handled by the event loop.
127 lock_nonblocking: bool = false,
128
129 /// Setting this to `.blocking` prevents `O.NONBLOCK` from being passed even
130 /// if `std.io.is_async`. It allows the use of `nosuspend` when calling functions
131 /// related to opening the file, reading, writing, and locking.
132 intended_io_mode: io.ModeOverride = io.default_mode,
133
134 /// Set this to allow the opened file to automatically become the
135 /// controlling TTY for the current process.
136 allow_ctty: bool = false,
137
138 pub fn isRead(self: OpenFlags) bool {
139 return self.mode != .write_only;
140 }
141
142 pub fn isWrite(self: OpenFlags) bool {
143 return self.mode != .read_only;
144 }
145 };
146
147 pub const CreateFlags = struct {
148 /// Whether the file will be created with read access.
149 read: bool = false,
150
151 /// If the file already exists, and is a regular file, and the access
152 /// mode allows writing, it will be truncated to length 0.
153 truncate: bool = true,
154
155 /// Ensures that this open call creates the file, otherwise causes
156 /// `error.PathAlreadyExists` to be returned.
157 exclusive: bool = false,
158
159 /// Open the file with an advisory lock to coordinate with other processes
160 /// accessing it at the same time. An exclusive lock will prevent other
161 /// processes from acquiring a lock. A shared lock will prevent other
162 /// processes from acquiring a exclusive lock, but does not prevent
163 /// other process from getting their own shared locks.
164 ///
165 /// The lock is advisory, except on Linux in very specific circumstances[1].
166 /// This means that a process that does not respect the locking API can still get access
167 /// to the file, despite the lock.
168 ///
169 /// On these operating systems, the lock is acquired atomically with
170 /// opening the file:
171 /// * Darwin
172 /// * DragonFlyBSD
173 /// * FreeBSD
174 /// * Haiku
175 /// * NetBSD
176 /// * OpenBSD
177 /// On these operating systems, the lock is acquired via a separate syscall
178 /// after opening the file:
179 /// * Linux
180 /// * Windows
181 ///
182 /// [1]: https://www.kernel.org/doc/Documentation/filesystems/mandatory-locking.txt
183 lock: Lock = .none,
184
185 /// Sets whether or not to wait until the file is locked to return. If set to true,
186 /// `error.WouldBlock` will be returned. Otherwise, the file will wait until the file
187 /// is available to proceed.
188 /// In async I/O mode, non-blocking at the OS level is
189 /// determined by `intended_io_mode`, and `true` means `error.WouldBlock` is returned,
190 /// and `false` means `error.WouldBlock` is handled by the event loop.
191 lock_nonblocking: bool = false,
192
193 /// For POSIX systems this is the file system mode the file will
194 /// be created with. On other systems this is always 0.
195 mode: Mode = default_mode,
196
197 /// Setting this to `.blocking` prevents `O.NONBLOCK` from being passed even
198 /// if `std.io.is_async`. It allows the use of `nosuspend` when calling functions
199 /// related to opening the file, reading, writing, and locking.
200 intended_io_mode: io.ModeOverride = io.default_mode,
201 };
202
203 /// Upon success, the stream is in an uninitialized state. To continue using it,
204 /// you must use the open() function.
205 pub fn close(self: File) void {
206 if (is_windows) {
207 windows.CloseHandle(self.handle);
208 } else if (self.capable_io_mode != self.intended_io_mode) {
209 std.event.Loop.instance.?.close(self.handle);
210 } else {
211 os.close(self.handle);
212 }
213 }
214
215 pub const SyncError = os.SyncError;
216
217 /// Blocks until all pending file contents and metadata modifications
218 /// for the file have been synchronized with the underlying filesystem.
219 ///
220 /// Note that this does not ensure that metadata for the
221 /// directory containing the file has also reached disk.
222 pub fn sync(self: File) SyncError!void {
223 return os.fsync(self.handle);
224 }
225
226 /// Test whether the file refers to a terminal.
227 /// See also `supportsAnsiEscapeCodes`.
228 pub fn isTty(self: File) bool {
229 return os.isatty(self.handle);
230 }
231
232 /// Test whether ANSI escape codes will be treated as such.
233 pub fn supportsAnsiEscapeCodes(self: File) bool {
234 if (builtin.os.tag == .windows) {
235 var console_mode: os.windows.DWORD = 0;
236 if (os.windows.kernel32.GetConsoleMode(self.handle, &console_mode) != 0) {
237 if (console_mode & os.windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0) return true;
238 }
239
240 return os.isCygwinPty(self.handle);
241 }
242 if (builtin.os.tag == .wasi) {
243 // WASI sanitizes stdout when fd is a tty so ANSI escape codes
244 // will not be interpreted as actual cursor commands, and
245 // stderr is always sanitized.
246 return false;
247 }
248 if (self.isTty()) {
249 if (self.handle == os.STDOUT_FILENO or self.handle == os.STDERR_FILENO) {
250 if (os.getenvZ("TERM")) |term| {
251 if (std.mem.eql(u8, term, "dumb"))
252 return false;
253 }
254 }
255 return true;
256 }
257 return false;
258 }
259
260 pub const SetEndPosError = os.TruncateError;
261
262 /// Shrinks or expands the file.
263 /// The file offset after this call is left unchanged.
264 pub fn setEndPos(self: File, length: u64) SetEndPosError!void {
265 try os.ftruncate(self.handle, length);
266 }
267
268 pub const SeekError = os.SeekError;
269
270 /// Repositions read/write file offset relative to the current offset.
271 /// TODO: integrate with async I/O
272 pub fn seekBy(self: File, offset: i64) SeekError!void {
273 return os.lseek_CUR(self.handle, offset);
274 }
275
276 /// Repositions read/write file offset relative to the end.
277 /// TODO: integrate with async I/O
278 pub fn seekFromEnd(self: File, offset: i64) SeekError!void {
279 return os.lseek_END(self.handle, offset);
280 }
281
282 /// Repositions read/write file offset relative to the beginning.
283 /// TODO: integrate with async I/O
284 pub fn seekTo(self: File, offset: u64) SeekError!void {
285 return os.lseek_SET(self.handle, offset);
286 }
287
288 pub const GetSeekPosError = os.SeekError || os.FStatError;
289
290 /// TODO: integrate with async I/O
291 pub fn getPos(self: File) GetSeekPosError!u64 {
292 return os.lseek_CUR_get(self.handle);
293 }
294
295 /// TODO: integrate with async I/O
296 pub fn getEndPos(self: File) GetSeekPosError!u64 {
297 if (builtin.os.tag == .windows) {
298 return windows.GetFileSizeEx(self.handle);
299 }
300 return (try self.stat()).size;
301 }
302
303 pub const ModeError = os.FStatError;
304
305 /// TODO: integrate with async I/O
306 pub fn mode(self: File) ModeError!Mode {
307 if (builtin.os.tag == .windows) {
308 return 0;
309 }
310 return (try self.stat()).mode;
311 }
312
313 pub const Stat = struct {
314 /// A number that the system uses to point to the file metadata. This
315 /// number is not guaranteed to be unique across time, as some file
316 /// systems may reuse an inode after its file has been deleted. Some
317 /// systems may change the inode of a file over time.
318 ///
319 /// On Linux, the inode is a structure that stores the metadata, and
320 /// the inode _number_ is what you see here: the index number of the
321 /// inode.
322 ///
323 /// The FileIndex on Windows is similar. It is a number for a file that
324 /// is unique to each filesystem.
325 inode: INode,
326 size: u64,
327 /// This is available on POSIX systems and is always 0 otherwise.
328 mode: Mode,
329 kind: Kind,
330
331 /// Access time in nanoseconds, relative to UTC 1970-01-01.
332 atime: i128,
333 /// Last modification time in nanoseconds, relative to UTC 1970-01-01.
334 mtime: i128,
335 /// Creation time in nanoseconds, relative to UTC 1970-01-01.
336 ctime: i128,
337
338 pub fn fromSystem(st: os.system.Stat) Stat {
339 const atime = st.atime();
340 const mtime = st.mtime();
341 const ctime = st.ctime();
342 const kind: Kind = if (builtin.os.tag == .wasi and !builtin.link_libc) switch (st.filetype) {
343 .BLOCK_DEVICE => .block_device,
344 .CHARACTER_DEVICE => .character_device,
345 .DIRECTORY => .directory,
346 .SYMBOLIC_LINK => .sym_link,
347 .REGULAR_FILE => .file,
348 .SOCKET_STREAM, .SOCKET_DGRAM => .unix_domain_socket,
349 else => .unknown,
350 } else blk: {
351 const m = st.mode & os.S.IFMT;
352 switch (m) {
353 os.S.IFBLK => break :blk .block_device,
354 os.S.IFCHR => break :blk .character_device,
355 os.S.IFDIR => break :blk .directory,
356 os.S.IFIFO => break :blk .named_pipe,
357 os.S.IFLNK => break :blk .sym_link,
358 os.S.IFREG => break :blk .file,
359 os.S.IFSOCK => break :blk .unix_domain_socket,
360 else => {},
361 }
362 if (builtin.os.tag.isSolarish()) switch (m) {
363 os.S.IFDOOR => break :blk .door,
364 os.S.IFPORT => break :blk .event_port,
365 else => {},
366 };
367
368 break :blk .unknown;
369 };
370
371 return Stat{
372 .inode = st.ino,
373 .size = @as(u64, @bitCast(st.size)),
374 .mode = st.mode,
375 .kind = kind,
376 .atime = @as(i128, atime.tv_sec) * std.time.ns_per_s + atime.tv_nsec,
377 .mtime = @as(i128, mtime.tv_sec) * std.time.ns_per_s + mtime.tv_nsec,
378 .ctime = @as(i128, ctime.tv_sec) * std.time.ns_per_s + ctime.tv_nsec,
379 };
380 }
381 };
382
383 pub const StatError = os.FStatError;
384
385 /// TODO: integrate with async I/O
386 pub fn stat(self: File) StatError!Stat {
387 if (builtin.os.tag == .windows) {
388 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
389 var info: windows.FILE_ALL_INFORMATION = undefined;
390 const rc = windows.ntdll.NtQueryInformationFile(self.handle, &io_status_block, &info, @sizeOf(windows.FILE_ALL_INFORMATION), .FileAllInformation);
391 switch (rc) {
392 .SUCCESS => {},
393 // Buffer overflow here indicates that there is more information available than was able to be stored in the buffer
394 // size provided. This is treated as success because the type of variable-length information that this would be relevant for
395 // (name, volume name, etc) we don't care about.
396 .BUFFER_OVERFLOW => {},
397 .INVALID_PARAMETER => unreachable,
398 .ACCESS_DENIED => return error.AccessDenied,
399 else => return windows.unexpectedStatus(rc),
400 }
401 return Stat{
402 .inode = info.InternalInformation.IndexNumber,
403 .size = @as(u64, @bitCast(info.StandardInformation.EndOfFile)),
404 .mode = 0,
405 .kind = if (info.StandardInformation.Directory == 0) .file else .directory,
406 .atime = windows.fromSysTime(info.BasicInformation.LastAccessTime),
407 .mtime = windows.fromSysTime(info.BasicInformation.LastWriteTime),
408 .ctime = windows.fromSysTime(info.BasicInformation.CreationTime),
409 };
410 }
411
412 const st = try os.fstat(self.handle);
413 return Stat.fromSystem(st);
414 }
415
416 pub const ChmodError = std.os.FChmodError;
417
418 /// Changes the mode of the file.
419 /// The process must have the correct privileges in order to do this
420 /// successfully, or must have the effective user ID matching the owner
421 /// of the file.
422 pub fn chmod(self: File, new_mode: Mode) ChmodError!void {
423 try os.fchmod(self.handle, new_mode);
424 }
425
426 pub const ChownError = std.os.FChownError;
427
428 /// Changes the owner and group of the file.
429 /// The process must have the correct privileges in order to do this
430 /// successfully. The group may be changed by the owner of the file to
431 /// any group of which the owner is a member. If the owner or group is
432 /// specified as `null`, the ID is not changed.
433 pub fn chown(self: File, owner: ?Uid, group: ?Gid) ChownError!void {
434 try os.fchown(self.handle, owner, group);
435 }
436
437 /// Cross-platform representation of permissions on a file.
438 /// The `readonly` and `setReadonly` are the only methods available across all platforms.
439 /// Platform-specific functionality is available through the `inner` field.
440 pub const Permissions = struct {
441 /// You may use the `inner` field to use platform-specific functionality
442 inner: switch (builtin.os.tag) {
443 .windows => PermissionsWindows,
444 else => PermissionsUnix,
445 },
446
447 const Self = @This();
448
449 /// Returns `true` if permissions represent an unwritable file.
450 /// On Unix, `true` is returned only if no class has write permissions.
451 pub fn readOnly(self: Self) bool {
452 return self.inner.readOnly();
453 }
454
455 /// Sets whether write permissions are provided.
456 /// On Unix, this affects *all* classes. If this is undesired, use `unixSet`.
457 /// This method *DOES NOT* set permissions on the filesystem: use `File.setPermissions(permissions)`
458 pub fn setReadOnly(self: *Self, read_only: bool) void {
459 self.inner.setReadOnly(read_only);
460 }
461 };
462
463 pub const PermissionsWindows = struct {
464 attributes: os.windows.DWORD,
465
466 const Self = @This();
467
468 /// Returns `true` if permissions represent an unwritable file.
469 pub fn readOnly(self: Self) bool {
470 return self.attributes & os.windows.FILE_ATTRIBUTE_READONLY != 0;
471 }
472
473 /// Sets whether write permissions are provided.
474 /// This method *DOES NOT* set permissions on the filesystem: use `File.setPermissions(permissions)`
475 pub fn setReadOnly(self: *Self, read_only: bool) void {
476 if (read_only) {
477 self.attributes |= os.windows.FILE_ATTRIBUTE_READONLY;
478 } else {
479 self.attributes &= ~@as(os.windows.DWORD, os.windows.FILE_ATTRIBUTE_READONLY);
480 }
481 }
482 };
483
484 pub const PermissionsUnix = struct {
485 mode: Mode,
486
487 const Self = @This();
488
489 /// Returns `true` if permissions represent an unwritable file.
490 /// `true` is returned only if no class has write permissions.
491 pub fn readOnly(self: Self) bool {
492 return self.mode & 0o222 == 0;
493 }
494
495 /// Sets whether write permissions are provided.
496 /// This affects *all* classes. If this is undesired, use `unixSet`.
497 /// This method *DOES NOT* set permissions on the filesystem: use `File.setPermissions(permissions)`
498 pub fn setReadOnly(self: *Self, read_only: bool) void {
499 if (read_only) {
500 self.mode &= ~@as(Mode, 0o222);
501 } else {
502 self.mode |= @as(Mode, 0o222);
503 }
504 }
505
506 pub const Class = enum(u2) {
507 user = 2,
508 group = 1,
509 other = 0,
510 };
511
512 pub const Permission = enum(u3) {
513 read = 0o4,
514 write = 0o2,
515 execute = 0o1,
516 };
517
518 /// Returns `true` if the chosen class has the selected permission.
519 /// This method is only available on Unix platforms.
520 pub fn unixHas(self: Self, class: Class, permission: Permission) bool {
521 const mask = @as(Mode, @intFromEnum(permission)) << @as(u3, @intFromEnum(class)) * 3;
522 return self.mode & mask != 0;
523 }
524
525 /// Sets the permissions for the chosen class. Any permissions set to `null` are left unchanged.
526 /// This method *DOES NOT* set permissions on the filesystem: use `File.setPermissions(permissions)`
527 pub fn unixSet(self: *Self, class: Class, permissions: struct {
528 read: ?bool = null,
529 write: ?bool = null,
530 execute: ?bool = null,
531 }) void {
532 const shift = @as(u3, @intFromEnum(class)) * 3;
533 if (permissions.read) |r| {
534 if (r) {
535 self.mode |= @as(Mode, 0o4) << shift;
536 } else {
537 self.mode &= ~(@as(Mode, 0o4) << shift);
538 }
539 }
540 if (permissions.write) |w| {
541 if (w) {
542 self.mode |= @as(Mode, 0o2) << shift;
543 } else {
544 self.mode &= ~(@as(Mode, 0o2) << shift);
545 }
546 }
547 if (permissions.execute) |x| {
548 if (x) {
549 self.mode |= @as(Mode, 0o1) << shift;
550 } else {
551 self.mode &= ~(@as(Mode, 0o1) << shift);
552 }
553 }
554 }
555
556 /// Returns a `Permissions` struct representing the permissions from the passed mode.
557 pub fn unixNew(new_mode: Mode) Self {
558 return Self{
559 .mode = new_mode,
560 };
561 }
562 };
563
564 pub const SetPermissionsError = ChmodError;
565
566 /// Sets permissions according to the provided `Permissions` struct.
567 /// This method is *NOT* available on WASI
568 pub fn setPermissions(self: File, permissions: Permissions) SetPermissionsError!void {
569 switch (builtin.os.tag) {
570 .windows => {
571 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
572 var info = windows.FILE_BASIC_INFORMATION{
573 .CreationTime = 0,
574 .LastAccessTime = 0,
575 .LastWriteTime = 0,
576 .ChangeTime = 0,
577 .FileAttributes = permissions.inner.attributes,
578 };
579 const rc = windows.ntdll.NtSetInformationFile(
580 self.handle,
581 &io_status_block,
582 &info,
583 @sizeOf(windows.FILE_BASIC_INFORMATION),
584 .FileBasicInformation,
585 );
586 switch (rc) {
587 .SUCCESS => return,
588 .INVALID_HANDLE => unreachable,
589 .ACCESS_DENIED => return error.AccessDenied,
590 else => return windows.unexpectedStatus(rc),
591 }
592 },
593 .wasi => @compileError("Unsupported OS"), // Wasi filesystem does not *yet* support chmod
594 else => {
595 try self.chmod(permissions.inner.mode);
596 },
597 }
598 }
599
600 /// Cross-platform representation of file metadata.
601 /// Platform-specific functionality is available through the `inner` field.
602 pub const Metadata = struct {
603 /// You may use the `inner` field to use platform-specific functionality
604 inner: switch (builtin.os.tag) {
605 .windows => MetadataWindows,
606 .linux => MetadataLinux,
607 else => MetadataUnix,
608 },
609
610 const Self = @This();
611
612 /// Returns the size of the file
613 pub fn size(self: Self) u64 {
614 return self.inner.size();
615 }
616
617 /// Returns a `Permissions` struct, representing the permissions on the file
618 pub fn permissions(self: Self) Permissions {
619 return self.inner.permissions();
620 }
621
622 /// Returns the `Kind` of file.
623 /// On Windows, can only return: `.file`, `.directory`, `.sym_link` or `.unknown`
624 pub fn kind(self: Self) Kind {
625 return self.inner.kind();
626 }
627
628 /// Returns the last time the file was accessed in nanoseconds since UTC 1970-01-01
629 pub fn accessed(self: Self) i128 {
630 return self.inner.accessed();
631 }
632
633 /// Returns the time the file was modified in nanoseconds since UTC 1970-01-01
634 pub fn modified(self: Self) i128 {
635 return self.inner.modified();
636 }
637
638 /// Returns the time the file was created in nanoseconds since UTC 1970-01-01
639 /// On Windows, this cannot return null
640 /// On Linux, this returns null if the filesystem does not support creation times, or if the kernel is older than 4.11
641 /// On Unices, this returns null if the filesystem or OS does not support creation times
642 /// On MacOS, this returns the ctime if the filesystem does not support creation times; this is insanity, and yet another reason to hate on Apple
643 pub fn created(self: Self) ?i128 {
644 return self.inner.created();
645 }
646 };
647
648 pub const MetadataUnix = struct {
649 stat: os.Stat,
650
651 const Self = @This();
652
653 /// Returns the size of the file
654 pub fn size(self: Self) u64 {
655 return @as(u64, @intCast(self.stat.size));
656 }
657
658 /// Returns a `Permissions` struct, representing the permissions on the file
659 pub fn permissions(self: Self) Permissions {
660 return Permissions{ .inner = PermissionsUnix{ .mode = self.stat.mode } };
661 }
662
663 /// Returns the `Kind` of the file
664 pub fn kind(self: Self) Kind {
665 if (builtin.os.tag == .wasi and !builtin.link_libc) return switch (self.stat.filetype) {
666 .BLOCK_DEVICE => .block_device,
667 .CHARACTER_DEVICE => .character_device,
668 .DIRECTORY => .directory,
669 .SYMBOLIC_LINK => .sym_link,
670 .REGULAR_FILE => .file,
671 .SOCKET_STREAM, .SOCKET_DGRAM => .unix_domain_socket,
672 else => .unknown,
673 };
674
675 const m = self.stat.mode & os.S.IFMT;
676
677 switch (m) {
678 os.S.IFBLK => return .block_device,
679 os.S.IFCHR => return .character_device,
680 os.S.IFDIR => return .directory,
681 os.S.IFIFO => return .named_pipe,
682 os.S.IFLNK => return .sym_link,
683 os.S.IFREG => return .file,
684 os.S.IFSOCK => return .unix_domain_socket,
685 else => {},
686 }
687
688 if (builtin.os.tag.isSolarish()) switch (m) {
689 os.S.IFDOOR => return .door,
690 os.S.IFPORT => return .event_port,
691 else => {},
692 };
693
694 return .unknown;
695 }
696
697 /// Returns the last time the file was accessed in nanoseconds since UTC 1970-01-01
698 pub fn accessed(self: Self) i128 {
699 const atime = self.stat.atime();
700 return @as(i128, atime.tv_sec) * std.time.ns_per_s + atime.tv_nsec;
701 }
702
703 /// Returns the last time the file was modified in nanoseconds since UTC 1970-01-01
704 pub fn modified(self: Self) i128 {
705 const mtime = self.stat.mtime();
706 return @as(i128, mtime.tv_sec) * std.time.ns_per_s + mtime.tv_nsec;
707 }
708
709 /// Returns the time the file was created in nanoseconds since UTC 1970-01-01.
710 /// Returns null if this is not supported by the OS or filesystem
711 pub fn created(self: Self) ?i128 {
712 if (!@hasDecl(@TypeOf(self.stat), "birthtime")) return null;
713 const birthtime = self.stat.birthtime();
714
715 // If the filesystem doesn't support this the value *should* be:
716 // On FreeBSD: tv_nsec = 0, tv_sec = -1
717 // On NetBSD and OpenBSD: tv_nsec = 0, tv_sec = 0
718 // On MacOS, it is set to ctime -- we cannot detect this!!
719 switch (builtin.os.tag) {
720 .freebsd => if (birthtime.tv_sec == -1 and birthtime.tv_nsec == 0) return null,
721 .netbsd, .openbsd => if (birthtime.tv_sec == 0 and birthtime.tv_nsec == 0) return null,
722 .macos => {},
723 else => @compileError("Creation time detection not implemented for OS"),
724 }
725
726 return @as(i128, birthtime.tv_sec) * std.time.ns_per_s + birthtime.tv_nsec;
727 }
728 };
729
730 /// `MetadataUnix`, but using Linux's `statx` syscall.
731 /// On Linux versions below 4.11, `statx` will be filled with data from stat.
732 pub const MetadataLinux = struct {
733 statx: os.linux.Statx,
734
735 const Self = @This();
736
737 /// Returns the size of the file
738 pub fn size(self: Self) u64 {
739 return self.statx.size;
740 }
741
742 /// Returns a `Permissions` struct, representing the permissions on the file
743 pub fn permissions(self: Self) Permissions {
744 return Permissions{ .inner = PermissionsUnix{ .mode = self.statx.mode } };
745 }
746
747 /// Returns the `Kind` of the file
748 pub fn kind(self: Self) Kind {
749 const m = self.statx.mode & os.S.IFMT;
750
751 switch (m) {
752 os.S.IFBLK => return .block_device,
753 os.S.IFCHR => return .character_device,
754 os.S.IFDIR => return .directory,
755 os.S.IFIFO => return .named_pipe,
756 os.S.IFLNK => return .sym_link,
757 os.S.IFREG => return .file,
758 os.S.IFSOCK => return .unix_domain_socket,
759 else => {},
760 }
761
762 return .unknown;
763 }
764
765 /// Returns the last time the file was accessed in nanoseconds since UTC 1970-01-01
766 pub fn accessed(self: Self) i128 {
767 return @as(i128, self.statx.atime.tv_sec) * std.time.ns_per_s + self.statx.atime.tv_nsec;
768 }
769
770 /// Returns the last time the file was modified in nanoseconds since UTC 1970-01-01
771 pub fn modified(self: Self) i128 {
772 return @as(i128, self.statx.mtime.tv_sec) * std.time.ns_per_s + self.statx.mtime.tv_nsec;
773 }
774
775 /// Returns the time the file was created in nanoseconds since UTC 1970-01-01.
776 /// Returns null if this is not supported by the filesystem, or on kernels before than version 4.11
777 pub fn created(self: Self) ?i128 {
778 if (self.statx.mask & os.linux.STATX_BTIME == 0) return null;
779 return @as(i128, self.statx.btime.tv_sec) * std.time.ns_per_s + self.statx.btime.tv_nsec;
780 }
781 };
782
783 pub const MetadataWindows = struct {
784 attributes: windows.DWORD,
785 reparse_tag: windows.DWORD,
786 _size: u64,
787 access_time: i128,
788 modified_time: i128,
789 creation_time: i128,
790
791 const Self = @This();
792
793 /// Returns the size of the file
794 pub fn size(self: Self) u64 {
795 return self._size;
796 }
797
798 /// Returns a `Permissions` struct, representing the permissions on the file
799 pub fn permissions(self: Self) Permissions {
800 return Permissions{ .inner = PermissionsWindows{ .attributes = self.attributes } };
801 }
802
803 /// Returns the `Kind` of the file.
804 /// Can only return: `.file`, `.directory`, `.sym_link` or `.unknown`
805 pub fn kind(self: Self) Kind {
806 if (self.attributes & windows.FILE_ATTRIBUTE_REPARSE_POINT != 0) {
807 if (self.reparse_tag & 0x20000000 != 0) {
808 return .sym_link;
809 }
810 } else if (self.attributes & windows.FILE_ATTRIBUTE_DIRECTORY != 0) {
811 return .directory;
812 } else {
813 return .file;
814 }
815 return .unknown;
816 }
817
818 /// Returns the last time the file was accessed in nanoseconds since UTC 1970-01-01
819 pub fn accessed(self: Self) i128 {
820 return self.access_time;
821 }
822
823 /// Returns the time the file was modified in nanoseconds since UTC 1970-01-01
824 pub fn modified(self: Self) i128 {
825 return self.modified_time;
826 }
827
828 /// Returns the time the file was created in nanoseconds since UTC 1970-01-01.
829 /// This never returns null, only returning an optional for compatibility with other OSes
830 pub fn created(self: Self) ?i128 {
831 return self.creation_time;
832 }
833 };
834
835 pub const MetadataError = os.FStatError;
836
837 pub fn metadata(self: File) MetadataError!Metadata {
838 return Metadata{
839 .inner = switch (builtin.os.tag) {
840 .windows => blk: {
841 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
842 var info: windows.FILE_ALL_INFORMATION = undefined;
843
844 const rc = windows.ntdll.NtQueryInformationFile(self.handle, &io_status_block, &info, @sizeOf(windows.FILE_ALL_INFORMATION), .FileAllInformation);
845 switch (rc) {
846 .SUCCESS => {},
847 // Buffer overflow here indicates that there is more information available than was able to be stored in the buffer
848 // size provided. This is treated as success because the type of variable-length information that this would be relevant for
849 // (name, volume name, etc) we don't care about.
850 .BUFFER_OVERFLOW => {},
851 .INVALID_PARAMETER => unreachable,
852 .ACCESS_DENIED => return error.AccessDenied,
853 else => return windows.unexpectedStatus(rc),
854 }
855
856 const reparse_tag: windows.DWORD = reparse_blk: {
857 if (info.BasicInformation.FileAttributes & windows.FILE_ATTRIBUTE_REPARSE_POINT != 0) {
858 var reparse_buf: [windows.MAXIMUM_REPARSE_DATA_BUFFER_SIZE]u8 = undefined;
859 try windows.DeviceIoControl(self.handle, windows.FSCTL_GET_REPARSE_POINT, null, reparse_buf[0..]);
860 const reparse_struct: *const windows.REPARSE_DATA_BUFFER = @ptrCast(@alignCast(&reparse_buf[0]));
861 break :reparse_blk reparse_struct.ReparseTag;
862 }
863 break :reparse_blk 0;
864 };
865
866 break :blk MetadataWindows{
867 .attributes = info.BasicInformation.FileAttributes,
868 .reparse_tag = reparse_tag,
869 ._size = @as(u64, @bitCast(info.StandardInformation.EndOfFile)),
870 .access_time = windows.fromSysTime(info.BasicInformation.LastAccessTime),
871 .modified_time = windows.fromSysTime(info.BasicInformation.LastWriteTime),
872 .creation_time = windows.fromSysTime(info.BasicInformation.CreationTime),
873 };
874 },
875 .linux => blk: {
876 var stx = mem.zeroes(os.linux.Statx);
877 const rcx = os.linux.statx(self.handle, "\x00", os.linux.AT.EMPTY_PATH, os.linux.STATX_TYPE | os.linux.STATX_MODE | os.linux.STATX_ATIME | os.linux.STATX_MTIME | os.linux.STATX_BTIME, &stx);
878
879 switch (os.errno(rcx)) {
880 .SUCCESS => {},
881 // NOSYS happens when `statx` is unsupported, which is the case on kernel versions before 4.11
882 // Here, we call `fstat` and fill `stx` with the data we need
883 .NOSYS => {
884 const st = try os.fstat(self.handle);
885
886 stx.mode = @as(u16, @intCast(st.mode));
887
888 // Hacky conversion from timespec to statx_timestamp
889 stx.atime = std.mem.zeroes(os.linux.statx_timestamp);
890 stx.atime.tv_sec = st.atim.tv_sec;
891 stx.atime.tv_nsec = @as(u32, @intCast(st.atim.tv_nsec)); // Guaranteed to succeed (tv_nsec is always below 10^9)
892
893 stx.mtime = std.mem.zeroes(os.linux.statx_timestamp);
894 stx.mtime.tv_sec = st.mtim.tv_sec;
895 stx.mtime.tv_nsec = @as(u32, @intCast(st.mtim.tv_nsec));
896
897 stx.mask = os.linux.STATX_BASIC_STATS | os.linux.STATX_MTIME;
898 },
899 .BADF => unreachable,
900 .FAULT => unreachable,
901 .NOMEM => return error.SystemResources,
902 else => |err| return os.unexpectedErrno(err),
903 }
904
905 break :blk MetadataLinux{
906 .statx = stx,
907 };
908 },
909 else => blk: {
910 const st = try os.fstat(self.handle);
911 break :blk MetadataUnix{
912 .stat = st,
913 };
914 },
915 },
916 };
917 }
918
919 pub const UpdateTimesError = os.FutimensError || windows.SetFileTimeError;
920
921 /// The underlying file system may have a different granularity than nanoseconds,
922 /// and therefore this function cannot guarantee any precision will be stored.
923 /// Further, the maximum value is limited by the system ABI. When a value is provided
924 /// that exceeds this range, the value is clamped to the maximum.
925 /// TODO: integrate with async I/O
926 pub fn updateTimes(
927 self: File,
928 /// access timestamp in nanoseconds
929 atime: i128,
930 /// last modification timestamp in nanoseconds
931 mtime: i128,
932 ) UpdateTimesError!void {
933 if (builtin.os.tag == .windows) {
934 const atime_ft = windows.nanoSecondsToFileTime(atime);
935 const mtime_ft = windows.nanoSecondsToFileTime(mtime);
936 return windows.SetFileTime(self.handle, null, &atime_ft, &mtime_ft);
937 }
938 const times = [2]os.timespec{
939 os.timespec{
940 .tv_sec = math.cast(isize, @divFloor(atime, std.time.ns_per_s)) orelse maxInt(isize),
941 .tv_nsec = math.cast(isize, @mod(atime, std.time.ns_per_s)) orelse maxInt(isize),
942 },
943 os.timespec{
944 .tv_sec = math.cast(isize, @divFloor(mtime, std.time.ns_per_s)) orelse maxInt(isize),
945 .tv_nsec = math.cast(isize, @mod(mtime, std.time.ns_per_s)) orelse maxInt(isize),
946 },
947 };
948 try os.futimens(self.handle, &times);
949 }
950
951 /// Reads all the bytes from the current position to the end of the file.
952 /// On success, caller owns returned buffer.
953 /// If the file is larger than `max_bytes`, returns `error.FileTooBig`.
954 pub fn readToEndAlloc(self: File, allocator: mem.Allocator, max_bytes: usize) ![]u8 {
955 return self.readToEndAllocOptions(allocator, max_bytes, null, @alignOf(u8), null);
956 }
957
958 /// Reads all the bytes from the current position to the end of the file.
959 /// On success, caller owns returned buffer.
960 /// If the file is larger than `max_bytes`, returns `error.FileTooBig`.
961 /// If `size_hint` is specified the initial buffer size is calculated using
962 /// that value, otherwise an arbitrary value is used instead.
963 /// Allows specifying alignment and a sentinel value.
964 pub fn readToEndAllocOptions(
965 self: File,
966 allocator: mem.Allocator,
967 max_bytes: usize,
968 size_hint: ?usize,
969 comptime alignment: u29,
970 comptime optional_sentinel: ?u8,
971 ) !(if (optional_sentinel) |s| [:s]align(alignment) u8 else []align(alignment) u8) {
972 // If no size hint is provided fall back to the size=0 code path
973 const size = size_hint orelse 0;
974
975 // The file size returned by stat is used as hint to set the buffer
976 // size. If the reported size is zero, as it happens on Linux for files
977 // in /proc, a small buffer is allocated instead.
978 const initial_cap = (if (size > 0) size else 1024) + @intFromBool(optional_sentinel != null);
979 var array_list = try std.ArrayListAligned(u8, alignment).initCapacity(allocator, initial_cap);
980 defer array_list.deinit();
981
982 self.reader().readAllArrayListAligned(alignment, &array_list, max_bytes) catch |err| switch (err) {
983 error.StreamTooLong => return error.FileTooBig,
984 else => |e| return e,
985 };
986
987 if (optional_sentinel) |sentinel| {
988 return try array_list.toOwnedSliceSentinel(sentinel);
989 } else {
990 return try array_list.toOwnedSlice();
991 }
992 }
993
994 pub const ReadError = os.ReadError;
995 pub const PReadError = os.PReadError;
996
997 pub fn read(self: File, buffer: []u8) ReadError!usize {
998 if (is_windows) {
999 return windows.ReadFile(self.handle, buffer, null, self.intended_io_mode);
1000 }
1001
1002 if (self.intended_io_mode == .blocking) {
1003 return os.read(self.handle, buffer);
1004 } else {
1005 return std.event.Loop.instance.?.read(self.handle, buffer, self.capable_io_mode != self.intended_io_mode);
1006 }
1007 }
1008
1009 /// Returns the number of bytes read. If the number read is smaller than `buffer.len`, it
1010 /// means the file reached the end. Reaching the end of a file is not an error condition.
1011 pub fn readAll(self: File, buffer: []u8) ReadError!usize {
1012 var index: usize = 0;
1013 while (index != buffer.len) {
1014 const amt = try self.read(buffer[index..]);
1015 if (amt == 0) break;
1016 index += amt;
1017 }
1018 return index;
1019 }
1020
1021 /// On Windows, this function currently does alter the file pointer.
1022 /// https://github.com/ziglang/zig/issues/12783
1023 pub fn pread(self: File, buffer: []u8, offset: u64) PReadError!usize {
1024 if (is_windows) {
1025 return windows.ReadFile(self.handle, buffer, offset, self.intended_io_mode);
1026 }
1027
1028 if (self.intended_io_mode == .blocking) {
1029 return os.pread(self.handle, buffer, offset);
1030 } else {
1031 return std.event.Loop.instance.?.pread(self.handle, buffer, offset, self.capable_io_mode != self.intended_io_mode);
1032 }
1033 }
1034
1035 /// Returns the number of bytes read. If the number read is smaller than `buffer.len`, it
1036 /// means the file reached the end. Reaching the end of a file is not an error condition.
1037 /// On Windows, this function currently does alter the file pointer.
1038 /// https://github.com/ziglang/zig/issues/12783
1039 pub fn preadAll(self: File, buffer: []u8, offset: u64) PReadError!usize {
1040 var index: usize = 0;
1041 while (index != buffer.len) {
1042 const amt = try self.pread(buffer[index..], offset + index);
1043 if (amt == 0) break;
1044 index += amt;
1045 }
1046 return index;
1047 }
1048
1049 /// See https://github.com/ziglang/zig/issues/7699
1050 pub fn readv(self: File, iovecs: []const os.iovec) ReadError!usize {
1051 if (is_windows) {
1052 // TODO improve this to use ReadFileScatter
1053 if (iovecs.len == 0) return @as(usize, 0);
1054 const first = iovecs[0];
1055 return windows.ReadFile(self.handle, first.iov_base[0..first.iov_len], null, self.intended_io_mode);
1056 }
1057
1058 if (self.intended_io_mode == .blocking) {
1059 return os.readv(self.handle, iovecs);
1060 } else {
1061 return std.event.Loop.instance.?.readv(self.handle, iovecs, self.capable_io_mode != self.intended_io_mode);
1062 }
1063 }
1064
1065 /// Returns the number of bytes read. If the number read is smaller than the total bytes
1066 /// from all the buffers, it means the file reached the end. Reaching the end of a file
1067 /// is not an error condition.
1068 ///
1069 /// The `iovecs` parameter is mutable because:
1070 /// * This function needs to mutate the fields in order to handle partial
1071 /// reads from the underlying OS layer.
1072 /// * The OS layer expects pointer addresses to be inside the application's address space
1073 /// even if the length is zero. Meanwhile, in Zig, slices may have undefined pointer
1074 /// addresses when the length is zero. So this function modifies the iov_base fields
1075 /// when the length is zero.
1076 ///
1077 /// Related open issue: https://github.com/ziglang/zig/issues/7699
1078 pub fn readvAll(self: File, iovecs: []os.iovec) ReadError!usize {
1079 if (iovecs.len == 0) return 0;
1080
1081 // We use the address of this local variable for all zero-length
1082 // vectors so that the OS does not complain that we are giving it
1083 // addresses outside the application's address space.
1084 var garbage: [1]u8 = undefined;
1085 for (iovecs) |*v| {
1086 if (v.iov_len == 0) v.iov_base = &garbage;
1087 }
1088
1089 var i: usize = 0;
1090 var off: usize = 0;
1091 while (true) {
1092 var amt = try self.readv(iovecs[i..]);
1093 var eof = amt == 0;
1094 off += amt;
1095 while (amt >= iovecs[i].iov_len) {
1096 amt -= iovecs[i].iov_len;
1097 i += 1;
1098 if (i >= iovecs.len) return off;
1099 eof = false;
1100 }
1101 if (eof) return off;
1102 iovecs[i].iov_base += amt;
1103 iovecs[i].iov_len -= amt;
1104 }
1105 }
1106
1107 /// See https://github.com/ziglang/zig/issues/7699
1108 /// On Windows, this function currently does alter the file pointer.
1109 /// https://github.com/ziglang/zig/issues/12783
1110 pub fn preadv(self: File, iovecs: []const os.iovec, offset: u64) PReadError!usize {
1111 if (is_windows) {
1112 // TODO improve this to use ReadFileScatter
1113 if (iovecs.len == 0) return @as(usize, 0);
1114 const first = iovecs[0];
1115 return windows.ReadFile(self.handle, first.iov_base[0..first.iov_len], offset, self.intended_io_mode);
1116 }
1117
1118 if (self.intended_io_mode == .blocking) {
1119 return os.preadv(self.handle, iovecs, offset);
1120 } else {
1121 return std.event.Loop.instance.?.preadv(self.handle, iovecs, offset, self.capable_io_mode != self.intended_io_mode);
1122 }
1123 }
1124
1125 /// Returns the number of bytes read. If the number read is smaller than the total bytes
1126 /// from all the buffers, it means the file reached the end. Reaching the end of a file
1127 /// is not an error condition.
1128 /// The `iovecs` parameter is mutable because this function needs to mutate the fields in
1129 /// order to handle partial reads from the underlying OS layer.
1130 /// See https://github.com/ziglang/zig/issues/7699
1131 /// On Windows, this function currently does alter the file pointer.
1132 /// https://github.com/ziglang/zig/issues/12783
1133 pub fn preadvAll(self: File, iovecs: []os.iovec, offset: u64) PReadError!usize {
1134 if (iovecs.len == 0) return 0;
1135
1136 var i: usize = 0;
1137 var off: usize = 0;
1138 while (true) {
1139 var amt = try self.preadv(iovecs[i..], offset + off);
1140 var eof = amt == 0;
1141 off += amt;
1142 while (amt >= iovecs[i].iov_len) {
1143 amt -= iovecs[i].iov_len;
1144 i += 1;
1145 if (i >= iovecs.len) return off;
1146 eof = false;
1147 }
1148 if (eof) return off;
1149 iovecs[i].iov_base += amt;
1150 iovecs[i].iov_len -= amt;
1151 }
1152 }
1153
1154 pub const WriteError = os.WriteError;
1155 pub const PWriteError = os.PWriteError;
1156
1157 pub fn write(self: File, bytes: []const u8) WriteError!usize {
1158 if (is_windows) {
1159 return windows.WriteFile(self.handle, bytes, null, self.intended_io_mode);
1160 }
1161
1162 if (self.intended_io_mode == .blocking) {
1163 return os.write(self.handle, bytes);
1164 } else {
1165 return std.event.Loop.instance.?.write(self.handle, bytes, self.capable_io_mode != self.intended_io_mode);
1166 }
1167 }
1168
1169 pub fn writeAll(self: File, bytes: []const u8) WriteError!void {
1170 var index: usize = 0;
1171 while (index < bytes.len) {
1172 index += try self.write(bytes[index..]);
1173 }
1174 }
1175
1176 /// On Windows, this function currently does alter the file pointer.
1177 /// https://github.com/ziglang/zig/issues/12783
1178 pub fn pwrite(self: File, bytes: []const u8, offset: u64) PWriteError!usize {
1179 if (is_windows) {
1180 return windows.WriteFile(self.handle, bytes, offset, self.intended_io_mode);
1181 }
1182
1183 if (self.intended_io_mode == .blocking) {
1184 return os.pwrite(self.handle, bytes, offset);
1185 } else {
1186 return std.event.Loop.instance.?.pwrite(self.handle, bytes, offset, self.capable_io_mode != self.intended_io_mode);
1187 }
1188 }
1189
1190 /// On Windows, this function currently does alter the file pointer.
1191 /// https://github.com/ziglang/zig/issues/12783
1192 pub fn pwriteAll(self: File, bytes: []const u8, offset: u64) PWriteError!void {
1193 var index: usize = 0;
1194 while (index < bytes.len) {
1195 index += try self.pwrite(bytes[index..], offset + index);
1196 }
1197 }
1198
1199 /// See https://github.com/ziglang/zig/issues/7699
1200 /// See equivalent function: `std.net.Stream.writev`.
1201 pub fn writev(self: File, iovecs: []const os.iovec_const) WriteError!usize {
1202 if (is_windows) {
1203 // TODO improve this to use WriteFileScatter
1204 if (iovecs.len == 0) return @as(usize, 0);
1205 const first = iovecs[0];
1206 return windows.WriteFile(self.handle, first.iov_base[0..first.iov_len], null, self.intended_io_mode);
1207 }
1208
1209 if (self.intended_io_mode == .blocking) {
1210 return os.writev(self.handle, iovecs);
1211 } else {
1212 return std.event.Loop.instance.?.writev(self.handle, iovecs, self.capable_io_mode != self.intended_io_mode);
1213 }
1214 }
1215
1216 /// The `iovecs` parameter is mutable because:
1217 /// * This function needs to mutate the fields in order to handle partial
1218 /// writes from the underlying OS layer.
1219 /// * The OS layer expects pointer addresses to be inside the application's address space
1220 /// even if the length is zero. Meanwhile, in Zig, slices may have undefined pointer
1221 /// addresses when the length is zero. So this function modifies the iov_base fields
1222 /// when the length is zero.
1223 /// See https://github.com/ziglang/zig/issues/7699
1224 /// See equivalent function: `std.net.Stream.writevAll`.
1225 pub fn writevAll(self: File, iovecs: []os.iovec_const) WriteError!void {
1226 if (iovecs.len == 0) return;
1227
1228 // We use the address of this local variable for all zero-length
1229 // vectors so that the OS does not complain that we are giving it
1230 // addresses outside the application's address space.
1231 var garbage: [1]u8 = undefined;
1232 for (iovecs) |*v| {
1233 if (v.iov_len == 0) v.iov_base = &garbage;
1234 }
1235
1236 var i: usize = 0;
1237 while (true) {
1238 var amt = try self.writev(iovecs[i..]);
1239 while (amt >= iovecs[i].iov_len) {
1240 amt -= iovecs[i].iov_len;
1241 i += 1;
1242 if (i >= iovecs.len) return;
1243 }
1244 iovecs[i].iov_base += amt;
1245 iovecs[i].iov_len -= amt;
1246 }
1247 }
1248
1249 /// See https://github.com/ziglang/zig/issues/7699
1250 /// On Windows, this function currently does alter the file pointer.
1251 /// https://github.com/ziglang/zig/issues/12783
1252 pub fn pwritev(self: File, iovecs: []os.iovec_const, offset: u64) PWriteError!usize {
1253 if (is_windows) {
1254 // TODO improve this to use WriteFileScatter
1255 if (iovecs.len == 0) return @as(usize, 0);
1256 const first = iovecs[0];
1257 return windows.WriteFile(self.handle, first.iov_base[0..first.iov_len], offset, self.intended_io_mode);
1258 }
1259
1260 if (self.intended_io_mode == .blocking) {
1261 return os.pwritev(self.handle, iovecs, offset);
1262 } else {
1263 return std.event.Loop.instance.?.pwritev(self.handle, iovecs, offset, self.capable_io_mode != self.intended_io_mode);
1264 }
1265 }
1266
1267 /// The `iovecs` parameter is mutable because this function needs to mutate the fields in
1268 /// order to handle partial writes from the underlying OS layer.
1269 /// See https://github.com/ziglang/zig/issues/7699
1270 /// On Windows, this function currently does alter the file pointer.
1271 /// https://github.com/ziglang/zig/issues/12783
1272 pub fn pwritevAll(self: File, iovecs: []os.iovec_const, offset: u64) PWriteError!void {
1273 if (iovecs.len == 0) return;
1274
1275 var i: usize = 0;
1276 var off: u64 = 0;
1277 while (true) {
1278 var amt = try self.pwritev(iovecs[i..], offset + off);
1279 off += amt;
1280 while (amt >= iovecs[i].iov_len) {
1281 amt -= iovecs[i].iov_len;
1282 i += 1;
1283 if (i >= iovecs.len) return;
1284 }
1285 iovecs[i].iov_base += amt;
1286 iovecs[i].iov_len -= amt;
1287 }
1288 }
1289
1290 pub const CopyRangeError = os.CopyFileRangeError;
1291
1292 pub fn copyRange(in: File, in_offset: u64, out: File, out_offset: u64, len: u64) CopyRangeError!u64 {
1293 const adjusted_len = math.cast(usize, len) orelse math.maxInt(usize);
1294 const result = try os.copy_file_range(in.handle, in_offset, out.handle, out_offset, adjusted_len, 0);
1295 return result;
1296 }
1297
1298 /// Returns the number of bytes copied. If the number read is smaller than `buffer.len`, it
1299 /// means the in file reached the end. Reaching the end of a file is not an error condition.
1300 pub fn copyRangeAll(in: File, in_offset: u64, out: File, out_offset: u64, len: u64) CopyRangeError!u64 {
1301 var total_bytes_copied: u64 = 0;
1302 var in_off = in_offset;
1303 var out_off = out_offset;
1304 while (total_bytes_copied < len) {
1305 const amt_copied = try copyRange(in, in_off, out, out_off, len - total_bytes_copied);
1306 if (amt_copied == 0) return total_bytes_copied;
1307 total_bytes_copied += amt_copied;
1308 in_off += amt_copied;
1309 out_off += amt_copied;
1310 }
1311 return total_bytes_copied;
1312 }
1313
1314 pub const WriteFileOptions = struct {
1315 in_offset: u64 = 0,
1316
1317 /// `null` means the entire file. `0` means no bytes from the file.
1318 /// When this is `null`, trailers must be sent in a separate writev() call
1319 /// due to a flaw in the BSD sendfile API. Other operating systems, such as
1320 /// Linux, already do this anyway due to API limitations.
1321 /// If the size of the source file is known, passing the size here will save one syscall.
1322 in_len: ?u64 = null,
1323
1324 headers_and_trailers: []os.iovec_const = &[0]os.iovec_const{},
1325
1326 /// The trailer count is inferred from `headers_and_trailers.len - header_count`
1327 header_count: usize = 0,
1328 };
1329
1330 pub const WriteFileError = ReadError || error{EndOfStream} || WriteError;
1331
1332 pub fn writeFileAll(self: File, in_file: File, args: WriteFileOptions) WriteFileError!void {
1333 return self.writeFileAllSendfile(in_file, args) catch |err| switch (err) {
1334 error.Unseekable,
1335 error.FastOpenAlreadyInProgress,
1336 error.MessageTooBig,
1337 error.FileDescriptorNotASocket,
1338 error.NetworkUnreachable,
1339 error.NetworkSubsystemFailed,
1340 => return self.writeFileAllUnseekable(in_file, args),
1341
1342 else => |e| return e,
1343 };
1344 }
1345
1346 /// Does not try seeking in either of the File parameters.
1347 /// See `writeFileAll` as an alternative to calling this.
1348 pub fn writeFileAllUnseekable(self: File, in_file: File, args: WriteFileOptions) WriteFileError!void {
1349 const headers = args.headers_and_trailers[0..args.header_count];
1350 const trailers = args.headers_and_trailers[args.header_count..];
1351
1352 try self.writevAll(headers);
1353
1354 try in_file.reader().skipBytes(args.in_offset, .{ .buf_size = 4096 });
1355
1356 var fifo = std.fifo.LinearFifo(u8, .{ .Static = 4096 }).init();
1357 if (args.in_len) |len| {
1358 var stream = std.io.limitedReader(in_file.reader(), len);
1359 try fifo.pump(stream.reader(), self.writer());
1360 } else {
1361 try fifo.pump(in_file.reader(), self.writer());
1362 }
1363
1364 try self.writevAll(trailers);
1365 }
1366
1367 /// Low level function which can fail for OS-specific reasons.
1368 /// See `writeFileAll` as an alternative to calling this.
1369 /// TODO integrate with async I/O
1370 fn writeFileAllSendfile(self: File, in_file: File, args: WriteFileOptions) os.SendFileError!void {
1371 const count = blk: {
1372 if (args.in_len) |l| {
1373 if (l == 0) {
1374 return self.writevAll(args.headers_and_trailers);
1375 } else {
1376 break :blk l;
1377 }
1378 } else {
1379 break :blk 0;
1380 }
1381 };
1382 const headers = args.headers_and_trailers[0..args.header_count];
1383 const trailers = args.headers_and_trailers[args.header_count..];
1384 const zero_iovec = &[0]os.iovec_const{};
1385 // When reading the whole file, we cannot put the trailers in the sendfile() syscall,
1386 // because we have no way to determine whether a partial write is past the end of the file or not.
1387 const trls = if (count == 0) zero_iovec else trailers;
1388 const offset = args.in_offset;
1389 const out_fd = self.handle;
1390 const in_fd = in_file.handle;
1391 const flags = 0;
1392 var amt: usize = 0;
1393 hdrs: {
1394 var i: usize = 0;
1395 while (i < headers.len) {
1396 amt = try os.sendfile(out_fd, in_fd, offset, count, headers[i..], trls, flags);
1397 while (amt >= headers[i].iov_len) {
1398 amt -= headers[i].iov_len;
1399 i += 1;
1400 if (i >= headers.len) break :hdrs;
1401 }
1402 headers[i].iov_base += amt;
1403 headers[i].iov_len -= amt;
1404 }
1405 }
1406 if (count == 0) {
1407 var off: u64 = amt;
1408 while (true) {
1409 amt = try os.sendfile(out_fd, in_fd, offset + off, 0, zero_iovec, zero_iovec, flags);
1410 if (amt == 0) break;
1411 off += amt;
1412 }
1413 } else {
1414 var off: u64 = amt;
1415 while (off < count) {
1416 amt = try os.sendfile(out_fd, in_fd, offset + off, count - off, zero_iovec, trailers, flags);
1417 off += amt;
1418 }
1419 amt = @as(usize, @intCast(off - count));
1420 }
1421 var i: usize = 0;
1422 while (i < trailers.len) {
1423 while (amt >= trailers[i].iov_len) {
1424 amt -= trailers[i].iov_len;
1425 i += 1;
1426 if (i >= trailers.len) return;
1427 }
1428 trailers[i].iov_base += amt;
1429 trailers[i].iov_len -= amt;
1430 amt = try os.writev(self.handle, trailers[i..]);
1431 }
1432 }
1433
1434 pub const Reader = io.Reader(File, ReadError, read);
1435
1436 pub fn reader(file: File) Reader {
1437 return .{ .context = file };
1438 }
1439
1440 pub const Writer = io.Writer(File, WriteError, write);
1441
1442 pub fn writer(file: File) Writer {
1443 return .{ .context = file };
1444 }
1445
1446 pub const SeekableStream = io.SeekableStream(
1447 File,
1448 SeekError,
1449 GetSeekPosError,
1450 seekTo,
1451 seekBy,
1452 getPos,
1453 getEndPos,
1454 );
1455
1456 pub fn seekableStream(file: File) SeekableStream {
1457 return .{ .context = file };
1458 }
1459
1460 const range_off: windows.LARGE_INTEGER = 0;
1461 const range_len: windows.LARGE_INTEGER = 1;
1462
1463 pub const LockError = error{
1464 SystemResources,
1465 FileLocksNotSupported,
1466 } || os.UnexpectedError;
1467
1468 /// Blocks when an incompatible lock is held by another process.
1469 /// A process may hold only one type of lock (shared or exclusive) on
1470 /// a file. When a process terminates in any way, the lock is released.
1471 ///
1472 /// Assumes the file is unlocked.
1473 ///
1474 /// TODO: integrate with async I/O
1475 pub fn lock(file: File, l: Lock) LockError!void {
1476 if (is_windows) {
1477 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
1478 const exclusive = switch (l) {
1479 .none => return,
1480 .shared => false,
1481 .exclusive => true,
1482 };
1483 return windows.LockFile(
1484 file.handle,
1485 null,
1486 null,
1487 null,
1488 &io_status_block,
1489 &range_off,
1490 &range_len,
1491 null,
1492 windows.FALSE, // non-blocking=false
1493 @intFromBool(exclusive),
1494 ) catch |err| switch (err) {
1495 error.WouldBlock => unreachable, // non-blocking=false
1496 else => |e| return e,
1497 };
1498 } else {
1499 return os.flock(file.handle, switch (l) {
1500 .none => os.LOCK.UN,
1501 .shared => os.LOCK.SH,
1502 .exclusive => os.LOCK.EX,
1503 }) catch |err| switch (err) {
1504 error.WouldBlock => unreachable, // non-blocking=false
1505 else => |e| return e,
1506 };
1507 }
1508 }
1509
1510 /// Assumes the file is locked.
1511 pub fn unlock(file: File) void {
1512 if (is_windows) {
1513 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
1514 return windows.UnlockFile(
1515 file.handle,
1516 &io_status_block,
1517 &range_off,
1518 &range_len,
1519 null,
1520 ) catch |err| switch (err) {
1521 error.RangeNotLocked => unreachable, // Function assumes unlocked.
1522 error.Unexpected => unreachable, // Resource deallocation must succeed.
1523 };
1524 } else {
1525 return os.flock(file.handle, os.LOCK.UN) catch |err| switch (err) {
1526 error.WouldBlock => unreachable, // unlocking can't block
1527 error.SystemResources => unreachable, // We are deallocating resources.
1528 error.FileLocksNotSupported => unreachable, // We already got the lock.
1529 error.Unexpected => unreachable, // Resource deallocation must succeed.
1530 };
1531 }
1532 }
1533
1534 /// Attempts to obtain a lock, returning `true` if the lock is
1535 /// obtained, and `false` if there was an existing incompatible lock held.
1536 /// A process may hold only one type of lock (shared or exclusive) on
1537 /// a file. When a process terminates in any way, the lock is released.
1538 ///
1539 /// Assumes the file is unlocked.
1540 ///
1541 /// TODO: integrate with async I/O
1542 pub fn tryLock(file: File, l: Lock) LockError!bool {
1543 if (is_windows) {
1544 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
1545 const exclusive = switch (l) {
1546 .none => return,
1547 .shared => false,
1548 .exclusive => true,
1549 };
1550 windows.LockFile(
1551 file.handle,
1552 null,
1553 null,
1554 null,
1555 &io_status_block,
1556 &range_off,
1557 &range_len,
1558 null,
1559 windows.TRUE, // non-blocking=true
1560 @intFromBool(exclusive),
1561 ) catch |err| switch (err) {
1562 error.WouldBlock => return false,
1563 else => |e| return e,
1564 };
1565 } else {
1566 os.flock(file.handle, switch (l) {
1567 .none => os.LOCK.UN,
1568 .shared => os.LOCK.SH | os.LOCK.NB,
1569 .exclusive => os.LOCK.EX | os.LOCK.NB,
1570 }) catch |err| switch (err) {
1571 error.WouldBlock => return false,
1572 else => |e| return e,
1573 };
1574 }
1575 return true;
1576 }
1577
1578 /// Assumes the file is already locked in exclusive mode.
1579 /// Atomically modifies the lock to be in shared mode, without releasing it.
1580 ///
1581 /// TODO: integrate with async I/O
1582 pub fn downgradeLock(file: File) LockError!void {
1583 if (is_windows) {
1584 // On Windows it works like a semaphore + exclusivity flag. To implement this
1585 // function, we first obtain another lock in shared mode. This changes the
1586 // exclusivity flag, but increments the semaphore to 2. So we follow up with
1587 // an NtUnlockFile which decrements the semaphore but does not modify the
1588 // exclusivity flag.
1589 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
1590 windows.LockFile(
1591 file.handle,
1592 null,
1593 null,
1594 null,
1595 &io_status_block,
1596 &range_off,
1597 &range_len,
1598 null,
1599 windows.TRUE, // non-blocking=true
1600 windows.FALSE, // exclusive=false
1601 ) catch |err| switch (err) {
1602 error.WouldBlock => unreachable, // File was not locked in exclusive mode.
1603 else => |e| return e,
1604 };
1605 return windows.UnlockFile(
1606 file.handle,
1607 &io_status_block,
1608 &range_off,
1609 &range_len,
1610 null,
1611 ) catch |err| switch (err) {
1612 error.RangeNotLocked => unreachable, // File was not locked.
1613 error.Unexpected => unreachable, // Resource deallocation must succeed.
1614 };
1615 } else {
1616 return os.flock(file.handle, os.LOCK.SH | os.LOCK.NB) catch |err| switch (err) {
1617 error.WouldBlock => unreachable, // File was not locked in exclusive mode.
1618 else => |e| return e,
1619 };
1620 }
1621 }
1622};
lib/std/fs/test.zig+52-56
...@@ -8,10 +8,8 @@ const wasi = std.os.wasi;...@@ -8,10 +8,8 @@ const wasi = std.os.wasi;
88
9const ArenaAllocator = std.heap.ArenaAllocator;9const ArenaAllocator = std.heap.ArenaAllocator;
10const Dir = std.fs.Dir;10const Dir = std.fs.Dir;
11const IterableDir = std.fs.IterableDir;
12const File = std.fs.File;11const File = std.fs.File;
13const tmpDir = testing.tmpDir;12const tmpDir = testing.tmpDir;
14const tmpIterableDir = testing.tmpIterableDir;
1513
16const PathType = enum {14const PathType = enum {
17 relative,15 relative,
...@@ -74,19 +72,17 @@ const PathType = enum {...@@ -74,19 +72,17 @@ const PathType = enum {
74const TestContext = struct {72const TestContext = struct {
75 path_type: PathType,73 path_type: PathType,
76 arena: ArenaAllocator,74 arena: ArenaAllocator,
77 tmp: testing.TmpIterableDir,75 tmp: testing.TmpDir,
78 dir: std.fs.Dir,76 dir: std.fs.Dir,
79 iterable_dir: std.fs.IterableDir,
80 transform_fn: *const PathType.TransformFn,77 transform_fn: *const PathType.TransformFn,
8178
82 pub fn init(path_type: PathType, allocator: mem.Allocator, transform_fn: *const PathType.TransformFn) TestContext {79 pub fn init(path_type: PathType, allocator: mem.Allocator, transform_fn: *const PathType.TransformFn) TestContext {
83 const tmp = tmpIterableDir(.{});80 const tmp = tmpDir(.{ .iterate = true });
84 return .{81 return .{
85 .path_type = path_type,82 .path_type = path_type,
86 .arena = ArenaAllocator.init(allocator),83 .arena = ArenaAllocator.init(allocator),
87 .tmp = tmp,84 .tmp = tmp,
88 .dir = tmp.iterable_dir.dir,85 .dir = tmp.dir,
89 .iterable_dir = tmp.iterable_dir,
90 .transform_fn = transform_fn,86 .transform_fn = transform_fn,
91 };87 };
92 }88 }
...@@ -323,28 +319,28 @@ fn testReadLinkAbsolute(target_path: []const u8, symlink_path: []const u8) !void...@@ -323,28 +319,28 @@ fn testReadLinkAbsolute(target_path: []const u8, symlink_path: []const u8) !void
323}319}
324320
325test "Dir.Iterator" {321test "Dir.Iterator" {
326 var tmp_dir = tmpIterableDir(.{});322 var tmp_dir = tmpDir(.{ .iterate = true });
327 defer tmp_dir.cleanup();323 defer tmp_dir.cleanup();
328324
329 // First, create a couple of entries to iterate over.325 // First, create a couple of entries to iterate over.
330 const file = try tmp_dir.iterable_dir.dir.createFile("some_file", .{});326 const file = try tmp_dir.dir.createFile("some_file", .{});
331 file.close();327 file.close();
332328
333 try tmp_dir.iterable_dir.dir.makeDir("some_dir");329 try tmp_dir.dir.makeDir("some_dir");
334330
335 var arena = ArenaAllocator.init(testing.allocator);331 var arena = ArenaAllocator.init(testing.allocator);
336 defer arena.deinit();332 defer arena.deinit();
337 const allocator = arena.allocator();333 const allocator = arena.allocator();
338334
339 var entries = std.ArrayList(IterableDir.Entry).init(allocator);335 var entries = std.ArrayList(Dir.Entry).init(allocator);
340336
341 // Create iterator.337 // Create iterator.
342 var iter = tmp_dir.iterable_dir.iterate();338 var iter = tmp_dir.dir.iterate();
343 while (try iter.next()) |entry| {339 while (try iter.next()) |entry| {
344 // We cannot just store `entry` as on Windows, we're re-using the name buffer340 // We cannot just store `entry` as on Windows, we're re-using the name buffer
345 // which means we'll actually share the `name` pointer between entries!341 // which means we'll actually share the `name` pointer between entries!
346 const name = try allocator.dupe(u8, entry.name);342 const name = try allocator.dupe(u8, entry.name);
347 try entries.append(.{ .name = name, .kind = entry.kind });343 try entries.append(Dir.Entry{ .name = name, .kind = entry.kind });
348 }344 }
349345
350 try testing.expectEqual(@as(usize, 2), entries.items.len); // note that the Iterator skips '.' and '..'346 try testing.expectEqual(@as(usize, 2), entries.items.len); // note that the Iterator skips '.' and '..'
...@@ -353,7 +349,7 @@ test "Dir.Iterator" {...@@ -353,7 +349,7 @@ test "Dir.Iterator" {
353}349}
354350
355test "Dir.Iterator many entries" {351test "Dir.Iterator many entries" {
356 var tmp_dir = tmpIterableDir(.{});352 var tmp_dir = tmpDir(.{ .iterate = true });
357 defer tmp_dir.cleanup();353 defer tmp_dir.cleanup();
358354
359 const num = 1024;355 const num = 1024;
...@@ -361,7 +357,7 @@ test "Dir.Iterator many entries" {...@@ -361,7 +357,7 @@ test "Dir.Iterator many entries" {
361 var buf: [4]u8 = undefined; // Enough to store "1024".357 var buf: [4]u8 = undefined; // Enough to store "1024".
362 while (i < num) : (i += 1) {358 while (i < num) : (i += 1) {
363 const name = try std.fmt.bufPrint(&buf, "{}", .{i});359 const name = try std.fmt.bufPrint(&buf, "{}", .{i});
364 const file = try tmp_dir.iterable_dir.dir.createFile(name, .{});360 const file = try tmp_dir.dir.createFile(name, .{});
365 file.close();361 file.close();
366 }362 }
367363
...@@ -369,10 +365,10 @@ test "Dir.Iterator many entries" {...@@ -369,10 +365,10 @@ test "Dir.Iterator many entries" {
369 defer arena.deinit();365 defer arena.deinit();
370 const allocator = arena.allocator();366 const allocator = arena.allocator();
371367
372 var entries = std.ArrayList(IterableDir.Entry).init(allocator);368 var entries = std.ArrayList(Dir.Entry).init(allocator);
373369
374 // Create iterator.370 // Create iterator.
375 var iter = tmp_dir.iterable_dir.iterate();371 var iter = tmp_dir.dir.iterate();
376 while (try iter.next()) |entry| {372 while (try iter.next()) |entry| {
377 // We cannot just store `entry` as on Windows, we're re-using the name buffer373 // We cannot just store `entry` as on Windows, we're re-using the name buffer
378 // which means we'll actually share the `name` pointer between entries!374 // which means we'll actually share the `name` pointer between entries!
...@@ -388,14 +384,14 @@ test "Dir.Iterator many entries" {...@@ -388,14 +384,14 @@ test "Dir.Iterator many entries" {
388}384}
389385
390test "Dir.Iterator twice" {386test "Dir.Iterator twice" {
391 var tmp_dir = tmpIterableDir(.{});387 var tmp_dir = tmpDir(.{ .iterate = true });
392 defer tmp_dir.cleanup();388 defer tmp_dir.cleanup();
393389
394 // First, create a couple of entries to iterate over.390 // First, create a couple of entries to iterate over.
395 const file = try tmp_dir.iterable_dir.dir.createFile("some_file", .{});391 const file = try tmp_dir.dir.createFile("some_file", .{});
396 file.close();392 file.close();
397393
398 try tmp_dir.iterable_dir.dir.makeDir("some_dir");394 try tmp_dir.dir.makeDir("some_dir");
399395
400 var arena = ArenaAllocator.init(testing.allocator);396 var arena = ArenaAllocator.init(testing.allocator);
401 defer arena.deinit();397 defer arena.deinit();
...@@ -403,15 +399,15 @@ test "Dir.Iterator twice" {...@@ -403,15 +399,15 @@ test "Dir.Iterator twice" {
403399
404 var i: u8 = 0;400 var i: u8 = 0;
405 while (i < 2) : (i += 1) {401 while (i < 2) : (i += 1) {
406 var entries = std.ArrayList(IterableDir.Entry).init(allocator);402 var entries = std.ArrayList(Dir.Entry).init(allocator);
407403
408 // Create iterator.404 // Create iterator.
409 var iter = tmp_dir.iterable_dir.iterate();405 var iter = tmp_dir.dir.iterate();
410 while (try iter.next()) |entry| {406 while (try iter.next()) |entry| {
411 // We cannot just store `entry` as on Windows, we're re-using the name buffer407 // We cannot just store `entry` as on Windows, we're re-using the name buffer
412 // which means we'll actually share the `name` pointer between entries!408 // which means we'll actually share the `name` pointer between entries!
413 const name = try allocator.dupe(u8, entry.name);409 const name = try allocator.dupe(u8, entry.name);
414 try entries.append(.{ .name = name, .kind = entry.kind });410 try entries.append(Dir.Entry{ .name = name, .kind = entry.kind });
415 }411 }
416412
417 try testing.expectEqual(@as(usize, 2), entries.items.len); // note that the Iterator skips '.' and '..'413 try testing.expectEqual(@as(usize, 2), entries.items.len); // note that the Iterator skips '.' and '..'
...@@ -421,25 +417,25 @@ test "Dir.Iterator twice" {...@@ -421,25 +417,25 @@ test "Dir.Iterator twice" {
421}417}
422418
423test "Dir.Iterator reset" {419test "Dir.Iterator reset" {
424 var tmp_dir = tmpIterableDir(.{});420 var tmp_dir = tmpDir(.{ .iterate = true });
425 defer tmp_dir.cleanup();421 defer tmp_dir.cleanup();
426422
427 // First, create a couple of entries to iterate over.423 // First, create a couple of entries to iterate over.
428 const file = try tmp_dir.iterable_dir.dir.createFile("some_file", .{});424 const file = try tmp_dir.dir.createFile("some_file", .{});
429 file.close();425 file.close();
430426
431 try tmp_dir.iterable_dir.dir.makeDir("some_dir");427 try tmp_dir.dir.makeDir("some_dir");
432428
433 var arena = ArenaAllocator.init(testing.allocator);429 var arena = ArenaAllocator.init(testing.allocator);
434 defer arena.deinit();430 defer arena.deinit();
435 const allocator = arena.allocator();431 const allocator = arena.allocator();
436432
437 // Create iterator.433 // Create iterator.
438 var iter = tmp_dir.iterable_dir.iterate();434 var iter = tmp_dir.dir.iterate();
439435
440 var i: u8 = 0;436 var i: u8 = 0;
441 while (i < 2) : (i += 1) {437 while (i < 2) : (i += 1) {
442 var entries = std.ArrayList(IterableDir.Entry).init(allocator);438 var entries = std.ArrayList(Dir.Entry).init(allocator);
443439
444 while (try iter.next()) |entry| {440 while (try iter.next()) |entry| {
445 // We cannot just store `entry` as on Windows, we're re-using the name buffer441 // We cannot just store `entry` as on Windows, we're re-using the name buffer
...@@ -461,10 +457,10 @@ test "Dir.Iterator but dir is deleted during iteration" {...@@ -461,10 +457,10 @@ test "Dir.Iterator but dir is deleted during iteration" {
461 defer tmp.cleanup();457 defer tmp.cleanup();
462458
463 // Create directory and setup an iterator for it459 // Create directory and setup an iterator for it
464 var iterable_subdir = try tmp.dir.makeOpenPathIterable("subdir", .{});460 var subdir = try tmp.dir.makeOpenPath("subdir", .{ .iterate = true });
465 defer iterable_subdir.close();461 defer subdir.close();
466462
467 var iterator = iterable_subdir.iterate();463 var iterator = subdir.iterate();
468464
469 // Create something to iterate over within the subdir465 // Create something to iterate over within the subdir
470 try tmp.dir.makePath("subdir/b");466 try tmp.dir.makePath("subdir/b");
...@@ -485,11 +481,11 @@ test "Dir.Iterator but dir is deleted during iteration" {...@@ -485,11 +481,11 @@ test "Dir.Iterator but dir is deleted during iteration" {
485 }481 }
486}482}
487483
488fn entryEql(lhs: IterableDir.Entry, rhs: IterableDir.Entry) bool {484fn entryEql(lhs: Dir.Entry, rhs: Dir.Entry) bool {
489 return mem.eql(u8, lhs.name, rhs.name) and lhs.kind == rhs.kind;485 return mem.eql(u8, lhs.name, rhs.name) and lhs.kind == rhs.kind;
490}486}
491487
492fn contains(entries: *const std.ArrayList(IterableDir.Entry), el: IterableDir.Entry) bool {488fn contains(entries: *const std.ArrayList(Dir.Entry), el: Dir.Entry) bool {
493 for (entries.items) |entry| {489 for (entries.items) |entry| {
494 if (entryEql(entry, el)) return true;490 if (entryEql(entry, el)) return true;
495 }491 }
...@@ -963,10 +959,10 @@ test "makePath in a directory that no longer exists" {...@@ -963,10 +959,10 @@ test "makePath in a directory that no longer exists" {
963 try testing.expectError(error.FileNotFound, tmp.dir.makePath("sub-path"));959 try testing.expectError(error.FileNotFound, tmp.dir.makePath("sub-path"));
964}960}
965961
966fn testFilenameLimits(iterable_dir: IterableDir, maxed_filename: []const u8) !void {962fn testFilenameLimits(iterable_dir: Dir, maxed_filename: []const u8) !void {
967 // setup, create a dir and a nested file both with maxed filenames, and walk the dir963 // setup, create a dir and a nested file both with maxed filenames, and walk the dir
968 {964 {
969 var maxed_dir = try iterable_dir.dir.makeOpenPath(maxed_filename, .{});965 var maxed_dir = try iterable_dir.makeOpenPath(maxed_filename, .{});
970 defer maxed_dir.close();966 defer maxed_dir.close();
971967
972 try maxed_dir.writeFile(maxed_filename, "");968 try maxed_dir.writeFile(maxed_filename, "");
...@@ -983,27 +979,27 @@ fn testFilenameLimits(iterable_dir: IterableDir, maxed_filename: []const u8) !vo...@@ -983,27 +979,27 @@ fn testFilenameLimits(iterable_dir: IterableDir, maxed_filename: []const u8) !vo
983 }979 }
984980
985 // ensure that we can delete the tree981 // ensure that we can delete the tree
986 try iterable_dir.dir.deleteTree(maxed_filename);982 try iterable_dir.deleteTree(maxed_filename);
987}983}
988984
989test "max file name component lengths" {985test "max file name component lengths" {
990 var tmp = tmpIterableDir(.{});986 var tmp = tmpDir(.{ .iterate = true });
991 defer tmp.cleanup();987 defer tmp.cleanup();
992988
993 if (builtin.os.tag == .windows) {989 if (builtin.os.tag == .windows) {
994 // U+FFFF is the character with the largest code point that is encoded as a single990 // U+FFFF is the character with the largest code point that is encoded as a single
995 // UTF-16 code unit, so Windows allows for NAME_MAX of them.991 // UTF-16 code unit, so Windows allows for NAME_MAX of them.
996 const maxed_windows_filename = ("\u{FFFF}".*) ** std.os.windows.NAME_MAX;992 const maxed_windows_filename = ("\u{FFFF}".*) ** std.os.windows.NAME_MAX;
997 try testFilenameLimits(tmp.iterable_dir, &maxed_windows_filename);993 try testFilenameLimits(tmp.dir, &maxed_windows_filename);
998 } else if (builtin.os.tag == .wasi) {994 } else if (builtin.os.tag == .wasi) {
999 // On WASI, the maxed filename depends on the host OS, so in order for this test to995 // On WASI, the maxed filename depends on the host OS, so in order for this test to
1000 // work on any host, we need to use a length that will work for all platforms996 // work on any host, we need to use a length that will work for all platforms
1001 // (i.e. the minimum MAX_NAME_BYTES of all supported platforms).997 // (i.e. the minimum MAX_NAME_BYTES of all supported platforms).
1002 const maxed_wasi_filename = [_]u8{'1'} ** 255;998 const maxed_wasi_filename = [_]u8{'1'} ** 255;
1003 try testFilenameLimits(tmp.iterable_dir, &maxed_wasi_filename);999 try testFilenameLimits(tmp.dir, &maxed_wasi_filename);
1004 } else {1000 } else {
1005 const maxed_ascii_filename = [_]u8{'1'} ** std.fs.MAX_NAME_BYTES;1001 const maxed_ascii_filename = [_]u8{'1'} ** std.fs.MAX_NAME_BYTES;
1006 try testFilenameLimits(tmp.iterable_dir, &maxed_ascii_filename);1002 try testFilenameLimits(tmp.dir, &maxed_ascii_filename);
1007 }1003 }
1008}1004}
10091005
...@@ -1384,7 +1380,7 @@ test "open file with exclusive nonblocking lock twice (absolute paths)" {...@@ -1384,7 +1380,7 @@ test "open file with exclusive nonblocking lock twice (absolute paths)" {
1384test "walker" {1380test "walker" {
1385 if (builtin.os.tag == .wasi and builtin.link_libc) return error.SkipZigTest;1381 if (builtin.os.tag == .wasi and builtin.link_libc) return error.SkipZigTest;
13861382
1387 var tmp = tmpIterableDir(.{});1383 var tmp = tmpDir(.{ .iterate = true });
1388 defer tmp.cleanup();1384 defer tmp.cleanup();
13891385
1390 // iteration order of walker is undefined, so need lookup maps to check against1386 // iteration order of walker is undefined, so need lookup maps to check against
...@@ -1410,10 +1406,10 @@ test "walker" {...@@ -1410,10 +1406,10 @@ test "walker" {
1410 });1406 });
14111407
1412 for (expected_paths.kvs) |kv| {1408 for (expected_paths.kvs) |kv| {
1413 try tmp.iterable_dir.dir.makePath(kv.key);1409 try tmp.dir.makePath(kv.key);
1414 }1410 }
14151411
1416 var walker = try tmp.iterable_dir.walk(testing.allocator);1412 var walker = try tmp.dir.walk(testing.allocator);
1417 defer walker.deinit();1413 defer walker.deinit();
14181414
1419 var num_walked: usize = 0;1415 var num_walked: usize = 0;
...@@ -1437,17 +1433,17 @@ test "walker" {...@@ -1437,17 +1433,17 @@ test "walker" {
1437test "walker without fully iterating" {1433test "walker without fully iterating" {
1438 if (builtin.os.tag == .wasi and builtin.link_libc) return error.SkipZigTest;1434 if (builtin.os.tag == .wasi and builtin.link_libc) return error.SkipZigTest;
14391435
1440 var tmp = tmpIterableDir(.{});1436 var tmp = tmpDir(.{ .iterate = true });
1441 defer tmp.cleanup();1437 defer tmp.cleanup();
14421438
1443 var walker = try tmp.iterable_dir.walk(testing.allocator);1439 var walker = try tmp.dir.walk(testing.allocator);
1444 defer walker.deinit();1440 defer walker.deinit();
14451441
1446 // Create 2 directories inside the tmp directory, but then only iterate once before breaking.1442 // Create 2 directories inside the tmp directory, but then only iterate once before breaking.
1447 // This ensures that walker doesn't try to close the initial directory when not fully iterating.1443 // This ensures that walker doesn't try to close the initial directory when not fully iterating.
14481444
1449 try tmp.iterable_dir.dir.makePath("a");1445 try tmp.dir.makePath("a");
1450 try tmp.iterable_dir.dir.makePath("b");1446 try tmp.dir.makePath("b");
14511447
1452 var num_walked: usize = 0;1448 var num_walked: usize = 0;
1453 while (try walker.next()) |_| {1449 while (try walker.next()) |_| {
...@@ -1490,7 +1486,7 @@ test ". and .. in fs.Dir functions" {...@@ -1490,7 +1486,7 @@ test ". and .. in fs.Dir functions" {
14901486
1491 try ctx.dir.writeFile(update_path, "something");1487 try ctx.dir.writeFile(update_path, "something");
1492 const prev_status = try ctx.dir.updateFile(file_path, ctx.dir, update_path, .{});1488 const prev_status = try ctx.dir.updateFile(file_path, ctx.dir, update_path, .{});
1493 try testing.expectEqual(fs.PrevStatus.stale, prev_status);1489 try testing.expectEqual(fs.Dir.PrevStatus.stale, prev_status);
14941490
1495 try ctx.dir.deleteDir(subdir_path);1491 try ctx.dir.deleteDir(subdir_path);
1496 }1492 }
...@@ -1536,7 +1532,7 @@ test ". and .. in absolute functions" {...@@ -1536,7 +1532,7 @@ test ". and .. in absolute functions" {
1536 try update_file.writeAll("something");1532 try update_file.writeAll("something");
1537 update_file.close();1533 update_file.close();
1538 const prev_status = try fs.updateFileAbsolute(created_file_path, update_file_path, .{});1534 const prev_status = try fs.updateFileAbsolute(created_file_path, update_file_path, .{});
1539 try testing.expectEqual(fs.PrevStatus.stale, prev_status);1535 try testing.expectEqual(fs.Dir.PrevStatus.stale, prev_status);
15401536
1541 try fs.deleteDirAbsolute(subdir_path);1537 try fs.deleteDirAbsolute(subdir_path);
1542}1538}
...@@ -1556,11 +1552,11 @@ test "chmod" {...@@ -1556,11 +1552,11 @@ test "chmod" {
1556 try testing.expectEqual(@as(File.Mode, 0o644), (try file.stat()).mode & 0o7777);1552 try testing.expectEqual(@as(File.Mode, 0o644), (try file.stat()).mode & 0o7777);
15571553
1558 try tmp.dir.makeDir("test_dir");1554 try tmp.dir.makeDir("test_dir");
1559 var iterable_dir = try tmp.dir.openIterableDir("test_dir", .{});1555 var dir = try tmp.dir.openDir("test_dir", .{ .iterate = true });
1560 defer iterable_dir.close();1556 defer dir.close();
15611557
1562 try iterable_dir.chmod(0o700);1558 try dir.chmod(0o700);
1563 try testing.expectEqual(@as(File.Mode, 0o700), (try iterable_dir.dir.stat()).mode & 0o7777);1559 try testing.expectEqual(@as(File.Mode, 0o700), (try dir.stat()).mode & 0o7777);
1564}1560}
15651561
1566test "chown" {1562test "chown" {
...@@ -1576,9 +1572,9 @@ test "chown" {...@@ -1576,9 +1572,9 @@ test "chown" {
15761572
1577 try tmp.dir.makeDir("test_dir");1573 try tmp.dir.makeDir("test_dir");
15781574
1579 var iterable_dir = try tmp.dir.openIterableDir("test_dir", .{});1575 var dir = try tmp.dir.openDir("test_dir", .{ .iterate = true });
1580 defer iterable_dir.close();1576 defer dir.close();
1581 try iterable_dir.chown(null, null);1577 try dir.chown(null, null);
1582}1578}
15831579
1584test "File.Metadata" {1580test "File.Metadata" {
lib/std/os.zig+1-1
...@@ -402,7 +402,7 @@ pub fn fchown(fd: fd_t, owner: ?uid_t, group: ?gid_t) FChownError!void {...@@ -402,7 +402,7 @@ pub fn fchown(fd: fd_t, owner: ?uid_t, group: ?gid_t) FChownError!void {
402 switch (system.getErrno(res)) {402 switch (system.getErrno(res)) {
403 .SUCCESS => return,403 .SUCCESS => return,
404 .INTR => continue,404 .INTR => continue,
405 .BADF => unreachable, // Can be reached if the fd refers to a non-iterable directory.405 .BADF => unreachable, // Can be reached if the fd refers to a directory opened without `OpenDirOptions{ .iterate = true }`
406406
407 .FAULT => unreachable,407 .FAULT => unreachable,
408 .INVAL => unreachable,408 .INVAL => unreachable,
lib/std/testing.zig-38
...@@ -543,22 +543,6 @@ pub const TmpDir = struct {...@@ -543,22 +543,6 @@ pub const TmpDir = struct {
543 }543 }
544};544};
545545
546pub const TmpIterableDir = struct {
547 iterable_dir: std.fs.IterableDir,
548 parent_dir: std.fs.Dir,
549 sub_path: [sub_path_len]u8,
550
551 const random_bytes_count = 12;
552 const sub_path_len = std.fs.base64_encoder.calcSize(random_bytes_count);
553
554 pub fn cleanup(self: *TmpIterableDir) void {
555 self.iterable_dir.close();
556 self.parent_dir.deleteTree(&self.sub_path) catch {};
557 self.parent_dir.close();
558 self.* = undefined;
559 }
560};
561
562pub fn tmpDir(opts: std.fs.Dir.OpenDirOptions) TmpDir {546pub fn tmpDir(opts: std.fs.Dir.OpenDirOptions) TmpDir {
563 var random_bytes: [TmpDir.random_bytes_count]u8 = undefined;547 var random_bytes: [TmpDir.random_bytes_count]u8 = undefined;
564 std.crypto.random.bytes(&random_bytes);548 std.crypto.random.bytes(&random_bytes);
...@@ -581,28 +565,6 @@ pub fn tmpDir(opts: std.fs.Dir.OpenDirOptions) TmpDir {...@@ -581,28 +565,6 @@ pub fn tmpDir(opts: std.fs.Dir.OpenDirOptions) TmpDir {
581 };565 };
582}566}
583567
584pub fn tmpIterableDir(opts: std.fs.Dir.OpenDirOptions) TmpIterableDir {
585 var random_bytes: [TmpIterableDir.random_bytes_count]u8 = undefined;
586 std.crypto.random.bytes(&random_bytes);
587 var sub_path: [TmpIterableDir.sub_path_len]u8 = undefined;
588 _ = std.fs.base64_encoder.encode(&sub_path, &random_bytes);
589
590 const cwd = std.fs.cwd();
591 var cache_dir = cwd.makeOpenPath("zig-cache", .{}) catch
592 @panic("unable to make tmp dir for testing: unable to make and open zig-cache dir");
593 defer cache_dir.close();
594 const parent_dir = cache_dir.makeOpenPath("tmp", .{}) catch
595 @panic("unable to make tmp dir for testing: unable to make and open zig-cache/tmp dir");
596 const dir = parent_dir.makeOpenPathIterable(&sub_path, opts) catch
597 @panic("unable to make tmp dir for testing: unable to make and open the tmp dir");
598
599 return .{
600 .iterable_dir = dir,
601 .parent_dir = parent_dir,
602 .sub_path = sub_path,
603 };
604}
605
606test "expectEqual nested array" {568test "expectEqual nested array" {
607 const a = [2][2]f32{569 const a = [2][2]f32{
608 [_]f32{ 1.0, 0.0 },570 [_]f32{ 1.0, 0.0 },
src/Package/Fetch.zig+14-12
...@@ -280,7 +280,7 @@ pub fn run(f: *Fetch) RunError!void {...@@ -280,7 +280,7 @@ pub fn run(f: *Fetch) RunError!void {
280 },280 },
281 .remote => |remote| remote,281 .remote => |remote| remote,
282 .path_or_url => |path_or_url| {282 .path_or_url => |path_or_url| {
283 if (fs.cwd().openIterableDir(path_or_url, .{})) |dir| {283 if (fs.cwd().openDir(path_or_url, .{ .iterate = true })) |dir| {
284 var resource: Resource = .{ .dir = dir };284 var resource: Resource = .{ .dir = dir };
285 return runResource(f, path_or_url, &resource, null);285 return runResource(f, path_or_url, &resource, null);
286 } else |dir_err| {286 } else |dir_err| {
...@@ -363,7 +363,9 @@ fn runResource(...@@ -363,7 +363,9 @@ fn runResource(
363 var tmp_directory: Cache.Directory = .{363 var tmp_directory: Cache.Directory = .{
364 .path = tmp_directory_path,364 .path = tmp_directory_path,
365 .handle = handle: {365 .handle = handle: {
366 const dir = cache_root.handle.makeOpenPathIterable(tmp_dir_sub_path, .{}) catch |err| {366 const dir = cache_root.handle.makeOpenPath(tmp_dir_sub_path, .{
367 .iterate = true,
368 }) catch |err| {
367 try eb.addRootErrorMessage(.{369 try eb.addRootErrorMessage(.{
368 .msg = try eb.printString("unable to create temporary directory '{s}': {s}", .{370 .msg = try eb.printString("unable to create temporary directory '{s}': {s}", .{
369 tmp_directory_path, @errorName(err),371 tmp_directory_path, @errorName(err),
...@@ -371,7 +373,7 @@ fn runResource(...@@ -371,7 +373,7 @@ fn runResource(
371 });373 });
372 return error.FetchFailed;374 return error.FetchFailed;
373 };375 };
374 break :handle dir.dir;376 break :handle dir;
375 },377 },
376 };378 };
377 defer tmp_directory.handle.close();379 defer tmp_directory.handle.close();
...@@ -400,9 +402,9 @@ fn runResource(...@@ -400,9 +402,9 @@ fn runResource(
400 if (builtin.os.tag == .linux and f.job_queue.work_around_btrfs_bug) {402 if (builtin.os.tag == .linux and f.job_queue.work_around_btrfs_bug) {
401 // https://github.com/ziglang/zig/issues/17095403 // https://github.com/ziglang/zig/issues/17095
402 tmp_directory.handle.close();404 tmp_directory.handle.close();
403 const iterable_dir = cache_root.handle.makeOpenPathIterable(tmp_dir_sub_path, .{}) catch405 tmp_directory.handle = cache_root.handle.makeOpenPath(tmp_dir_sub_path, .{
404 @panic("btrfs workaround failed");406 .iterate = true,
405 tmp_directory.handle = iterable_dir.dir;407 }) catch @panic("btrfs workaround failed");
406 }408 }
407409
408 f.actual_hash = try computeHash(f, tmp_directory, filter);410 f.actual_hash = try computeHash(f, tmp_directory, filter);
...@@ -717,7 +719,7 @@ const Resource = union(enum) {...@@ -717,7 +719,7 @@ const Resource = union(enum) {
717 file: fs.File,719 file: fs.File,
718 http_request: std.http.Client.Request,720 http_request: std.http.Client.Request,
719 git: Git,721 git: Git,
720 dir: fs.IterableDir,722 dir: fs.Dir,
721723
722 const Git = struct {724 const Git = struct {
723 fetch_stream: git.Session.FetchStream,725 fetch_stream: git.Session.FetchStream,
...@@ -1198,7 +1200,7 @@ fn unpackGitPack(f: *Fetch, out_dir: fs.Dir, resource: *Resource) anyerror!void...@@ -1198,7 +1200,7 @@ fn unpackGitPack(f: *Fetch, out_dir: fs.Dir, resource: *Resource) anyerror!void
1198 try out_dir.deleteTree(".git");1200 try out_dir.deleteTree(".git");
1199}1201}
12001202
1201fn recursiveDirectoryCopy(f: *Fetch, dir: fs.IterableDir, tmp_dir: fs.Dir) anyerror!void {1203fn recursiveDirectoryCopy(f: *Fetch, dir: fs.Dir, tmp_dir: fs.Dir) anyerror!void {
1202 const gpa = f.arena.child_allocator;1204 const gpa = f.arena.child_allocator;
1203 // Recursive directory copy.1205 // Recursive directory copy.
1204 var it = try dir.walk(gpa);1206 var it = try dir.walk(gpa);
...@@ -1207,7 +1209,7 @@ fn recursiveDirectoryCopy(f: *Fetch, dir: fs.IterableDir, tmp_dir: fs.Dir) anyer...@@ -1207,7 +1209,7 @@ fn recursiveDirectoryCopy(f: *Fetch, dir: fs.IterableDir, tmp_dir: fs.Dir) anyer
1207 switch (entry.kind) {1209 switch (entry.kind) {
1208 .directory => {}, // omit empty directories1210 .directory => {}, // omit empty directories
1209 .file => {1211 .file => {
1210 dir.dir.copyFile(1212 dir.copyFile(
1211 entry.path,1213 entry.path,
1212 tmp_dir,1214 tmp_dir,
1213 entry.path,1215 entry.path,
...@@ -1215,14 +1217,14 @@ fn recursiveDirectoryCopy(f: *Fetch, dir: fs.IterableDir, tmp_dir: fs.Dir) anyer...@@ -1215,14 +1217,14 @@ fn recursiveDirectoryCopy(f: *Fetch, dir: fs.IterableDir, tmp_dir: fs.Dir) anyer
1215 ) catch |err| switch (err) {1217 ) catch |err| switch (err) {
1216 error.FileNotFound => {1218 error.FileNotFound => {
1217 if (fs.path.dirname(entry.path)) |dirname| try tmp_dir.makePath(dirname);1219 if (fs.path.dirname(entry.path)) |dirname| try tmp_dir.makePath(dirname);
1218 try dir.dir.copyFile(entry.path, tmp_dir, entry.path, .{});1220 try dir.copyFile(entry.path, tmp_dir, entry.path, .{});
1219 },1221 },
1220 else => |e| return e,1222 else => |e| return e,
1221 };1223 };
1222 },1224 },
1223 .sym_link => {1225 .sym_link => {
1224 var buf: [fs.MAX_PATH_BYTES]u8 = undefined;1226 var buf: [fs.MAX_PATH_BYTES]u8 = undefined;
1225 const link_name = try dir.dir.readLink(entry.path, &buf);1227 const link_name = try dir.readLink(entry.path, &buf);
1226 // TODO: if this would create a symlink to outside1228 // TODO: if this would create a symlink to outside
1227 // the destination directory, fail with an error instead.1229 // the destination directory, fail with an error instead.
1228 tmp_dir.symLink(link_name, entry.path, .{}) catch |err| switch (err) {1230 tmp_dir.symLink(link_name, entry.path, .{}) catch |err| switch (err) {
...@@ -1296,7 +1298,7 @@ fn computeHash(...@@ -1296,7 +1298,7 @@ fn computeHash(
1296 var sus_dirs: std.StringArrayHashMapUnmanaged(void) = .{};1298 var sus_dirs: std.StringArrayHashMapUnmanaged(void) = .{};
1297 defer sus_dirs.deinit(gpa);1299 defer sus_dirs.deinit(gpa);
12981300
1299 var walker = try @as(fs.IterableDir, .{ .dir = tmp_directory.handle }).walk(gpa);1301 var walker = try tmp_directory.handle.walk(gpa);
1300 defer walker.deinit();1302 defer walker.deinit();
13011303
1302 {1304 {
src/Package/Fetch/git.zig+4-4
...@@ -1384,11 +1384,11 @@ test "packfile indexing and checkout" {...@@ -1384,11 +1384,11 @@ test "packfile indexing and checkout" {
1384 var repository = try Repository.init(testing.allocator, pack_file, index_file);1384 var repository = try Repository.init(testing.allocator, pack_file, index_file);
1385 defer repository.deinit();1385 defer repository.deinit();
13861386
1387 var worktree = testing.tmpIterableDir(.{});1387 var worktree = testing.tmpDir(.{ .iterate = true });
1388 defer worktree.cleanup();1388 defer worktree.cleanup();
13891389
1390 const commit_id = try parseOid("dd582c0720819ab7130b103635bd7271b9fd4feb");1390 const commit_id = try parseOid("dd582c0720819ab7130b103635bd7271b9fd4feb");
1391 try repository.checkout(worktree.iterable_dir.dir, commit_id);1391 try repository.checkout(worktree.dir, commit_id);
13921392
1393 const expected_files: []const []const u8 = &.{1393 const expected_files: []const []const u8 = &.{
1394 "dir/file",1394 "dir/file",
...@@ -1410,7 +1410,7 @@ test "packfile indexing and checkout" {...@@ -1410,7 +1410,7 @@ test "packfile indexing and checkout" {
1410 var actual_files: std.ArrayListUnmanaged([]u8) = .{};1410 var actual_files: std.ArrayListUnmanaged([]u8) = .{};
1411 defer actual_files.deinit(testing.allocator);1411 defer actual_files.deinit(testing.allocator);
1412 defer for (actual_files.items) |file| testing.allocator.free(file);1412 defer for (actual_files.items) |file| testing.allocator.free(file);
1413 var walker = try worktree.iterable_dir.walk(testing.allocator);1413 var walker = try worktree.dir.walk(testing.allocator);
1414 defer walker.deinit();1414 defer walker.deinit();
1415 while (try walker.next()) |entry| {1415 while (try walker.next()) |entry| {
1416 if (entry.kind != .file) continue;1416 if (entry.kind != .file) continue;
...@@ -1442,7 +1442,7 @@ test "packfile indexing and checkout" {...@@ -1442,7 +1442,7 @@ test "packfile indexing and checkout" {
1442 \\revision 191442 \\revision 19
1443 \\1443 \\
1444 ;1444 ;
1445 const actual_file_contents = try worktree.iterable_dir.dir.readFileAlloc(testing.allocator, "file", max_file_size);1445 const actual_file_contents = try worktree.dir.readFileAlloc(testing.allocator, "file", max_file_size);
1446 defer testing.allocator.free(actual_file_contents);1446 defer testing.allocator.free(actual_file_contents);
1447 try testing.expectEqualStrings(expected_file_contents, actual_file_contents);1447 try testing.expectEqualStrings(expected_file_contents, actual_file_contents);
1448}1448}
src/main.zig+6-6
...@@ -5698,13 +5698,13 @@ fn fmtPathDir(...@@ -5698,13 +5698,13 @@ fn fmtPathDir(
5698 parent_dir: fs.Dir,5698 parent_dir: fs.Dir,
5699 parent_sub_path: []const u8,5699 parent_sub_path: []const u8,
5700) FmtError!void {5700) FmtError!void {
5701 var iterable_dir = try parent_dir.openIterableDir(parent_sub_path, .{});5701 var dir = try parent_dir.openDir(parent_sub_path, .{ .iterate = true });
5702 defer iterable_dir.close();5702 defer dir.close();
57035703
5704 const stat = try iterable_dir.dir.stat();5704 const stat = try dir.stat();
5705 if (try fmt.seen.fetchPut(stat.inode, {})) |_| return;5705 if (try fmt.seen.fetchPut(stat.inode, {})) |_| return;
57065706
5707 var dir_it = iterable_dir.iterate();5707 var dir_it = dir.iterate();
5708 while (try dir_it.next()) |entry| {5708 while (try dir_it.next()) |entry| {
5709 const is_dir = entry.kind == .directory;5709 const is_dir = entry.kind == .directory;
57105710
...@@ -5715,9 +5715,9 @@ fn fmtPathDir(...@@ -5715,9 +5715,9 @@ fn fmtPathDir(
5715 defer fmt.gpa.free(full_path);5715 defer fmt.gpa.free(full_path);
57165716
5717 if (is_dir) {5717 if (is_dir) {
5718 try fmtPathDir(fmt, full_path, check_mode, iterable_dir.dir, entry.name);5718 try fmtPathDir(fmt, full_path, check_mode, dir, entry.name);
5719 } else {5719 } else {
5720 fmtPathFile(fmt, full_path, check_mode, iterable_dir.dir, entry.name) catch |err| {5720 fmtPathFile(fmt, full_path, check_mode, dir, entry.name) catch |err| {
5721 warn("unable to format '{s}': {s}", .{ full_path, @errorName(err) });5721 warn("unable to format '{s}': {s}", .{ full_path, @errorName(err) });
5722 fmt.any_error = true;5722 fmt.any_error = true;
5723 return;5723 return;
src/windows_sdk.zig+11-3
...@@ -14,7 +14,11 @@ const product_version_max_length = version_major_minor_max_length + ".65535".len...@@ -14,7 +14,11 @@ const product_version_max_length = version_major_minor_max_length + ".65535".len
14/// Iterates via `iterator` and collects all folders with names starting with `optional_prefix`14/// Iterates via `iterator` and collects all folders with names starting with `optional_prefix`
15/// and similar to SemVer. Returns slice of folder names sorted in descending order.15/// and similar to SemVer. Returns slice of folder names sorted in descending order.
16/// Caller owns result.16/// Caller owns result.
17fn iterateAndFilterBySemVer(iterator: *std.fs.IterableDir.Iterator, allocator: std.mem.Allocator, comptime optional_prefix: ?[]const u8) error{ OutOfMemory, VersionNotFound }![][]const u8 {17fn iterateAndFilterBySemVer(
18 iterator: *std.fs.Dir.Iterator,
19 allocator: std.mem.Allocator,
20 comptime optional_prefix: ?[]const u8,
21) error{ OutOfMemory, VersionNotFound }![][]const u8 {
18 var dirs_filtered_list = std.ArrayList([]const u8).init(allocator);22 var dirs_filtered_list = std.ArrayList([]const u8).init(allocator);
19 errdefer {23 errdefer {
20 for (dirs_filtered_list.items) |filtered_dir| allocator.free(filtered_dir);24 for (dirs_filtered_list.items) |filtered_dir| allocator.free(filtered_dir);
...@@ -476,7 +480,9 @@ pub const Windows81Sdk = struct {...@@ -476,7 +480,9 @@ pub const Windows81Sdk = struct {
476 if (!std.fs.path.isAbsolute(sdk_lib_dir_path)) return error.Windows81SdkNotFound;480 if (!std.fs.path.isAbsolute(sdk_lib_dir_path)) return error.Windows81SdkNotFound;
477481
478 // enumerate files in sdk path looking for latest version482 // enumerate files in sdk path looking for latest version
479 var sdk_lib_dir = std.fs.openIterableDirAbsolute(sdk_lib_dir_path, .{}) catch |err| switch (err) {483 var sdk_lib_dir = std.fs.openDirAbsolute(sdk_lib_dir_path, .{
484 .iterate = true,
485 }) catch |err| switch (err) {
480 error.NameTooLong => return error.PathTooLong,486 error.NameTooLong => return error.PathTooLong,
481 else => return error.Windows81SdkNotFound,487 else => return error.Windows81SdkNotFound,
482 };488 };
...@@ -727,7 +733,9 @@ const MsvcLibDir = struct {...@@ -727,7 +733,9 @@ const MsvcLibDir = struct {
727 if (!std.fs.path.isAbsolute(visualstudio_folder_path)) return error.PathNotFound;733 if (!std.fs.path.isAbsolute(visualstudio_folder_path)) return error.PathNotFound;
728 // enumerate folders that contain `privateregistry.bin`, looking for all versions734 // enumerate folders that contain `privateregistry.bin`, looking for all versions
729 // f.i. %localappdata%\Microsoft\VisualStudio\17.0_9e9cbb98\735 // f.i. %localappdata%\Microsoft\VisualStudio\17.0_9e9cbb98\
730 var visualstudio_folder = std.fs.openIterableDirAbsolute(visualstudio_folder_path, .{}) catch return error.PathNotFound;736 var visualstudio_folder = std.fs.openDirAbsolute(visualstudio_folder_path, .{
737 .iterate = true,
738 }) catch return error.PathNotFound;
731 defer visualstudio_folder.close();739 defer visualstudio_folder.close();
732740
733 var iterator = visualstudio_folder.iterate();741 var iterator = visualstudio_folder.iterate();
test/src/Cases.zig+5-5
...@@ -368,7 +368,7 @@ pub fn addCompile(...@@ -368,7 +368,7 @@ pub fn addCompile(
368/// Each file should include a test manifest as a contiguous block of comments at368/// Each file should include a test manifest as a contiguous block of comments at
369/// the end of the file. The first line should be the test type, followed by a set of369/// the end of the file. The first line should be the test type, followed by a set of
370/// key-value config values, followed by a blank line, then the expected output.370/// key-value config values, followed by a blank line, then the expected output.
371pub fn addFromDir(ctx: *Cases, dir: std.fs.IterableDir) void {371pub fn addFromDir(ctx: *Cases, dir: std.fs.Dir) void {
372 var current_file: []const u8 = "none";372 var current_file: []const u8 = "none";
373 ctx.addFromDirInner(dir, &current_file) catch |err| {373 ctx.addFromDirInner(dir, &current_file) catch |err| {
374 std.debug.panicExtra(374 std.debug.panicExtra(
...@@ -382,7 +382,7 @@ pub fn addFromDir(ctx: *Cases, dir: std.fs.IterableDir) void {...@@ -382,7 +382,7 @@ pub fn addFromDir(ctx: *Cases, dir: std.fs.IterableDir) void {
382382
383fn addFromDirInner(383fn addFromDirInner(
384 ctx: *Cases,384 ctx: *Cases,
385 iterable_dir: std.fs.IterableDir,385 iterable_dir: std.fs.Dir,
386 /// This is kept up to date with the currently being processed file so386 /// This is kept up to date with the currently being processed file so
387 /// that if any errors occur the caller knows it happened during this file.387 /// that if any errors occur the caller knows it happened during this file.
388 current_file: *[]const u8,388 current_file: *[]const u8,
...@@ -416,7 +416,7 @@ fn addFromDirInner(...@@ -416,7 +416,7 @@ fn addFromDirInner(
416 }416 }
417417
418 const max_file_size = 10 * 1024 * 1024;418 const max_file_size = 10 * 1024 * 1024;
419 const src = try iterable_dir.dir.readFileAllocOptions(ctx.arena, filename, max_file_size, null, 1, 0);419 const src = try iterable_dir.readFileAllocOptions(ctx.arena, filename, max_file_size, null, 1, 0);
420420
421 // Parse the manifest421 // Parse the manifest
422 var manifest = try TestManifest.parse(ctx.arena, src);422 var manifest = try TestManifest.parse(ctx.arena, src);
...@@ -1246,7 +1246,7 @@ pub fn main() !void {...@@ -1246,7 +1246,7 @@ pub fn main() !void {
1246 var filenames = std.ArrayList([]const u8).init(arena);1246 var filenames = std.ArrayList([]const u8).init(arena);
12471247
1248 const case_dirname = std.fs.path.dirname(case_file_path).?;1248 const case_dirname = std.fs.path.dirname(case_file_path).?;
1249 var iterable_dir = try std.fs.cwd().openIterableDir(case_dirname, .{});1249 var iterable_dir = try std.fs.cwd().openDir(case_dirname, .{ .iterate = true });
1250 defer iterable_dir.close();1250 defer iterable_dir.close();
12511251
1252 if (std.mem.endsWith(u8, case_file_path, ".0.zig")) {1252 if (std.mem.endsWith(u8, case_file_path, ".0.zig")) {
...@@ -1280,7 +1280,7 @@ pub fn main() !void {...@@ -1280,7 +1280,7 @@ pub fn main() !void {
12801280
1281 for (batch) |filename| {1281 for (batch) |filename| {
1282 const max_file_size = 10 * 1024 * 1024;1282 const max_file_size = 10 * 1024 * 1024;
1283 const src = try iterable_dir.dir.readFileAllocOptions(arena, filename, max_file_size, null, 1, 0);1283 const src = try iterable_dir.readFileAllocOptions(arena, filename, max_file_size, null, 1, 0);
12841284
1285 // Parse the manifest1285 // Parse the manifest
1286 var manifest = try TestManifest.parse(arena, src);1286 var manifest = try TestManifest.parse(arena, src);
test/tests.zig+1-1
...@@ -1288,7 +1288,7 @@ pub fn addCases(...@@ -1288,7 +1288,7 @@ pub fn addCases(
12881288
1289 var cases = @import("src/Cases.zig").init(gpa, arena);1289 var cases = @import("src/Cases.zig").init(gpa, arena);
12901290
1291 var dir = try b.build_root.handle.openIterableDir("test/cases", .{});1291 var dir = try b.build_root.handle.openDir("test/cases", .{ .iterate = true });
1292 defer dir.close();1292 defer dir.close();
12931293
1294 cases.addFromDir(dir);1294 cases.addFromDir(dir);
tools/generate_JSONTestSuite.zig+1-1
...@@ -18,7 +18,7 @@ pub fn main() !void {...@@ -18,7 +18,7 @@ pub fn main() !void {
18 );18 );
1919
20 var names = std.ArrayList([]const u8).init(allocator);20 var names = std.ArrayList([]const u8).init(allocator);
21 var cwd = try std.fs.cwd().openIterableDir(".", .{});21 var cwd = try std.fs.cwd().openDir(".", .{ .iterate = true });
22 var it = cwd.iterate();22 var it = cwd.iterate();
23 while (try it.next()) |entry| {23 while (try it.next()) |entry| {
24 try names.append(try allocator.dupe(u8, entry.name));24 try names.append(try allocator.dupe(u8, entry.name));
tools/process_headers.zig+3-3
...@@ -382,14 +382,14 @@ pub fn main() !void {...@@ -382,14 +382,14 @@ pub fn main() !void {
382 try dir_stack.append(target_include_dir);382 try dir_stack.append(target_include_dir);
383383
384 while (dir_stack.popOrNull()) |full_dir_name| {384 while (dir_stack.popOrNull()) |full_dir_name| {
385 var iterable_dir = std.fs.cwd().openIterableDir(full_dir_name, .{}) catch |err| switch (err) {385 var dir = std.fs.cwd().openDir(full_dir_name, .{ .iterate = true }) catch |err| switch (err) {
386 error.FileNotFound => continue :search,386 error.FileNotFound => continue :search,
387 error.AccessDenied => continue :search,387 error.AccessDenied => continue :search,
388 else => return err,388 else => return err,
389 };389 };
390 defer iterable_dir.close();390 defer dir.close();
391391
392 var dir_it = iterable_dir.iterate();392 var dir_it = dir.iterate();
393393
394 while (try dir_it.next()) |entry| {394 while (try dir_it.next()) |entry| {
395 const full_path = try std.fs.path.join(allocator, &[_][]const u8{ full_dir_name, entry.name });395 const full_path = try std.fs.path.join(allocator, &[_][]const u8{ full_dir_name, entry.name });
tools/update-license-headers.zig+4-4
...@@ -14,9 +14,9 @@ pub fn main() !void {...@@ -14,9 +14,9 @@ pub fn main() !void {
1414
15 const args = try std.process.argsAlloc(arena);15 const args = try std.process.argsAlloc(arena);
16 const path_to_walk = args[1];16 const path_to_walk = args[1];
17 const iterable_dir = try std.fs.cwd().openIterableDir(path_to_walk, .{});17 const dir = try std.fs.cwd().openDir(path_to_walk, .{ .iterate = true });
1818
19 var walker = try iterable_dir.walk(arena);19 var walker = try dir.walk(arena);
20 defer walker.deinit();20 defer walker.deinit();
2121
22 var buffer: [500]u8 = undefined;22 var buffer: [500]u8 = undefined;
...@@ -30,7 +30,7 @@ pub fn main() !void {...@@ -30,7 +30,7 @@ pub fn main() !void {
30 node.activate();30 node.activate();
31 defer node.end();31 defer node.end();
3232
33 const source = try iterable_dir.dir.readFileAlloc(arena, entry.path, 20 * 1024 * 1024);33 const source = try dir.readFileAlloc(arena, entry.path, 20 * 1024 * 1024);
34 if (!std.mem.startsWith(u8, source, expected_header)) {34 if (!std.mem.startsWith(u8, source, expected_header)) {
35 std.debug.print("no match: {s}\n", .{entry.path});35 std.debug.print("no match: {s}\n", .{entry.path});
36 continue;36 continue;
...@@ -42,6 +42,6 @@ pub fn main() !void {...@@ -42,6 +42,6 @@ pub fn main() !void {
42 std.mem.copy(u8, new_source, new_header);42 std.mem.copy(u8, new_source, new_header);
43 std.mem.copy(u8, new_source[new_header.len..], truncated_source);43 std.mem.copy(u8, new_source[new_header.len..], truncated_source);
4444
45 try iterable_dir.dir.writeFile(entry.path, new_source);45 try dir.writeFile(entry.path, new_source);
46 }46 }
47}47}
tools/update-linux-headers.zig+3-3
...@@ -190,14 +190,14 @@ pub fn main() !void {...@@ -190,14 +190,14 @@ pub fn main() !void {
190 try dir_stack.append(target_include_dir);190 try dir_stack.append(target_include_dir);
191191
192 while (dir_stack.popOrNull()) |full_dir_name| {192 while (dir_stack.popOrNull()) |full_dir_name| {
193 var iterable_dir = std.fs.cwd().openIterableDir(full_dir_name, .{}) catch |err| switch (err) {193 var dir = std.fs.cwd().openDir(full_dir_name, .{ .iterate = true }) catch |err| switch (err) {
194 error.FileNotFound => continue :search,194 error.FileNotFound => continue :search,
195 error.AccessDenied => continue :search,195 error.AccessDenied => continue :search,
196 else => return err,196 else => return err,
197 };197 };
198 defer iterable_dir.close();198 defer dir.close();
199199
200 var dir_it = iterable_dir.iterate();200 var dir_it = dir.iterate();
201201
202 while (try dir_it.next()) |entry| {202 while (try dir_it.next()) |entry| {
203 const full_path = try std.fs.path.join(arena, &[_][]const u8{ full_dir_name, entry.name });203 const full_path = try std.fs.path.join(arena, &[_][]const u8{ full_dir_name, entry.name });
tools/update_glibc.zig+5-5
...@@ -47,7 +47,7 @@ pub fn main() !void {...@@ -47,7 +47,7 @@ pub fn main() !void {
4747
48 const dest_dir_path = try std.fmt.allocPrint(arena, "{s}/lib/libc/glibc", .{zig_src_path});48 const dest_dir_path = try std.fmt.allocPrint(arena, "{s}/lib/libc/glibc", .{zig_src_path});
4949
50 var dest_dir = fs.cwd().openIterableDir(dest_dir_path, .{}) catch |err| {50 var dest_dir = fs.cwd().openDir(dest_dir_path, .{ .iterate = true }) catch |err| {
51 fatal("unable to open destination directory '{s}': {s}", .{51 fatal("unable to open destination directory '{s}': {s}", .{
52 dest_dir_path, @errorName(err),52 dest_dir_path, @errorName(err),
53 });53 });
...@@ -72,14 +72,14 @@ pub fn main() !void {...@@ -72,14 +72,14 @@ pub fn main() !void {
72 if (mem.endsWith(u8, entry.path, ext)) continue :walk;72 if (mem.endsWith(u8, entry.path, ext)) continue :walk;
73 }73 }
7474
75 glibc_src_dir.copyFile(entry.path, dest_dir.dir, entry.path, .{}) catch |err| {75 glibc_src_dir.copyFile(entry.path, dest_dir, entry.path, .{}) catch |err| {
76 log.warn("unable to copy '{s}/{s}' to '{s}/{s}': {s}", .{76 log.warn("unable to copy '{s}/{s}' to '{s}/{s}': {s}", .{
77 glibc_src_path, entry.path,77 glibc_src_path, entry.path,
78 dest_dir_path, entry.path,78 dest_dir_path, entry.path,
79 @errorName(err),79 @errorName(err),
80 });80 });
81 if (err == error.FileNotFound) {81 if (err == error.FileNotFound) {
82 try dest_dir.dir.deleteFile(entry.path);82 try dest_dir.deleteFile(entry.path);
83 }83 }
84 };84 };
85 }85 }
...@@ -88,7 +88,7 @@ pub fn main() !void {...@@ -88,7 +88,7 @@ pub fn main() !void {
88 // Warn about duplicated files inside glibc/include/* that can be omitted88 // Warn about duplicated files inside glibc/include/* that can be omitted
89 // because they are already in generic-glibc/*.89 // because they are already in generic-glibc/*.
9090
91 var include_dir = dest_dir.dir.openIterableDir("include", .{}) catch |err| {91 var include_dir = dest_dir.openDir("include", .{ .iterate = true }) catch |err| {
92 fatal("unable to open directory '{s}/include': {s}", .{92 fatal("unable to open directory '{s}/include': {s}", .{
93 dest_dir_path, @errorName(err),93 dest_dir_path, @errorName(err),
94 });94 });
...@@ -125,7 +125,7 @@ pub fn main() !void {...@@ -125,7 +125,7 @@ pub fn main() !void {
125 generic_glibc_path, entry.path, @errorName(e),125 generic_glibc_path, entry.path, @errorName(e),
126 }),126 }),
127 };127 };
128 const glibc_include_contents = include_dir.dir.readFileAlloc(128 const glibc_include_contents = include_dir.readFileAlloc(
129 arena,129 arena,
130 entry.path,130 entry.path,
131 max_file_size,131 max_file_size,
tools/update_spirv_features.zig+3-3
...@@ -226,7 +226,7 @@ pub fn main() !void {...@@ -226,7 +226,7 @@ pub fn main() !void {
226/// TODO: Unfortunately, neither repository contains a machine-readable list of extension dependencies.226/// TODO: Unfortunately, neither repository contains a machine-readable list of extension dependencies.
227fn gather_extensions(allocator: Allocator, spirv_registry_root: []const u8) ![]const []const u8 {227fn gather_extensions(allocator: Allocator, spirv_registry_root: []const u8) ![]const []const u8 {
228 const extensions_path = try fs.path.join(allocator, &.{ spirv_registry_root, "extensions" });228 const extensions_path = try fs.path.join(allocator, &.{ spirv_registry_root, "extensions" });
229 var extensions_dir = try fs.cwd().openIterableDir(extensions_path, .{});229 var extensions_dir = try fs.cwd().openDir(extensions_path, .{ .iterate = true });
230 defer extensions_dir.close();230 defer extensions_dir.close();
231231
232 var extensions = std.ArrayList([]const u8).init(allocator);232 var extensions = std.ArrayList([]const u8).init(allocator);
...@@ -235,7 +235,7 @@ fn gather_extensions(allocator: Allocator, spirv_registry_root: []const u8) ![]c...@@ -235,7 +235,7 @@ fn gather_extensions(allocator: Allocator, spirv_registry_root: []const u8) ![]c
235 while (try vendor_it.next()) |vendor_entry| {235 while (try vendor_it.next()) |vendor_entry| {
236 std.debug.assert(vendor_entry.kind == .directory); // If this fails, the structure of SPIRV-Registry has changed.236 std.debug.assert(vendor_entry.kind == .directory); // If this fails, the structure of SPIRV-Registry has changed.
237237
238 const vendor_dir = try extensions_dir.dir.openIterableDir(vendor_entry.name, .{});238 const vendor_dir = try extensions_dir.openDir(vendor_entry.name, .{ .iterate = true });
239 var ext_it = vendor_dir.iterate();239 var ext_it = vendor_dir.iterate();
240 while (try ext_it.next()) |ext_entry| {240 while (try ext_it.next()) |ext_entry| {
241 // There is both a HTML and asciidoc version of every spec (as well as some other directories),241 // There is both a HTML and asciidoc version of every spec (as well as some other directories),
...@@ -258,7 +258,7 @@ fn gather_extensions(allocator: Allocator, spirv_registry_root: []const u8) ![]c...@@ -258,7 +258,7 @@ fn gather_extensions(allocator: Allocator, spirv_registry_root: []const u8) ![]c
258 // SPV_EXT_name258 // SPV_EXT_name
259 // ```259 // ```
260260
261 const ext_spec = try vendor_dir.dir.readFileAlloc(allocator, ext_entry.name, std.math.maxInt(usize));261 const ext_spec = try vendor_dir.readFileAlloc(allocator, ext_entry.name, std.math.maxInt(usize));
262 const name_strings = "Name Strings";262 const name_strings = "Name Strings";
263263
264 const name_strings_offset = std.mem.indexOf(u8, ext_spec, name_strings) orelse return error.InvalidRegistry;264 const name_strings_offset = std.mem.indexOf(u8, ext_spec, name_strings) orelse return error.InvalidRegistry;