1const Dir = @This();
2const root = @import("root");
3
4const builtin = @import("builtin");
5const native_os = builtin.os.tag;
6
7const std = @import("../std.zig");
8const Io = std.Io;
9const File = Io.File;
10const assert = std.debug.assert;
11const Allocator = std.mem.Allocator;
12
13handle: Handle,
14
15pub const Handle = std.posix.fd_t;
16
17pub const path = std.fs.path;
18
19/// The maximum length of a file path that the operating system will accept.
20///
21/// Paths, including those returned from file system operations, may be longer
22/// than this length, but such paths cannot be successfully passed back in
23/// other file system operations. However, all path components returned by file
24/// system operations are assumed to fit into a `u8` array of this length.
25///
26/// The byte count includes room for a null sentinel byte.
27///
28/// * On Windows, `[]u8` file paths are encoded as
29/// [WTF-8](https://wtf-8.codeberg.page/).
30/// * On WASI, `[]u8` file paths are encoded as valid UTF-8.
31/// * On other platforms, `[]u8` file paths are opaque sequences of bytes with
32/// no particular encoding.
33pub const max_path_bytes = switch (native_os) {
34 .linux, .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos, .freebsd, .openbsd, .netbsd, .dragonfly, .haiku, .illumos, .plan9, .emscripten, .wasi, .serenity => std.posix.PATH_MAX,
35 // Each WTF-16LE code unit may be expanded to 3 WTF-8 bytes.
36 // If it would require 4 WTF-8 bytes, then there would be a surrogate
37 // pair in the WTF-16LE, and we (over)account 3 bytes for it that way.
38 // +1 for the null byte at the end, which can be encoded in 1 byte.
39 .windows => std.os.windows.PATH_MAX_WIDE * 3 + 1,
40 else => if (@hasDecl(root, "os") and @hasDecl(root.os, "PATH_MAX"))
41 root.os.PATH_MAX
42 else
43 @compileError("PATH_MAX not implemented for " ++ @tagName(native_os)),
44};
45
46/// This represents the maximum size of a `[]u8` file name component that
47/// the platform's common file systems support. File name components returned by file system
48/// operations are likely to fit into a `u8` array of this length, but
49/// (depending on the platform) this assumption may not hold for every configuration.
50/// The byte count does not include a null sentinel byte.
51/// On Windows, `[]u8` file name components are encoded as [WTF-8](https://wtf-8.codeberg.page/).
52/// On WASI, file name components are encoded as valid UTF-8.
53/// On other platforms, `[]u8` components are an opaque sequence of bytes with no particular encoding.
54pub const max_name_bytes = switch (native_os) {
55 .linux, .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos, .freebsd, .openbsd, .netbsd, .dragonfly, .illumos, .serenity, .psp => std.posix.NAME_MAX,
56 // Haiku's NAME_MAX includes the null terminator, so subtract one.
57 .haiku => std.posix.NAME_MAX - 1,
58 // Each WTF-16LE character may be expanded to 3 WTF-8 bytes.
59 // If it would require 4 WTF-8 bytes, then there would be a surrogate
60 // pair in the WTF-16LE, and we (over)account 3 bytes for it that way.
61 .windows => std.os.windows.NAME_MAX * 3,
62 // For WASI, the MAX_NAME will depend on the host OS, so it needs to be
63 // as large as the largest max_name_bytes (Windows) in order to work on any host OS.
64 // TODO determine if this is a reasonable approach
65 .wasi => std.os.windows.NAME_MAX * 3,
66 else => if (@hasDecl(root, "os") and @hasDecl(root.os, "NAME_MAX"))
67 root.os.NAME_MAX
68 else
69 @compileError("NAME_MAX not implemented for " ++ @tagName(native_os)),
70};
71
72pub const Entry = struct {
73 name: []const u8,
74 kind: File.Kind,
75 inode: File.INode,
76};
77
78/// Returns a handle to the current working directory.
79///
80/// It is not opened with iteration capability. Iterating over the result is
81/// illegal behavior.
82///
83/// Closing the returned `Dir` is checked illegal behavior.
84///
85/// On POSIX targets, this function is comptime-callable.
86///
87/// This function is overridable via `std.Options.cwd`.
88pub fn cwd() Dir {
89 const cwdFn = std.Options.cwd orelse return switch (native_os) {
90 .windows => .{ .handle = std.os.windows.peb().ProcessParameters.CurrentDirectory.Handle },
91 .wasi => .{ .handle = 3 }, // Expect the first preopen to be current working directory.
92 else => .{ .handle = std.posix.AT.FDCWD },
93 };
94 return cwdFn();
95}
96
97pub const Reader = struct {
98 dir: Dir,
99 state: State,
100 /// Stores I/O implementation specific data.
101 buffer: []align(@alignOf(usize)) u8,
102 /// Index of next entry in `buffer`.
103 index: usize,
104 /// Fill position of `buffer`.
105 end: usize,
106
107 /// A length for `buffer` that allows all implementations to function.
108 pub const min_buffer_len = switch (native_os) {
109 .linux => std.mem.alignForward(usize, @sizeOf(std.os.linux.dirent64), 8) +
110 std.mem.alignForward(usize, max_name_bytes, 8),
111 .windows => len: {
112 const max_info_len = @sizeOf(std.os.windows.FILE_BOTH_DIR_INFORMATION) + std.os.windows.NAME_MAX * 2;
113 const info_align = @alignOf(std.os.windows.FILE_BOTH_DIR_INFORMATION);
114 const reserved_len = std.mem.alignForward(usize, max_name_bytes, info_align) - max_info_len;
115 break :len std.mem.alignForward(usize, reserved_len, info_align) + max_info_len;
116 },
117 .wasi => @sizeOf(std.os.wasi.dirent_t) +
118 std.mem.alignForward(usize, max_name_bytes, @alignOf(std.os.wasi.dirent_t)),
119 .openbsd => std.c.S.BLKSIZE,
120 else => if (builtin.link_libc) @sizeOf(std.c.dirent) else std.mem.alignForward(usize, max_name_bytes, @alignOf(usize)),
121 };
122
123 pub const State = enum {
124 /// Indicates the next call to `read` should rewind and start over the
125 /// directory listing.
126 reset,
127 reading,
128 finished,
129 };
130
131 pub const Error = error{
132 AccessDenied,
133 PermissionDenied,
134 SystemResources,
135 } || Io.UnexpectedError || Io.Cancelable;
136
137 /// Asserts that `buffer` has length at least `min_buffer_len`.
138 pub fn init(dir: Dir, buffer: []align(@alignOf(usize)) u8) Reader {
139 assert(buffer.len >= min_buffer_len);
140 return .{
141 .dir = dir,
142 .state = .reset,
143 .index = 0,
144 .end = 0,
145 .buffer = buffer,
146 };
147 }
148
149 /// All `Entry.name` are invalidated with the next call to `read` or
150 /// `next`.
151 pub fn read(r: *Reader, io: Io, buffer: []Entry) Error!usize {
152 return io.vtable.dirRead(io.userdata, r, buffer);
153 }
154
155 /// `Entry.name` is invalidated with the next call to `read` or `next`.
156 pub fn next(r: *Reader, io: Io) Error!?Entry {
157 var buffer: [1]Entry = undefined;
158 while (true) {
159 const n = try read(r, io, &buffer);
160 if (n == 1) return buffer[0];
161 if (r.state == .finished) return null;
162 }
163 }
164
165 pub fn reset(r: *Reader) void {
166 r.state = .reset;
167 r.index = 0;
168 r.end = 0;
169 }
170};
171
172/// This API is designed for convenience rather than performance:
173/// * It chooses a buffer size rather than allowing the user to provide one.
174/// * It is movable by only requesting one `Entry` at a time from the `Io`
175/// implementation rather than doing batch operations.
176///
177/// Still, it will do a decent job of minimizing syscall overhead. For a
178/// lower level abstraction, see `Reader`. For a higher level abstraction,
179/// see `Walker`.
180pub const Iterator = struct {
181 reader: Reader,
182 reader_buffer: [reader_buffer_len]u8 align(@alignOf(usize)),
183
184 pub const reader_buffer_len = 2048;
185
186 comptime {
187 assert(reader_buffer_len >= Reader.min_buffer_len);
188 }
189
190 pub const Error = Reader.Error;
191
192 pub fn init(dir: Dir, reader_state: Reader.State) Iterator {
193 return .{
194 .reader = .{
195 .dir = dir,
196 .state = reader_state,
197 .index = 0,
198 .end = 0,
199 .buffer = undefined,
200 },
201 .reader_buffer = undefined,
202 };
203 }
204
205 pub fn next(it: *Iterator, io: Io) Error!?Entry {
206 it.reader.buffer = &it.reader_buffer;
207 return it.reader.next(io);
208 }
209};
210
211pub fn iterate(dir: Dir) Iterator {
212 return .init(dir, .reset);
213}
214
215/// Like `iterate`, but will not reset the directory cursor before the first
216/// iteration. This should only be used in cases where it is known that the
217/// `Dir` has not had its cursor modified yet (e.g. it was just opened).
218pub fn iterateAssumeFirstIteration(dir: Dir) Iterator {
219 return .init(dir, .reading);
220}
221
222pub const SelectiveWalker = struct {
223 stack: std.ArrayList(StackItem),
224 name_buffer: std.ArrayList(u8),
225 allocator: Allocator,
226
227 pub const Error = Iterator.Error || Allocator.Error;
228
229 const StackItem = struct {
230 iter: Iterator,
231 dirname_len: usize,
232 };
233
234 /// After each call to this function, and on deinit(), the memory returned
235 /// from this function becomes invalid. A copy must be made in order to keep
236 /// a reference to the path.
237 pub fn next(self: *SelectiveWalker, io: Io) Error!?Walker.Entry {
238 while (self.stack.items.len > 0) {
239 const top = &self.stack.items[self.stack.items.len - 1];
240 var dirname_len = top.dirname_len;
241 if (top.iter.next(io) catch |err| {
242 // If we get an error, then we want the user to be able to continue
243 // walking if they want, which means that we need to pop the directory
244 // that errored from the stack. Otherwise, all future `next` calls would
245 // likely just fail with the same error.
246 var item = self.stack.pop().?;
247 if (self.stack.items.len != 0) {
248 item.iter.reader.dir.close(io);
249 }
250 return err;
251 }) |entry| {
252 self.name_buffer.shrinkRetainingCapacity(dirname_len);
253 if (self.name_buffer.items.len != 0) {
254 try self.name_buffer.append(self.allocator, path.sep);
255 dirname_len += 1;
256 }
257 try self.name_buffer.ensureUnusedCapacity(self.allocator, entry.name.len + 1);
258 self.name_buffer.appendSliceAssumeCapacity(entry.name);
259 self.name_buffer.appendAssumeCapacity(0);
260 const walker_entry: Walker.Entry = .{
261 .dir = top.iter.reader.dir,
262 .basename = self.name_buffer.items[dirname_len .. self.name_buffer.items.len - 1 :0],
263 .path = self.name_buffer.items[0 .. self.name_buffer.items.len - 1 :0],
264 .kind = entry.kind,
265 };
266 return walker_entry;
267 } else {
268 var item = self.stack.pop().?;
269 if (self.stack.items.len != 0) {
270 item.iter.reader.dir.close(io);
271 }
272 }
273 }
274 return null;
275 }
276
277 /// Traverses into the directory, continuing walking one level down.
278 pub fn enter(self: *SelectiveWalker, io: Io, entry: Walker.Entry) !void {
279 if (entry.kind != .directory) {
280 @branchHint(.cold);
281 return;
282 }
283
284 var new_dir = entry.dir.openDir(io, entry.basename, .{ .iterate = true }) catch |err| {
285 switch (err) {
286 error.NameTooLong => unreachable,
287 else => |e| return e,
288 }
289 };
290 errdefer new_dir.close(io);
291
292 try self.stack.append(self.allocator, .{
293 .iter = new_dir.iterateAssumeFirstIteration(),
294 .dirname_len = self.name_buffer.items.len - 1,
295 });
296 }
297
298 pub fn deinit(self: *SelectiveWalker) void {
299 self.name_buffer.deinit(self.allocator);
300 self.stack.deinit(self.allocator);
301 }
302
303 /// Leaves the current directory, continuing walking one level up.
304 /// If the current entry is a directory entry, then the "current directory"
305 /// will pertain to that entry if `enter` is called before `leave`.
306 pub fn leave(self: *SelectiveWalker, io: Io) void {
307 var item = self.stack.pop().?;
308 if (self.stack.items.len != 0) {
309 @branchHint(.likely);
310 item.iter.reader.dir.close(io);
311 }
312 }
313};
314
315/// Recursively iterates over a directory, but requires the user to
316/// opt-in to recursing into each directory entry.
317///
318/// `dir` must have been opened with `OpenOptions.iterate` set to `true`.
319///
320/// `Walker.deinit` releases allocated memory and directory handles.
321///
322/// The order of returned file system entries is undefined.
323///
324/// `dir` will not be closed after walking it.
325///
326/// See also `walk`.
327pub fn walkSelectively(dir: Dir, allocator: Allocator) !SelectiveWalker {
328 var stack: std.ArrayList(SelectiveWalker.StackItem) = .empty;
329
330 try stack.append(allocator, .{
331 .iter = dir.iterate(),
332 .dirname_len = 0,
333 });
334
335 return .{
336 .stack = stack,
337 .name_buffer = .empty,
338 .allocator = allocator,
339 };
340}
341
342pub const Walker = struct {
343 inner: SelectiveWalker,
344
345 pub const Entry = struct {
346 /// The containing directory. This can be used to operate directly on `basename`
347 /// rather than `path`, avoiding `error.NameTooLong` for deeply nested paths.
348 /// The directory remains open until `next` or `deinit` is called.
349 dir: Dir,
350 basename: [:0]const u8,
351 path: [:0]const u8,
352 kind: File.Kind,
353
354 /// Returns the depth of the entry relative to the initial directory.
355 /// Returns 1 for a direct child of the initial directory, 2 for an entry
356 /// within a direct child of the initial directory, etc.
357 pub fn depth(self: Walker.Entry) usize {
358 return std.mem.countScalar(u8, self.path, path.sep) + 1;
359 }
360 };
361
362 /// After each call to this function, and on deinit(), the memory returned
363 /// from this function becomes invalid. A copy must be made in order to keep
364 /// a reference to the path.
365 pub fn next(self: *Walker, io: Io) !?Walker.Entry {
366 const entry = try self.inner.next(io);
367 if (entry != null and entry.?.kind == .directory) {
368 try self.inner.enter(io, entry.?);
369 }
370 return entry;
371 }
372
373 pub fn deinit(self: *Walker) void {
374 self.inner.deinit();
375 }
376
377 /// Leaves the current directory, continuing walking one level up.
378 /// If the current entry is a directory entry, then the "current directory"
379 /// is the directory pertaining to the current entry.
380 pub fn leave(self: *Walker, io: Io) void {
381 self.inner.leave(io);
382 }
383};
384
385/// Recursively iterates over a directory.
386///
387/// `dir` must have been opened with `OpenOptions.iterate` set to `true`.
388///
389/// `Walker.deinit` releases allocated memory and directory handles.
390///
391/// The order of returned file system entries is undefined.
392///
393/// `dir` will not be closed after walking it.
394///
395/// See also:
396/// * `walkSelectively`
397pub fn walk(dir: Dir, allocator: Allocator) Allocator.Error!Walker {
398 return .{ .inner = try walkSelectively(dir, allocator) };
399}
400
401pub const PathNameError = error{
402 /// Returned when an insufficient buffer is provided that cannot fit the
403 /// path name.
404 NameTooLong,
405 /// File system cannot encode the requested file name bytes.
406 /// Could be due to invalid WTF-8 on Windows, invalid UTF-8 on WASI,
407 /// invalid characters on Windows, etc. Filesystem and operating specific.
408 BadPathName,
409};
410
411pub const AccessError = error{
412 /// The requested `AccessOptions` would be denied to the file, or search
413 /// permission is denied for one of the directories in the path prefix.
414 AccessDenied,
415 /// Write permission was requested but the file is immutable.
416 PermissionDenied,
417 FileNotFound,
418 InputOutput,
419 SystemResources,
420 FileBusy,
421 SymLinkLoop,
422 ReadOnlyFileSystem,
423} || PathNameError || Io.Cancelable || Io.UnexpectedError;
424
425pub const AccessOptions = packed struct {
426 follow_symlinks: bool = true,
427 read: bool = false,
428 write: bool = false,
429 execute: bool = false,
430};
431
432/// Test accessing `sub_path`.
433///
434/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
435/// On WASI, `sub_path` should be encoded as valid UTF-8.
436/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
437///
438/// Be careful of Time-Of-Check-Time-Of-Use race conditions when using this
439/// function. For example, instead of testing if a file exists and then opening
440/// it, just open it and handle the error for file not found.
441pub fn access(dir: Dir, io: Io, sub_path: []const u8, options: AccessOptions) AccessError!void {
442 return io.vtable.dirAccess(io.userdata, dir, sub_path, options);
443}
444
445pub fn accessAbsolute(io: Io, absolute_path: []const u8, options: AccessOptions) AccessError!void {
446 assert(path.isAbsolute(absolute_path));
447 return access(.cwd(), io, absolute_path, options);
448}
449
450pub const OpenError = error{
451 FileNotFound,
452 NotDir,
453 AccessDenied,
454 PermissionDenied,
455 SymLinkLoop,
456 ProcessFdQuotaExceeded,
457 SystemFdQuotaExceeded,
458 NoDevice,
459 SystemResources,
460 /// On Windows, `\\server` or `\\server\share` was not found.
461 NetworkNotFound,
462} || PathNameError || Io.Cancelable || Io.UnexpectedError;
463
464pub const OpenOptions = struct {
465 /// `true` means the opened directory can be used as the `Dir` parameter
466 /// for functions which operate based on an open directory handle. When `false`,
467 /// such operations are Illegal Behavior.
468 access_sub_paths: bool = true,
469 /// `true` means the opened directory can be scanned for the files and sub-directories
470 /// of the result. It means the `iterate` function can be called.
471 iterate: bool = false,
472 /// `false` means it won't dereference the symlinks.
473 follow_symlinks: bool = true,
474};
475
476/// Opens a directory at the given path. The directory is a system resource that remains
477/// open until `close` is called on the result.
478///
479/// The directory cannot be iterated unless the `iterate` option is set to `true`.
480///
481/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
482/// On WASI, `sub_path` should be encoded as valid UTF-8.
483/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
484pub fn openDir(dir: Dir, io: Io, sub_path: []const u8, options: OpenOptions) OpenError!Dir {
485 return io.vtable.dirOpenDir(io.userdata, dir, sub_path, options);
486}
487
488pub fn openDirAbsolute(io: Io, absolute_path: []const u8, options: OpenOptions) OpenError!Dir {
489 assert(path.isAbsolute(absolute_path));
490 return openDir(.cwd(), io, absolute_path, options);
491}
492
493pub fn close(dir: Dir, io: Io) void {
494 return io.vtable.dirClose(io.userdata, (&dir)[0..1]);
495}
496
497pub fn closeMany(io: Io, dirs: []const Dir) void {
498 return io.vtable.dirClose(io.userdata, dirs);
499}
500
501pub const OpenFileOptions = struct {
502 mode: Mode = .read_only,
503 /// Determines the behavior when opening a path that refers to a directory.
504 ///
505 /// If set to true, directories may be opened, but `error.IsDir` is still
506 /// possible in certain scenarios, e.g. attempting to open a directory with
507 /// write permissions.
508 ///
509 /// If set to false, `error.IsDir` will always be returned when opening a directory.
510 ///
511 /// When set to false:
512 /// * On Windows, the behavior is implemented without any extra syscalls.
513 /// * On other operating systems, the behavior is implemented with an additional
514 /// `fstat` syscall.
515 allow_directory: bool = true,
516 /// Indicates intent for only some operations to be performed on this
517 /// opened file:
518 /// * `close`
519 /// * `stat`
520 /// On Linux and FreeBSD, this corresponds to `std.posix.O.PATH`.
521 path_only: bool = false,
522 /// Open the file with an advisory lock to coordinate with other processes
523 /// accessing it at the same time. An exclusive lock will prevent other
524 /// processes from acquiring a lock. A shared lock will prevent other
525 /// processes from acquiring a exclusive lock, but does not prevent
526 /// other process from getting their own shared locks.
527 ///
528 /// The lock is advisory, except on Linux in very specific circumstances[1].
529 /// This means that a process that does not respect the locking API can still get access
530 /// to the file, despite the lock.
531 ///
532 /// On these operating systems, the lock is acquired atomically with
533 /// opening the file:
534 /// * Darwin
535 /// * DragonFlyBSD
536 /// * FreeBSD
537 /// * Haiku
538 /// * NetBSD
539 /// * OpenBSD
540 /// On these operating systems, the lock is acquired via a separate syscall
541 /// after opening the file:
542 /// * Linux
543 /// * Windows
544 ///
545 /// [1]: https://www.kernel.org/doc/Documentation/filesystems/mandatory-locking.txt
546 lock: File.Lock = .none,
547 /// Sets whether or not to wait until the file is locked to return. If set to true,
548 /// `error.WouldBlock` will be returned. Otherwise, the file will wait until the file
549 /// is available to proceed.
550 lock_nonblocking: bool = false,
551 /// Set this to allow the opened file to automatically become the
552 /// controlling TTY for the current process.
553 allow_ctty: bool = false,
554 follow_symlinks: bool = true,
555 /// If supported by the operating system, attempted path resolution that
556 /// would escape the directory instead returns `error.AccessDenied`. If
557 /// unsupported, this option is ignored.
558 resolve_beneath: bool = false,
559
560 pub const Mode = enum { read_only, write_only, read_write };
561
562 pub fn isRead(self: OpenFileOptions) bool {
563 return self.mode != .write_only;
564 }
565
566 pub fn isWrite(self: OpenFileOptions) bool {
567 return self.mode != .read_only;
568 }
569};
570
571/// Opens a file for reading or writing, without attempting to create a new file.
572///
573/// To create a new file, see `createFile`.
574///
575/// Allocates a resource to be released with `File.close`.
576///
577/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
578/// On WASI, `sub_path` should be encoded as valid UTF-8.
579/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
580pub fn openFile(dir: Dir, io: Io, sub_path: []const u8, options: OpenFileOptions) File.OpenError!File {
581 return io.vtable.dirOpenFile(io.userdata, dir, sub_path, options);
582}
583
584pub fn openFileAbsolute(io: Io, absolute_path: []const u8, options: OpenFileOptions) File.OpenError!File {
585 assert(path.isAbsolute(absolute_path));
586 return openFile(.cwd(), io, absolute_path, options);
587}
588
589pub const CreateFileOptions = struct {
590 /// Whether the file will be created with read access.
591 read: bool = false,
592 /// If the file already exists, and is a regular file, and the access
593 /// mode allows writing, it will be truncated to length 0.
594 truncate: bool = true,
595 /// Ensures that this open call creates the file, otherwise causes
596 /// `error.PathAlreadyExists` to be returned.
597 exclusive: bool = false,
598 /// Open the file with an advisory lock to coordinate with other processes
599 /// accessing it at the same time. An exclusive lock will prevent other
600 /// processes from acquiring a lock. A shared lock will prevent other
601 /// processes from acquiring a exclusive lock, but does not prevent
602 /// other process from getting their own shared locks.
603 ///
604 /// The lock is advisory, except on Linux in very specific circumstances[1].
605 /// This means that a process that does not respect the locking API can still get access
606 /// to the file, despite the lock.
607 ///
608 /// On these operating systems, the lock is acquired atomically with
609 /// opening the file:
610 /// * Darwin
611 /// * DragonFlyBSD
612 /// * FreeBSD
613 /// * Haiku
614 /// * NetBSD
615 /// * OpenBSD
616 /// On these operating systems, the lock is acquired via a separate syscall
617 /// after opening the file:
618 /// * Linux
619 /// * Windows
620 ///
621 /// [1]: https://www.kernel.org/doc/Documentation/filesystems/mandatory-locking.txt
622 lock: File.Lock = .none,
623 /// Sets whether or not to wait until the file is locked to return. If set to true,
624 /// `error.WouldBlock` will be returned. Otherwise, the file will wait until the file
625 /// is available to proceed.
626 lock_nonblocking: bool = false,
627 permissions: Permissions = .default_file,
628 /// If supported by the operating system, attempted path resolution that
629 /// would escape the directory instead returns `error.AccessDenied`. If
630 /// unsupported, this option is ignored.
631 resolve_beneath: bool = false,
632};
633
634/// Creates, opens, or overwrites a file with write access.
635///
636/// Allocates a resource to be dellocated with `File.close`.
637///
638/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
639/// On WASI, `sub_path` should be encoded as valid UTF-8.
640/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
641pub fn createFile(dir: Dir, io: Io, sub_path: []const u8, flags: CreateFileOptions) File.OpenError!File {
642 return io.vtable.dirCreateFile(io.userdata, dir, sub_path, flags);
643}
644
645pub fn createFileAbsolute(io: Io, absolute_path: []const u8, flags: CreateFileOptions) File.OpenError!File {
646 return createFile(.cwd(), io, absolute_path, flags);
647}
648
649pub const WriteFileOptions = struct {
650 /// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
651 /// On WASI, `sub_path` should be encoded as valid UTF-8.
652 /// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
653 sub_path: []const u8,
654 data: []const u8,
655 flags: CreateFileOptions = .{},
656};
657
658pub const WriteFileError = File.Writer.Error || File.OpenError;
659
660/// Writes content to the file system, using the file creation flags provided.
661pub fn writeFile(dir: Dir, io: Io, options: WriteFileOptions) WriteFileError!void {
662 var file = try dir.createFile(io, options.sub_path, options.flags);
663 defer file.close(io);
664 try file.writeStreamingAll(io, options.data);
665}
666
667pub const PrevStatus = enum {
668 stale,
669 fresh,
670};
671
672pub const UpdateFileError = File.OpenError;
673
674/// Check the file size, mtime, and permissions of `source_path` and `dest_path`. If
675/// they are equal, does nothing. Otherwise, atomically copies `source_path` to
676/// `dest_path`, creating the parent directory hierarchy as needed. The
677/// destination file gains the mtime, atime, and permissions of the source file so
678/// that the next call to `updateFile` will not need a copy.
679///
680/// Returns the previous status of the file before updating.
681///
682/// * On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
683/// * On WASI, both paths should be encoded as valid UTF-8.
684/// * On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
685pub fn updateFile(
686 source_dir: Dir,
687 io: Io,
688 source_path: []const u8,
689 dest_dir: Dir,
690 /// If directories in this path do not exist, they are created.
691 dest_path: []const u8,
692 options: CopyFileOptions,
693) !PrevStatus {
694 var src_file = try source_dir.openFile(io, source_path, .{});
695 defer src_file.close(io);
696
697 const src_stat = try src_file.stat(io);
698 const actual_permissions = options.permissions orelse src_stat.permissions;
699 check_dest_stat: {
700 const dest_stat = blk: {
701 var dest_file = dest_dir.openFile(io, dest_path, .{}) catch |err| switch (err) {
702 error.FileNotFound => break :check_dest_stat,
703 else => |e| return e,
704 };
705 defer dest_file.close(io);
706
707 break :blk try dest_file.stat(io);
708 };
709
710 if (src_stat.size == dest_stat.size and
711 src_stat.mtime.nanoseconds == dest_stat.mtime.nanoseconds and
712 actual_permissions == dest_stat.permissions)
713 {
714 return .fresh;
715 }
716 }
717
718 var atomic_file = try dest_dir.createFileAtomic(io, dest_path, .{
719 .permissions = actual_permissions,
720 .make_path = true,
721 .replace = true,
722 });
723 defer atomic_file.deinit(io);
724
725 var buffer: [1024]u8 = undefined; // Used only when direct fd-to-fd is not available.
726 var file_writer = atomic_file.file.writer(io, &buffer);
727
728 var src_reader: File.Reader = .initSize(src_file, io, &.{}, src_stat.size);
729 const dest_writer = &file_writer.interface;
730
731 _ = dest_writer.sendFileAll(&src_reader, .unlimited) catch |err| switch (err) {
732 error.ReadFailed => return src_reader.err.?,
733 error.WriteFailed => return file_writer.err.?,
734 };
735 try file_writer.flush();
736 try file_writer.file.setTimestamps(io, .{
737 .access_timestamp = .init(src_stat.atime),
738 .modify_timestamp = .init(src_stat.mtime),
739 });
740 try atomic_file.replace(io);
741 return .stale;
742}
743
744pub const ReadFileError = File.OpenError || File.Reader.Error;
745
746/// Read all of file contents using a preallocated buffer.
747///
748/// The returned slice has the same pointer as `buffer`. If the length matches `buffer.len`
749/// the situation is ambiguous. It could either mean that the entire file was read, and
750/// it exactly fits the buffer, or it could mean the buffer was not big enough for the
751/// entire file.
752///
753/// * On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
754/// * On WASI, `file_path` should be encoded as valid UTF-8.
755/// * On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
756pub fn readFile(dir: Dir, io: Io, file_path: []const u8, buffer: []u8) ReadFileError![]u8 {
757 var file = try dir.openFile(io, file_path, .{
758 // We can take advantage of this on Windows since it doesn't involve any extra syscalls,
759 // so we can get error.IsDir during open rather than during the read.
760 .allow_directory = if (native_os == .windows) false else true,
761 });
762 defer file.close(io);
763
764 var reader = file.reader(io, &.{});
765 const n = reader.interface.readSliceShort(buffer) catch |err| switch (err) {
766 error.ReadFailed => return reader.err.?,
767 };
768
769 return buffer[0..n];
770}
771
772pub const CreateDirError = error{
773 /// In WASI, this error may occur when the file descriptor does
774 /// not hold the required rights to create a new directory relative to it.
775 AccessDenied,
776 PermissionDenied,
777 DiskQuota,
778 PathAlreadyExists,
779 SymLinkLoop,
780 LinkQuotaExceeded,
781 FileNotFound,
782 SystemResources,
783 NoSpaceLeft,
784 NotDir,
785 ReadOnlyFileSystem,
786 NoDevice,
787 /// On Windows, `\\server` or `\\server\share` was not found.
788 NetworkNotFound,
789} || PathNameError || Io.Cancelable || Io.UnexpectedError;
790
791/// Creates a single directory with a relative or absolute path.
792///
793/// * On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
794/// * On WASI, `sub_path` should be encoded as valid UTF-8.
795/// * On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
796///
797/// Related:
798/// * `createDirPath`
799/// * `createDirAbsolute`
800pub fn createDir(dir: Dir, io: Io, sub_path: []const u8, permissions: Permissions) CreateDirError!void {
801 return io.vtable.dirCreateDir(io.userdata, dir, sub_path, permissions);
802}
803
804/// Create a new directory, based on an absolute path.
805///
806/// Asserts that the path is absolute. See `createDir` for a function that
807/// operates on both absolute and relative paths.
808///
809/// On Windows, `absolute_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
810/// On WASI, `absolute_path` should be encoded as valid UTF-8.
811/// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding.
812pub fn createDirAbsolute(io: Io, absolute_path: []const u8, permissions: Permissions) CreateDirError!void {
813 assert(path.isAbsolute(absolute_path));
814 return createDir(.cwd(), io, absolute_path, permissions);
815}
816
817test createDirAbsolute {}
818
819pub const CreateDirPathError = CreateDirError || StatFileError;
820
821/// Creates parent directories with default permissions as necessary to ensure
822/// `sub_path` exists as a directory.
823///
824/// Returns success if the path already exists and is a directory.
825///
826/// This function may not be atomic. If it returns an error, the file system
827/// may have been modified.
828///
829/// Fails on an empty path with `error.BadPathName` as that is not a path that
830/// can be created.
831///
832/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
833/// On WASI, `sub_path` should be encoded as valid UTF-8.
834/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
835///
836/// Paths containing `..` components are handled differently depending on the platform:
837/// - On Windows, `..` are resolved before the path is passed to NtCreateFile, meaning
838/// a `sub_path` like "first/../second" will resolve to "second" and only a
839/// `./second` directory will be created.
840/// - On other platforms, `..` are not resolved before the path is passed to `mkdirat`,
841/// meaning a `sub_path` like "first/../second" will create both a `./first`
842/// and a `./second` directory.
843///
844/// See also:
845/// * `createDirPathStatus`
846pub fn createDirPath(dir: Dir, io: Io, sub_path: []const u8) CreateDirPathError!void {
847 _ = try io.vtable.dirCreateDirPath(io.userdata, dir, sub_path, .default_dir);
848}
849
850pub const CreatePathStatus = enum { existed, created };
851
852/// Same as `createDirPath` except returns whether the path already existed or was
853/// successfully created.
854pub fn createDirPathStatus(dir: Dir, io: Io, sub_path: []const u8, permissions: Permissions) CreateDirPathError!CreatePathStatus {
855 return io.vtable.dirCreateDirPath(io.userdata, dir, sub_path, permissions);
856}
857
858pub const CreateDirPathOpenError = CreateDirError || OpenError || StatFileError;
859
860pub const CreateDirPathOpenOptions = struct {
861 open_options: OpenOptions = .{},
862 permissions: Permissions = .default_dir,
863};
864
865/// Performs the equivalent of `createDirPath` followed by `openDir`, atomically if possible.
866///
867/// When this operation is canceled, it may leave the file system in a
868/// partially modified state.
869///
870/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
871/// On WASI, `sub_path` should be encoded as valid UTF-8.
872/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
873pub fn createDirPathOpen(dir: Dir, io: Io, sub_path: []const u8, options: CreateDirPathOpenOptions) CreateDirPathOpenError!Dir {
874 return io.vtable.dirCreateDirPathOpen(io.userdata, dir, sub_path, options.permissions, options.open_options);
875}
876
877pub const Stat = File.Stat;
878pub const StatError = File.StatError;
879
880pub fn stat(dir: Dir, io: Io) StatError!Stat {
881 return io.vtable.dirStat(io.userdata, dir);
882}
883
884pub const StatFileError = File.OpenError || File.StatError;
885
886pub const StatFileOptions = struct {
887 follow_symlinks: bool = true,
888};
889
890/// Returns metadata for a file inside the directory.
891///
892/// On Windows, this requires three syscalls. On other operating systems, it
893/// only takes one.
894///
895/// Symlinks are followed.
896///
897/// `sub_path` may be absolute, in which case `self` is ignored.
898///
899/// * On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
900/// * On WASI, `sub_path` should be encoded as valid UTF-8.
901/// * On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
902pub fn statFile(dir: Dir, io: Io, sub_path: []const u8, options: StatFileOptions) StatFileError!Stat {
903 return io.vtable.dirStatFile(io.userdata, dir, sub_path, options);
904}
905
906pub const RealPathError = File.RealPathError;
907
908/// Obtains the canonicalized absolute path name of `sub_path` relative to this
909/// `Dir`. If `sub_path` is absolute, ignores this `Dir` handle and obtains the
910/// canonicalized absolute pathname of `sub_path` argument.
911///
912/// This function has limited platform support, and using it can lead to
913/// unnecessary failures and race conditions. It is generally advisable to
914/// avoid this function entirely.
915pub fn realPath(dir: Dir, io: Io, out_buffer: []u8) RealPathError!usize {
916 return io.vtable.dirRealPath(io.userdata, dir, out_buffer);
917}
918
919pub const RealPathFileError = RealPathError || PathNameError;
920
921/// Obtains the canonicalized absolute path name of `sub_path` relative to this
922/// `Dir`. If `sub_path` is absolute, ignores this `Dir` handle and obtains the
923/// canonicalized absolute pathname of `sub_path` argument.
924///
925/// This function has limited platform support, and using it can lead to
926/// unnecessary failures and race conditions. It is generally advisable to
927/// avoid this function entirely.
928///
929/// See also:
930/// * `realPathFileAlloc`.
931/// * `realPathFileAbsolute`.
932pub fn realPathFile(dir: Dir, io: Io, sub_path: []const u8, out_buffer: []u8) RealPathFileError!usize {
933 return io.vtable.dirRealPathFile(io.userdata, dir, sub_path, out_buffer);
934}
935
936pub const RealPathFileAllocError = RealPathFileError || Allocator.Error;
937
938/// Same as `realPathFile` except allocates result.
939///
940/// This function has limited platform support, and using it can lead to
941/// unnecessary failures and race conditions. It is generally advisable to
942/// avoid this function entirely.
943///
944/// See also:
945/// * `realPathFile`.
946/// * `realPathFileAbsolute`.
947pub fn realPathFileAlloc(dir: Dir, io: Io, sub_path: []const u8, allocator: Allocator) RealPathFileAllocError![:0]u8 {
948 var buffer: [max_path_bytes]u8 = undefined;
949 const n = try realPathFile(dir, io, sub_path, &buffer);
950 return allocator.dupeSentinel(u8, buffer[0..n], 0);
951}
952
953/// Same as `realPathFile` except `absolute_path` is asserted to be an absolute
954/// path.
955///
956/// This function has limited platform support, and using it can lead to
957/// unnecessary failures and race conditions. It is generally advisable to
958/// avoid this function entirely.
959///
960/// See also:
961/// * `realPathFile`.
962/// * `realPathFileAlloc`.
963pub fn realPathFileAbsolute(io: Io, absolute_path: []const u8, out_buffer: []u8) RealPathFileError!usize {
964 assert(path.isAbsolute(absolute_path));
965 return io.vtable.dirRealPathFile(io.userdata, .cwd(), absolute_path, out_buffer);
966}
967
968/// Same as `realPathFileAbsolute` except allocates result.
969///
970/// This function has limited platform support, and using it can lead to
971/// unnecessary failures and race conditions. It is generally advisable to
972/// avoid this function entirely.
973///
974/// See also:
975/// * `realPathFileAbsolute`.
976/// * `realPathFile`.
977pub fn realPathFileAbsoluteAlloc(io: Io, absolute_path: []const u8, allocator: Allocator) RealPathFileAllocError![:0]u8 {
978 var buffer: [max_path_bytes]u8 = undefined;
979 const n = try realPathFileAbsolute(io, absolute_path, &buffer);
980 return allocator.dupeSentinel(u8, buffer[0..n], 0);
981}
982
983pub const DeleteFileError = error{
984 FileNotFound,
985 /// In WASI, this error may occur when the file descriptor does
986 /// not hold the required rights to unlink a resource by path relative to it.
987 AccessDenied,
988 PermissionDenied,
989 FileBusy,
990 FileSystem,
991 IsDir,
992 SymLinkLoop,
993 NotDir,
994 SystemResources,
995 ReadOnlyFileSystem,
996 /// On Windows, `\\server` or `\\server\share` was not found.
997 NetworkNotFound,
998} || PathNameError || Io.Cancelable || Io.UnexpectedError;
999
1000/// Delete a file name and possibly the file it refers to, based on an open directory handle.
1001///
1002/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1003/// On WASI, `sub_path` should be encoded as valid UTF-8.
1004/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
1005///
1006/// Asserts that the path parameter has no null bytes.
1007pub fn deleteFile(dir: Dir, io: Io, sub_path: []const u8) DeleteFileError!void {
1008 return io.vtable.dirDeleteFile(io.userdata, dir, sub_path);
1009}
1010
1011pub fn deleteFileAbsolute(io: Io, absolute_path: []const u8) DeleteFileError!void {
1012 assert(path.isAbsolute(absolute_path));
1013 return deleteFile(.cwd(), io, absolute_path);
1014}
1015
1016test deleteFileAbsolute {}
1017
1018pub const DeleteDirError = error{
1019 DirNotEmpty,
1020 FileNotFound,
1021 AccessDenied,
1022 PermissionDenied,
1023 FileBusy,
1024 FileSystem,
1025 SymLinkLoop,
1026 NotDir,
1027 SystemResources,
1028 ReadOnlyFileSystem,
1029 /// On Windows, `\\server` or `\\server\share` was not found.
1030 NetworkNotFound,
1031} || PathNameError || Io.Cancelable || Io.UnexpectedError;
1032
1033/// Returns `error.DirNotEmpty` if the directory is not empty.
1034///
1035/// To delete a directory recursively, see `deleteTree`.
1036///
1037/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1038/// On WASI, `sub_path` should be encoded as valid UTF-8.
1039/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
1040pub fn deleteDir(dir: Dir, io: Io, sub_path: []const u8) DeleteDirError!void {
1041 return io.vtable.dirDeleteDir(io.userdata, dir, sub_path);
1042}
1043
1044/// Same as `deleteDir` except the path is absolute.
1045///
1046/// On Windows, `dir_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1047/// On WASI, `dir_path` should be encoded as valid UTF-8.
1048/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding.
1049pub fn deleteDirAbsolute(io: Io, absolute_path: []const u8) DeleteDirError!void {
1050 assert(path.isAbsolute(absolute_path));
1051 return deleteDir(.cwd(), io, absolute_path);
1052}
1053
1054pub const RenameError = error{
1055 /// In WASI, this error may occur when the file descriptor does
1056 /// not hold the required rights to rename a resource by path relative to it.
1057 AccessDenied,
1058 /// Attempted to replace a nonempty directory.
1059 DirNotEmpty,
1060 PermissionDenied,
1061 /// The file attempted to be moved or replaced is a running executable.
1062 FileBusy,
1063 DiskQuota,
1064 IsDir,
1065 SymLinkLoop,
1066 LinkQuotaExceeded,
1067 FileNotFound,
1068 NotDir,
1069 SystemResources,
1070 NoSpaceLeft,
1071 ReadOnlyFileSystem,
1072 CrossDevice,
1073 NoDevice,
1074 PipeBusy,
1075 /// On Windows, `\\server` or `\\server\share` was not found.
1076 NetworkNotFound,
1077 /// On Windows, antivirus software is enabled by default. It can be
1078 /// disabled, but Windows Update sometimes ignores the user's preference
1079 /// and re-enables it. When enabled, antivirus software on Windows
1080 /// intercepts file system operations and makes them significantly slower
1081 /// in addition to possibly failing with this error code.
1082 AntivirusInterference,
1083 HardwareFailure,
1084} || PathNameError || Io.Cancelable || Io.UnexpectedError;
1085
1086/// Change the name or location of a file or directory.
1087///
1088/// If `new_sub_path` already exists, it will be replaced.
1089///
1090/// Renaming a file over an existing directory or a directory over an existing
1091/// file will fail with `error.IsDir` or `error.NotDir`
1092///
1093/// * On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1094/// * On WASI, both paths should be encoded as valid UTF-8.
1095/// * On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
1096pub fn rename(
1097 old_dir: Dir,
1098 old_sub_path: []const u8,
1099 new_dir: Dir,
1100 new_sub_path: []const u8,
1101 io: Io,
1102) RenameError!void {
1103 return io.vtable.dirRename(io.userdata, old_dir, old_sub_path, new_dir, new_sub_path);
1104}
1105
1106pub fn renameAbsolute(old_path: []const u8, new_path: []const u8, io: Io) RenameError!void {
1107 assert(path.isAbsolute(old_path));
1108 assert(path.isAbsolute(new_path));
1109 const my_cwd = cwd();
1110 return io.vtable.dirRename(io.userdata, my_cwd, old_path, my_cwd, new_path);
1111}
1112
1113pub const RenamePreserveError = error{
1114 /// In WASI, this error may occur when the file descriptor does
1115 /// not hold the required rights to rename a resource by path relative to it.
1116 ///
1117 /// On Windows, this error may be returned instead of PathAlreadyExists when
1118 /// renaming a directory over an existing directory.
1119 ///
1120 /// On Darwin, this error may be returned when a component of either pathname
1121 /// refers to a "dataless" directory that requires materialization, and the I/O
1122 /// policy of the current thread or process disallows dataless directory materialization.
1123 AccessDenied,
1124 PathAlreadyExists,
1125 /// Operating system or file system does not support atomic nonreplacing
1126 /// rename.
1127 OperationUnsupported,
1128} || RenameError;
1129
1130/// Change the name or location of a file or directory.
1131///
1132/// If `new_sub_path` already exists, `error.PathAlreadyExists` will be returned.
1133///
1134/// * On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1135/// * On WASI, both paths should be encoded as valid UTF-8.
1136/// * On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
1137pub fn renamePreserve(
1138 old_dir: Dir,
1139 old_sub_path: []const u8,
1140 new_dir: Dir,
1141 new_sub_path: []const u8,
1142 io: Io,
1143) RenamePreserveError!void {
1144 return io.vtable.dirRenamePreserve(io.userdata, old_dir, old_sub_path, new_dir, new_sub_path);
1145}
1146
1147pub const HardLinkOptions = File.HardLinkOptions;
1148
1149pub const HardLinkError = File.HardLinkError;
1150
1151pub fn hardLink(
1152 old_dir: Dir,
1153 old_sub_path: []const u8,
1154 new_dir: Dir,
1155 new_sub_path: []const u8,
1156 io: Io,
1157 options: HardLinkOptions,
1158) HardLinkError!void {
1159 return io.vtable.dirHardLink(io.userdata, old_dir, old_sub_path, new_dir, new_sub_path, options);
1160}
1161
1162/// Use with `symLink`, `symLinkAtomic`, and `symLinkAbsolute` to
1163/// specify whether the symlink will point to a file or a directory. This value
1164/// is ignored on all hosts except Windows where creating symlinks to different
1165/// resource types, requires different flags. By default, `symLinkAbsolute` is
1166/// assumed to point to a file.
1167pub const SymLinkFlags = struct {
1168 is_directory: bool = false,
1169};
1170
1171pub const SymLinkError = error{
1172 /// In WASI, this error may occur when the file descriptor does
1173 /// not hold the required rights to create a new symbolic link relative to it.
1174 AccessDenied,
1175 PermissionDenied,
1176 DiskQuota,
1177 PathAlreadyExists,
1178 FileSystem,
1179 SymLinkLoop,
1180 FileNotFound,
1181 SystemResources,
1182 NoSpaceLeft,
1183 /// On Windows, `\\server` or `\\server\share` was not found.
1184 NetworkNotFound,
1185 ReadOnlyFileSystem,
1186 NotDir,
1187} || PathNameError || Io.Cancelable || Io.UnexpectedError;
1188
1189/// Creates a symbolic link named `sym_link_path` which contains the string `target_path`.
1190///
1191/// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent
1192/// one; the latter case is known as a dangling link.
1193///
1194/// If `sym_link_path` exists, it will not be overwritten.
1195///
1196/// On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1197/// On WASI, both paths should be encoded as valid UTF-8.
1198/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
1199pub fn symLink(
1200 dir: Dir,
1201 io: Io,
1202 target_path: []const u8,
1203 sym_link_path: []const u8,
1204 flags: SymLinkFlags,
1205) SymLinkError!void {
1206 return io.vtable.dirSymLink(io.userdata, dir, target_path, sym_link_path, flags);
1207}
1208
1209pub fn symLinkAbsolute(
1210 io: Io,
1211 target_path: []const u8,
1212 sym_link_path: []const u8,
1213 flags: SymLinkFlags,
1214) SymLinkError!void {
1215 assert(path.isAbsolute(target_path));
1216 assert(path.isAbsolute(sym_link_path));
1217 return symLink(.cwd(), io, target_path, sym_link_path, flags);
1218}
1219
1220/// Same as `symLink`, except tries to create the symbolic link until it
1221/// succeeds or encounters an error other than `error.PathAlreadyExists`.
1222///
1223/// * On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1224/// * On WASI, both paths should be encoded as valid UTF-8.
1225/// * On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
1226pub fn symLinkAtomic(
1227 dir: Dir,
1228 io: Io,
1229 target_path: []const u8,
1230 sym_link_path: []const u8,
1231 flags: SymLinkFlags,
1232) !void {
1233 if (dir.symLink(io, target_path, sym_link_path, flags)) {
1234 return;
1235 } else |err| switch (err) {
1236 error.PathAlreadyExists => {},
1237 else => |e| return e,
1238 }
1239
1240 const dirname = path.dirname(sym_link_path) orelse ".";
1241
1242 const rand_len = @sizeOf(u64) * 2;
1243 const temp_path_len = dirname.len + 1 + rand_len;
1244 var temp_path_buf: [max_path_bytes]u8 = undefined;
1245
1246 if (temp_path_len > temp_path_buf.len) return error.NameTooLong;
1247 @memcpy(temp_path_buf[0..dirname.len], dirname);
1248 temp_path_buf[dirname.len] = path.sep;
1249
1250 const temp_path = temp_path_buf[0..temp_path_len];
1251
1252 var random_integer: u64 = undefined;
1253
1254 while (true) {
1255 io.random(@ptrCast(&random_integer));
1256 temp_path[dirname.len + 1 ..][0..rand_len].* = std.fmt.hex(random_integer);
1257
1258 if (dir.symLink(io, target_path, temp_path, flags)) {
1259 return dir.rename(temp_path, dir, sym_link_path, io);
1260 } else |err| switch (err) {
1261 error.PathAlreadyExists => continue,
1262 else => |e| return e,
1263 }
1264 }
1265}
1266
1267pub const ReadLinkError = error{
1268 /// In WASI, this error may occur when the file descriptor does
1269 /// not hold the required rights to read value of a symbolic link relative to it.
1270 AccessDenied,
1271 PermissionDenied,
1272 FileSystem,
1273 SymLinkLoop,
1274 FileNotFound,
1275 SystemResources,
1276 NotLink,
1277 NotDir,
1278 /// Windows-only. This error may occur if the opened reparse point is
1279 /// of unsupported type.
1280 UnsupportedReparsePointType,
1281 /// On Windows, `\\server` or `\\server\share` was not found.
1282 NetworkNotFound,
1283 /// On Windows, antivirus software is enabled by default. It can be
1284 /// disabled, but Windows Update sometimes ignores the user's preference
1285 /// and re-enables it. When enabled, antivirus software on Windows
1286 /// intercepts file system operations and makes them significantly slower
1287 /// in addition to possibly failing with this error code.
1288 AntivirusInterference,
1289 /// File attempted to be opened is a running executable.
1290 FileBusy,
1291} || PathNameError || Io.Cancelable || Io.UnexpectedError;
1292
1293/// Obtain target of a symbolic link.
1294///
1295/// Returns how many bytes of `buffer` are populated.
1296///
1297/// Asserts that the path parameter has no null bytes.
1298///
1299/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1300/// On WASI, `sub_path` should be encoded as valid UTF-8.
1301/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
1302pub fn readLink(dir: Dir, io: Io, sub_path: []const u8, buffer: []u8) ReadLinkError!usize {
1303 return io.vtable.dirReadLink(io.userdata, dir, sub_path, buffer);
1304}
1305
1306/// Same as `readLink`, except it asserts the path is absolute.
1307///
1308/// On Windows, `path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1309/// On WASI, `path` should be encoded as valid UTF-8.
1310/// On other platforms, `path` is an opaque sequence of bytes with no particular encoding.
1311pub fn readLinkAbsolute(io: Io, absolute_path: []const u8, buffer: []u8) ReadLinkError!usize {
1312 assert(path.isAbsolute(absolute_path));
1313 return io.vtable.dirReadLink(io.userdata, .cwd(), absolute_path, buffer);
1314}
1315
1316pub const ReadFileAllocError = File.OpenError || File.Reader.Error || Allocator.Error || error{
1317 /// File size reached or exceeded the provided limit.
1318 StreamTooLong,
1319};
1320
1321/// Reads all the bytes from the named file. On success, caller owns returned
1322/// buffer.
1323///
1324/// If the file size is already known, a better alternative is to initialize a
1325/// `File.Reader`.
1326///
1327/// If the file size cannot be obtained, an error is returned. If
1328/// this is a realistic possibility, a better alternative is to initialize a
1329/// `File.Reader` which handles this seamlessly.
1330pub fn readFileAlloc(
1331 dir: Dir,
1332 io: Io,
1333 /// On Windows, should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1334 /// On WASI, should be encoded as valid UTF-8.
1335 /// On other platforms, an opaque sequence of bytes with no particular encoding.
1336 sub_path: []const u8,
1337 /// Used to allocate the result.
1338 gpa: Allocator,
1339 /// If reached or exceeded, `error.StreamTooLong` is returned instead.
1340 limit: Io.Limit,
1341) ReadFileAllocError![]u8 {
1342 return readFileAllocOptions(dir, io, sub_path, gpa, limit, .of(u8), null);
1343}
1344
1345/// Reads all the bytes from the named file. On success, caller owns returned
1346/// buffer.
1347///
1348/// If the file size is already known, a better alternative is to initialize a
1349/// `File.Reader`.
1350pub fn readFileAllocOptions(
1351 dir: Dir,
1352 io: Io,
1353 /// On Windows, should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1354 /// On WASI, should be encoded as valid UTF-8.
1355 /// On other platforms, an opaque sequence of bytes with no particular encoding.
1356 sub_path: []const u8,
1357 /// Used to allocate the result.
1358 gpa: Allocator,
1359 /// If reached or exceeded, `error.StreamTooLong` is returned instead.
1360 limit: Io.Limit,
1361 comptime alignment: std.mem.Alignment,
1362 comptime sentinel: ?u8,
1363) ReadFileAllocError!(if (sentinel) |s| [:s]align(alignment.toByteUnits()) u8 else []align(alignment.toByteUnits()) u8) {
1364 var file = try dir.openFile(io, sub_path, .{
1365 // We can take advantage of this on Windows since it doesn't involve any extra syscalls,
1366 // so we can get error.IsDir during open rather than during the read.
1367 .allow_directory = if (native_os == .windows) false else true,
1368 });
1369 defer file.close(io);
1370 var file_reader = file.reader(io, &.{});
1371 return file_reader.interface.allocRemainingAlignedSentinel(gpa, limit, alignment, sentinel) catch |err| switch (err) {
1372 error.ReadFailed => return file_reader.err.?,
1373 error.OutOfMemory, error.StreamTooLong => |e| return e,
1374 };
1375}
1376
1377pub const DeleteTreeError = error{
1378 AccessDenied,
1379 PermissionDenied,
1380 FileTooBig,
1381 SymLinkLoop,
1382 ProcessFdQuotaExceeded,
1383 SystemFdQuotaExceeded,
1384 NoDevice,
1385 SystemResources,
1386 ReadOnlyFileSystem,
1387 FileSystem,
1388 FileBusy,
1389 /// One of the path components was not a directory.
1390 /// This error is unreachable if `sub_path` does not contain a path separator.
1391 NotDir,
1392 /// On Windows, `\\server` or `\\server\share` was not found.
1393 NetworkNotFound,
1394} || PathNameError || Io.Cancelable || Io.UnexpectedError;
1395
1396/// Whether `sub_path` describes a symlink, file, or directory, this function
1397/// removes it. If it cannot be removed because it is a non-empty directory,
1398/// this function recursively removes its entries and then tries again.
1399///
1400/// This operation is not atomic on most file systems.
1401///
1402/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1403/// On WASI, `sub_path` should be encoded as valid UTF-8.
1404/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
1405pub fn deleteTree(dir: Dir, io: Io, sub_path: []const u8) DeleteTreeError!void {
1406 var initial_iterable_dir = (try dir.deleteTreeOpenInitialSubpath(io, sub_path, .file)) orelse return;
1407
1408 const StackItem = struct {
1409 name: []const u8,
1410 parent_dir: Dir,
1411 iter: Iterator,
1412
1413 fn closeAll(inner_io: Io, items: []@This()) void {
1414 for (items) |*item| item.iter.reader.dir.close(inner_io);
1415 }
1416 };
1417
1418 var stack_buffer: [16]StackItem = undefined;
1419 var stack = std.ArrayList(StackItem).initBuffer(&stack_buffer);
1420 defer StackItem.closeAll(io, stack.items);
1421
1422 stack.appendAssumeCapacity(.{
1423 .name = sub_path,
1424 .parent_dir = dir,
1425 .iter = initial_iterable_dir.iterateAssumeFirstIteration(),
1426 });
1427
1428 process_stack: while (stack.items.len != 0) {
1429 var top = &stack.items[stack.items.len - 1];
1430 while (try top.iter.next(io)) |entry| {
1431 var treat_as_dir = entry.kind == .directory;
1432 handle_entry: while (true) {
1433 if (treat_as_dir) {
1434 if (stack.unusedCapacitySlice().len >= 1) {
1435 var iterable_dir = top.iter.reader.dir.openDir(io, entry.name, .{
1436 .follow_symlinks = false,
1437 .iterate = true,
1438 }) catch |err| switch (err) {
1439 error.NotDir => {
1440 treat_as_dir = false;
1441 continue :handle_entry;
1442 },
1443 error.FileNotFound => {
1444 // That's fine, we were trying to remove this directory anyway.
1445 break :handle_entry;
1446 },
1447
1448 error.AccessDenied,
1449 error.PermissionDenied,
1450 error.SymLinkLoop,
1451 error.ProcessFdQuotaExceeded,
1452 error.NameTooLong,
1453 error.SystemFdQuotaExceeded,
1454 error.NoDevice,
1455 error.SystemResources,
1456 error.Unexpected,
1457 error.BadPathName,
1458 error.NetworkNotFound,
1459 error.Canceled,
1460 => |e| return e,
1461 };
1462 stack.appendAssumeCapacity(.{
1463 .name = entry.name,
1464 .parent_dir = top.iter.reader.dir,
1465 .iter = iterable_dir.iterateAssumeFirstIteration(),
1466 });
1467 continue :process_stack;
1468 } else {
1469 try top.iter.reader.dir.deleteTreeMinStackSizeWithKindHint(io, entry.name, entry.kind);
1470 break :handle_entry;
1471 }
1472 } else {
1473 if (top.iter.reader.dir.deleteFile(io, entry.name)) {
1474 break :handle_entry;
1475 } else |err| switch (err) {
1476 error.FileNotFound => break :handle_entry,
1477
1478 // Impossible because we do not pass any path separators.
1479 error.NotDir => unreachable,
1480
1481 error.IsDir => {
1482 treat_as_dir = true;
1483 continue :handle_entry;
1484 },
1485
1486 error.AccessDenied,
1487 error.PermissionDenied,
1488 error.SymLinkLoop,
1489 error.NameTooLong,
1490 error.SystemResources,
1491 error.ReadOnlyFileSystem,
1492 error.FileSystem,
1493 error.FileBusy,
1494 error.BadPathName,
1495 error.NetworkNotFound,
1496 error.Canceled,
1497 error.Unexpected,
1498 => |e| return e,
1499 }
1500 }
1501 }
1502 }
1503
1504 // On Windows, we can't delete until the dir's handle has been closed, so
1505 // close it before we try to delete.
1506 top.iter.reader.dir.close(io);
1507
1508 // In order to avoid double-closing the directory when cleaning up
1509 // the stack in the case of an error, we save the relevant portions and
1510 // pop the value from the stack.
1511 const parent_dir = top.parent_dir;
1512 const name = top.name;
1513 stack.items.len -= 1;
1514
1515 var need_to_retry: bool = false;
1516 parent_dir.deleteDir(io, name) catch |err| switch (err) {
1517 error.FileNotFound => {},
1518 error.DirNotEmpty => need_to_retry = true,
1519 else => |e| return e,
1520 };
1521
1522 if (need_to_retry) {
1523 // Since we closed the handle that the previous iterator used, we
1524 // need to re-open the dir and re-create the iterator.
1525 var iterable_dir = iterable_dir: {
1526 var treat_as_dir = true;
1527 handle_entry: while (true) {
1528 if (treat_as_dir) {
1529 break :iterable_dir parent_dir.openDir(io, name, .{
1530 .follow_symlinks = false,
1531 .iterate = true,
1532 }) catch |err| switch (err) {
1533 error.NotDir => {
1534 treat_as_dir = false;
1535 continue :handle_entry;
1536 },
1537 error.FileNotFound => {
1538 // That's fine, we were trying to remove this directory anyway.
1539 continue :process_stack;
1540 },
1541
1542 error.AccessDenied,
1543 error.PermissionDenied,
1544 error.SymLinkLoop,
1545 error.ProcessFdQuotaExceeded,
1546 error.NameTooLong,
1547 error.SystemFdQuotaExceeded,
1548 error.NoDevice,
1549 error.SystemResources,
1550 error.Unexpected,
1551 error.BadPathName,
1552 error.NetworkNotFound,
1553 error.Canceled,
1554 => |e| return e,
1555 };
1556 } else {
1557 if (parent_dir.deleteFile(io, name)) {
1558 continue :process_stack;
1559 } else |err| switch (err) {
1560 error.FileNotFound => continue :process_stack,
1561
1562 // Impossible because we do not pass any path separators.
1563 error.NotDir => unreachable,
1564
1565 error.IsDir => {
1566 treat_as_dir = true;
1567 continue :handle_entry;
1568 },
1569
1570 error.AccessDenied,
1571 error.PermissionDenied,
1572 error.SymLinkLoop,
1573 error.NameTooLong,
1574 error.SystemResources,
1575 error.ReadOnlyFileSystem,
1576 error.FileSystem,
1577 error.FileBusy,
1578 error.BadPathName,
1579 error.NetworkNotFound,
1580 error.Canceled,
1581 error.Unexpected,
1582 => |e| return e,
1583 }
1584 }
1585 }
1586 };
1587 // We know there is room on the stack since we are just re-adding
1588 // the StackItem that we previously popped.
1589 stack.appendAssumeCapacity(.{
1590 .name = name,
1591 .parent_dir = parent_dir,
1592 .iter = iterable_dir.iterateAssumeFirstIteration(),
1593 });
1594 continue :process_stack;
1595 }
1596 }
1597}
1598
1599/// Like `deleteTree`, but only keeps one `Iterator` active at a time to minimize the function's stack size.
1600/// This is slower than `deleteTree` but uses less stack space.
1601/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1602/// On WASI, `sub_path` should be encoded as valid UTF-8.
1603/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
1604pub fn deleteTreeMinStackSize(dir: Dir, io: Io, sub_path: []const u8) DeleteTreeError!void {
1605 return dir.deleteTreeMinStackSizeWithKindHint(io, sub_path, .file);
1606}
1607
1608fn deleteTreeMinStackSizeWithKindHint(parent: Dir, io: Io, sub_path: []const u8, kind_hint: File.Kind) DeleteTreeError!void {
1609 start_over: while (true) {
1610 var dir = (try parent.deleteTreeOpenInitialSubpath(io, sub_path, kind_hint)) orelse return;
1611 var cleanup_dir_parent: ?Dir = null;
1612 defer if (cleanup_dir_parent) |*d| d.close(io);
1613
1614 var cleanup_dir = true;
1615 defer if (cleanup_dir) dir.close(io);
1616
1617 // Valid use of max_path_bytes because dir_name_buf will only
1618 // ever store a single path component that was returned from the
1619 // filesystem.
1620 var dir_name_buf: [max_path_bytes]u8 = undefined;
1621 var dir_name: []const u8 = sub_path;
1622
1623 // Here we must avoid recursion, in order to provide O(1) memory guarantee of this function.
1624 // Go through each entry and if it is not a directory, delete it. If it is a directory,
1625 // open it, and close the original directory. Repeat. Then start the entire operation over.
1626
1627 scan_dir: while (true) {
1628 var dir_it = dir.iterateAssumeFirstIteration();
1629 dir_it: while (try dir_it.next(io)) |entry| {
1630 var treat_as_dir = entry.kind == .directory;
1631 handle_entry: while (true) {
1632 if (treat_as_dir) {
1633 const new_dir = dir.openDir(io, entry.name, .{
1634 .follow_symlinks = false,
1635 .iterate = true,
1636 }) catch |err| switch (err) {
1637 error.NotDir => {
1638 treat_as_dir = false;
1639 continue :handle_entry;
1640 },
1641 error.FileNotFound => {
1642 // That's fine, we were trying to remove this directory anyway.
1643 continue :dir_it;
1644 },
1645
1646 error.AccessDenied,
1647 error.PermissionDenied,
1648 error.SymLinkLoop,
1649 error.ProcessFdQuotaExceeded,
1650 error.NameTooLong,
1651 error.SystemFdQuotaExceeded,
1652 error.NoDevice,
1653 error.SystemResources,
1654 error.Unexpected,
1655 error.BadPathName,
1656 error.NetworkNotFound,
1657 error.Canceled,
1658 => |e| return e,
1659 };
1660 if (cleanup_dir_parent) |*d| d.close(io);
1661 cleanup_dir_parent = dir;
1662 dir = new_dir;
1663 const result = dir_name_buf[0..entry.name.len];
1664 @memcpy(result, entry.name);
1665 dir_name = result;
1666 continue :scan_dir;
1667 } else {
1668 if (dir.deleteFile(io, entry.name)) {
1669 continue :dir_it;
1670 } else |err| switch (err) {
1671 error.FileNotFound => continue :dir_it,
1672
1673 // Impossible because we do not pass any path separators.
1674 error.NotDir => unreachable,
1675
1676 error.IsDir => {
1677 treat_as_dir = true;
1678 continue :handle_entry;
1679 },
1680
1681 error.AccessDenied,
1682 error.PermissionDenied,
1683 error.SymLinkLoop,
1684 error.NameTooLong,
1685 error.SystemResources,
1686 error.ReadOnlyFileSystem,
1687 error.FileSystem,
1688 error.FileBusy,
1689 error.BadPathName,
1690 error.NetworkNotFound,
1691 error.Canceled,
1692 error.Unexpected,
1693 => |e| return e,
1694 }
1695 }
1696 }
1697 }
1698 // Reached the end of the directory entries, which means we successfully deleted all of them.
1699 // Now to remove the directory itself.
1700 dir.close(io);
1701 cleanup_dir = false;
1702
1703 if (cleanup_dir_parent) |d| {
1704 d.deleteDir(io, dir_name) catch |err| switch (err) {
1705 // These two things can happen due to file system race conditions.
1706 error.FileNotFound, error.DirNotEmpty => continue :start_over,
1707 else => |e| return e,
1708 };
1709 continue :start_over;
1710 } else {
1711 parent.deleteDir(io, sub_path) catch |err| switch (err) {
1712 error.FileNotFound => return,
1713 error.DirNotEmpty => continue :start_over,
1714 else => |e| return e,
1715 };
1716 return;
1717 }
1718 }
1719 }
1720}
1721
1722/// On successful delete, returns null.
1723fn deleteTreeOpenInitialSubpath(dir: Dir, io: Io, sub_path: []const u8, kind_hint: File.Kind) !?Dir {
1724 return iterable_dir: {
1725 // Treat as a file by default
1726 var treat_as_dir = kind_hint == .directory;
1727
1728 handle_entry: while (true) {
1729 if (treat_as_dir) {
1730 break :iterable_dir dir.openDir(io, sub_path, .{
1731 .follow_symlinks = false,
1732 .iterate = true,
1733 }) catch |err| switch (err) {
1734 error.NotDir => {
1735 treat_as_dir = false;
1736 continue :handle_entry;
1737 },
1738 error.FileNotFound => {
1739 // That's fine, we were trying to remove this directory anyway.
1740 return null;
1741 },
1742
1743 error.AccessDenied,
1744 error.PermissionDenied,
1745 error.SymLinkLoop,
1746 error.ProcessFdQuotaExceeded,
1747 error.NameTooLong,
1748 error.SystemFdQuotaExceeded,
1749 error.NoDevice,
1750 error.SystemResources,
1751 error.Unexpected,
1752 error.BadPathName,
1753 error.NetworkNotFound,
1754 error.Canceled,
1755 => |e| return e,
1756 };
1757 } else {
1758 if (dir.deleteFile(io, sub_path)) {
1759 return null;
1760 } else |err| switch (err) {
1761 error.FileNotFound => return null,
1762
1763 error.IsDir => {
1764 treat_as_dir = true;
1765 continue :handle_entry;
1766 },
1767
1768 error.AccessDenied,
1769 error.PermissionDenied,
1770 error.SymLinkLoop,
1771 error.NameTooLong,
1772 error.SystemResources,
1773 error.ReadOnlyFileSystem,
1774 error.NotDir,
1775 error.FileSystem,
1776 error.FileBusy,
1777 error.BadPathName,
1778 error.NetworkNotFound,
1779 error.Canceled,
1780 error.Unexpected,
1781 => |e| return e,
1782 }
1783 }
1784 }
1785 };
1786}
1787
1788pub const CopyFileOptions = struct {
1789 /// When this is `null` the permissions are copied from the source file.
1790 permissions: ?File.Permissions = null,
1791 make_path: bool = false,
1792 replace: bool = true,
1793};
1794
1795pub const CopyFileError = File.OpenError || File.StatError ||
1796 CreateFileAtomicError || File.Atomic.ReplaceError || File.Atomic.LinkError ||
1797 File.Reader.Error || File.Writer.Error || error{InvalidFileName};
1798
1799/// Atomically creates a new file at `dest_path` within `dest_dir` with the
1800/// same contents as `source_path` within `source_dir`.
1801///
1802/// Whether to overwrite the existing file is determined by `options`.
1803///
1804/// On Linux, until https://patchwork.kernel.org/patch/9636735/ is merged and
1805/// readily available, there is a possibility of power loss or application
1806/// termination leaving temporary files present in the same directory as
1807/// dest_path.
1808///
1809/// On Windows, both paths should be encoded as
1810/// [WTF-8](https://wtf-8.codeberg.page/). On WASI, both paths should be
1811/// encoded as valid UTF-8. On other platforms, both paths are an opaque
1812/// sequence of bytes with no particular encoding.
1813pub fn copyFile(
1814 source_dir: Dir,
1815 source_path: []const u8,
1816 dest_dir: Dir,
1817 dest_path: []const u8,
1818 io: Io,
1819 options: CopyFileOptions,
1820) CopyFileError!void {
1821 const file = try source_dir.openFile(io, source_path, .{});
1822 var file_reader: File.Reader = .init(file, io, &.{});
1823 defer file_reader.file.close(io);
1824
1825 const permissions = options.permissions orelse blk: {
1826 const st = try file_reader.file.stat(io);
1827 file_reader.size = st.size;
1828 break :blk st.permissions;
1829 };
1830
1831 var atomic_file = try dest_dir.createFileAtomic(io, dest_path, .{
1832 .permissions = permissions,
1833 .make_path = options.make_path,
1834 .replace = options.replace,
1835 });
1836 defer atomic_file.deinit(io);
1837
1838 var buffer: [1024]u8 = undefined; // Used only when direct fd-to-fd is not available.
1839 var file_writer = atomic_file.file.writer(io, &buffer);
1840
1841 _ = file_writer.interface.sendFileAll(&file_reader, .unlimited) catch |err| switch (err) {
1842 error.ReadFailed => return file_reader.err.?,
1843 error.WriteFailed => return file_writer.err.?,
1844 };
1845
1846 try file_writer.flush();
1847
1848 switch (options.replace) {
1849 true => try atomic_file.replace(io),
1850 false => try atomic_file.link(io),
1851 }
1852}
1853
1854/// Same as `copyFile`, except asserts that both `source_path` and `dest_path`
1855/// are absolute.
1856///
1857/// On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1858/// On WASI, both paths should be encoded as valid UTF-8.
1859/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
1860pub fn copyFileAbsolute(
1861 source_path: []const u8,
1862 dest_path: []const u8,
1863 io: Io,
1864 options: CopyFileOptions,
1865) !void {
1866 assert(path.isAbsolute(source_path));
1867 assert(path.isAbsolute(dest_path));
1868 const my_cwd = cwd();
1869 return copyFile(my_cwd, source_path, my_cwd, dest_path, io, options);
1870}
1871
1872test copyFileAbsolute {}
1873
1874pub const CreateFileAtomicOptions = struct {
1875 permissions: File.Permissions = .default_file,
1876 make_path: bool = false,
1877 /// Tells whether the unnamed file will be ultimately created with
1878 /// `File.Atomic.link` or `File.Atomic.replace`.
1879 ///
1880 /// If this value is incorrect it will cause an assertion failure in
1881 /// `File.Atomic.replace`.
1882 replace: bool = false,
1883};
1884
1885pub const CreateFileAtomicError = error{
1886 NoDevice,
1887 /// On Windows, `\\server` or `\\server\share` was not found.
1888 NetworkNotFound,
1889 /// On Windows, antivirus software is enabled by default. It can be
1890 /// disabled, but Windows Update sometimes ignores the user's preference
1891 /// and re-enables it. When enabled, antivirus software on Windows
1892 /// intercepts file system operations and makes them significantly slower
1893 /// in addition to possibly failing with this error code.
1894 AntivirusInterference,
1895 /// In WASI, this error may occur when the file descriptor does
1896 /// not hold the required rights to open a new resource relative to it.
1897 AccessDenied,
1898 PermissionDenied,
1899 SymLinkLoop,
1900 ProcessFdQuotaExceeded,
1901 SystemFdQuotaExceeded,
1902 /// Either:
1903 /// * One of the path components does not exist.
1904 /// * Cwd was used, but cwd has been deleted.
1905 /// * The path associated with the open directory handle has been deleted.
1906 FileNotFound,
1907 /// Insufficient kernel memory was available.
1908 SystemResources,
1909 /// A new path cannot be created because the device has no room for the new file.
1910 NoSpaceLeft,
1911 /// A component used as a directory in the path was not, in fact, a directory.
1912 NotDir,
1913 WouldBlock,
1914 ReadOnlyFileSystem,
1915 /// The file attempted to be created is a running executable.
1916 FileBusy,
1917} || Io.Dir.PathNameError || Io.Cancelable || Io.UnexpectedError;
1918
1919/// Create an unnamed ephemeral file that can eventually be atomically
1920/// materialized into `sub_path`.
1921///
1922/// The returned `File.Atomic` provides API to emulate the behavior in case it
1923/// is not directly supported by the underlying operating system.
1924///
1925/// * On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1926/// * On WASI, `sub_path` should be encoded as valid UTF-8.
1927/// * On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
1928pub fn createFileAtomic(
1929 dir: Dir,
1930 io: Io,
1931 sub_path: []const u8,
1932 options: CreateFileAtomicOptions,
1933) CreateFileAtomicError!File.Atomic {
1934 return io.vtable.dirCreateFileAtomic(io.userdata, dir, sub_path, options);
1935}
1936
1937pub const SetPermissionsError = File.SetPermissionsError;
1938pub const Permissions = File.Permissions;
1939
1940/// Also known as "chmod".
1941///
1942/// The process must have the correct privileges in order to do this
1943/// successfully, or must have the effective user ID matching the owner
1944/// of the directory. Additionally, the directory must have been opened
1945/// with `OpenOptions.iterate` set to `true`.
1946pub fn setPermissions(dir: Dir, io: Io, new_permissions: File.Permissions) SetPermissionsError!void {
1947 return io.vtable.dirSetPermissions(io.userdata, dir, new_permissions);
1948}
1949
1950pub const SetFilePermissionsError = PathNameError || SetPermissionsError || error{
1951 ProcessFdQuotaExceeded,
1952 SystemFdQuotaExceeded,
1953 /// `SetFilePermissionsOptions.follow_symlinks` was set to false, which is
1954 /// not allowed by the file system or operating system.
1955 OperationUnsupported,
1956};
1957
1958pub const SetFilePermissionsOptions = struct {
1959 follow_symlinks: bool = true,
1960};
1961
1962/// Also known as "fchmodat".
1963pub fn setFilePermissions(
1964 dir: Dir,
1965 io: Io,
1966 sub_path: []const u8,
1967 new_permissions: File.Permissions,
1968 options: SetFilePermissionsOptions,
1969) SetFilePermissionsError!void {
1970 return io.vtable.dirSetFilePermissions(io.userdata, dir, sub_path, new_permissions, options);
1971}
1972
1973pub const SetOwnerError = File.SetOwnerError;
1974
1975/// Also known as "chown".
1976///
1977/// The process must have the correct privileges in order to do this
1978/// successfully. The group may be changed by the owner of the directory to
1979/// any group of which the owner is a member. Additionally, the directory
1980/// must have been opened with `OpenOptions.iterate` set to `true`. If the
1981/// owner or group is specified as `null`, the ID is not changed.
1982pub fn setOwner(dir: Dir, io: Io, owner: ?File.Uid, group: ?File.Gid) SetOwnerError!void {
1983 return io.vtable.dirSetOwner(io.userdata, dir, owner, group);
1984}
1985
1986pub const SetFileOwnerError = PathNameError || SetOwnerError;
1987
1988pub const SetFileOwnerOptions = struct {
1989 follow_symlinks: bool = true,
1990};
1991
1992/// Also known as "fchownat".
1993pub fn setFileOwner(
1994 dir: Dir,
1995 io: Io,
1996 sub_path: []const u8,
1997 owner: ?File.Uid,
1998 group: ?File.Gid,
1999 options: SetFileOwnerOptions,
2000) SetFileOwnerError!void {
2001 return io.vtable.dirSetFileOwner(io.userdata, dir, sub_path, owner, group, options);
2002}
2003
2004pub const SetTimestampsError = File.SetTimestampsError || PathNameError;
2005
2006pub const SetTimestampsOptions = struct {
2007 follow_symlinks: bool = true,
2008 access_timestamp: File.SetTimestamp = .unchanged,
2009 modify_timestamp: File.SetTimestamp = .unchanged,
2010};
2011
2012/// The granularity that ultimately is stored depends on the combination of
2013/// operating system and file system. When a value as provided that exceeds
2014/// this range, the value is clamped to the maximum.
2015pub fn setTimestamps(
2016 dir: Dir,
2017 io: Io,
2018 sub_path: []const u8,
2019 options: SetTimestampsOptions,
2020) SetTimestampsError!void {
2021 return io.vtable.dirSetTimestamps(io.userdata, dir, sub_path, options);
2022}
2023
2024pub const SetTimestampsNowOptions = struct {
2025 follow_symlinks: bool = true,
2026};
2027
2028/// Sets the accessed and modification timestamps of the provided path to the
2029/// current wall clock time.
2030///
2031/// The granularity that ultimately is stored depends on the combination of
2032/// operating system and file system.
2033pub fn setTimestampsNow(
2034 dir: Dir,
2035 io: Io,
2036 sub_path: []const u8,
2037 options: SetTimestampsNowOptions,
2038) SetTimestampsError!void {
2039 return io.vtable.dirSetTimestamps(io.userdata, dir, sub_path, .{
2040 .follow_symlinks = options.follow_symlinks,
2041 .access_timestamp = .now,
2042 .modify_timestamp = .now,
2043 });
2044}
2045
2046test {
2047 _ = &setFileOwner;
2048 _ = &setTimestampsNow;
2049}