master
1//! Deprecated in favor of `Io.Dir`.
2const Dir = @This();
3
4const builtin = @import("builtin");
5const native_os = builtin.os.tag;
6
7const std = @import("../std.zig");
8const Io = std.Io;
9const File = std.fs.File;
10const AtomicFile = std.fs.AtomicFile;
11const base64_encoder = fs.base64_encoder;
12const posix = std.posix;
13const mem = std.mem;
14const path = fs.path;
15const fs = std.fs;
16const Allocator = std.mem.Allocator;
17const assert = std.debug.assert;
18const linux = std.os.linux;
19const windows = std.os.windows;
20const have_flock = @TypeOf(posix.system.flock) != void;
21
22fd: Handle,
23
24pub const Handle = posix.fd_t;
25
26pub const default_mode = 0o755;
27
28pub const Entry = struct {
29 name: []const u8,
30 kind: Kind,
31
32 pub const Kind = File.Kind;
33};
34
35const IteratorError = error{
36 AccessDenied,
37 PermissionDenied,
38 SystemResources,
39} || posix.UnexpectedError;
40
41pub const Iterator = switch (native_os) {
42 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos, .freebsd, .netbsd, .dragonfly, .openbsd, .illumos => struct {
43 dir: Dir,
44 seek: i64,
45 buf: [1024]u8 align(@alignOf(posix.system.dirent)),
46 index: usize,
47 end_index: usize,
48 first_iter: bool,
49
50 const Self = @This();
51
52 pub const Error = IteratorError;
53
54 /// Memory such as file names referenced in this returned entry becomes invalid
55 /// with subsequent calls to `next`, as well as when this `Dir` is deinitialized.
56 pub fn next(self: *Self) Error!?Entry {
57 switch (native_os) {
58 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => return self.nextDarwin(),
59 .freebsd, .netbsd, .dragonfly, .openbsd => return self.nextBsd(),
60 .illumos => return self.nextIllumos(),
61 else => @compileError("unimplemented"),
62 }
63 }
64
65 fn nextDarwin(self: *Self) !?Entry {
66 start_over: while (true) {
67 if (self.index >= self.end_index) {
68 if (self.first_iter) {
69 posix.lseek_SET(self.dir.fd, 0) catch unreachable; // EBADF here likely means that the Dir was not opened with iteration permissions
70 self.first_iter = false;
71 }
72 const rc = posix.system.getdirentries(
73 self.dir.fd,
74 &self.buf,
75 self.buf.len,
76 &self.seek,
77 );
78 if (rc == 0) return null;
79 if (rc < 0) {
80 switch (posix.errno(rc)) {
81 .BADF => unreachable, // Dir is invalid or was opened without iteration ability
82 .FAULT => unreachable,
83 .NOTDIR => unreachable,
84 .INVAL => unreachable,
85 else => |err| return posix.unexpectedErrno(err),
86 }
87 }
88 self.index = 0;
89 self.end_index = @as(usize, @intCast(rc));
90 }
91 const darwin_entry = @as(*align(1) posix.system.dirent, @ptrCast(&self.buf[self.index]));
92 const next_index = self.index + darwin_entry.reclen;
93 self.index = next_index;
94
95 const name = @as([*]u8, @ptrCast(&darwin_entry.name))[0..darwin_entry.namlen];
96
97 if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..") or (darwin_entry.ino == 0)) {
98 continue :start_over;
99 }
100
101 const entry_kind: Entry.Kind = switch (darwin_entry.type) {
102 posix.DT.BLK => .block_device,
103 posix.DT.CHR => .character_device,
104 posix.DT.DIR => .directory,
105 posix.DT.FIFO => .named_pipe,
106 posix.DT.LNK => .sym_link,
107 posix.DT.REG => .file,
108 posix.DT.SOCK => .unix_domain_socket,
109 posix.DT.WHT => .whiteout,
110 else => .unknown,
111 };
112 return Entry{
113 .name = name,
114 .kind = entry_kind,
115 };
116 }
117 }
118
119 fn nextIllumos(self: *Self) !?Entry {
120 start_over: while (true) {
121 if (self.index >= self.end_index) {
122 if (self.first_iter) {
123 posix.lseek_SET(self.dir.fd, 0) catch unreachable; // EBADF here likely means that the Dir was not opened with iteration permissions
124 self.first_iter = false;
125 }
126 const rc = posix.system.getdents(self.dir.fd, &self.buf, self.buf.len);
127 switch (posix.errno(rc)) {
128 .SUCCESS => {},
129 .BADF => unreachable, // Dir is invalid or was opened without iteration ability
130 .FAULT => unreachable,
131 .NOTDIR => unreachable,
132 .INVAL => unreachable,
133 else => |err| return posix.unexpectedErrno(err),
134 }
135 if (rc == 0) return null;
136 self.index = 0;
137 self.end_index = @as(usize, @intCast(rc));
138 }
139 const entry = @as(*align(1) posix.system.dirent, @ptrCast(&self.buf[self.index]));
140 const next_index = self.index + entry.reclen;
141 self.index = next_index;
142
143 const name = mem.sliceTo(@as([*:0]u8, @ptrCast(&entry.name)), 0);
144 if (mem.eql(u8, name, ".") or mem.eql(u8, name, ".."))
145 continue :start_over;
146
147 // illumos dirent doesn't expose type, so we have to call stat to get it.
148 const stat_info = posix.fstatat(
149 self.dir.fd,
150 name,
151 posix.AT.SYMLINK_NOFOLLOW,
152 ) catch |err| switch (err) {
153 error.NameTooLong => unreachable,
154 error.SymLinkLoop => unreachable,
155 error.FileNotFound => unreachable, // lost the race
156 else => |e| return e,
157 };
158 const entry_kind: Entry.Kind = switch (stat_info.mode & posix.S.IFMT) {
159 posix.S.IFIFO => .named_pipe,
160 posix.S.IFCHR => .character_device,
161 posix.S.IFDIR => .directory,
162 posix.S.IFBLK => .block_device,
163 posix.S.IFREG => .file,
164 posix.S.IFLNK => .sym_link,
165 posix.S.IFSOCK => .unix_domain_socket,
166 posix.S.IFDOOR => .door,
167 posix.S.IFPORT => .event_port,
168 else => .unknown,
169 };
170 return Entry{
171 .name = name,
172 .kind = entry_kind,
173 };
174 }
175 }
176
177 fn nextBsd(self: *Self) !?Entry {
178 start_over: while (true) {
179 if (self.index >= self.end_index) {
180 if (self.first_iter) {
181 posix.lseek_SET(self.dir.fd, 0) catch unreachable; // EBADF here likely means that the Dir was not opened with iteration permissions
182 self.first_iter = false;
183 }
184 const rc = posix.system.getdents(self.dir.fd, &self.buf, self.buf.len);
185 switch (posix.errno(rc)) {
186 .SUCCESS => {},
187 .BADF => unreachable, // Dir is invalid or was opened without iteration ability
188 .FAULT => unreachable,
189 .NOTDIR => unreachable,
190 .INVAL => unreachable,
191 // Introduced in freebsd 13.2: directory unlinked but still open.
192 // To be consistent, iteration ends if the directory being iterated is deleted during iteration.
193 .NOENT => return null,
194 else => |err| return posix.unexpectedErrno(err),
195 }
196 if (rc == 0) return null;
197 self.index = 0;
198 self.end_index = @as(usize, @intCast(rc));
199 }
200 const bsd_entry = @as(*align(1) posix.system.dirent, @ptrCast(&self.buf[self.index]));
201 const next_index = self.index +
202 if (@hasField(posix.system.dirent, "reclen")) bsd_entry.reclen else bsd_entry.reclen();
203 self.index = next_index;
204
205 const name = @as([*]u8, @ptrCast(&bsd_entry.name))[0..bsd_entry.namlen];
206
207 const skip_zero_fileno = switch (native_os) {
208 // fileno=0 is used to mark invalid entries or deleted files.
209 .openbsd, .netbsd => true,
210 else => false,
211 };
212 if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..") or
213 (skip_zero_fileno and bsd_entry.fileno == 0))
214 {
215 continue :start_over;
216 }
217
218 const entry_kind: Entry.Kind = switch (bsd_entry.type) {
219 posix.DT.BLK => .block_device,
220 posix.DT.CHR => .character_device,
221 posix.DT.DIR => .directory,
222 posix.DT.FIFO => .named_pipe,
223 posix.DT.LNK => .sym_link,
224 posix.DT.REG => .file,
225 posix.DT.SOCK => .unix_domain_socket,
226 posix.DT.WHT => .whiteout,
227 else => .unknown,
228 };
229 return Entry{
230 .name = name,
231 .kind = entry_kind,
232 };
233 }
234 }
235
236 pub fn reset(self: *Self) void {
237 self.index = 0;
238 self.end_index = 0;
239 self.first_iter = true;
240 }
241 },
242 .haiku => struct {
243 dir: Dir,
244 buf: [@sizeOf(DirEnt) + posix.PATH_MAX]u8 align(@alignOf(DirEnt)),
245 offset: usize,
246 index: usize,
247 end_index: usize,
248 first_iter: bool,
249
250 const Self = @This();
251 const DirEnt = posix.system.DirEnt;
252
253 pub const Error = IteratorError;
254
255 /// Memory such as file names referenced in this returned entry becomes invalid
256 /// with subsequent calls to `next`, as well as when this `Dir` is deinitialized.
257 pub fn next(self: *Self) Error!?Entry {
258 while (true) {
259 if (self.index >= self.end_index) {
260 if (self.first_iter) {
261 switch (@as(posix.E, @enumFromInt(posix.system._kern_rewind_dir(self.dir.fd)))) {
262 .SUCCESS => {},
263 .BADF => unreachable, // Dir is invalid
264 .FAULT => unreachable,
265 .NOTDIR => unreachable,
266 .INVAL => unreachable,
267 .ACCES => return error.AccessDenied,
268 .PERM => return error.PermissionDenied,
269 else => |err| return posix.unexpectedErrno(err),
270 }
271 self.first_iter = false;
272 }
273 const rc = posix.system._kern_read_dir(
274 self.dir.fd,
275 &self.buf,
276 self.buf.len,
277 self.buf.len / @sizeOf(DirEnt),
278 );
279 if (rc == 0) return null;
280 if (rc < 0) {
281 switch (@as(posix.E, @enumFromInt(rc))) {
282 .BADF => unreachable, // Dir is invalid
283 .FAULT => unreachable,
284 .NOTDIR => unreachable,
285 .INVAL => unreachable,
286 .OVERFLOW => unreachable,
287 .ACCES => return error.AccessDenied,
288 .PERM => return error.PermissionDenied,
289 else => |err| return posix.unexpectedErrno(err),
290 }
291 }
292 self.offset = 0;
293 self.index = 0;
294 self.end_index = @intCast(rc);
295 }
296 const dirent: *DirEnt = @ptrCast(@alignCast(&self.buf[self.offset]));
297 self.offset += dirent.reclen;
298 self.index += 1;
299 const name = mem.span(dirent.getName());
300 if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..") or dirent.ino == 0) continue;
301
302 var stat_info: posix.Stat = undefined;
303 switch (@as(posix.E, @enumFromInt(posix.system._kern_read_stat(
304 self.dir.fd,
305 name,
306 false,
307 &stat_info,
308 @sizeOf(posix.Stat),
309 )))) {
310 .SUCCESS => {},
311 .INVAL => unreachable,
312 .BADF => unreachable, // Dir is invalid
313 .NOMEM => return error.SystemResources,
314 .ACCES => return error.AccessDenied,
315 .PERM => return error.PermissionDenied,
316 .FAULT => unreachable,
317 .NAMETOOLONG => unreachable,
318 .LOOP => unreachable,
319 .NOENT => continue,
320 else => |err| return posix.unexpectedErrno(err),
321 }
322 const statmode = stat_info.mode & posix.S.IFMT;
323
324 const entry_kind: Entry.Kind = switch (statmode) {
325 posix.S.IFDIR => .directory,
326 posix.S.IFBLK => .block_device,
327 posix.S.IFCHR => .character_device,
328 posix.S.IFLNK => .sym_link,
329 posix.S.IFREG => .file,
330 posix.S.IFIFO => .named_pipe,
331 else => .unknown,
332 };
333
334 return Entry{
335 .name = name,
336 .kind = entry_kind,
337 };
338 }
339 }
340
341 pub fn reset(self: *Self) void {
342 self.index = 0;
343 self.end_index = 0;
344 self.first_iter = true;
345 }
346 },
347 .linux => struct {
348 dir: Dir,
349 buf: [1024]u8 align(@alignOf(linux.dirent64)),
350 index: usize,
351 end_index: usize,
352 first_iter: bool,
353
354 const Self = @This();
355
356 pub const Error = IteratorError;
357
358 /// Memory such as file names referenced in this returned entry becomes invalid
359 /// with subsequent calls to `next`, as well as when this `Dir` is deinitialized.
360 pub fn next(self: *Self) Error!?Entry {
361 return self.nextLinux() catch |err| switch (err) {
362 // To be consistent across platforms, iteration ends if the directory being iterated is deleted during iteration.
363 // This matches the behavior of non-Linux UNIX platforms.
364 error.DirNotFound => null,
365 else => |e| return e,
366 };
367 }
368
369 pub const ErrorLinux = error{DirNotFound} || IteratorError;
370
371 /// Implementation of `next` that can return `error.DirNotFound` if the directory being
372 /// iterated was deleted during iteration (this error is Linux specific).
373 pub fn nextLinux(self: *Self) ErrorLinux!?Entry {
374 start_over: while (true) {
375 if (self.index >= self.end_index) {
376 if (self.first_iter) {
377 posix.lseek_SET(self.dir.fd, 0) catch unreachable; // EBADF here likely means that the Dir was not opened with iteration permissions
378 self.first_iter = false;
379 }
380 const rc = linux.getdents64(self.dir.fd, &self.buf, self.buf.len);
381 switch (linux.errno(rc)) {
382 .SUCCESS => {},
383 .BADF => unreachable, // Dir is invalid or was opened without iteration ability
384 .FAULT => unreachable,
385 .NOTDIR => unreachable,
386 .NOENT => return error.DirNotFound, // The directory being iterated was deleted during iteration.
387 .INVAL => return error.Unexpected, // Linux may in some cases return EINVAL when reading /proc/$PID/net.
388 .ACCES => return error.AccessDenied, // Do not have permission to iterate this directory.
389 else => |err| return posix.unexpectedErrno(err),
390 }
391 if (rc == 0) return null;
392 self.index = 0;
393 self.end_index = rc;
394 }
395 const linux_entry = @as(*align(1) linux.dirent64, @ptrCast(&self.buf[self.index]));
396 const next_index = self.index + linux_entry.reclen;
397 self.index = next_index;
398
399 const name = mem.sliceTo(@as([*:0]u8, @ptrCast(&linux_entry.name)), 0);
400
401 // skip . and .. entries
402 if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) {
403 continue :start_over;
404 }
405
406 const entry_kind: Entry.Kind = switch (linux_entry.type) {
407 linux.DT.BLK => .block_device,
408 linux.DT.CHR => .character_device,
409 linux.DT.DIR => .directory,
410 linux.DT.FIFO => .named_pipe,
411 linux.DT.LNK => .sym_link,
412 linux.DT.REG => .file,
413 linux.DT.SOCK => .unix_domain_socket,
414 else => .unknown,
415 };
416 return Entry{
417 .name = name,
418 .kind = entry_kind,
419 };
420 }
421 }
422
423 pub fn reset(self: *Self) void {
424 self.index = 0;
425 self.end_index = 0;
426 self.first_iter = true;
427 }
428 },
429 .windows => struct {
430 dir: Dir,
431 buf: [1024]u8 align(@alignOf(windows.FILE_BOTH_DIR_INFORMATION)),
432 index: usize,
433 end_index: usize,
434 first_iter: bool,
435 name_data: [fs.max_name_bytes]u8,
436
437 const Self = @This();
438
439 pub const Error = IteratorError;
440
441 /// Memory such as file names referenced in this returned entry becomes invalid
442 /// with subsequent calls to `next`, as well as when this `Dir` is deinitialized.
443 pub fn next(self: *Self) Error!?Entry {
444 const w = windows;
445 while (true) {
446 if (self.index >= self.end_index) {
447 var io: w.IO_STATUS_BLOCK = undefined;
448 const rc = w.ntdll.NtQueryDirectoryFile(
449 self.dir.fd,
450 null,
451 null,
452 null,
453 &io,
454 &self.buf,
455 self.buf.len,
456 .FileBothDirectoryInformation,
457 w.FALSE,
458 null,
459 if (self.first_iter) @as(w.BOOLEAN, w.TRUE) else @as(w.BOOLEAN, w.FALSE),
460 );
461 self.first_iter = false;
462 if (io.Information == 0) return null;
463 self.index = 0;
464 self.end_index = io.Information;
465 switch (rc) {
466 .SUCCESS => {},
467 .ACCESS_DENIED => return error.AccessDenied, // Double-check that the Dir was opened with iteration ability
468
469 else => return w.unexpectedStatus(rc),
470 }
471 }
472
473 // While the official api docs guarantee FILE_BOTH_DIR_INFORMATION to be aligned properly
474 // this may not always be the case (e.g. due to faulty VM/Sandboxing tools)
475 const dir_info: *align(2) w.FILE_BOTH_DIR_INFORMATION = @ptrCast(@alignCast(&self.buf[self.index]));
476 if (dir_info.NextEntryOffset != 0) {
477 self.index += dir_info.NextEntryOffset;
478 } else {
479 self.index = self.buf.len;
480 }
481
482 const name_wtf16le = @as([*]u16, @ptrCast(&dir_info.FileName))[0 .. dir_info.FileNameLength / 2];
483
484 if (mem.eql(u16, name_wtf16le, &[_]u16{'.'}) or mem.eql(u16, name_wtf16le, &[_]u16{ '.', '.' }))
485 continue;
486 const name_wtf8_len = std.unicode.wtf16LeToWtf8(self.name_data[0..], name_wtf16le);
487 const name_wtf8 = self.name_data[0..name_wtf8_len];
488 const kind: Entry.Kind = blk: {
489 const attrs = dir_info.FileAttributes;
490 if (attrs & w.FILE_ATTRIBUTE_DIRECTORY != 0) break :blk .directory;
491 if (attrs & w.FILE_ATTRIBUTE_REPARSE_POINT != 0) break :blk .sym_link;
492 break :blk .file;
493 };
494 return Entry{
495 .name = name_wtf8,
496 .kind = kind,
497 };
498 }
499 }
500
501 pub fn reset(self: *Self) void {
502 self.index = 0;
503 self.end_index = 0;
504 self.first_iter = true;
505 }
506 },
507 .wasi => struct {
508 dir: Dir,
509 buf: [1024]u8 align(@alignOf(std.os.wasi.dirent_t)),
510 cookie: u64,
511 index: usize,
512 end_index: usize,
513
514 const Self = @This();
515
516 pub const Error = IteratorError;
517
518 /// Memory such as file names referenced in this returned entry becomes invalid
519 /// with subsequent calls to `next`, as well as when this `Dir` is deinitialized.
520 pub fn next(self: *Self) Error!?Entry {
521 return self.nextWasi() catch |err| switch (err) {
522 // To be consistent across platforms, iteration ends if the directory being iterated is deleted during iteration.
523 // This matches the behavior of non-Linux UNIX platforms.
524 error.DirNotFound => null,
525 else => |e| return e,
526 };
527 }
528
529 pub const ErrorWasi = error{DirNotFound} || IteratorError;
530
531 /// Implementation of `next` that can return platform-dependent errors depending on the host platform.
532 /// When the host platform is Linux, `error.DirNotFound` can be returned if the directory being
533 /// iterated was deleted during iteration.
534 pub fn nextWasi(self: *Self) ErrorWasi!?Entry {
535 // We intentinally use fd_readdir even when linked with libc,
536 // since its implementation is exactly the same as below,
537 // and we avoid the code complexity here.
538 const w = std.os.wasi;
539 start_over: while (true) {
540 // According to the WASI spec, the last entry might be truncated,
541 // so we need to check if the left buffer contains the whole dirent.
542 if (self.end_index - self.index < @sizeOf(w.dirent_t)) {
543 var bufused: usize = undefined;
544 switch (w.fd_readdir(self.dir.fd, &self.buf, self.buf.len, self.cookie, &bufused)) {
545 .SUCCESS => {},
546 .BADF => unreachable, // Dir is invalid or was opened without iteration ability
547 .FAULT => unreachable,
548 .NOTDIR => unreachable,
549 .INVAL => unreachable,
550 .NOENT => return error.DirNotFound, // The directory being iterated was deleted during iteration.
551 .NOTCAPABLE => return error.AccessDenied,
552 else => |err| return posix.unexpectedErrno(err),
553 }
554 if (bufused == 0) return null;
555 self.index = 0;
556 self.end_index = bufused;
557 }
558 const entry = @as(*align(1) w.dirent_t, @ptrCast(&self.buf[self.index]));
559 const entry_size = @sizeOf(w.dirent_t);
560 const name_index = self.index + entry_size;
561 if (name_index + entry.namlen > self.end_index) {
562 // This case, the name is truncated, so we need to call readdir to store the entire name.
563 self.end_index = self.index; // Force fd_readdir in the next loop.
564 continue :start_over;
565 }
566 const name = self.buf[name_index .. name_index + entry.namlen];
567
568 const next_index = name_index + entry.namlen;
569 self.index = next_index;
570 self.cookie = entry.next;
571
572 // skip . and .. entries
573 if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) {
574 continue :start_over;
575 }
576
577 const entry_kind: Entry.Kind = switch (entry.type) {
578 .BLOCK_DEVICE => .block_device,
579 .CHARACTER_DEVICE => .character_device,
580 .DIRECTORY => .directory,
581 .SYMBOLIC_LINK => .sym_link,
582 .REGULAR_FILE => .file,
583 .SOCKET_STREAM, .SOCKET_DGRAM => .unix_domain_socket,
584 else => .unknown,
585 };
586 return Entry{
587 .name = name,
588 .kind = entry_kind,
589 };
590 }
591 }
592
593 pub fn reset(self: *Self) void {
594 self.index = 0;
595 self.end_index = 0;
596 self.cookie = std.os.wasi.DIRCOOKIE_START;
597 }
598 },
599 else => @compileError("unimplemented"),
600};
601
602pub fn iterate(self: Dir) Iterator {
603 return self.iterateImpl(true);
604}
605
606/// Like `iterate`, but will not reset the directory cursor before the first
607/// iteration. This should only be used in cases where it is known that the
608/// `Dir` has not had its cursor modified yet (e.g. it was just opened).
609pub fn iterateAssumeFirstIteration(self: Dir) Iterator {
610 return self.iterateImpl(false);
611}
612
613fn iterateImpl(self: Dir, first_iter_start_value: bool) Iterator {
614 switch (native_os) {
615 .driverkit,
616 .ios,
617 .maccatalyst,
618 .macos,
619 .tvos,
620 .visionos,
621 .watchos,
622 .freebsd,
623 .netbsd,
624 .dragonfly,
625 .openbsd,
626 .illumos,
627 => return Iterator{
628 .dir = self,
629 .seek = 0,
630 .index = 0,
631 .end_index = 0,
632 .buf = undefined,
633 .first_iter = first_iter_start_value,
634 },
635 .linux => return Iterator{
636 .dir = self,
637 .index = 0,
638 .end_index = 0,
639 .buf = undefined,
640 .first_iter = first_iter_start_value,
641 },
642 .haiku => return Iterator{
643 .dir = self,
644 .offset = 0,
645 .index = 0,
646 .end_index = 0,
647 .buf = undefined,
648 .first_iter = first_iter_start_value,
649 },
650 .windows => return Iterator{
651 .dir = self,
652 .index = 0,
653 .end_index = 0,
654 .first_iter = first_iter_start_value,
655 .buf = undefined,
656 .name_data = undefined,
657 },
658 .wasi => return Iterator{
659 .dir = self,
660 .cookie = std.os.wasi.DIRCOOKIE_START,
661 .index = 0,
662 .end_index = 0,
663 .buf = undefined,
664 },
665 else => @compileError("unimplemented"),
666 }
667}
668
669pub const SelectiveWalker = struct {
670 stack: std.ArrayList(Walker.StackItem),
671 name_buffer: std.ArrayList(u8),
672 allocator: Allocator,
673
674 pub const Error = IteratorError || Allocator.Error;
675
676 /// After each call to this function, and on deinit(), the memory returned
677 /// from this function becomes invalid. A copy must be made in order to keep
678 /// a reference to the path.
679 pub fn next(self: *SelectiveWalker) Error!?Walker.Entry {
680 while (self.stack.items.len > 0) {
681 const top = &self.stack.items[self.stack.items.len - 1];
682 var dirname_len = top.dirname_len;
683 if (top.iter.next() catch |err| {
684 // If we get an error, then we want the user to be able to continue
685 // walking if they want, which means that we need to pop the directory
686 // that errored from the stack. Otherwise, all future `next` calls would
687 // likely just fail with the same error.
688 var item = self.stack.pop().?;
689 if (self.stack.items.len != 0) {
690 item.iter.dir.close();
691 }
692 return err;
693 }) |entry| {
694 self.name_buffer.shrinkRetainingCapacity(dirname_len);
695 if (self.name_buffer.items.len != 0) {
696 try self.name_buffer.append(self.allocator, fs.path.sep);
697 dirname_len += 1;
698 }
699 try self.name_buffer.ensureUnusedCapacity(self.allocator, entry.name.len + 1);
700 self.name_buffer.appendSliceAssumeCapacity(entry.name);
701 self.name_buffer.appendAssumeCapacity(0);
702 const walker_entry: Walker.Entry = .{
703 .dir = top.iter.dir,
704 .basename = self.name_buffer.items[dirname_len .. self.name_buffer.items.len - 1 :0],
705 .path = self.name_buffer.items[0 .. self.name_buffer.items.len - 1 :0],
706 .kind = entry.kind,
707 };
708 return walker_entry;
709 } else {
710 var item = self.stack.pop().?;
711 if (self.stack.items.len != 0) {
712 item.iter.dir.close();
713 }
714 }
715 }
716 return null;
717 }
718
719 /// Traverses into the directory, continuing walking one level down.
720 pub fn enter(self: *SelectiveWalker, entry: Walker.Entry) !void {
721 if (entry.kind != .directory) {
722 @branchHint(.cold);
723 return;
724 }
725
726 var new_dir = entry.dir.openDir(entry.basename, .{ .iterate = true }) catch |err| {
727 switch (err) {
728 error.NameTooLong => unreachable,
729 else => |e| return e,
730 }
731 };
732 errdefer new_dir.close();
733
734 try self.stack.append(self.allocator, .{
735 .iter = new_dir.iterateAssumeFirstIteration(),
736 .dirname_len = self.name_buffer.items.len - 1,
737 });
738 }
739
740 pub fn deinit(self: *SelectiveWalker) void {
741 self.name_buffer.deinit(self.allocator);
742 self.stack.deinit(self.allocator);
743 }
744
745 /// Leaves the current directory, continuing walking one level up.
746 /// If the current entry is a directory entry, then the "current directory"
747 /// will pertain to that entry if `enter` is called before `leave`.
748 pub fn leave(self: *SelectiveWalker) void {
749 var item = self.stack.pop().?;
750 if (self.stack.items.len != 0) {
751 @branchHint(.likely);
752 item.iter.dir.close();
753 }
754 }
755};
756
757/// Recursively iterates over a directory, but requires the user to
758/// opt-in to recursing into each directory entry.
759///
760/// `self` must have been opened with `OpenOptions{.iterate = true}`.
761///
762/// `Walker.deinit` releases allocated memory and directory handles.
763///
764/// The order of returned file system entries is undefined.
765///
766/// `self` will not be closed after walking it.
767///
768/// See also `walk`.
769pub fn walkSelectively(self: Dir, allocator: Allocator) !SelectiveWalker {
770 var stack: std.ArrayList(Walker.StackItem) = .empty;
771
772 try stack.append(allocator, .{
773 .iter = self.iterate(),
774 .dirname_len = 0,
775 });
776
777 return .{
778 .stack = stack,
779 .name_buffer = .{},
780 .allocator = allocator,
781 };
782}
783
784pub const Walker = struct {
785 inner: SelectiveWalker,
786
787 pub const Entry = struct {
788 /// The containing directory. This can be used to operate directly on `basename`
789 /// rather than `path`, avoiding `error.NameTooLong` for deeply nested paths.
790 /// The directory remains open until `next` or `deinit` is called.
791 dir: Dir,
792 basename: [:0]const u8,
793 path: [:0]const u8,
794 kind: Dir.Entry.Kind,
795
796 /// Returns the depth of the entry relative to the initial directory.
797 /// Returns 1 for a direct child of the initial directory, 2 for an entry
798 /// within a direct child of the initial directory, etc.
799 pub fn depth(self: Walker.Entry) usize {
800 return mem.countScalar(u8, self.path, fs.path.sep) + 1;
801 }
802 };
803
804 const StackItem = struct {
805 iter: Dir.Iterator,
806 dirname_len: usize,
807 };
808
809 /// After each call to this function, and on deinit(), the memory returned
810 /// from this function becomes invalid. A copy must be made in order to keep
811 /// a reference to the path.
812 pub fn next(self: *Walker) !?Walker.Entry {
813 const entry = try self.inner.next();
814 if (entry != null and entry.?.kind == .directory) {
815 try self.inner.enter(entry.?);
816 }
817 return entry;
818 }
819
820 pub fn deinit(self: *Walker) void {
821 self.inner.deinit();
822 }
823
824 /// Leaves the current directory, continuing walking one level up.
825 /// If the current entry is a directory entry, then the "current directory"
826 /// is the directory pertaining to the current entry.
827 pub fn leave(self: *Walker) void {
828 self.inner.leave();
829 }
830};
831
832/// Recursively iterates over a directory.
833///
834/// `self` must have been opened with `OpenOptions{.iterate = true}`.
835///
836/// `Walker.deinit` releases allocated memory and directory handles.
837///
838/// The order of returned file system entries is undefined.
839///
840/// `self` will not be closed after walking it.
841///
842/// See also `walkSelectively`.
843pub fn walk(self: Dir, allocator: Allocator) Allocator.Error!Walker {
844 return .{
845 .inner = try walkSelectively(self, allocator),
846 };
847}
848
849pub const OpenError = Io.Dir.OpenError;
850
851pub fn close(self: *Dir) void {
852 posix.close(self.fd);
853 self.* = undefined;
854}
855
856/// Deprecated in favor of `Io.Dir.openFile`.
857pub fn openFile(self: Dir, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File {
858 var threaded: Io.Threaded = .init_single_threaded;
859 const io = threaded.ioBasic();
860 return .adaptFromNewApi(try Io.Dir.openFile(self.adaptToNewApi(), io, sub_path, flags));
861}
862
863/// Deprecated in favor of `Io.Dir.createFile`.
864pub fn createFile(self: Dir, sub_path: []const u8, flags: File.CreateFlags) File.OpenError!File {
865 var threaded: Io.Threaded = .init_single_threaded;
866 const io = threaded.ioBasic();
867 const new_file = try Io.Dir.createFile(self.adaptToNewApi(), io, sub_path, flags);
868 return .adaptFromNewApi(new_file);
869}
870
871/// Deprecated in favor of `Io.Dir.MakeError`.
872pub const MakeError = Io.Dir.MakeError;
873
874/// Deprecated in favor of `Io.Dir.makeDir`.
875pub fn makeDir(self: Dir, sub_path: []const u8) MakeError!void {
876 var threaded: Io.Threaded = .init_single_threaded;
877 const io = threaded.ioBasic();
878 return Io.Dir.makeDir(.{ .handle = self.fd }, io, sub_path);
879}
880
881/// Deprecated in favor of `Io.Dir.makeDir`.
882pub fn makeDirZ(self: Dir, sub_path: [*:0]const u8) MakeError!void {
883 try posix.mkdiratZ(self.fd, sub_path, default_mode);
884}
885
886/// Deprecated in favor of `Io.Dir.makeDir`.
887pub fn makeDirW(self: Dir, sub_path: [*:0]const u16) MakeError!void {
888 try posix.mkdiratW(self.fd, mem.span(sub_path), default_mode);
889}
890
891/// Deprecated in favor of `Io.Dir.makePath`.
892pub fn makePath(self: Dir, sub_path: []const u8) MakePathError!void {
893 _ = try self.makePathStatus(sub_path);
894}
895
896/// Deprecated in favor of `Io.Dir.MakePathStatus`.
897pub const MakePathStatus = Io.Dir.MakePathStatus;
898/// Deprecated in favor of `Io.Dir.MakePathError`.
899pub const MakePathError = Io.Dir.MakePathError;
900
901/// Deprecated in favor of `Io.Dir.makePathStatus`.
902pub fn makePathStatus(self: Dir, sub_path: []const u8) MakePathError!MakePathStatus {
903 var threaded: Io.Threaded = .init_single_threaded;
904 const io = threaded.ioBasic();
905 return Io.Dir.makePathStatus(.{ .handle = self.fd }, io, sub_path);
906}
907
908/// Deprecated in favor of `Io.Dir.makeOpenPath`.
909pub fn makeOpenPath(dir: Dir, sub_path: []const u8, options: OpenOptions) Io.Dir.MakeOpenPathError!Dir {
910 var threaded: Io.Threaded = .init_single_threaded;
911 const io = threaded.ioBasic();
912 return .adaptFromNewApi(try Io.Dir.makeOpenPath(dir.adaptToNewApi(), io, sub_path, options));
913}
914
915pub const RealPathError = posix.RealPathError || error{Canceled};
916
917/// This function returns the canonicalized absolute pathname of
918/// `pathname` relative to this `Dir`. If `pathname` is absolute, ignores this
919/// `Dir` handle and returns the canonicalized absolute pathname of `pathname`
920/// argument.
921/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
922/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
923/// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
924/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
925/// This function is not universally supported by all platforms.
926/// Currently supported hosts are: Linux, macOS, and Windows.
927/// See also `Dir.realpathZ`, `Dir.realpathW`, and `Dir.realpathAlloc`.
928pub fn realpath(self: Dir, pathname: []const u8, out_buffer: []u8) RealPathError![]u8 {
929 if (native_os == .wasi) {
930 @compileError("realpath is not available on WASI");
931 }
932 if (native_os == .windows) {
933 var pathname_w = try windows.sliceToPrefixedFileW(self.fd, pathname);
934
935 const wide_slice = try self.realpathW2(pathname_w.span(), &pathname_w.data);
936
937 const len = std.unicode.calcWtf8Len(wide_slice);
938 if (len > out_buffer.len)
939 return error.NameTooLong;
940
941 const end_index = std.unicode.wtf16LeToWtf8(out_buffer, wide_slice);
942 return out_buffer[0..end_index];
943 }
944 const pathname_c = try posix.toPosixPath(pathname);
945 return self.realpathZ(&pathname_c, out_buffer);
946}
947
948/// Same as `Dir.realpath` except `pathname` is null-terminated.
949/// See also `Dir.realpath`, `realpathZ`.
950pub fn realpathZ(self: Dir, pathname: [*:0]const u8, out_buffer: []u8) RealPathError![]u8 {
951 if (native_os == .windows) {
952 var pathname_w = try windows.cStrToPrefixedFileW(self.fd, pathname);
953
954 const wide_slice = try self.realpathW2(pathname_w.span(), &pathname_w.data);
955
956 const len = std.unicode.calcWtf8Len(wide_slice);
957 if (len > out_buffer.len)
958 return error.NameTooLong;
959
960 const end_index = std.unicode.wtf16LeToWtf8(out_buffer, wide_slice);
961 return out_buffer[0..end_index];
962 }
963
964 var flags: posix.O = .{};
965 if (@hasField(posix.O, "NONBLOCK")) flags.NONBLOCK = true;
966 if (@hasField(posix.O, "CLOEXEC")) flags.CLOEXEC = true;
967 if (@hasField(posix.O, "PATH")) flags.PATH = true;
968
969 const fd = posix.openatZ(self.fd, pathname, flags, 0) catch |err| switch (err) {
970 error.FileLocksNotSupported => return error.Unexpected,
971 error.FileBusy => return error.Unexpected,
972 error.WouldBlock => return error.Unexpected,
973 else => |e| return e,
974 };
975 defer posix.close(fd);
976
977 var buffer: [fs.max_path_bytes]u8 = undefined;
978 const out_path = try std.os.getFdPath(fd, &buffer);
979
980 if (out_path.len > out_buffer.len) {
981 return error.NameTooLong;
982 }
983
984 const result = out_buffer[0..out_path.len];
985 @memcpy(result, out_path);
986 return result;
987}
988
989/// Deprecated: use `realpathW2`.
990///
991/// Windows-only. Same as `Dir.realpath` except `pathname` is WTF16 LE encoded.
992/// The result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
993/// See also `Dir.realpath`, `realpathW`.
994pub fn realpathW(self: Dir, pathname: []const u16, out_buffer: []u8) RealPathError![]u8 {
995 var wide_buf: [std.os.windows.PATH_MAX_WIDE]u16 = undefined;
996 const wide_slice = try self.realpathW2(pathname, &wide_buf);
997
998 const len = std.unicode.calcWtf8Len(wide_slice);
999 if (len > out_buffer.len) return error.NameTooLong;
1000
1001 const end_index = std.unicode.wtf16LeToWtf8(&out_buffer, wide_slice);
1002 return out_buffer[0..end_index];
1003}
1004
1005/// Windows-only. Same as `Dir.realpath` except
1006/// * `pathname` and the result are WTF-16 LE encoded
1007/// * `pathname` is relative or has the NT namespace prefix. See `windows.wToPrefixedFileW` for details.
1008///
1009/// Additionally, `pathname` will never be accessed after `out_buffer` has been written to, so it
1010/// is safe to reuse a single buffer for both.
1011///
1012/// See also `Dir.realpath`, `realpathW`.
1013pub fn realpathW2(self: Dir, pathname: []const u16, out_buffer: []u16) RealPathError![]u16 {
1014 const w = windows;
1015
1016 const access_mask = w.GENERIC_READ | w.SYNCHRONIZE;
1017 const share_access = w.FILE_SHARE_READ | w.FILE_SHARE_WRITE | w.FILE_SHARE_DELETE;
1018 const creation = w.FILE_OPEN;
1019 const h_file = blk: {
1020 const res = w.OpenFile(pathname, .{
1021 .dir = self.fd,
1022 .access_mask = access_mask,
1023 .share_access = share_access,
1024 .creation = creation,
1025 .filter = .any,
1026 }) catch |err| switch (err) {
1027 error.WouldBlock => unreachable,
1028 else => |e| return e,
1029 };
1030 break :blk res;
1031 };
1032 defer w.CloseHandle(h_file);
1033
1034 return w.GetFinalPathNameByHandle(h_file, .{}, out_buffer);
1035}
1036
1037pub const RealPathAllocError = RealPathError || Allocator.Error;
1038
1039/// Same as `Dir.realpath` except caller must free the returned memory.
1040/// See also `Dir.realpath`.
1041pub fn realpathAlloc(self: Dir, allocator: Allocator, pathname: []const u8) RealPathAllocError![]u8 {
1042 // Use of max_path_bytes here is valid as the realpath function does not
1043 // have a variant that takes an arbitrary-size buffer.
1044 // TODO(#4812): Consider reimplementing realpath or using the POSIX.1-2008
1045 // NULL out parameter (GNU's canonicalize_file_name) to handle overelong
1046 // paths. musl supports passing NULL but restricts the output to PATH_MAX
1047 // anyway.
1048 var buf: [fs.max_path_bytes]u8 = undefined;
1049 return allocator.dupe(u8, try self.realpath(pathname, buf[0..]));
1050}
1051
1052/// Changes the current working directory to the open directory handle.
1053/// This modifies global state and can have surprising effects in multi-
1054/// threaded applications. Most applications and especially libraries should
1055/// not call this function as a general rule, however it can have use cases
1056/// in, for example, implementing a shell, or child process execution.
1057/// Not all targets support this. For example, WASI does not have the concept
1058/// of a current working directory.
1059pub fn setAsCwd(self: Dir) !void {
1060 if (native_os == .wasi) {
1061 @compileError("changing cwd is not currently possible in WASI");
1062 }
1063 if (native_os == .windows) {
1064 var dir_path_buffer: [windows.PATH_MAX_WIDE]u16 = undefined;
1065 const dir_path = try windows.GetFinalPathNameByHandle(self.fd, .{}, &dir_path_buffer);
1066 if (builtin.link_libc) {
1067 return posix.chdirW(dir_path);
1068 }
1069 return windows.SetCurrentDirectory(dir_path);
1070 }
1071 try posix.fchdir(self.fd);
1072}
1073
1074/// Deprecated in favor of `Io.Dir.OpenOptions`.
1075pub const OpenOptions = Io.Dir.OpenOptions;
1076
1077/// Deprecated in favor of `Io.Dir.openDir`.
1078pub fn openDir(self: Dir, sub_path: []const u8, args: OpenOptions) OpenError!Dir {
1079 var threaded: Io.Threaded = .init_single_threaded;
1080 const io = threaded.ioBasic();
1081 return .adaptFromNewApi(try Io.Dir.openDir(.{ .handle = self.fd }, io, sub_path, args));
1082}
1083
1084pub const DeleteFileError = posix.UnlinkError;
1085
1086/// Delete a file name and possibly the file it refers to, based on an open directory handle.
1087/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1088/// On WASI, `sub_path` should be encoded as valid UTF-8.
1089/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
1090/// Asserts that the path parameter has no null bytes.
1091pub fn deleteFile(self: Dir, sub_path: []const u8) DeleteFileError!void {
1092 if (native_os == .windows) {
1093 const sub_path_w = try windows.sliceToPrefixedFileW(self.fd, sub_path);
1094 return self.deleteFileW(sub_path_w.span());
1095 } else if (native_os == .wasi and !builtin.link_libc) {
1096 posix.unlinkat(self.fd, sub_path, 0) catch |err| switch (err) {
1097 error.DirNotEmpty => unreachable, // not passing AT.REMOVEDIR
1098 else => |e| return e,
1099 };
1100 } else {
1101 const sub_path_c = try posix.toPosixPath(sub_path);
1102 return self.deleteFileZ(&sub_path_c);
1103 }
1104}
1105
1106/// Same as `deleteFile` except the parameter is null-terminated.
1107pub fn deleteFileZ(self: Dir, sub_path_c: [*:0]const u8) DeleteFileError!void {
1108 posix.unlinkatZ(self.fd, sub_path_c, 0) catch |err| switch (err) {
1109 error.DirNotEmpty => unreachable, // not passing AT.REMOVEDIR
1110 error.AccessDenied, error.PermissionDenied => |e| switch (native_os) {
1111 // non-Linux POSIX systems return permission errors when trying to delete a
1112 // directory, so we need to handle that case specifically and translate the error
1113 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos, .freebsd, .netbsd, .dragonfly, .openbsd, .illumos => {
1114 // Don't follow symlinks to match unlinkat (which acts on symlinks rather than follows them)
1115 const fstat = posix.fstatatZ(self.fd, sub_path_c, posix.AT.SYMLINK_NOFOLLOW) catch return e;
1116 const is_dir = fstat.mode & posix.S.IFMT == posix.S.IFDIR;
1117 return if (is_dir) error.IsDir else e;
1118 },
1119 else => return e,
1120 },
1121 else => |e| return e,
1122 };
1123}
1124
1125/// Same as `deleteFile` except the parameter is WTF-16 LE encoded.
1126pub fn deleteFileW(self: Dir, sub_path_w: []const u16) DeleteFileError!void {
1127 posix.unlinkatW(self.fd, sub_path_w, 0) catch |err| switch (err) {
1128 error.DirNotEmpty => unreachable, // not passing AT.REMOVEDIR
1129 else => |e| return e,
1130 };
1131}
1132
1133pub const DeleteDirError = error{
1134 DirNotEmpty,
1135 FileNotFound,
1136 AccessDenied,
1137 PermissionDenied,
1138 FileBusy,
1139 FileSystem,
1140 SymLinkLoop,
1141 NameTooLong,
1142 NotDir,
1143 SystemResources,
1144 ReadOnlyFileSystem,
1145 /// WASI: file paths must be valid UTF-8.
1146 /// Windows: file paths provided by the user must be valid WTF-8.
1147 /// https://wtf-8.codeberg.page/
1148 BadPathName,
1149 /// On Windows, `\\server` or `\\server\share` was not found.
1150 NetworkNotFound,
1151 ProcessNotFound,
1152 Unexpected,
1153};
1154
1155/// Returns `error.DirNotEmpty` if the directory is not empty.
1156/// To delete a directory recursively, see `deleteTree`.
1157/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1158/// On WASI, `sub_path` should be encoded as valid UTF-8.
1159/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
1160/// Asserts that the path parameter has no null bytes.
1161pub fn deleteDir(self: Dir, sub_path: []const u8) DeleteDirError!void {
1162 if (native_os == .windows) {
1163 const sub_path_w = try windows.sliceToPrefixedFileW(self.fd, sub_path);
1164 return self.deleteDirW(sub_path_w.span());
1165 } else if (native_os == .wasi and !builtin.link_libc) {
1166 posix.unlinkat(self.fd, sub_path, posix.AT.REMOVEDIR) catch |err| switch (err) {
1167 error.IsDir => unreachable, // not possible since we pass AT.REMOVEDIR
1168 else => |e| return e,
1169 };
1170 } else {
1171 const sub_path_c = try posix.toPosixPath(sub_path);
1172 return self.deleteDirZ(&sub_path_c);
1173 }
1174}
1175
1176/// Same as `deleteDir` except the parameter is null-terminated.
1177pub fn deleteDirZ(self: Dir, sub_path_c: [*:0]const u8) DeleteDirError!void {
1178 posix.unlinkatZ(self.fd, sub_path_c, posix.AT.REMOVEDIR) catch |err| switch (err) {
1179 error.IsDir => unreachable, // not possible since we pass AT.REMOVEDIR
1180 else => |e| return e,
1181 };
1182}
1183
1184/// Same as `deleteDir` except the parameter is WTF16LE, NT prefixed.
1185/// This function is Windows-only.
1186pub fn deleteDirW(self: Dir, sub_path_w: []const u16) DeleteDirError!void {
1187 posix.unlinkatW(self.fd, sub_path_w, posix.AT.REMOVEDIR) catch |err| switch (err) {
1188 error.IsDir => unreachable, // not possible since we pass AT.REMOVEDIR
1189 else => |e| return e,
1190 };
1191}
1192
1193pub const RenameError = posix.RenameError;
1194
1195/// Change the name or location of a file or directory.
1196/// If new_sub_path already exists, it will be replaced.
1197/// Renaming a file over an existing directory or a directory
1198/// over an existing file will fail with `error.IsDir` or `error.NotDir`
1199/// On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1200/// On WASI, both paths should be encoded as valid UTF-8.
1201/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
1202pub fn rename(self: Dir, old_sub_path: []const u8, new_sub_path: []const u8) RenameError!void {
1203 return posix.renameat(self.fd, old_sub_path, self.fd, new_sub_path);
1204}
1205
1206/// Same as `rename` except the parameters are null-terminated.
1207pub fn renameZ(self: Dir, old_sub_path_z: [*:0]const u8, new_sub_path_z: [*:0]const u8) RenameError!void {
1208 return posix.renameatZ(self.fd, old_sub_path_z, self.fd, new_sub_path_z);
1209}
1210
1211/// Same as `rename` except the parameters are WTF16LE, NT prefixed.
1212/// This function is Windows-only.
1213pub fn renameW(self: Dir, old_sub_path_w: []const u16, new_sub_path_w: []const u16) RenameError!void {
1214 return posix.renameatW(self.fd, old_sub_path_w, self.fd, new_sub_path_w, windows.TRUE);
1215}
1216
1217/// Use with `Dir.symLink`, `Dir.atomicSymLink`, and `symLinkAbsolute` to
1218/// specify whether the symlink will point to a file or a directory. This value
1219/// is ignored on all hosts except Windows where creating symlinks to different
1220/// resource types, requires different flags. By default, `symLinkAbsolute` is
1221/// assumed to point to a file.
1222pub const SymLinkFlags = struct {
1223 is_directory: bool = false,
1224};
1225
1226/// Creates a symbolic link named `sym_link_path` which contains the string `target_path`.
1227/// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent
1228/// one; the latter case is known as a dangling link.
1229/// If `sym_link_path` exists, it will not be overwritten.
1230/// On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1231/// On WASI, both paths should be encoded as valid UTF-8.
1232/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
1233pub fn symLink(
1234 self: Dir,
1235 target_path: []const u8,
1236 sym_link_path: []const u8,
1237 flags: SymLinkFlags,
1238) !void {
1239 if (native_os == .wasi and !builtin.link_libc) {
1240 return self.symLinkWasi(target_path, sym_link_path, flags);
1241 }
1242 if (native_os == .windows) {
1243 // Target path does not use sliceToPrefixedFileW because certain paths
1244 // are handled differently when creating a symlink than they would be
1245 // when converting to an NT namespaced path. CreateSymbolicLink in
1246 // symLinkW will handle the necessary conversion.
1247 var target_path_w: windows.PathSpace = undefined;
1248 target_path_w.len = try windows.wtf8ToWtf16Le(&target_path_w.data, target_path);
1249 target_path_w.data[target_path_w.len] = 0;
1250 // However, we need to canonicalize any path separators to `\`, since if
1251 // the target path is relative, then it must use `\` as the path separator.
1252 mem.replaceScalar(
1253 u16,
1254 target_path_w.data[0..target_path_w.len],
1255 mem.nativeToLittle(u16, '/'),
1256 mem.nativeToLittle(u16, '\\'),
1257 );
1258
1259 const sym_link_path_w = try windows.sliceToPrefixedFileW(self.fd, sym_link_path);
1260 return self.symLinkW(target_path_w.span(), sym_link_path_w.span(), flags);
1261 }
1262 const target_path_c = try posix.toPosixPath(target_path);
1263 const sym_link_path_c = try posix.toPosixPath(sym_link_path);
1264 return self.symLinkZ(&target_path_c, &sym_link_path_c, flags);
1265}
1266
1267/// WASI-only. Same as `symLink` except targeting WASI.
1268pub fn symLinkWasi(
1269 self: Dir,
1270 target_path: []const u8,
1271 sym_link_path: []const u8,
1272 _: SymLinkFlags,
1273) !void {
1274 return posix.symlinkat(target_path, self.fd, sym_link_path);
1275}
1276
1277/// Same as `symLink`, except the pathname parameters are null-terminated.
1278pub fn symLinkZ(
1279 self: Dir,
1280 target_path_c: [*:0]const u8,
1281 sym_link_path_c: [*:0]const u8,
1282 flags: SymLinkFlags,
1283) !void {
1284 if (native_os == .windows) {
1285 const target_path_w = try windows.cStrToPrefixedFileW(self.fd, target_path_c);
1286 const sym_link_path_w = try windows.cStrToPrefixedFileW(self.fd, sym_link_path_c);
1287 return self.symLinkW(target_path_w.span(), sym_link_path_w.span(), flags);
1288 }
1289 return posix.symlinkatZ(target_path_c, self.fd, sym_link_path_c);
1290}
1291
1292/// Windows-only. Same as `symLink` except the pathname parameters
1293/// are WTF16 LE encoded.
1294pub fn symLinkW(
1295 self: Dir,
1296 /// WTF-16, does not need to be NT-prefixed. The NT-prefixing
1297 /// of this path is handled by CreateSymbolicLink.
1298 /// Any path separators must be `\`, not `/`.
1299 target_path_w: [:0]const u16,
1300 /// WTF-16, must be NT-prefixed or relative
1301 sym_link_path_w: []const u16,
1302 flags: SymLinkFlags,
1303) !void {
1304 return windows.CreateSymbolicLink(self.fd, sym_link_path_w, target_path_w, flags.is_directory);
1305}
1306
1307/// Same as `symLink`, except tries to create the symbolic link until it
1308/// succeeds or encounters an error other than `error.PathAlreadyExists`.
1309///
1310/// * On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1311/// * On WASI, both paths should be encoded as valid UTF-8.
1312/// * On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
1313pub fn atomicSymLink(
1314 dir: Dir,
1315 target_path: []const u8,
1316 sym_link_path: []const u8,
1317 flags: SymLinkFlags,
1318) !void {
1319 if (dir.symLink(target_path, sym_link_path, flags)) {
1320 return;
1321 } else |err| switch (err) {
1322 error.PathAlreadyExists => {},
1323 else => |e| return e,
1324 }
1325
1326 const dirname = path.dirname(sym_link_path) orelse ".";
1327
1328 const rand_len = @sizeOf(u64) * 2;
1329 const temp_path_len = dirname.len + 1 + rand_len;
1330 var temp_path_buf: [fs.max_path_bytes]u8 = undefined;
1331
1332 if (temp_path_len > temp_path_buf.len) return error.NameTooLong;
1333 @memcpy(temp_path_buf[0..dirname.len], dirname);
1334 temp_path_buf[dirname.len] = path.sep;
1335
1336 const temp_path = temp_path_buf[0..temp_path_len];
1337
1338 while (true) {
1339 const random_integer = std.crypto.random.int(u64);
1340 temp_path[dirname.len + 1 ..][0..rand_len].* = std.fmt.hex(random_integer);
1341
1342 if (dir.symLink(target_path, temp_path, flags)) {
1343 return dir.rename(temp_path, sym_link_path);
1344 } else |err| switch (err) {
1345 error.PathAlreadyExists => continue,
1346 else => |e| return e,
1347 }
1348 }
1349}
1350
1351pub const ReadLinkError = posix.ReadLinkError;
1352
1353/// Read value of a symbolic link.
1354/// The return value is a slice of `buffer`, from index `0`.
1355/// Asserts that the path parameter has no null bytes.
1356/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1357/// On WASI, `sub_path` should be encoded as valid UTF-8.
1358/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
1359pub fn readLink(self: Dir, sub_path: []const u8, buffer: []u8) ReadLinkError![]u8 {
1360 if (native_os == .wasi and !builtin.link_libc) {
1361 return self.readLinkWasi(sub_path, buffer);
1362 }
1363 if (native_os == .windows) {
1364 var sub_path_w = try windows.sliceToPrefixedFileW(self.fd, sub_path);
1365 const result_w = try self.readLinkW(sub_path_w.span(), &sub_path_w.data);
1366
1367 const len = std.unicode.calcWtf8Len(result_w);
1368 if (len > buffer.len) return error.NameTooLong;
1369
1370 const end_index = std.unicode.wtf16LeToWtf8(buffer, result_w);
1371 return buffer[0..end_index];
1372 }
1373 const sub_path_c = try posix.toPosixPath(sub_path);
1374 return self.readLinkZ(&sub_path_c, buffer);
1375}
1376
1377/// WASI-only. Same as `readLink` except targeting WASI.
1378pub fn readLinkWasi(self: Dir, sub_path: []const u8, buffer: []u8) ![]u8 {
1379 return posix.readlinkat(self.fd, sub_path, buffer);
1380}
1381
1382/// Same as `readLink`, except the `sub_path_c` parameter is null-terminated.
1383pub fn readLinkZ(self: Dir, sub_path_c: [*:0]const u8, buffer: []u8) ![]u8 {
1384 if (native_os == .windows) {
1385 var sub_path_w = try windows.cStrToPrefixedFileW(self.fd, sub_path_c);
1386 const result_w = try self.readLinkW(sub_path_w.span(), &sub_path_w.data);
1387
1388 const len = std.unicode.calcWtf8Len(result_w);
1389 if (len > buffer.len) return error.NameTooLong;
1390
1391 const end_index = std.unicode.wtf16LeToWtf8(buffer, result_w);
1392 return buffer[0..end_index];
1393 }
1394 return posix.readlinkatZ(self.fd, sub_path_c, buffer);
1395}
1396
1397/// Windows-only. Same as `readLink` except the path parameter
1398/// is WTF-16 LE encoded, NT-prefixed.
1399///
1400/// `sub_path_w` will never be accessed after `buffer` has been written to, so it
1401/// is safe to reuse a single buffer for both.
1402pub fn readLinkW(self: Dir, sub_path_w: []const u16, buffer: []u16) ![]u16 {
1403 return windows.ReadLink(self.fd, sub_path_w, buffer);
1404}
1405
1406/// Deprecated in favor of `Io.Dir.readFile`.
1407pub fn readFile(self: Dir, file_path: []const u8, buffer: []u8) ![]u8 {
1408 var threaded: Io.Threaded = .init_single_threaded;
1409 const io = threaded.ioBasic();
1410 return Io.Dir.readFile(.{ .handle = self.fd }, io, file_path, buffer);
1411}
1412
1413pub const ReadFileAllocError = File.OpenError || File.ReadError || Allocator.Error || error{
1414 /// File size reached or exceeded the provided limit.
1415 StreamTooLong,
1416};
1417
1418/// Reads all the bytes from the named file. On success, caller owns returned
1419/// buffer.
1420///
1421/// If the file size is already known, a better alternative is to initialize a
1422/// `File.Reader`.
1423///
1424/// If the file size cannot be obtained, an error is returned. If
1425/// this is a realistic possibility, a better alternative is to initialize a
1426/// `File.Reader` which handles this seamlessly.
1427pub fn readFileAlloc(
1428 dir: Dir,
1429 /// On Windows, should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1430 /// On WASI, should be encoded as valid UTF-8.
1431 /// On other platforms, an opaque sequence of bytes with no particular encoding.
1432 sub_path: []const u8,
1433 /// Used to allocate the result.
1434 gpa: Allocator,
1435 /// If reached or exceeded, `error.StreamTooLong` is returned instead.
1436 limit: Io.Limit,
1437) ReadFileAllocError![]u8 {
1438 return readFileAllocOptions(dir, sub_path, gpa, limit, .of(u8), null);
1439}
1440
1441/// Reads all the bytes from the named file. On success, caller owns returned
1442/// buffer.
1443///
1444/// If the file size is already known, a better alternative is to initialize a
1445/// `File.Reader`.
1446///
1447/// TODO move this function to Io.Dir
1448pub fn readFileAllocOptions(
1449 dir: Dir,
1450 /// On Windows, should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1451 /// On WASI, should be encoded as valid UTF-8.
1452 /// On other platforms, an opaque sequence of bytes with no particular encoding.
1453 sub_path: []const u8,
1454 /// Used to allocate the result.
1455 gpa: Allocator,
1456 /// If reached or exceeded, `error.StreamTooLong` is returned instead.
1457 limit: Io.Limit,
1458 comptime alignment: std.mem.Alignment,
1459 comptime sentinel: ?u8,
1460) ReadFileAllocError!(if (sentinel) |s| [:s]align(alignment.toByteUnits()) u8 else []align(alignment.toByteUnits()) u8) {
1461 var threaded: Io.Threaded = .init_single_threaded;
1462 const io = threaded.ioBasic();
1463
1464 var file = try dir.openFile(sub_path, .{});
1465 defer file.close();
1466 var file_reader = file.reader(io, &.{});
1467 return file_reader.interface.allocRemainingAlignedSentinel(gpa, limit, alignment, sentinel) catch |err| switch (err) {
1468 error.ReadFailed => return file_reader.err.?,
1469 error.OutOfMemory, error.StreamTooLong => |e| return e,
1470 };
1471}
1472
1473pub const DeleteTreeError = error{
1474 AccessDenied,
1475 PermissionDenied,
1476 FileTooBig,
1477 SymLinkLoop,
1478 ProcessFdQuotaExceeded,
1479 NameTooLong,
1480 SystemFdQuotaExceeded,
1481 NoDevice,
1482 SystemResources,
1483 ReadOnlyFileSystem,
1484 FileSystem,
1485 FileBusy,
1486 DeviceBusy,
1487 ProcessNotFound,
1488 /// One of the path components was not a directory.
1489 /// This error is unreachable if `sub_path` does not contain a path separator.
1490 NotDir,
1491 /// WASI: file paths must be valid UTF-8.
1492 /// Windows: file paths provided by the user must be valid WTF-8.
1493 /// https://wtf-8.codeberg.page/
1494 /// On Windows, file paths cannot contain these characters:
1495 /// '/', '*', '?', '"', '<', '>', '|'
1496 BadPathName,
1497 /// On Windows, `\\server` or `\\server\share` was not found.
1498 NetworkNotFound,
1499
1500 Canceled,
1501} || posix.UnexpectedError;
1502
1503/// Whether `sub_path` describes a symlink, file, or directory, this function
1504/// removes it. If it cannot be removed because it is a non-empty directory,
1505/// this function recursively removes its entries and then tries again.
1506/// This operation is not atomic on most file systems.
1507/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1508/// On WASI, `sub_path` should be encoded as valid UTF-8.
1509/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
1510pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void {
1511 var initial_iterable_dir = (try self.deleteTreeOpenInitialSubpath(sub_path, .file)) orelse return;
1512
1513 const StackItem = struct {
1514 name: []const u8,
1515 parent_dir: Dir,
1516 iter: Dir.Iterator,
1517
1518 fn closeAll(items: []@This()) void {
1519 for (items) |*item| item.iter.dir.close();
1520 }
1521 };
1522
1523 var stack_buffer: [16]StackItem = undefined;
1524 var stack = std.ArrayList(StackItem).initBuffer(&stack_buffer);
1525 defer StackItem.closeAll(stack.items);
1526
1527 stack.appendAssumeCapacity(.{
1528 .name = sub_path,
1529 .parent_dir = self,
1530 .iter = initial_iterable_dir.iterateAssumeFirstIteration(),
1531 });
1532
1533 process_stack: while (stack.items.len != 0) {
1534 var top = &stack.items[stack.items.len - 1];
1535 while (try top.iter.next()) |entry| {
1536 var treat_as_dir = entry.kind == .directory;
1537 handle_entry: while (true) {
1538 if (treat_as_dir) {
1539 if (stack.unusedCapacitySlice().len >= 1) {
1540 var iterable_dir = top.iter.dir.openDir(entry.name, .{
1541 .follow_symlinks = false,
1542 .iterate = true,
1543 }) catch |err| switch (err) {
1544 error.NotDir => {
1545 treat_as_dir = false;
1546 continue :handle_entry;
1547 },
1548 error.FileNotFound => {
1549 // That's fine, we were trying to remove this directory anyway.
1550 break :handle_entry;
1551 },
1552
1553 error.AccessDenied,
1554 error.PermissionDenied,
1555 error.SymLinkLoop,
1556 error.ProcessFdQuotaExceeded,
1557 error.NameTooLong,
1558 error.SystemFdQuotaExceeded,
1559 error.NoDevice,
1560 error.SystemResources,
1561 error.Unexpected,
1562 error.BadPathName,
1563 error.NetworkNotFound,
1564 error.DeviceBusy,
1565 error.Canceled,
1566 => |e| return e,
1567 };
1568 stack.appendAssumeCapacity(.{
1569 .name = entry.name,
1570 .parent_dir = top.iter.dir,
1571 .iter = iterable_dir.iterateAssumeFirstIteration(),
1572 });
1573 continue :process_stack;
1574 } else {
1575 try top.iter.dir.deleteTreeMinStackSizeWithKindHint(entry.name, entry.kind);
1576 break :handle_entry;
1577 }
1578 } else {
1579 if (top.iter.dir.deleteFile(entry.name)) {
1580 break :handle_entry;
1581 } else |err| switch (err) {
1582 error.FileNotFound => break :handle_entry,
1583
1584 // Impossible because we do not pass any path separators.
1585 error.NotDir => unreachable,
1586
1587 error.IsDir => {
1588 treat_as_dir = true;
1589 continue :handle_entry;
1590 },
1591
1592 error.AccessDenied,
1593 error.PermissionDenied,
1594 error.SymLinkLoop,
1595 error.NameTooLong,
1596 error.SystemResources,
1597 error.ReadOnlyFileSystem,
1598 error.FileSystem,
1599 error.FileBusy,
1600 error.BadPathName,
1601 error.NetworkNotFound,
1602 error.Unexpected,
1603 => |e| return e,
1604 }
1605 }
1606 }
1607 }
1608
1609 // On Windows, we can't delete until the dir's handle has been closed, so
1610 // close it before we try to delete.
1611 top.iter.dir.close();
1612
1613 // In order to avoid double-closing the directory when cleaning up
1614 // the stack in the case of an error, we save the relevant portions and
1615 // pop the value from the stack.
1616 const parent_dir = top.parent_dir;
1617 const name = top.name;
1618 stack.items.len -= 1;
1619
1620 var need_to_retry: bool = false;
1621 parent_dir.deleteDir(name) catch |err| switch (err) {
1622 error.FileNotFound => {},
1623 error.DirNotEmpty => need_to_retry = true,
1624 else => |e| return e,
1625 };
1626
1627 if (need_to_retry) {
1628 // Since we closed the handle that the previous iterator used, we
1629 // need to re-open the dir and re-create the iterator.
1630 var iterable_dir = iterable_dir: {
1631 var treat_as_dir = true;
1632 handle_entry: while (true) {
1633 if (treat_as_dir) {
1634 break :iterable_dir parent_dir.openDir(name, .{
1635 .follow_symlinks = false,
1636 .iterate = true,
1637 }) catch |err| switch (err) {
1638 error.NotDir => {
1639 treat_as_dir = false;
1640 continue :handle_entry;
1641 },
1642 error.FileNotFound => {
1643 // That's fine, we were trying to remove this directory anyway.
1644 continue :process_stack;
1645 },
1646
1647 error.AccessDenied,
1648 error.PermissionDenied,
1649 error.SymLinkLoop,
1650 error.ProcessFdQuotaExceeded,
1651 error.NameTooLong,
1652 error.SystemFdQuotaExceeded,
1653 error.NoDevice,
1654 error.SystemResources,
1655 error.Unexpected,
1656 error.BadPathName,
1657 error.NetworkNotFound,
1658 error.DeviceBusy,
1659 error.Canceled,
1660 => |e| return e,
1661 };
1662 } else {
1663 if (parent_dir.deleteFile(name)) {
1664 continue :process_stack;
1665 } else |err| switch (err) {
1666 error.FileNotFound => continue :process_stack,
1667
1668 // Impossible because we do not pass any path separators.
1669 error.NotDir => unreachable,
1670
1671 error.IsDir => {
1672 treat_as_dir = true;
1673 continue :handle_entry;
1674 },
1675
1676 error.AccessDenied,
1677 error.PermissionDenied,
1678 error.SymLinkLoop,
1679 error.NameTooLong,
1680 error.SystemResources,
1681 error.ReadOnlyFileSystem,
1682 error.FileSystem,
1683 error.FileBusy,
1684 error.BadPathName,
1685 error.NetworkNotFound,
1686 error.Unexpected,
1687 => |e| return e,
1688 }
1689 }
1690 }
1691 };
1692 // We know there is room on the stack since we are just re-adding
1693 // the StackItem that we previously popped.
1694 stack.appendAssumeCapacity(.{
1695 .name = name,
1696 .parent_dir = parent_dir,
1697 .iter = iterable_dir.iterateAssumeFirstIteration(),
1698 });
1699 continue :process_stack;
1700 }
1701 }
1702}
1703
1704/// Like `deleteTree`, but only keeps one `Iterator` active at a time to minimize the function's stack size.
1705/// This is slower than `deleteTree` but uses less stack space.
1706/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1707/// On WASI, `sub_path` should be encoded as valid UTF-8.
1708/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
1709pub fn deleteTreeMinStackSize(self: Dir, sub_path: []const u8) DeleteTreeError!void {
1710 return self.deleteTreeMinStackSizeWithKindHint(sub_path, .file);
1711}
1712
1713fn deleteTreeMinStackSizeWithKindHint(self: Dir, sub_path: []const u8, kind_hint: File.Kind) DeleteTreeError!void {
1714 start_over: while (true) {
1715 var dir = (try self.deleteTreeOpenInitialSubpath(sub_path, kind_hint)) orelse return;
1716 var cleanup_dir_parent: ?Dir = null;
1717 defer if (cleanup_dir_parent) |*d| d.close();
1718
1719 var cleanup_dir = true;
1720 defer if (cleanup_dir) dir.close();
1721
1722 // Valid use of max_path_bytes because dir_name_buf will only
1723 // ever store a single path component that was returned from the
1724 // filesystem.
1725 var dir_name_buf: [fs.max_path_bytes]u8 = undefined;
1726 var dir_name: []const u8 = sub_path;
1727
1728 // Here we must avoid recursion, in order to provide O(1) memory guarantee of this function.
1729 // Go through each entry and if it is not a directory, delete it. If it is a directory,
1730 // open it, and close the original directory. Repeat. Then start the entire operation over.
1731
1732 scan_dir: while (true) {
1733 var dir_it = dir.iterateAssumeFirstIteration();
1734 dir_it: while (try dir_it.next()) |entry| {
1735 var treat_as_dir = entry.kind == .directory;
1736 handle_entry: while (true) {
1737 if (treat_as_dir) {
1738 const new_dir = dir.openDir(entry.name, .{
1739 .follow_symlinks = false,
1740 .iterate = true,
1741 }) catch |err| switch (err) {
1742 error.NotDir => {
1743 treat_as_dir = false;
1744 continue :handle_entry;
1745 },
1746 error.FileNotFound => {
1747 // That's fine, we were trying to remove this directory anyway.
1748 continue :dir_it;
1749 },
1750
1751 error.AccessDenied,
1752 error.PermissionDenied,
1753 error.SymLinkLoop,
1754 error.ProcessFdQuotaExceeded,
1755 error.NameTooLong,
1756 error.SystemFdQuotaExceeded,
1757 error.NoDevice,
1758 error.SystemResources,
1759 error.Unexpected,
1760 error.BadPathName,
1761 error.NetworkNotFound,
1762 error.DeviceBusy,
1763 error.Canceled,
1764 => |e| return e,
1765 };
1766 if (cleanup_dir_parent) |*d| d.close();
1767 cleanup_dir_parent = dir;
1768 dir = new_dir;
1769 const result = dir_name_buf[0..entry.name.len];
1770 @memcpy(result, entry.name);
1771 dir_name = result;
1772 continue :scan_dir;
1773 } else {
1774 if (dir.deleteFile(entry.name)) {
1775 continue :dir_it;
1776 } else |err| switch (err) {
1777 error.FileNotFound => continue :dir_it,
1778
1779 // Impossible because we do not pass any path separators.
1780 error.NotDir => unreachable,
1781
1782 error.IsDir => {
1783 treat_as_dir = true;
1784 continue :handle_entry;
1785 },
1786
1787 error.AccessDenied,
1788 error.PermissionDenied,
1789 error.SymLinkLoop,
1790 error.NameTooLong,
1791 error.SystemResources,
1792 error.ReadOnlyFileSystem,
1793 error.FileSystem,
1794 error.FileBusy,
1795 error.BadPathName,
1796 error.NetworkNotFound,
1797 error.Unexpected,
1798 => |e| return e,
1799 }
1800 }
1801 }
1802 }
1803 // Reached the end of the directory entries, which means we successfully deleted all of them.
1804 // Now to remove the directory itself.
1805 dir.close();
1806 cleanup_dir = false;
1807
1808 if (cleanup_dir_parent) |d| {
1809 d.deleteDir(dir_name) catch |err| switch (err) {
1810 // These two things can happen due to file system race conditions.
1811 error.FileNotFound, error.DirNotEmpty => continue :start_over,
1812 else => |e| return e,
1813 };
1814 continue :start_over;
1815 } else {
1816 self.deleteDir(sub_path) catch |err| switch (err) {
1817 error.FileNotFound => return,
1818 error.DirNotEmpty => continue :start_over,
1819 else => |e| return e,
1820 };
1821 return;
1822 }
1823 }
1824 }
1825}
1826
1827/// On successful delete, returns null.
1828fn deleteTreeOpenInitialSubpath(self: Dir, sub_path: []const u8, kind_hint: File.Kind) !?Dir {
1829 return iterable_dir: {
1830 // Treat as a file by default
1831 var treat_as_dir = kind_hint == .directory;
1832
1833 handle_entry: while (true) {
1834 if (treat_as_dir) {
1835 break :iterable_dir self.openDir(sub_path, .{
1836 .follow_symlinks = false,
1837 .iterate = true,
1838 }) catch |err| switch (err) {
1839 error.NotDir => {
1840 treat_as_dir = false;
1841 continue :handle_entry;
1842 },
1843 error.FileNotFound => {
1844 // That's fine, we were trying to remove this directory anyway.
1845 return null;
1846 },
1847
1848 error.AccessDenied,
1849 error.PermissionDenied,
1850 error.SymLinkLoop,
1851 error.ProcessFdQuotaExceeded,
1852 error.NameTooLong,
1853 error.SystemFdQuotaExceeded,
1854 error.NoDevice,
1855 error.SystemResources,
1856 error.Unexpected,
1857 error.BadPathName,
1858 error.DeviceBusy,
1859 error.NetworkNotFound,
1860 error.Canceled,
1861 => |e| return e,
1862 };
1863 } else {
1864 if (self.deleteFile(sub_path)) {
1865 return null;
1866 } else |err| switch (err) {
1867 error.FileNotFound => return null,
1868
1869 error.IsDir => {
1870 treat_as_dir = true;
1871 continue :handle_entry;
1872 },
1873
1874 error.AccessDenied,
1875 error.PermissionDenied,
1876 error.SymLinkLoop,
1877 error.NameTooLong,
1878 error.SystemResources,
1879 error.ReadOnlyFileSystem,
1880 error.NotDir,
1881 error.FileSystem,
1882 error.FileBusy,
1883 error.BadPathName,
1884 error.NetworkNotFound,
1885 error.Unexpected,
1886 => |e| return e,
1887 }
1888 }
1889 }
1890 };
1891}
1892
1893pub const WriteFileError = File.WriteError || File.OpenError;
1894
1895pub const WriteFileOptions = struct {
1896 /// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1897 /// On WASI, `sub_path` should be encoded as valid UTF-8.
1898 /// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
1899 sub_path: []const u8,
1900 data: []const u8,
1901 flags: File.CreateFlags = .{},
1902};
1903
1904/// Writes content to the file system, using the file creation flags provided.
1905pub fn writeFile(self: Dir, options: WriteFileOptions) WriteFileError!void {
1906 var file = try self.createFile(options.sub_path, options.flags);
1907 defer file.close();
1908 try file.writeAll(options.data);
1909}
1910
1911/// Deprecated in favor of `Io.Dir.AccessError`.
1912pub const AccessError = Io.Dir.AccessError;
1913
1914/// Deprecated in favor of `Io.Dir.access`.
1915pub fn access(self: Dir, sub_path: []const u8, options: Io.Dir.AccessOptions) AccessError!void {
1916 var threaded: Io.Threaded = .init_single_threaded;
1917 const io = threaded.ioBasic();
1918 return Io.Dir.access(self.adaptToNewApi(), io, sub_path, options);
1919}
1920
1921pub const CopyFileOptions = struct {
1922 /// When this is `null` the mode is copied from the source file.
1923 override_mode: ?File.Mode = null,
1924};
1925
1926pub const CopyFileError = File.OpenError || File.StatError ||
1927 AtomicFile.InitError || AtomicFile.FinishError ||
1928 File.ReadError || File.WriteError || error{InvalidFileName};
1929
1930/// Atomically creates a new file at `dest_path` within `dest_dir` with the
1931/// same contents as `source_path` within `source_dir`, overwriting any already
1932/// existing file.
1933///
1934/// On Linux, until https://patchwork.kernel.org/patch/9636735/ is merged and
1935/// readily available, there is a possibility of power loss or application
1936/// termination leaving temporary files present in the same directory as
1937/// dest_path.
1938///
1939/// On Windows, both paths should be encoded as
1940/// [WTF-8](https://wtf-8.codeberg.page/). On WASI, both paths should be
1941/// encoded as valid UTF-8. On other platforms, both paths are an opaque
1942/// sequence of bytes with no particular encoding.
1943///
1944/// TODO move this function to Io.Dir
1945pub fn copyFile(
1946 source_dir: Dir,
1947 source_path: []const u8,
1948 dest_dir: Dir,
1949 dest_path: []const u8,
1950 options: CopyFileOptions,
1951) CopyFileError!void {
1952 var threaded: Io.Threaded = .init_single_threaded;
1953 const io = threaded.ioBasic();
1954
1955 const file = try source_dir.openFile(source_path, .{});
1956 var file_reader: File.Reader = .init(.{ .handle = file.handle }, io, &.{});
1957 defer file_reader.file.close(io);
1958
1959 const mode = options.override_mode orelse blk: {
1960 const st = try file_reader.file.stat(io);
1961 file_reader.size = st.size;
1962 break :blk st.mode;
1963 };
1964
1965 var buffer: [1024]u8 = undefined; // Used only when direct fd-to-fd is not available.
1966 var atomic_file = try dest_dir.atomicFile(dest_path, .{
1967 .mode = mode,
1968 .write_buffer = &buffer,
1969 });
1970 defer atomic_file.deinit();
1971
1972 _ = atomic_file.file_writer.interface.sendFileAll(&file_reader, .unlimited) catch |err| switch (err) {
1973 error.ReadFailed => return file_reader.err.?,
1974 error.WriteFailed => return atomic_file.file_writer.err.?,
1975 };
1976
1977 try atomic_file.finish();
1978}
1979
1980pub const AtomicFileOptions = struct {
1981 mode: File.Mode = File.default_mode,
1982 make_path: bool = false,
1983 write_buffer: []u8,
1984};
1985
1986/// Directly access the `.file` field, and then call `AtomicFile.finish` to
1987/// atomically replace `dest_path` with contents.
1988/// Always call `AtomicFile.deinit` to clean up, regardless of whether
1989/// `AtomicFile.finish` succeeded. `dest_path` must remain valid until
1990/// `AtomicFile.deinit` is called.
1991/// On Windows, `dest_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1992/// On WASI, `dest_path` should be encoded as valid UTF-8.
1993/// On other platforms, `dest_path` is an opaque sequence of bytes with no particular encoding.
1994pub fn atomicFile(self: Dir, dest_path: []const u8, options: AtomicFileOptions) !AtomicFile {
1995 if (fs.path.dirname(dest_path)) |dirname| {
1996 const dir = if (options.make_path)
1997 try self.makeOpenPath(dirname, .{})
1998 else
1999 try self.openDir(dirname, .{});
2000
2001 return .init(fs.path.basename(dest_path), options.mode, dir, true, options.write_buffer);
2002 } else {
2003 return .init(dest_path, options.mode, self, false, options.write_buffer);
2004 }
2005}
2006
2007pub const Stat = File.Stat;
2008pub const StatError = File.StatError;
2009
2010/// Deprecated in favor of `Io.Dir.stat`.
2011pub fn stat(self: Dir) StatError!Stat {
2012 const file: File = .{ .handle = self.fd };
2013 return file.stat();
2014}
2015
2016pub const StatFileError = File.OpenError || File.StatError || posix.FStatAtError;
2017
2018/// Deprecated in favor of `Io.Dir.statPath`.
2019pub fn statFile(self: Dir, sub_path: []const u8) StatFileError!Stat {
2020 var threaded: Io.Threaded = .init_single_threaded;
2021 const io = threaded.ioBasic();
2022 return Io.Dir.statPath(.{ .handle = self.fd }, io, sub_path, .{});
2023}
2024
2025pub const ChmodError = File.ChmodError;
2026
2027/// Changes the mode of the directory.
2028/// The process must have the correct privileges in order to do this
2029/// successfully, or must have the effective user ID matching the owner
2030/// of the directory. Additionally, the directory must have been opened
2031/// with `OpenOptions{ .iterate = true }`.
2032pub fn chmod(self: Dir, new_mode: File.Mode) ChmodError!void {
2033 const file: File = .{ .handle = self.fd };
2034 try file.chmod(new_mode);
2035}
2036
2037/// Changes the owner and group of the directory.
2038/// The process must have the correct privileges in order to do this
2039/// successfully. The group may be changed by the owner of the directory to
2040/// any group of which the owner is a member. Additionally, the directory
2041/// must have been opened with `OpenOptions{ .iterate = true }`. If the
2042/// owner or group is specified as `null`, the ID is not changed.
2043pub fn chown(self: Dir, owner: ?File.Uid, group: ?File.Gid) ChownError!void {
2044 const file: File = .{ .handle = self.fd };
2045 try file.chown(owner, group);
2046}
2047
2048pub const ChownError = File.ChownError;
2049
2050const Permissions = File.Permissions;
2051pub const SetPermissionsError = File.SetPermissionsError;
2052
2053/// Sets permissions according to the provided `Permissions` struct.
2054/// This method is *NOT* available on WASI
2055pub fn setPermissions(self: Dir, permissions: Permissions) SetPermissionsError!void {
2056 const file: File = .{ .handle = self.fd };
2057 try file.setPermissions(permissions);
2058}
2059
2060pub fn adaptToNewApi(dir: Dir) Io.Dir {
2061 return .{ .handle = dir.fd };
2062}
2063
2064pub fn adaptFromNewApi(dir: Io.Dir) Dir {
2065 return .{ .fd = dir.handle };
2066}