authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-11-22 13:50:31-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-11-22 15:24:57-07:00
logc95e2e65fac6afec5c57f6716b1f4e8e7a7292fb
tree818aaacaa27fb754107fb0f5ef5a19cb80c94831
parente357550610aef476390ed7191eeaf7597a8e9d53

std.fs: extract Dir into separate file


4 files changed, 2567 insertions(+), 2531 deletions(-)

CMakeLists.txt+1
......@@ -247,6 +247,7 @@ set(ZIG_STAGE2_SOURCES
247247 "${CMAKE_SOURCE_DIR}/lib/std/fmt/errol/lookup.zig"
248248 "${CMAKE_SOURCE_DIR}/lib/std/fmt/parse_float.zig"
249249 "${CMAKE_SOURCE_DIR}/lib/std/fs.zig"
250 "${CMAKE_SOURCE_DIR}/lib/std/fs/Dir.zig"
250251 "${CMAKE_SOURCE_DIR}/lib/std/fs/file.zig"
251252 "${CMAKE_SOURCE_DIR}/lib/std/fs/get_app_data_dir.zig"
252253 "${CMAKE_SOURCE_DIR}/lib/std/fs/path.zig"
lib/std/fs.zig+31-2529
......@@ -11,6 +11,8 @@ const math = std.math;
1111
1212const is_darwin = builtin.os.tag.isDarwin();
1313
14pub const Dir = @import("fs/Dir.zig");
15
1416pub const has_executable_bit = switch (builtin.os.tag) {
1517 .windows, .wasi => false,
1618 else => true,
......@@ -120,24 +122,14 @@ pub fn atomicSymLink(allocator: Allocator, existing_path: []const u8, new_path:
120122 }
121123}
122124
123pub const PrevStatus = enum {
124 stale,
125 fresh,
126};
127
128pub const CopyFileOptions = struct {
129 /// When this is `null` the mode is copied from the source file.
130 override_mode: ?File.Mode = null,
131};
132
133125/// Same as `Dir.updateFile`, except asserts that both `source_path` and `dest_path`
134126/// are absolute. See `Dir.updateFile` for a function that operates on both
135127/// absolute and relative paths.
136128pub fn updateFileAbsolute(
137129 source_path: []const u8,
138130 dest_path: []const u8,
139 args: CopyFileOptions,
140) !PrevStatus {
131 args: Dir.CopyFileOptions,
132) !Dir.PrevStatus {
141133 assert(path.isAbsolute(source_path));
142134 assert(path.isAbsolute(dest_path));
143135 const my_cwd = cwd();
......@@ -147,7 +139,11 @@ pub fn updateFileAbsolute(
147139/// Same as `Dir.copyFile`, except asserts that both `source_path` and `dest_path`
148140/// are absolute. See `Dir.copyFile` for a function that operates on both
149141/// absolute and relative paths.
150pub fn copyFileAbsolute(source_path: []const u8, dest_path: []const u8, args: CopyFileOptions) !void {
142pub fn copyFileAbsolute(
143 source_path: []const u8,
144 dest_path: []const u8,
145 args: Dir.CopyFileOptions,
146) !void {
151147 assert(path.isAbsolute(source_path));
152148 assert(path.isAbsolute(dest_path));
153149 const my_cwd = cwd();
......@@ -164,7 +160,7 @@ pub const AtomicFile = struct {
164160 close_dir_on_deinit: bool,
165161 dir: Dir,
166162
167 const InitError = File.OpenError;
163 pub const InitError = File.OpenError;
168164
169165 const RANDOM_BYTES = 12;
170166 const TMP_PATH_LEN = base64_encoder.calcSize(RANDOM_BYTES);
......@@ -233,26 +229,24 @@ pub const AtomicFile = struct {
233229 }
234230};
235231
236const default_new_dir_mode = 0o755;
237
238232/// Create a new directory, based on an absolute path.
239233/// Asserts that the path is absolute. See `Dir.makeDir` for a function that operates
240234/// on both absolute and relative paths.
241235pub fn makeDirAbsolute(absolute_path: []const u8) !void {
242236 assert(path.isAbsolute(absolute_path));
243 return os.mkdir(absolute_path, default_new_dir_mode);
237 return os.mkdir(absolute_path, Dir.default_mode);
244238}
245239
246240/// Same as `makeDirAbsolute` except the parameter is a null-terminated UTF-8-encoded string.
247241pub fn makeDirAbsoluteZ(absolute_path_z: [*:0]const u8) !void {
248242 assert(path.isAbsoluteZ(absolute_path_z));
249 return os.mkdirZ(absolute_path_z, default_new_dir_mode);
243 return os.mkdirZ(absolute_path_z, Dir.default_mode);
250244}
251245
252246/// Same as `makeDirAbsolute` except the parameter is a null-terminated WTF-16-encoded string.
253247pub fn makeDirAbsoluteW(absolute_path_w: [*:0]const u16) !void {
254248 assert(path.isAbsoluteWindowsW(absolute_path_w));
255 return os.mkdirW(absolute_path_w, default_new_dir_mode);
249 return os.mkdirW(absolute_path_w, Dir.default_mode);
256250}
257251
258252/// Same as `Dir.deleteDir` except the path is absolute.
......@@ -310,2449 +304,6 @@ pub fn renameW(old_dir: Dir, old_sub_path_w: []const u16, new_dir: Dir, new_sub_
310304 return os.renameatW(old_dir.fd, old_sub_path_w, new_dir.fd, new_sub_path_w);
311305}
312306
313pub const Dir = struct {
314 fd: os.fd_t,
315
316 pub const Entry = struct {
317 name: []const u8,
318 kind: Kind,
319
320 pub const Kind = File.Kind;
321 };
322
323 const IteratorError = error{ AccessDenied, SystemResources } || os.UnexpectedError;
324
325 pub const Iterator = switch (builtin.os.tag) {
326 .macos, .ios, .freebsd, .netbsd, .dragonfly, .openbsd, .solaris, .illumos => struct {
327 dir: Dir,
328 seek: i64,
329 buf: [1024]u8, // TODO align(@alignOf(os.system.dirent)),
330 index: usize,
331 end_index: usize,
332 first_iter: bool,
333
334 const Self = @This();
335
336 pub const Error = IteratorError;
337
338 /// Memory such as file names referenced in this returned entry becomes invalid
339 /// with subsequent calls to `next`, as well as when this `Dir` is deinitialized.
340 pub fn next(self: *Self) Error!?Entry {
341 switch (builtin.os.tag) {
342 .macos, .ios => return self.nextDarwin(),
343 .freebsd, .netbsd, .dragonfly, .openbsd => return self.nextBsd(),
344 .solaris, .illumos => return self.nextSolaris(),
345 else => @compileError("unimplemented"),
346 }
347 }
348
349 fn nextDarwin(self: *Self) !?Entry {
350 start_over: while (true) {
351 if (self.index >= self.end_index) {
352 if (self.first_iter) {
353 std.os.lseek_SET(self.dir.fd, 0) catch unreachable; // EBADF here likely means that the Dir was not opened with iteration permissions
354 self.first_iter = false;
355 }
356 const rc = os.system.__getdirentries64(
357 self.dir.fd,
358 &self.buf,
359 self.buf.len,
360 &self.seek,
361 );
362 if (rc == 0) return null;
363 if (rc < 0) {
364 switch (os.errno(rc)) {
365 .BADF => unreachable, // Dir is invalid or was opened without iteration ability
366 .FAULT => unreachable,
367 .NOTDIR => unreachable,
368 .INVAL => unreachable,
369 else => |err| return os.unexpectedErrno(err),
370 }
371 }
372 self.index = 0;
373 self.end_index = @as(usize, @intCast(rc));
374 }
375 const darwin_entry = @as(*align(1) os.system.dirent, @ptrCast(&self.buf[self.index]));
376 const next_index = self.index + darwin_entry.reclen();
377 self.index = next_index;
378
379 const name = @as([*]u8, @ptrCast(&darwin_entry.d_name))[0..darwin_entry.d_namlen];
380
381 if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..") or (darwin_entry.d_ino == 0)) {
382 continue :start_over;
383 }
384
385 const entry_kind: Entry.Kind = switch (darwin_entry.d_type) {
386 os.DT.BLK => .block_device,
387 os.DT.CHR => .character_device,
388 os.DT.DIR => .directory,
389 os.DT.FIFO => .named_pipe,
390 os.DT.LNK => .sym_link,
391 os.DT.REG => .file,
392 os.DT.SOCK => .unix_domain_socket,
393 os.DT.WHT => .whiteout,
394 else => .unknown,
395 };
396 return Entry{
397 .name = name,
398 .kind = entry_kind,
399 };
400 }
401 }
402
403 fn nextSolaris(self: *Self) !?Entry {
404 start_over: while (true) {
405 if (self.index >= self.end_index) {
406 if (self.first_iter) {
407 std.os.lseek_SET(self.dir.fd, 0) catch unreachable; // EBADF here likely means that the Dir was not opened with iteration permissions
408 self.first_iter = false;
409 }
410 const rc = os.system.getdents(self.dir.fd, &self.buf, self.buf.len);
411 switch (os.errno(rc)) {
412 .SUCCESS => {},
413 .BADF => unreachable, // Dir is invalid or was opened without iteration ability
414 .FAULT => unreachable,
415 .NOTDIR => unreachable,
416 .INVAL => unreachable,
417 else => |err| return os.unexpectedErrno(err),
418 }
419 if (rc == 0) return null;
420 self.index = 0;
421 self.end_index = @as(usize, @intCast(rc));
422 }
423 const entry = @as(*align(1) os.system.dirent, @ptrCast(&self.buf[self.index]));
424 const next_index = self.index + entry.reclen();
425 self.index = next_index;
426
427 const name = mem.sliceTo(@as([*:0]u8, @ptrCast(&entry.d_name)), 0);
428 if (mem.eql(u8, name, ".") or mem.eql(u8, name, ".."))
429 continue :start_over;
430
431 // Solaris dirent doesn't expose d_type, so we have to call stat to get it.
432 const stat_info = os.fstatat(
433 self.dir.fd,
434 name,
435 os.AT.SYMLINK_NOFOLLOW,
436 ) catch |err| switch (err) {
437 error.NameTooLong => unreachable,
438 error.SymLinkLoop => unreachable,
439 error.FileNotFound => unreachable, // lost the race
440 else => |e| return e,
441 };
442 const entry_kind: Entry.Kind = switch (stat_info.mode & os.S.IFMT) {
443 os.S.IFIFO => .named_pipe,
444 os.S.IFCHR => .character_device,
445 os.S.IFDIR => .directory,
446 os.S.IFBLK => .block_device,
447 os.S.IFREG => .file,
448 os.S.IFLNK => .sym_link,
449 os.S.IFSOCK => .unix_domain_socket,
450 os.S.IFDOOR => .door,
451 os.S.IFPORT => .event_port,
452 else => .unknown,
453 };
454 return Entry{
455 .name = name,
456 .kind = entry_kind,
457 };
458 }
459 }
460
461 fn nextBsd(self: *Self) !?Entry {
462 start_over: while (true) {
463 if (self.index >= self.end_index) {
464 if (self.first_iter) {
465 std.os.lseek_SET(self.dir.fd, 0) catch unreachable; // EBADF here likely means that the Dir was not opened with iteration permissions
466 self.first_iter = false;
467 }
468 const rc = if (builtin.os.tag == .netbsd)
469 os.system.__getdents30(self.dir.fd, &self.buf, self.buf.len)
470 else
471 os.system.getdents(self.dir.fd, &self.buf, self.buf.len);
472 switch (os.errno(rc)) {
473 .SUCCESS => {},
474 .BADF => unreachable, // Dir is invalid or was opened without iteration ability
475 .FAULT => unreachable,
476 .NOTDIR => unreachable,
477 .INVAL => unreachable,
478 // Introduced in freebsd 13.2: directory unlinked but still open.
479 // To be consistent, iteration ends if the directory being iterated is deleted during iteration.
480 .NOENT => return null,
481 else => |err| return os.unexpectedErrno(err),
482 }
483 if (rc == 0) return null;
484 self.index = 0;
485 self.end_index = @as(usize, @intCast(rc));
486 }
487 const bsd_entry = @as(*align(1) os.system.dirent, @ptrCast(&self.buf[self.index]));
488 const next_index = self.index + bsd_entry.reclen();
489 self.index = next_index;
490
491 const name = @as([*]u8, @ptrCast(&bsd_entry.d_name))[0..bsd_entry.d_namlen];
492
493 const skip_zero_fileno = switch (builtin.os.tag) {
494 // d_fileno=0 is used to mark invalid entries or deleted files.
495 .openbsd, .netbsd => true,
496 else => false,
497 };
498 if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..") or
499 (skip_zero_fileno and bsd_entry.d_fileno == 0))
500 {
501 continue :start_over;
502 }
503
504 const entry_kind: Entry.Kind = switch (bsd_entry.d_type) {
505 os.DT.BLK => .block_device,
506 os.DT.CHR => .character_device,
507 os.DT.DIR => .directory,
508 os.DT.FIFO => .named_pipe,
509 os.DT.LNK => .sym_link,
510 os.DT.REG => .file,
511 os.DT.SOCK => .unix_domain_socket,
512 os.DT.WHT => .whiteout,
513 else => .unknown,
514 };
515 return Entry{
516 .name = name,
517 .kind = entry_kind,
518 };
519 }
520 }
521
522 pub fn reset(self: *Self) void {
523 self.index = 0;
524 self.end_index = 0;
525 self.first_iter = true;
526 }
527 },
528 .haiku => struct {
529 dir: Dir,
530 buf: [1024]u8, // TODO align(@alignOf(os.dirent64)),
531 index: usize,
532 end_index: usize,
533 first_iter: bool,
534
535 const Self = @This();
536
537 pub const Error = IteratorError;
538
539 /// Memory such as file names referenced in this returned entry becomes invalid
540 /// with subsequent calls to `next`, as well as when this `Dir` is deinitialized.
541 pub fn next(self: *Self) Error!?Entry {
542 start_over: while (true) {
543 // TODO: find a better max
544 const HAIKU_MAX_COUNT = 10000;
545 if (self.index >= self.end_index) {
546 if (self.first_iter) {
547 std.os.lseek_SET(self.dir.fd, 0) catch unreachable; // EBADF here likely means that the Dir was not opened with iteration permissions
548 self.first_iter = false;
549 }
550 const rc = os.system._kern_read_dir(
551 self.dir.fd,
552 &self.buf,
553 self.buf.len,
554 HAIKU_MAX_COUNT,
555 );
556 if (rc == 0) return null;
557 if (rc < 0) {
558 switch (os.errno(rc)) {
559 .BADF => unreachable, // Dir is invalid or was opened without iteration ability
560 .FAULT => unreachable,
561 .NOTDIR => unreachable,
562 .INVAL => unreachable,
563 else => |err| return os.unexpectedErrno(err),
564 }
565 }
566 self.index = 0;
567 self.end_index = @as(usize, @intCast(rc));
568 }
569 const haiku_entry = @as(*align(1) os.system.dirent, @ptrCast(&self.buf[self.index]));
570 const next_index = self.index + haiku_entry.reclen();
571 self.index = next_index;
572 const name = mem.sliceTo(@as([*:0]u8, @ptrCast(&haiku_entry.d_name)), 0);
573
574 if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..") or (haiku_entry.d_ino == 0)) {
575 continue :start_over;
576 }
577
578 var stat_info: os.Stat = undefined;
579 const rc = os.system._kern_read_stat(
580 self.dir.fd,
581 &haiku_entry.d_name,
582 false,
583 &stat_info,
584 0,
585 );
586 if (rc != 0) {
587 switch (os.errno(rc)) {
588 .SUCCESS => {},
589 .BADF => unreachable, // Dir is invalid or was opened without iteration ability
590 .FAULT => unreachable,
591 .NOTDIR => unreachable,
592 .INVAL => unreachable,
593 else => |err| return os.unexpectedErrno(err),
594 }
595 }
596 const statmode = stat_info.mode & os.S.IFMT;
597
598 const entry_kind: Entry.Kind = switch (statmode) {
599 os.S.IFDIR => .directory,
600 os.S.IFBLK => .block_device,
601 os.S.IFCHR => .character_device,
602 os.S.IFLNK => .sym_link,
603 os.S.IFREG => .file,
604 os.S.IFIFO => .named_pipe,
605 else => .unknown,
606 };
607
608 return Entry{
609 .name = name,
610 .kind = entry_kind,
611 };
612 }
613 }
614
615 pub fn reset(self: *Self) void {
616 self.index = 0;
617 self.end_index = 0;
618 self.first_iter = true;
619 }
620 },
621 .linux => struct {
622 dir: Dir,
623 // The if guard is solely there to prevent compile errors from missing `linux.dirent64`
624 // definition when compiling for other OSes. It doesn't do anything when compiling for Linux.
625 buf: [1024]u8 align(if (builtin.os.tag != .linux) 1 else @alignOf(linux.dirent64)),
626 index: usize,
627 end_index: usize,
628 first_iter: bool,
629
630 const Self = @This();
631 const linux = os.linux;
632
633 pub const Error = IteratorError;
634
635 /// Memory such as file names referenced in this returned entry becomes invalid
636 /// with subsequent calls to `next`, as well as when this `Dir` is deinitialized.
637 pub fn next(self: *Self) Error!?Entry {
638 return self.nextLinux() catch |err| switch (err) {
639 // To be consistent across platforms, iteration ends if the directory being iterated is deleted during iteration.
640 // This matches the behavior of non-Linux UNIX platforms.
641 error.DirNotFound => null,
642 else => |e| return e,
643 };
644 }
645
646 pub const ErrorLinux = error{DirNotFound} || IteratorError;
647
648 /// Implementation of `next` that can return `error.DirNotFound` if the directory being
649 /// iterated was deleted during iteration (this error is Linux specific).
650 pub fn nextLinux(self: *Self) ErrorLinux!?Entry {
651 start_over: while (true) {
652 if (self.index >= self.end_index) {
653 if (self.first_iter) {
654 std.os.lseek_SET(self.dir.fd, 0) catch unreachable; // EBADF here likely means that the Dir was not opened with iteration permissions
655 self.first_iter = false;
656 }
657 const rc = linux.getdents64(self.dir.fd, &self.buf, self.buf.len);
658 switch (linux.getErrno(rc)) {
659 .SUCCESS => {},
660 .BADF => unreachable, // Dir is invalid or was opened without iteration ability
661 .FAULT => unreachable,
662 .NOTDIR => unreachable,
663 .NOENT => return error.DirNotFound, // The directory being iterated was deleted during iteration.
664 .INVAL => return error.Unexpected, // Linux may in some cases return EINVAL when reading /proc/$PID/net.
665 .ACCES => return error.AccessDenied, // Do not have permission to iterate this directory.
666 else => |err| return os.unexpectedErrno(err),
667 }
668 if (rc == 0) return null;
669 self.index = 0;
670 self.end_index = rc;
671 }
672 const linux_entry = @as(*align(1) linux.dirent64, @ptrCast(&self.buf[self.index]));
673 const next_index = self.index + linux_entry.reclen();
674 self.index = next_index;
675
676 const name = mem.sliceTo(@as([*:0]u8, @ptrCast(&linux_entry.d_name)), 0);
677
678 // skip . and .. entries
679 if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) {
680 continue :start_over;
681 }
682
683 const entry_kind: Entry.Kind = switch (linux_entry.d_type) {
684 linux.DT.BLK => .block_device,
685 linux.DT.CHR => .character_device,
686 linux.DT.DIR => .directory,
687 linux.DT.FIFO => .named_pipe,
688 linux.DT.LNK => .sym_link,
689 linux.DT.REG => .file,
690 linux.DT.SOCK => .unix_domain_socket,
691 else => .unknown,
692 };
693 return Entry{
694 .name = name,
695 .kind = entry_kind,
696 };
697 }
698 }
699
700 pub fn reset(self: *Self) void {
701 self.index = 0;
702 self.end_index = 0;
703 self.first_iter = true;
704 }
705 },
706 .windows => struct {
707 dir: Dir,
708 buf: [1024]u8 align(@alignOf(os.windows.FILE_BOTH_DIR_INFORMATION)),
709 index: usize,
710 end_index: usize,
711 first_iter: bool,
712 name_data: [MAX_NAME_BYTES]u8,
713
714 const Self = @This();
715
716 pub const Error = IteratorError;
717
718 /// Memory such as file names referenced in this returned entry becomes invalid
719 /// with subsequent calls to `next`, as well as when this `Dir` is deinitialized.
720 pub fn next(self: *Self) Error!?Entry {
721 while (true) {
722 const w = os.windows;
723 if (self.index >= self.end_index) {
724 var io: w.IO_STATUS_BLOCK = undefined;
725 const rc = w.ntdll.NtQueryDirectoryFile(
726 self.dir.fd,
727 null,
728 null,
729 null,
730 &io,
731 &self.buf,
732 self.buf.len,
733 .FileBothDirectoryInformation,
734 w.FALSE,
735 null,
736 if (self.first_iter) @as(w.BOOLEAN, w.TRUE) else @as(w.BOOLEAN, w.FALSE),
737 );
738 self.first_iter = false;
739 if (io.Information == 0) return null;
740 self.index = 0;
741 self.end_index = io.Information;
742 switch (rc) {
743 .SUCCESS => {},
744 .ACCESS_DENIED => return error.AccessDenied, // Double-check that the Dir was opened with iteration ability
745
746 else => return w.unexpectedStatus(rc),
747 }
748 }
749
750 // While the official api docs guarantee FILE_BOTH_DIR_INFORMATION to be aligned properly
751 // this may not always be the case (e.g. due to faulty VM/Sandboxing tools)
752 const dir_info: *align(2) w.FILE_BOTH_DIR_INFORMATION = @ptrCast(@alignCast(&self.buf[self.index]));
753 if (dir_info.NextEntryOffset != 0) {
754 self.index += dir_info.NextEntryOffset;
755 } else {
756 self.index = self.buf.len;
757 }
758
759 const name_utf16le = @as([*]u16, @ptrCast(&dir_info.FileName))[0 .. dir_info.FileNameLength / 2];
760
761 if (mem.eql(u16, name_utf16le, &[_]u16{'.'}) or mem.eql(u16, name_utf16le, &[_]u16{ '.', '.' }))
762 continue;
763 // Trust that Windows gives us valid UTF-16LE
764 const name_utf8_len = std.unicode.utf16leToUtf8(self.name_data[0..], name_utf16le) catch unreachable;
765 const name_utf8 = self.name_data[0..name_utf8_len];
766 const kind: Entry.Kind = blk: {
767 const attrs = dir_info.FileAttributes;
768 if (attrs & w.FILE_ATTRIBUTE_DIRECTORY != 0) break :blk .directory;
769 if (attrs & w.FILE_ATTRIBUTE_REPARSE_POINT != 0) break :blk .sym_link;
770 break :blk .file;
771 };
772 return Entry{
773 .name = name_utf8,
774 .kind = kind,
775 };
776 }
777 }
778
779 pub fn reset(self: *Self) void {
780 self.index = 0;
781 self.end_index = 0;
782 self.first_iter = true;
783 }
784 },
785 .wasi => struct {
786 dir: Dir,
787 buf: [1024]u8, // TODO align(@alignOf(os.wasi.dirent_t)),
788 cookie: u64,
789 index: usize,
790 end_index: usize,
791
792 const Self = @This();
793
794 pub const Error = IteratorError;
795
796 /// Memory such as file names referenced in this returned entry becomes invalid
797 /// with subsequent calls to `next`, as well as when this `Dir` is deinitialized.
798 pub fn next(self: *Self) Error!?Entry {
799 return self.nextWasi() catch |err| switch (err) {
800 // To be consistent across platforms, iteration ends if the directory being iterated is deleted during iteration.
801 // This matches the behavior of non-Linux UNIX platforms.
802 error.DirNotFound => null,
803 else => |e| return e,
804 };
805 }
806
807 pub const ErrorWasi = error{DirNotFound} || IteratorError;
808
809 /// Implementation of `next` that can return platform-dependent errors depending on the host platform.
810 /// When the host platform is Linux, `error.DirNotFound` can be returned if the directory being
811 /// iterated was deleted during iteration.
812 pub fn nextWasi(self: *Self) ErrorWasi!?Entry {
813 // We intentinally use fd_readdir even when linked with libc,
814 // since its implementation is exactly the same as below,
815 // and we avoid the code complexity here.
816 const w = os.wasi;
817 start_over: while (true) {
818 // According to the WASI spec, the last entry might be truncated,
819 // so we need to check if the left buffer contains the whole dirent.
820 if (self.end_index - self.index < @sizeOf(w.dirent_t)) {
821 var bufused: usize = undefined;
822 switch (w.fd_readdir(self.dir.fd, &self.buf, self.buf.len, self.cookie, &bufused)) {
823 .SUCCESS => {},
824 .BADF => unreachable, // Dir is invalid or was opened without iteration ability
825 .FAULT => unreachable,
826 .NOTDIR => unreachable,
827 .INVAL => unreachable,
828 .NOENT => return error.DirNotFound, // The directory being iterated was deleted during iteration.
829 .NOTCAPABLE => return error.AccessDenied,
830 else => |err| return os.unexpectedErrno(err),
831 }
832 if (bufused == 0) return null;
833 self.index = 0;
834 self.end_index = bufused;
835 }
836 const entry = @as(*align(1) w.dirent_t, @ptrCast(&self.buf[self.index]));
837 const entry_size = @sizeOf(w.dirent_t);
838 const name_index = self.index + entry_size;
839 if (name_index + entry.d_namlen > self.end_index) {
840 // This case, the name is truncated, so we need to call readdir to store the entire name.
841 self.end_index = self.index; // Force fd_readdir in the next loop.
842 continue :start_over;
843 }
844 const name = self.buf[name_index .. name_index + entry.d_namlen];
845
846 const next_index = name_index + entry.d_namlen;
847 self.index = next_index;
848 self.cookie = entry.d_next;
849
850 // skip . and .. entries
851 if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) {
852 continue :start_over;
853 }
854
855 const entry_kind: Entry.Kind = switch (entry.d_type) {
856 .BLOCK_DEVICE => .block_device,
857 .CHARACTER_DEVICE => .character_device,
858 .DIRECTORY => .directory,
859 .SYMBOLIC_LINK => .sym_link,
860 .REGULAR_FILE => .file,
861 .SOCKET_STREAM, .SOCKET_DGRAM => .unix_domain_socket,
862 else => .unknown,
863 };
864 return Entry{
865 .name = name,
866 .kind = entry_kind,
867 };
868 }
869 }
870
871 pub fn reset(self: *Self) void {
872 self.index = 0;
873 self.end_index = 0;
874 self.cookie = os.wasi.DIRCOOKIE_START;
875 }
876 },
877 else => @compileError("unimplemented"),
878 };
879
880 pub fn iterate(self: Dir) Iterator {
881 return self.iterateImpl(true);
882 }
883
884 /// Like `iterate`, but will not reset the directory cursor before the first
885 /// iteration. This should only be used in cases where it is known that the
886 /// `Dir` has not had its cursor modified yet (e.g. it was just opened).
887 pub fn iterateAssumeFirstIteration(self: Dir) Iterator {
888 return self.iterateImpl(false);
889 }
890
891 fn iterateImpl(self: Dir, first_iter_start_value: bool) Iterator {
892 switch (builtin.os.tag) {
893 .macos,
894 .ios,
895 .freebsd,
896 .netbsd,
897 .dragonfly,
898 .openbsd,
899 .solaris,
900 .illumos,
901 => return Iterator{
902 .dir = self,
903 .seek = 0,
904 .index = 0,
905 .end_index = 0,
906 .buf = undefined,
907 .first_iter = first_iter_start_value,
908 },
909 .linux, .haiku => return Iterator{
910 .dir = self,
911 .index = 0,
912 .end_index = 0,
913 .buf = undefined,
914 .first_iter = first_iter_start_value,
915 },
916 .windows => return Iterator{
917 .dir = self,
918 .index = 0,
919 .end_index = 0,
920 .first_iter = first_iter_start_value,
921 .buf = undefined,
922 .name_data = undefined,
923 },
924 .wasi => return Iterator{
925 .dir = self,
926 .cookie = os.wasi.DIRCOOKIE_START,
927 .index = 0,
928 .end_index = 0,
929 .buf = undefined,
930 },
931 else => @compileError("unimplemented"),
932 }
933 }
934
935 pub const Walker = struct {
936 stack: std.ArrayList(StackItem),
937 name_buffer: std.ArrayList(u8),
938
939 pub const WalkerEntry = struct {
940 /// The containing directory. This can be used to operate directly on `basename`
941 /// rather than `path`, avoiding `error.NameTooLong` for deeply nested paths.
942 /// The directory remains open until `next` or `deinit` is called.
943 dir: Dir,
944 basename: []const u8,
945 path: []const u8,
946 kind: Dir.Entry.Kind,
947 };
948
949 const StackItem = struct {
950 iter: Dir.Iterator,
951 dirname_len: usize,
952 };
953
954 /// After each call to this function, and on deinit(), the memory returned
955 /// from this function becomes invalid. A copy must be made in order to keep
956 /// a reference to the path.
957 pub fn next(self: *Walker) !?WalkerEntry {
958 while (self.stack.items.len != 0) {
959 // `top` and `containing` become invalid after appending to `self.stack`
960 var top = &self.stack.items[self.stack.items.len - 1];
961 var containing = top;
962 var dirname_len = top.dirname_len;
963 if (top.iter.next() catch |err| {
964 // If we get an error, then we want the user to be able to continue
965 // walking if they want, which means that we need to pop the directory
966 // that errored from the stack. Otherwise, all future `next` calls would
967 // likely just fail with the same error.
968 var item = self.stack.pop();
969 if (self.stack.items.len != 0) {
970 item.iter.dir.close();
971 }
972 return err;
973 }) |base| {
974 self.name_buffer.shrinkRetainingCapacity(dirname_len);
975 if (self.name_buffer.items.len != 0) {
976 try self.name_buffer.append(path.sep);
977 dirname_len += 1;
978 }
979 try self.name_buffer.appendSlice(base.name);
980 if (base.kind == .directory) {
981 var new_dir = top.iter.dir.openDir(base.name, .{ .iterate = true }) catch |err| switch (err) {
982 error.NameTooLong => unreachable, // no path sep in base.name
983 else => |e| return e,
984 };
985 {
986 errdefer new_dir.close();
987 try self.stack.append(StackItem{
988 .iter = new_dir.iterateAssumeFirstIteration(),
989 .dirname_len = self.name_buffer.items.len,
990 });
991 top = &self.stack.items[self.stack.items.len - 1];
992 containing = &self.stack.items[self.stack.items.len - 2];
993 }
994 }
995 return WalkerEntry{
996 .dir = containing.iter.dir,
997 .basename = self.name_buffer.items[dirname_len..],
998 .path = self.name_buffer.items,
999 .kind = base.kind,
1000 };
1001 } else {
1002 var item = self.stack.pop();
1003 if (self.stack.items.len != 0) {
1004 item.iter.dir.close();
1005 }
1006 }
1007 }
1008 return null;
1009 }
1010
1011 pub fn deinit(self: *Walker) void {
1012 // Close any remaining directories except the initial one (which is always at index 0)
1013 if (self.stack.items.len > 1) {
1014 for (self.stack.items[1..]) |*item| {
1015 item.iter.dir.close();
1016 }
1017 }
1018 self.stack.deinit();
1019 self.name_buffer.deinit();
1020 }
1021 };
1022
1023 /// Recursively iterates over a directory.
1024 /// `self` must have been opened with `OpenDirOptions{.iterate = true}`.
1025 /// Must call `Walker.deinit` when done.
1026 /// The order of returned file system entries is undefined.
1027 /// `self` will not be closed after walking it.
1028 pub fn walk(self: Dir, allocator: Allocator) !Walker {
1029 var name_buffer = std.ArrayList(u8).init(allocator);
1030 errdefer name_buffer.deinit();
1031
1032 var stack = std.ArrayList(Walker.StackItem).init(allocator);
1033 errdefer stack.deinit();
1034
1035 try stack.append(Walker.StackItem{
1036 .iter = self.iterate(),
1037 .dirname_len = 0,
1038 });
1039
1040 return Walker{
1041 .stack = stack,
1042 .name_buffer = name_buffer,
1043 };
1044 }
1045
1046 pub const OpenError = error{
1047 FileNotFound,
1048 NotDir,
1049 InvalidHandle,
1050 AccessDenied,
1051 SymLinkLoop,
1052 ProcessFdQuotaExceeded,
1053 NameTooLong,
1054 SystemFdQuotaExceeded,
1055 NoDevice,
1056 SystemResources,
1057 InvalidUtf8,
1058 BadPathName,
1059 DeviceBusy,
1060 /// On Windows, `\\server` or `\\server\share` was not found.
1061 NetworkNotFound,
1062 } || os.UnexpectedError;
1063
1064 pub fn close(self: *Dir) void {
1065 if (need_async_thread) {
1066 std.event.Loop.instance.?.close(self.fd);
1067 } else {
1068 os.close(self.fd);
1069 }
1070 self.* = undefined;
1071 }
1072
1073 /// Opens a file for reading or writing, without attempting to create a new file.
1074 /// To create a new file, see `createFile`.
1075 /// Call `File.close` to release the resource.
1076 /// Asserts that the path parameter has no null bytes.
1077 pub fn openFile(self: Dir, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File {
1078 if (builtin.os.tag == .windows) {
1079 const path_w = try os.windows.sliceToPrefixedFileW(self.fd, sub_path);
1080 return self.openFileW(path_w.span(), flags);
1081 }
1082 if (builtin.os.tag == .wasi and !builtin.link_libc) {
1083 return self.openFileWasi(sub_path, flags);
1084 }
1085 const path_c = try os.toPosixPath(sub_path);
1086 return self.openFileZ(&path_c, flags);
1087 }
1088
1089 /// Same as `openFile` but WASI only.
1090 pub fn openFileWasi(self: Dir, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File {
1091 const w = os.wasi;
1092 var fdflags: w.fdflags_t = 0x0;
1093 var base: w.rights_t = 0x0;
1094 if (flags.isRead()) {
1095 base |= w.RIGHT.FD_READ | w.RIGHT.FD_TELL | w.RIGHT.FD_SEEK | w.RIGHT.FD_FILESTAT_GET;
1096 }
1097 if (flags.isWrite()) {
1098 fdflags |= w.FDFLAG.APPEND;
1099 base |= w.RIGHT.FD_WRITE |
1100 w.RIGHT.FD_TELL |
1101 w.RIGHT.FD_SEEK |
1102 w.RIGHT.FD_DATASYNC |
1103 w.RIGHT.FD_FDSTAT_SET_FLAGS |
1104 w.RIGHT.FD_SYNC |
1105 w.RIGHT.FD_ALLOCATE |
1106 w.RIGHT.FD_ADVISE |
1107 w.RIGHT.FD_FILESTAT_SET_TIMES |
1108 w.RIGHT.FD_FILESTAT_SET_SIZE;
1109 }
1110 const fd = try os.openatWasi(self.fd, sub_path, 0x0, 0x0, fdflags, base, 0x0);
1111 return File{ .handle = fd };
1112 }
1113
1114 /// Same as `openFile` but the path parameter is null-terminated.
1115 pub fn openFileZ(self: Dir, sub_path: [*:0]const u8, flags: File.OpenFlags) File.OpenError!File {
1116 if (builtin.os.tag == .windows) {
1117 const path_w = try os.windows.cStrToPrefixedFileW(self.fd, sub_path);
1118 return self.openFileW(path_w.span(), flags);
1119 }
1120
1121 var os_flags: u32 = 0;
1122 if (@hasDecl(os.O, "CLOEXEC")) os_flags = os.O.CLOEXEC;
1123
1124 // Use the O locking flags if the os supports them to acquire the lock
1125 // atomically.
1126 const has_flock_open_flags = @hasDecl(os.O, "EXLOCK");
1127 if (has_flock_open_flags) {
1128 // Note that the O.NONBLOCK flag is removed after the openat() call
1129 // is successful.
1130 const nonblocking_lock_flag: u32 = if (flags.lock_nonblocking)
1131 os.O.NONBLOCK
1132 else
1133 0;
1134 os_flags |= switch (flags.lock) {
1135 .none => @as(u32, 0),
1136 .shared => os.O.SHLOCK | nonblocking_lock_flag,
1137 .exclusive => os.O.EXLOCK | nonblocking_lock_flag,
1138 };
1139 }
1140 if (@hasDecl(os.O, "LARGEFILE")) {
1141 os_flags |= os.O.LARGEFILE;
1142 }
1143 if (@hasDecl(os.O, "NOCTTY") and !flags.allow_ctty) {
1144 os_flags |= os.O.NOCTTY;
1145 }
1146 os_flags |= switch (flags.mode) {
1147 .read_only => @as(u32, os.O.RDONLY),
1148 .write_only => @as(u32, os.O.WRONLY),
1149 .read_write => @as(u32, os.O.RDWR),
1150 };
1151 const fd = if (flags.intended_io_mode != .blocking)
1152 try std.event.Loop.instance.?.openatZ(self.fd, sub_path, os_flags, 0)
1153 else
1154 try os.openatZ(self.fd, sub_path, os_flags, 0);
1155 errdefer os.close(fd);
1156
1157 // WASI doesn't have os.flock so we intetinally check OS prior to the inner if block
1158 // since it is not compiltime-known and we need to avoid undefined symbol in Wasm.
1159 if (@hasDecl(os.system, "LOCK") and builtin.target.os.tag != .wasi) {
1160 if (!has_flock_open_flags and flags.lock != .none) {
1161 // TODO: integrate async I/O
1162 const lock_nonblocking = if (flags.lock_nonblocking) os.LOCK.NB else @as(i32, 0);
1163 try os.flock(fd, switch (flags.lock) {
1164 .none => unreachable,
1165 .shared => os.LOCK.SH | lock_nonblocking,
1166 .exclusive => os.LOCK.EX | lock_nonblocking,
1167 });
1168 }
1169 }
1170
1171 if (has_flock_open_flags and flags.lock_nonblocking) {
1172 var fl_flags = os.fcntl(fd, os.F.GETFL, 0) catch |err| switch (err) {
1173 error.FileBusy => unreachable,
1174 error.Locked => unreachable,
1175 error.PermissionDenied => unreachable,
1176 error.DeadLock => unreachable,
1177 error.LockedRegionLimitExceeded => unreachable,
1178 else => |e| return e,
1179 };
1180 fl_flags &= ~@as(usize, os.O.NONBLOCK);
1181 _ = os.fcntl(fd, os.F.SETFL, fl_flags) catch |err| switch (err) {
1182 error.FileBusy => unreachable,
1183 error.Locked => unreachable,
1184 error.PermissionDenied => unreachable,
1185 error.DeadLock => unreachable,
1186 error.LockedRegionLimitExceeded => unreachable,
1187 else => |e| return e,
1188 };
1189 }
1190
1191 return File{
1192 .handle = fd,
1193 .capable_io_mode = .blocking,
1194 .intended_io_mode = flags.intended_io_mode,
1195 };
1196 }
1197
1198 /// Same as `openFile` but Windows-only and the path parameter is
1199 /// [WTF-16](https://simonsapin.github.io/wtf-8/#potentially-ill-formed-utf-16) encoded.
1200 pub fn openFileW(self: Dir, sub_path_w: []const u16, flags: File.OpenFlags) File.OpenError!File {
1201 const w = os.windows;
1202 const file: File = .{
1203 .handle = try w.OpenFile(sub_path_w, .{
1204 .dir = self.fd,
1205 .access_mask = w.SYNCHRONIZE |
1206 (if (flags.isRead()) @as(u32, w.GENERIC_READ) else 0) |
1207 (if (flags.isWrite()) @as(u32, w.GENERIC_WRITE) else 0),
1208 .creation = w.FILE_OPEN,
1209 .io_mode = flags.intended_io_mode,
1210 }),
1211 .capable_io_mode = std.io.default_mode,
1212 .intended_io_mode = flags.intended_io_mode,
1213 };
1214 errdefer file.close();
1215 var io: w.IO_STATUS_BLOCK = undefined;
1216 const range_off: w.LARGE_INTEGER = 0;
1217 const range_len: w.LARGE_INTEGER = 1;
1218 const exclusive = switch (flags.lock) {
1219 .none => return file,
1220 .shared => false,
1221 .exclusive => true,
1222 };
1223 try w.LockFile(
1224 file.handle,
1225 null,
1226 null,
1227 null,
1228 &io,
1229 &range_off,
1230 &range_len,
1231 null,
1232 @intFromBool(flags.lock_nonblocking),
1233 @intFromBool(exclusive),
1234 );
1235 return file;
1236 }
1237
1238 /// Creates, opens, or overwrites a file with write access.
1239 /// Call `File.close` on the result when done.
1240 /// Asserts that the path parameter has no null bytes.
1241 pub fn createFile(self: Dir, sub_path: []const u8, flags: File.CreateFlags) File.OpenError!File {
1242 if (builtin.os.tag == .windows) {
1243 const path_w = try os.windows.sliceToPrefixedFileW(self.fd, sub_path);
1244 return self.createFileW(path_w.span(), flags);
1245 }
1246 if (builtin.os.tag == .wasi and !builtin.link_libc) {
1247 return self.createFileWasi(sub_path, flags);
1248 }
1249 const path_c = try os.toPosixPath(sub_path);
1250 return self.createFileZ(&path_c, flags);
1251 }
1252
1253 /// Same as `createFile` but WASI only.
1254 pub fn createFileWasi(self: Dir, sub_path: []const u8, flags: File.CreateFlags) File.OpenError!File {
1255 const w = os.wasi;
1256 var oflags = w.O.CREAT;
1257 var base: w.rights_t = w.RIGHT.FD_WRITE |
1258 w.RIGHT.FD_DATASYNC |
1259 w.RIGHT.FD_SEEK |
1260 w.RIGHT.FD_TELL |
1261 w.RIGHT.FD_FDSTAT_SET_FLAGS |
1262 w.RIGHT.FD_SYNC |
1263 w.RIGHT.FD_ALLOCATE |
1264 w.RIGHT.FD_ADVISE |
1265 w.RIGHT.FD_FILESTAT_SET_TIMES |
1266 w.RIGHT.FD_FILESTAT_SET_SIZE |
1267 w.RIGHT.FD_FILESTAT_GET;
1268 if (flags.read) {
1269 base |= w.RIGHT.FD_READ;
1270 }
1271 if (flags.truncate) {
1272 oflags |= w.O.TRUNC;
1273 }
1274 if (flags.exclusive) {
1275 oflags |= w.O.EXCL;
1276 }
1277 const fd = try os.openatWasi(self.fd, sub_path, 0x0, oflags, 0x0, base, 0x0);
1278 return File{ .handle = fd };
1279 }
1280
1281 /// Same as `createFile` but the path parameter is null-terminated.
1282 pub fn createFileZ(self: Dir, sub_path_c: [*:0]const u8, flags: File.CreateFlags) File.OpenError!File {
1283 if (builtin.os.tag == .windows) {
1284 const path_w = try os.windows.cStrToPrefixedFileW(self.fd, sub_path_c);
1285 return self.createFileW(path_w.span(), flags);
1286 }
1287
1288 // Use the O locking flags if the os supports them to acquire the lock
1289 // atomically.
1290 const has_flock_open_flags = @hasDecl(os.O, "EXLOCK");
1291 // Note that the O.NONBLOCK flag is removed after the openat() call
1292 // is successful.
1293 const nonblocking_lock_flag: u32 = if (has_flock_open_flags and flags.lock_nonblocking)
1294 os.O.NONBLOCK
1295 else
1296 0;
1297 const lock_flag: u32 = if (has_flock_open_flags) switch (flags.lock) {
1298 .none => @as(u32, 0),
1299 .shared => os.O.SHLOCK | nonblocking_lock_flag,
1300 .exclusive => os.O.EXLOCK | nonblocking_lock_flag,
1301 } else 0;
1302
1303 const O_LARGEFILE = if (@hasDecl(os.O, "LARGEFILE")) os.O.LARGEFILE else 0;
1304 const os_flags = lock_flag | O_LARGEFILE | os.O.CREAT | os.O.CLOEXEC |
1305 (if (flags.truncate) @as(u32, os.O.TRUNC) else 0) |
1306 (if (flags.read) @as(u32, os.O.RDWR) else os.O.WRONLY) |
1307 (if (flags.exclusive) @as(u32, os.O.EXCL) else 0);
1308 const fd = if (flags.intended_io_mode != .blocking)
1309 try std.event.Loop.instance.?.openatZ(self.fd, sub_path_c, os_flags, flags.mode)
1310 else
1311 try os.openatZ(self.fd, sub_path_c, os_flags, flags.mode);
1312 errdefer os.close(fd);
1313
1314 // WASI doesn't have os.flock so we intetinally check OS prior to the inner if block
1315 // since it is not compiltime-known and we need to avoid undefined symbol in Wasm.
1316 if (builtin.target.os.tag != .wasi) {
1317 if (!has_flock_open_flags and flags.lock != .none) {
1318 // TODO: integrate async I/O
1319 const lock_nonblocking = if (flags.lock_nonblocking) os.LOCK.NB else @as(i32, 0);
1320 try os.flock(fd, switch (flags.lock) {
1321 .none => unreachable,
1322 .shared => os.LOCK.SH | lock_nonblocking,
1323 .exclusive => os.LOCK.EX | lock_nonblocking,
1324 });
1325 }
1326 }
1327
1328 if (has_flock_open_flags and flags.lock_nonblocking) {
1329 var fl_flags = os.fcntl(fd, os.F.GETFL, 0) catch |err| switch (err) {
1330 error.FileBusy => unreachable,
1331 error.Locked => unreachable,
1332 error.PermissionDenied => unreachable,
1333 error.DeadLock => unreachable,
1334 error.LockedRegionLimitExceeded => unreachable,
1335 else => |e| return e,
1336 };
1337 fl_flags &= ~@as(usize, os.O.NONBLOCK);
1338 _ = os.fcntl(fd, os.F.SETFL, fl_flags) catch |err| switch (err) {
1339 error.FileBusy => unreachable,
1340 error.Locked => unreachable,
1341 error.PermissionDenied => unreachable,
1342 error.DeadLock => unreachable,
1343 error.LockedRegionLimitExceeded => unreachable,
1344 else => |e| return e,
1345 };
1346 }
1347
1348 return File{
1349 .handle = fd,
1350 .capable_io_mode = .blocking,
1351 .intended_io_mode = flags.intended_io_mode,
1352 };
1353 }
1354
1355 /// Same as `createFile` but Windows-only and the path parameter is
1356 /// [WTF-16](https://simonsapin.github.io/wtf-8/#potentially-ill-formed-utf-16) encoded.
1357 pub fn createFileW(self: Dir, sub_path_w: []const u16, flags: File.CreateFlags) File.OpenError!File {
1358 const w = os.windows;
1359 const read_flag = if (flags.read) @as(u32, w.GENERIC_READ) else 0;
1360 const file: File = .{
1361 .handle = try os.windows.OpenFile(sub_path_w, .{
1362 .dir = self.fd,
1363 .access_mask = w.SYNCHRONIZE | w.GENERIC_WRITE | read_flag,
1364 .creation = if (flags.exclusive)
1365 @as(u32, w.FILE_CREATE)
1366 else if (flags.truncate)
1367 @as(u32, w.FILE_OVERWRITE_IF)
1368 else
1369 @as(u32, w.FILE_OPEN_IF),
1370 .io_mode = flags.intended_io_mode,
1371 }),
1372 .capable_io_mode = std.io.default_mode,
1373 .intended_io_mode = flags.intended_io_mode,
1374 };
1375 errdefer file.close();
1376 var io: w.IO_STATUS_BLOCK = undefined;
1377 const range_off: w.LARGE_INTEGER = 0;
1378 const range_len: w.LARGE_INTEGER = 1;
1379 const exclusive = switch (flags.lock) {
1380 .none => return file,
1381 .shared => false,
1382 .exclusive => true,
1383 };
1384 try w.LockFile(
1385 file.handle,
1386 null,
1387 null,
1388 null,
1389 &io,
1390 &range_off,
1391 &range_len,
1392 null,
1393 @intFromBool(flags.lock_nonblocking),
1394 @intFromBool(exclusive),
1395 );
1396 return file;
1397 }
1398
1399 /// Creates a single directory with a relative or absolute path.
1400 /// To create multiple directories to make an entire path, see `makePath`.
1401 /// To operate on only absolute paths, see `makeDirAbsolute`.
1402 pub fn makeDir(self: Dir, sub_path: []const u8) !void {
1403 try os.mkdirat(self.fd, sub_path, default_new_dir_mode);
1404 }
1405
1406 /// Creates a single directory with a relative or absolute null-terminated UTF-8-encoded path.
1407 /// To create multiple directories to make an entire path, see `makePath`.
1408 /// To operate on only absolute paths, see `makeDirAbsoluteZ`.
1409 pub fn makeDirZ(self: Dir, sub_path: [*:0]const u8) !void {
1410 try os.mkdiratZ(self.fd, sub_path, default_new_dir_mode);
1411 }
1412
1413 /// Creates a single directory with a relative or absolute null-terminated WTF-16-encoded path.
1414 /// To create multiple directories to make an entire path, see `makePath`.
1415 /// To operate on only absolute paths, see `makeDirAbsoluteW`.
1416 pub fn makeDirW(self: Dir, sub_path: [*:0]const u16) !void {
1417 try os.mkdiratW(self.fd, sub_path, default_new_dir_mode);
1418 }
1419
1420 /// Calls makeDir iteratively to make an entire path
1421 /// (i.e. creating any parent directories that do not exist).
1422 /// Returns success if the path already exists and is a directory.
1423 /// This function is not atomic, and if it returns an error, the file system may
1424 /// have been modified regardless.
1425 pub fn makePath(self: Dir, sub_path: []const u8) !void {
1426 var it = try path.componentIterator(sub_path);
1427 var component = it.last() orelse return;
1428 while (true) {
1429 self.makeDir(component.path) catch |err| switch (err) {
1430 error.PathAlreadyExists => {
1431 // TODO stat the file and return an error if it's not a directory
1432 // this is important because otherwise a dangling symlink
1433 // could cause an infinite loop
1434 },
1435 error.FileNotFound => |e| {
1436 component = it.previous() orelse return e;
1437 continue;
1438 },
1439 else => |e| return e,
1440 };
1441 component = it.next() orelse return;
1442 }
1443 }
1444
1445 /// Calls makeOpenDirAccessMaskW iteratively to make an entire path
1446 /// (i.e. creating any parent directories that do not exist).
1447 /// Opens the dir if the path already exists and is a directory.
1448 /// This function is not atomic, and if it returns an error, the file system may
1449 /// have been modified regardless.
1450 fn makeOpenPathAccessMaskW(self: Dir, sub_path: []const u8, access_mask: u32, no_follow: bool) OpenError!Dir {
1451 const w = os.windows;
1452 var it = try path.componentIterator(sub_path);
1453 // If there are no components in the path, then create a dummy component with the full path.
1454 var component = it.last() orelse path.NativeUtf8ComponentIterator.Component{
1455 .name = "",
1456 .path = sub_path,
1457 };
1458
1459 while (true) {
1460 const sub_path_w = try w.sliceToPrefixedFileW(self.fd, component.path);
1461 const is_last = it.peekNext() == null;
1462 var result = self.makeOpenDirAccessMaskW(sub_path_w.span().ptr, access_mask, .{
1463 .no_follow = no_follow,
1464 .create_disposition = if (is_last) w.FILE_OPEN_IF else w.FILE_CREATE,
1465 }) catch |err| switch (err) {
1466 error.FileNotFound => |e| {
1467 component = it.previous() orelse return e;
1468 continue;
1469 },
1470 else => |e| return e,
1471 };
1472
1473 component = it.next() orelse return result;
1474 // Don't leak the intermediate file handles
1475 result.close();
1476 }
1477 }
1478
1479 /// This function performs `makePath`, followed by `openDir`.
1480 /// If supported by the OS, this operation is atomic. It is not atomic on
1481 /// all operating systems.
1482 /// On Windows, this function performs `makeOpenPathAccessMaskW`.
1483 pub fn makeOpenPath(self: Dir, sub_path: []const u8, open_dir_options: OpenDirOptions) !Dir {
1484 return switch (builtin.os.tag) {
1485 .windows => {
1486 const w = os.windows;
1487 const base_flags = w.STANDARD_RIGHTS_READ | w.FILE_READ_ATTRIBUTES | w.FILE_READ_EA |
1488 w.SYNCHRONIZE | w.FILE_TRAVERSE |
1489 (if (open_dir_options.iterate) w.FILE_LIST_DIRECTORY else 0);
1490
1491 return self.makeOpenPathAccessMaskW(sub_path, base_flags, open_dir_options.no_follow);
1492 },
1493 else => {
1494 return self.openDir(sub_path, open_dir_options) catch |err| switch (err) {
1495 error.FileNotFound => {
1496 try self.makePath(sub_path);
1497 return self.openDir(sub_path, open_dir_options);
1498 },
1499 else => |e| return e,
1500 };
1501 },
1502 };
1503 }
1504
1505 /// This function returns the canonicalized absolute pathname of
1506 /// `pathname` relative to this `Dir`. If `pathname` is absolute, ignores this
1507 /// `Dir` handle and returns the canonicalized absolute pathname of `pathname`
1508 /// argument.
1509 /// This function is not universally supported by all platforms.
1510 /// Currently supported hosts are: Linux, macOS, and Windows.
1511 /// See also `Dir.realpathZ`, `Dir.realpathW`, and `Dir.realpathAlloc`.
1512 pub fn realpath(self: Dir, pathname: []const u8, out_buffer: []u8) ![]u8 {
1513 if (builtin.os.tag == .wasi) {
1514 @compileError("realpath is not available on WASI");
1515 }
1516 if (builtin.os.tag == .windows) {
1517 const pathname_w = try os.windows.sliceToPrefixedFileW(self.fd, pathname);
1518 return self.realpathW(pathname_w.span(), out_buffer);
1519 }
1520 const pathname_c = try os.toPosixPath(pathname);
1521 return self.realpathZ(&pathname_c, out_buffer);
1522 }
1523
1524 /// Same as `Dir.realpath` except `pathname` is null-terminated.
1525 /// See also `Dir.realpath`, `realpathZ`.
1526 pub fn realpathZ(self: Dir, pathname: [*:0]const u8, out_buffer: []u8) ![]u8 {
1527 if (builtin.os.tag == .windows) {
1528 const pathname_w = try os.windows.cStrToPrefixedFileW(self.fd, pathname);
1529 return self.realpathW(pathname_w.span(), out_buffer);
1530 }
1531
1532 const flags = if (builtin.os.tag == .linux) os.O.PATH | os.O.NONBLOCK | os.O.CLOEXEC else os.O.NONBLOCK | os.O.CLOEXEC;
1533 const fd = os.openatZ(self.fd, pathname, flags, 0) catch |err| switch (err) {
1534 error.FileLocksNotSupported => unreachable,
1535 else => |e| return e,
1536 };
1537 defer os.close(fd);
1538
1539 // Use of MAX_PATH_BYTES here is valid as the realpath function does not
1540 // have a variant that takes an arbitrary-size buffer.
1541 // TODO(#4812): Consider reimplementing realpath or using the POSIX.1-2008
1542 // NULL out parameter (GNU's canonicalize_file_name) to handle overelong
1543 // paths. musl supports passing NULL but restricts the output to PATH_MAX
1544 // anyway.
1545 var buffer: [MAX_PATH_BYTES]u8 = undefined;
1546 const out_path = try os.getFdPath(fd, &buffer);
1547
1548 if (out_path.len > out_buffer.len) {
1549 return error.NameTooLong;
1550 }
1551
1552 const result = out_buffer[0..out_path.len];
1553 @memcpy(result, out_path);
1554 return result;
1555 }
1556
1557 /// Windows-only. Same as `Dir.realpath` except `pathname` is WTF16 encoded.
1558 /// See also `Dir.realpath`, `realpathW`.
1559 pub fn realpathW(self: Dir, pathname: []const u16, out_buffer: []u8) ![]u8 {
1560 const w = os.windows;
1561
1562 const access_mask = w.GENERIC_READ | w.SYNCHRONIZE;
1563 const share_access = w.FILE_SHARE_READ;
1564 const creation = w.FILE_OPEN;
1565 const h_file = blk: {
1566 const res = w.OpenFile(pathname, .{
1567 .dir = self.fd,
1568 .access_mask = access_mask,
1569 .share_access = share_access,
1570 .creation = creation,
1571 .io_mode = .blocking,
1572 .filter = .any,
1573 }) catch |err| switch (err) {
1574 error.WouldBlock => unreachable,
1575 else => |e| return e,
1576 };
1577 break :blk res;
1578 };
1579 defer w.CloseHandle(h_file);
1580
1581 // Use of MAX_PATH_BYTES here is valid as the realpath function does not
1582 // have a variant that takes an arbitrary-size buffer.
1583 // TODO(#4812): Consider reimplementing realpath or using the POSIX.1-2008
1584 // NULL out parameter (GNU's canonicalize_file_name) to handle overelong
1585 // paths. musl supports passing NULL but restricts the output to PATH_MAX
1586 // anyway.
1587 var buffer: [MAX_PATH_BYTES]u8 = undefined;
1588 const out_path = try os.getFdPath(h_file, &buffer);
1589
1590 if (out_path.len > out_buffer.len) {
1591 return error.NameTooLong;
1592 }
1593
1594 const result = out_buffer[0..out_path.len];
1595 @memcpy(result, out_path);
1596 return result;
1597 }
1598
1599 /// Same as `Dir.realpath` except caller must free the returned memory.
1600 /// See also `Dir.realpath`.
1601 pub fn realpathAlloc(self: Dir, allocator: Allocator, pathname: []const u8) ![]u8 {
1602 // Use of MAX_PATH_BYTES here is valid as the realpath function does not
1603 // have a variant that takes an arbitrary-size buffer.
1604 // TODO(#4812): Consider reimplementing realpath or using the POSIX.1-2008
1605 // NULL out parameter (GNU's canonicalize_file_name) to handle overelong
1606 // paths. musl supports passing NULL but restricts the output to PATH_MAX
1607 // anyway.
1608 var buf: [MAX_PATH_BYTES]u8 = undefined;
1609 return allocator.dupe(u8, try self.realpath(pathname, buf[0..]));
1610 }
1611
1612 /// Changes the current working directory to the open directory handle.
1613 /// This modifies global state and can have surprising effects in multi-
1614 /// threaded applications. Most applications and especially libraries should
1615 /// not call this function as a general rule, however it can have use cases
1616 /// in, for example, implementing a shell, or child process execution.
1617 /// Not all targets support this. For example, WASI does not have the concept
1618 /// of a current working directory.
1619 pub fn setAsCwd(self: Dir) !void {
1620 if (builtin.os.tag == .wasi) {
1621 @compileError("changing cwd is not currently possible in WASI");
1622 }
1623 if (builtin.os.tag == .windows) {
1624 var dir_path_buffer: [os.windows.PATH_MAX_WIDE]u16 = undefined;
1625 const dir_path = try os.windows.GetFinalPathNameByHandle(self.fd, .{}, &dir_path_buffer);
1626 if (builtin.link_libc) {
1627 return os.chdirW(dir_path);
1628 }
1629 return os.windows.SetCurrentDirectory(dir_path);
1630 }
1631 try os.fchdir(self.fd);
1632 }
1633
1634 pub const OpenDirOptions = struct {
1635 /// `true` means the opened directory can be used as the `Dir` parameter
1636 /// for functions which operate based on an open directory handle. When `false`,
1637 /// such operations are Illegal Behavior.
1638 access_sub_paths: bool = true,
1639
1640 /// `true` means the opened directory can be scanned for the files and sub-directories
1641 /// of the result. It means the `iterate` function can be called.
1642 iterate: bool = false,
1643
1644 /// `true` means it won't dereference the symlinks.
1645 no_follow: bool = false,
1646 };
1647
1648 /// Opens a directory at the given path. The directory is a system resource that remains
1649 /// open until `close` is called on the result.
1650 /// The directory cannot be iterated unless the `iterate` option is set to `true`.
1651 ///
1652 /// Asserts that the path parameter has no null bytes.
1653 pub fn openDir(self: Dir, sub_path: []const u8, args: OpenDirOptions) OpenError!Dir {
1654 if (builtin.os.tag == .windows) {
1655 const sub_path_w = try os.windows.sliceToPrefixedFileW(self.fd, sub_path);
1656 return self.openDirW(sub_path_w.span().ptr, args);
1657 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
1658 return self.openDirWasi(sub_path, args);
1659 } else {
1660 const sub_path_c = try os.toPosixPath(sub_path);
1661 return self.openDirZ(&sub_path_c, args);
1662 }
1663 }
1664
1665 /// Same as `openDir` except only WASI.
1666 pub fn openDirWasi(self: Dir, sub_path: []const u8, args: OpenDirOptions) OpenError!Dir {
1667 const w = os.wasi;
1668 var base: w.rights_t = w.RIGHT.FD_FILESTAT_GET | w.RIGHT.FD_FDSTAT_SET_FLAGS | w.RIGHT.FD_FILESTAT_SET_TIMES;
1669 if (args.access_sub_paths) {
1670 base |= w.RIGHT.FD_READDIR |
1671 w.RIGHT.PATH_CREATE_DIRECTORY |
1672 w.RIGHT.PATH_CREATE_FILE |
1673 w.RIGHT.PATH_LINK_SOURCE |
1674 w.RIGHT.PATH_LINK_TARGET |
1675 w.RIGHT.PATH_OPEN |
1676 w.RIGHT.PATH_READLINK |
1677 w.RIGHT.PATH_RENAME_SOURCE |
1678 w.RIGHT.PATH_RENAME_TARGET |
1679 w.RIGHT.PATH_FILESTAT_GET |
1680 w.RIGHT.PATH_FILESTAT_SET_SIZE |
1681 w.RIGHT.PATH_FILESTAT_SET_TIMES |
1682 w.RIGHT.PATH_SYMLINK |
1683 w.RIGHT.PATH_REMOVE_DIRECTORY |
1684 w.RIGHT.PATH_UNLINK_FILE;
1685 }
1686 const symlink_flags: w.lookupflags_t = if (args.no_follow) 0x0 else w.LOOKUP_SYMLINK_FOLLOW;
1687 // TODO do we really need all the rights here?
1688 const inheriting: w.rights_t = w.RIGHT.ALL ^ w.RIGHT.SOCK_SHUTDOWN;
1689
1690 const result = os.openatWasi(
1691 self.fd,
1692 sub_path,
1693 symlink_flags,
1694 w.O.DIRECTORY,
1695 0x0,
1696 base,
1697 inheriting,
1698 );
1699 const fd = result catch |err| switch (err) {
1700 error.FileTooBig => unreachable, // can't happen for directories
1701 error.IsDir => unreachable, // we're providing O.DIRECTORY
1702 error.NoSpaceLeft => unreachable, // not providing O.CREAT
1703 error.PathAlreadyExists => unreachable, // not providing O.CREAT
1704 error.FileLocksNotSupported => unreachable, // locking folders is not supported
1705 error.WouldBlock => unreachable, // can't happen for directories
1706 error.FileBusy => unreachable, // can't happen for directories
1707 else => |e| return e,
1708 };
1709 return Dir{ .fd = fd };
1710 }
1711
1712 /// Same as `openDir` except the parameter is null-terminated.
1713 pub fn openDirZ(self: Dir, sub_path_c: [*:0]const u8, args: OpenDirOptions) OpenError!Dir {
1714 if (builtin.os.tag == .windows) {
1715 const sub_path_w = try os.windows.cStrToPrefixedFileW(self.fd, sub_path_c);
1716 return self.openDirW(sub_path_w.span().ptr, args);
1717 }
1718 const symlink_flags: u32 = if (args.no_follow) os.O.NOFOLLOW else 0x0;
1719 if (!args.iterate) {
1720 const O_PATH = if (@hasDecl(os.O, "PATH")) os.O.PATH else 0;
1721 return self.openDirFlagsZ(sub_path_c, os.O.DIRECTORY | os.O.RDONLY | os.O.CLOEXEC | O_PATH | symlink_flags);
1722 } else {
1723 return self.openDirFlagsZ(sub_path_c, os.O.DIRECTORY | os.O.RDONLY | os.O.CLOEXEC | symlink_flags);
1724 }
1725 }
1726
1727 /// Same as `openDir` except the path parameter is WTF-16 encoded, NT-prefixed.
1728 /// This function asserts the target OS is Windows.
1729 pub fn openDirW(self: Dir, sub_path_w: [*:0]const u16, args: OpenDirOptions) OpenError!Dir {
1730 const w = os.windows;
1731 // TODO remove some of these flags if args.access_sub_paths is false
1732 const base_flags = w.STANDARD_RIGHTS_READ | w.FILE_READ_ATTRIBUTES | w.FILE_READ_EA |
1733 w.SYNCHRONIZE | w.FILE_TRAVERSE;
1734 const flags: u32 = if (args.iterate) base_flags | w.FILE_LIST_DIRECTORY else base_flags;
1735 const dir = try self.makeOpenDirAccessMaskW(sub_path_w, flags, .{
1736 .no_follow = args.no_follow,
1737 .create_disposition = w.FILE_OPEN,
1738 });
1739 return dir;
1740 }
1741
1742 /// `flags` must contain `os.O.DIRECTORY`.
1743 fn openDirFlagsZ(self: Dir, sub_path_c: [*:0]const u8, flags: u32) OpenError!Dir {
1744 const result = if (need_async_thread)
1745 std.event.Loop.instance.?.openatZ(self.fd, sub_path_c, flags, 0)
1746 else
1747 os.openatZ(self.fd, sub_path_c, flags, 0);
1748 const fd = result catch |err| switch (err) {
1749 error.FileTooBig => unreachable, // can't happen for directories
1750 error.IsDir => unreachable, // we're providing O.DIRECTORY
1751 error.NoSpaceLeft => unreachable, // not providing O.CREAT
1752 error.PathAlreadyExists => unreachable, // not providing O.CREAT
1753 error.FileLocksNotSupported => unreachable, // locking folders is not supported
1754 error.WouldBlock => unreachable, // can't happen for directories
1755 error.FileBusy => unreachable, // can't happen for directories
1756 else => |e| return e,
1757 };
1758 return Dir{ .fd = fd };
1759 }
1760
1761 const MakeOpenDirAccessMaskWOptions = struct {
1762 no_follow: bool,
1763 create_disposition: u32,
1764 };
1765
1766 fn makeOpenDirAccessMaskW(self: Dir, sub_path_w: [*:0]const u16, access_mask: u32, flags: MakeOpenDirAccessMaskWOptions) OpenError!Dir {
1767 const w = os.windows;
1768
1769 var result = Dir{
1770 .fd = undefined,
1771 };
1772
1773 const path_len_bytes = @as(u16, @intCast(mem.sliceTo(sub_path_w, 0).len * 2));
1774 var nt_name = w.UNICODE_STRING{
1775 .Length = path_len_bytes,
1776 .MaximumLength = path_len_bytes,
1777 .Buffer = @constCast(sub_path_w),
1778 };
1779 var attr = w.OBJECT_ATTRIBUTES{
1780 .Length = @sizeOf(w.OBJECT_ATTRIBUTES),
1781 .RootDirectory = if (path.isAbsoluteWindowsW(sub_path_w)) null else self.fd,
1782 .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
1783 .ObjectName = &nt_name,
1784 .SecurityDescriptor = null,
1785 .SecurityQualityOfService = null,
1786 };
1787 const open_reparse_point: w.DWORD = if (flags.no_follow) w.FILE_OPEN_REPARSE_POINT else 0x0;
1788 var io: w.IO_STATUS_BLOCK = undefined;
1789 const rc = w.ntdll.NtCreateFile(
1790 &result.fd,
1791 access_mask,
1792 &attr,
1793 &io,
1794 null,
1795 w.FILE_ATTRIBUTE_NORMAL,
1796 w.FILE_SHARE_READ | w.FILE_SHARE_WRITE,
1797 flags.create_disposition,
1798 w.FILE_DIRECTORY_FILE | w.FILE_SYNCHRONOUS_IO_NONALERT | w.FILE_OPEN_FOR_BACKUP_INTENT | open_reparse_point,
1799 null,
1800 0,
1801 );
1802
1803 switch (rc) {
1804 .SUCCESS => return result,
1805 .OBJECT_NAME_INVALID => return error.BadPathName,
1806 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
1807 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
1808 .NOT_A_DIRECTORY => return error.NotDir,
1809 // This can happen if the directory has 'List folder contents' permission set to 'Deny'
1810 // and the directory is trying to be opened for iteration.
1811 .ACCESS_DENIED => return error.AccessDenied,
1812 .INVALID_PARAMETER => unreachable,
1813 else => return w.unexpectedStatus(rc),
1814 }
1815 }
1816
1817 pub const DeleteFileError = os.UnlinkError;
1818
1819 /// Delete a file name and possibly the file it refers to, based on an open directory handle.
1820 /// Asserts that the path parameter has no null bytes.
1821 pub fn deleteFile(self: Dir, sub_path: []const u8) DeleteFileError!void {
1822 if (builtin.os.tag == .windows) {
1823 const sub_path_w = try os.windows.sliceToPrefixedFileW(self.fd, sub_path);
1824 return self.deleteFileW(sub_path_w.span());
1825 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
1826 os.unlinkat(self.fd, sub_path, 0) catch |err| switch (err) {
1827 error.DirNotEmpty => unreachable, // not passing AT.REMOVEDIR
1828 else => |e| return e,
1829 };
1830 } else {
1831 const sub_path_c = try os.toPosixPath(sub_path);
1832 return self.deleteFileZ(&sub_path_c);
1833 }
1834 }
1835
1836 /// Same as `deleteFile` except the parameter is null-terminated.
1837 pub fn deleteFileZ(self: Dir, sub_path_c: [*:0]const u8) DeleteFileError!void {
1838 os.unlinkatZ(self.fd, sub_path_c, 0) catch |err| switch (err) {
1839 error.DirNotEmpty => unreachable, // not passing AT.REMOVEDIR
1840 error.AccessDenied => |e| switch (builtin.os.tag) {
1841 // non-Linux POSIX systems return EPERM when trying to delete a directory, so
1842 // we need to handle that case specifically and translate the error
1843 .macos, .ios, .freebsd, .netbsd, .dragonfly, .openbsd, .solaris, .illumos => {
1844 // Don't follow symlinks to match unlinkat (which acts on symlinks rather than follows them)
1845 const fstat = os.fstatatZ(self.fd, sub_path_c, os.AT.SYMLINK_NOFOLLOW) catch return e;
1846 const is_dir = fstat.mode & os.S.IFMT == os.S.IFDIR;
1847 return if (is_dir) error.IsDir else e;
1848 },
1849 else => return e,
1850 },
1851 else => |e| return e,
1852 };
1853 }
1854
1855 /// Same as `deleteFile` except the parameter is WTF-16 encoded.
1856 pub fn deleteFileW(self: Dir, sub_path_w: []const u16) DeleteFileError!void {
1857 os.unlinkatW(self.fd, sub_path_w, 0) catch |err| switch (err) {
1858 error.DirNotEmpty => unreachable, // not passing AT.REMOVEDIR
1859 else => |e| return e,
1860 };
1861 }
1862
1863 pub const DeleteDirError = error{
1864 DirNotEmpty,
1865 FileNotFound,
1866 AccessDenied,
1867 FileBusy,
1868 FileSystem,
1869 SymLinkLoop,
1870 NameTooLong,
1871 NotDir,
1872 SystemResources,
1873 ReadOnlyFileSystem,
1874 InvalidUtf8,
1875 BadPathName,
1876 /// On Windows, `\\server` or `\\server\share` was not found.
1877 NetworkNotFound,
1878 Unexpected,
1879 };
1880
1881 /// Returns `error.DirNotEmpty` if the directory is not empty.
1882 /// To delete a directory recursively, see `deleteTree`.
1883 /// Asserts that the path parameter has no null bytes.
1884 pub fn deleteDir(self: Dir, sub_path: []const u8) DeleteDirError!void {
1885 if (builtin.os.tag == .windows) {
1886 const sub_path_w = try os.windows.sliceToPrefixedFileW(self.fd, sub_path);
1887 return self.deleteDirW(sub_path_w.span());
1888 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
1889 os.unlinkat(self.fd, sub_path, os.AT.REMOVEDIR) catch |err| switch (err) {
1890 error.IsDir => unreachable, // not possible since we pass AT.REMOVEDIR
1891 else => |e| return e,
1892 };
1893 } else {
1894 const sub_path_c = try os.toPosixPath(sub_path);
1895 return self.deleteDirZ(&sub_path_c);
1896 }
1897 }
1898
1899 /// Same as `deleteDir` except the parameter is null-terminated.
1900 pub fn deleteDirZ(self: Dir, sub_path_c: [*:0]const u8) DeleteDirError!void {
1901 os.unlinkatZ(self.fd, sub_path_c, os.AT.REMOVEDIR) catch |err| switch (err) {
1902 error.IsDir => unreachable, // not possible since we pass AT.REMOVEDIR
1903 else => |e| return e,
1904 };
1905 }
1906
1907 /// Same as `deleteDir` except the parameter is UTF16LE, NT prefixed.
1908 /// This function is Windows-only.
1909 pub fn deleteDirW(self: Dir, sub_path_w: []const u16) DeleteDirError!void {
1910 os.unlinkatW(self.fd, sub_path_w, os.AT.REMOVEDIR) catch |err| switch (err) {
1911 error.IsDir => unreachable, // not possible since we pass AT.REMOVEDIR
1912 else => |e| return e,
1913 };
1914 }
1915
1916 pub const RenameError = os.RenameError;
1917
1918 /// Change the name or location of a file or directory.
1919 /// If new_sub_path already exists, it will be replaced.
1920 /// Renaming a file over an existing directory or a directory
1921 /// over an existing file will fail with `error.IsDir` or `error.NotDir`
1922 pub fn rename(self: Dir, old_sub_path: []const u8, new_sub_path: []const u8) RenameError!void {
1923 return os.renameat(self.fd, old_sub_path, self.fd, new_sub_path);
1924 }
1925
1926 /// Same as `rename` except the parameters are null-terminated.
1927 pub fn renameZ(self: Dir, old_sub_path_z: [*:0]const u8, new_sub_path_z: [*:0]const u8) RenameError!void {
1928 return os.renameatZ(self.fd, old_sub_path_z, self.fd, new_sub_path_z);
1929 }
1930
1931 /// Same as `rename` except the parameters are UTF16LE, NT prefixed.
1932 /// This function is Windows-only.
1933 pub fn renameW(self: Dir, old_sub_path_w: []const u16, new_sub_path_w: []const u16) RenameError!void {
1934 return os.renameatW(self.fd, old_sub_path_w, self.fd, new_sub_path_w);
1935 }
1936
1937 /// Creates a symbolic link named `sym_link_path` which contains the string `target_path`.
1938 /// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent
1939 /// one; the latter case is known as a dangling link.
1940 /// If `sym_link_path` exists, it will not be overwritten.
1941 pub fn symLink(
1942 self: Dir,
1943 target_path: []const u8,
1944 sym_link_path: []const u8,
1945 flags: SymLinkFlags,
1946 ) !void {
1947 if (builtin.os.tag == .wasi and !builtin.link_libc) {
1948 return self.symLinkWasi(target_path, sym_link_path, flags);
1949 }
1950 if (builtin.os.tag == .windows) {
1951 // Target path does not use sliceToPrefixedFileW because certain paths
1952 // are handled differently when creating a symlink than they would be
1953 // when converting to an NT namespaced path. CreateSymbolicLink in
1954 // symLinkW will handle the necessary conversion.
1955 var target_path_w: os.windows.PathSpace = undefined;
1956 target_path_w.len = try std.unicode.utf8ToUtf16Le(&target_path_w.data, target_path);
1957 target_path_w.data[target_path_w.len] = 0;
1958 const sym_link_path_w = try os.windows.sliceToPrefixedFileW(self.fd, sym_link_path);
1959 return self.symLinkW(target_path_w.span(), sym_link_path_w.span(), flags);
1960 }
1961 const target_path_c = try os.toPosixPath(target_path);
1962 const sym_link_path_c = try os.toPosixPath(sym_link_path);
1963 return self.symLinkZ(&target_path_c, &sym_link_path_c, flags);
1964 }
1965
1966 /// WASI-only. Same as `symLink` except targeting WASI.
1967 pub fn symLinkWasi(
1968 self: Dir,
1969 target_path: []const u8,
1970 sym_link_path: []const u8,
1971 _: SymLinkFlags,
1972 ) !void {
1973 return os.symlinkat(target_path, self.fd, sym_link_path);
1974 }
1975
1976 /// Same as `symLink`, except the pathname parameters are null-terminated.
1977 pub fn symLinkZ(
1978 self: Dir,
1979 target_path_c: [*:0]const u8,
1980 sym_link_path_c: [*:0]const u8,
1981 flags: SymLinkFlags,
1982 ) !void {
1983 if (builtin.os.tag == .windows) {
1984 const target_path_w = try os.windows.cStrToPrefixedFileW(self.fd, target_path_c);
1985 const sym_link_path_w = try os.windows.cStrToPrefixedFileW(self.fd, sym_link_path_c);
1986 return self.symLinkW(target_path_w.span(), sym_link_path_w.span(), flags);
1987 }
1988 return os.symlinkatZ(target_path_c, self.fd, sym_link_path_c);
1989 }
1990
1991 /// Windows-only. Same as `symLink` except the pathname parameters
1992 /// are null-terminated, WTF16 encoded.
1993 pub fn symLinkW(
1994 self: Dir,
1995 /// WTF-16, does not need to be NT-prefixed. The NT-prefixing
1996 /// of this path is handled by CreateSymbolicLink.
1997 target_path_w: [:0]const u16,
1998 /// WTF-16, must be NT-prefixed or relative
1999 sym_link_path_w: []const u16,
2000 flags: SymLinkFlags,
2001 ) !void {
2002 return os.windows.CreateSymbolicLink(self.fd, sym_link_path_w, target_path_w, flags.is_directory);
2003 }
2004
2005 pub const ReadLinkError = os.ReadLinkError;
2006
2007 /// Read value of a symbolic link.
2008 /// The return value is a slice of `buffer`, from index `0`.
2009 /// Asserts that the path parameter has no null bytes.
2010 pub fn readLink(self: Dir, sub_path: []const u8, buffer: []u8) ReadLinkError![]u8 {
2011 if (builtin.os.tag == .wasi and !builtin.link_libc) {
2012 return self.readLinkWasi(sub_path, buffer);
2013 }
2014 if (builtin.os.tag == .windows) {
2015 const sub_path_w = try os.windows.sliceToPrefixedFileW(self.fd, sub_path);
2016 return self.readLinkW(sub_path_w.span(), buffer);
2017 }
2018 const sub_path_c = try os.toPosixPath(sub_path);
2019 return self.readLinkZ(&sub_path_c, buffer);
2020 }
2021
2022 /// WASI-only. Same as `readLink` except targeting WASI.
2023 pub fn readLinkWasi(self: Dir, sub_path: []const u8, buffer: []u8) ![]u8 {
2024 return os.readlinkat(self.fd, sub_path, buffer);
2025 }
2026
2027 /// Same as `readLink`, except the `pathname` parameter is null-terminated.
2028 pub fn readLinkZ(self: Dir, sub_path_c: [*:0]const u8, buffer: []u8) ![]u8 {
2029 if (builtin.os.tag == .windows) {
2030 const sub_path_w = try os.windows.cStrToPrefixedFileW(self.fd, sub_path_c);
2031 return self.readLinkW(sub_path_w.span(), buffer);
2032 }
2033 return os.readlinkatZ(self.fd, sub_path_c, buffer);
2034 }
2035
2036 /// Windows-only. Same as `readLink` except the pathname parameter
2037 /// is null-terminated, WTF16 encoded.
2038 pub fn readLinkW(self: Dir, sub_path_w: []const u16, buffer: []u8) ![]u8 {
2039 return os.windows.ReadLink(self.fd, sub_path_w, buffer);
2040 }
2041
2042 /// Read all of file contents using a preallocated buffer.
2043 /// The returned slice has the same pointer as `buffer`. If the length matches `buffer.len`
2044 /// the situation is ambiguous. It could either mean that the entire file was read, and
2045 /// it exactly fits the buffer, or it could mean the buffer was not big enough for the
2046 /// entire file.
2047 pub fn readFile(self: Dir, file_path: []const u8, buffer: []u8) ![]u8 {
2048 var file = try self.openFile(file_path, .{});
2049 defer file.close();
2050
2051 const end_index = try file.readAll(buffer);
2052 return buffer[0..end_index];
2053 }
2054
2055 /// On success, caller owns returned buffer.
2056 /// If the file is larger than `max_bytes`, returns `error.FileTooBig`.
2057 pub fn readFileAlloc(self: Dir, allocator: mem.Allocator, file_path: []const u8, max_bytes: usize) ![]u8 {
2058 return self.readFileAllocOptions(allocator, file_path, max_bytes, null, @alignOf(u8), null);
2059 }
2060
2061 /// On success, caller owns returned buffer.
2062 /// If the file is larger than `max_bytes`, returns `error.FileTooBig`.
2063 /// If `size_hint` is specified the initial buffer size is calculated using
2064 /// that value, otherwise the effective file size is used instead.
2065 /// Allows specifying alignment and a sentinel value.
2066 pub fn readFileAllocOptions(
2067 self: Dir,
2068 allocator: mem.Allocator,
2069 file_path: []const u8,
2070 max_bytes: usize,
2071 size_hint: ?usize,
2072 comptime alignment: u29,
2073 comptime optional_sentinel: ?u8,
2074 ) !(if (optional_sentinel) |s| [:s]align(alignment) u8 else []align(alignment) u8) {
2075 var file = try self.openFile(file_path, .{});
2076 defer file.close();
2077
2078 // If the file size doesn't fit a usize it'll be certainly greater than
2079 // `max_bytes`
2080 const stat_size = size_hint orelse math.cast(usize, try file.getEndPos()) orelse
2081 return error.FileTooBig;
2082
2083 return file.readToEndAllocOptions(allocator, max_bytes, stat_size, alignment, optional_sentinel);
2084 }
2085
2086 pub const DeleteTreeError = error{
2087 InvalidHandle,
2088 AccessDenied,
2089 FileTooBig,
2090 SymLinkLoop,
2091 ProcessFdQuotaExceeded,
2092 NameTooLong,
2093 SystemFdQuotaExceeded,
2094 NoDevice,
2095 SystemResources,
2096 ReadOnlyFileSystem,
2097 FileSystem,
2098 FileBusy,
2099 DeviceBusy,
2100
2101 /// One of the path components was not a directory.
2102 /// This error is unreachable if `sub_path` does not contain a path separator.
2103 NotDir,
2104
2105 /// On Windows, file paths must be valid Unicode.
2106 InvalidUtf8,
2107
2108 /// On Windows, file paths cannot contain these characters:
2109 /// '/', '*', '?', '"', '<', '>', '|'
2110 BadPathName,
2111
2112 /// On Windows, `\\server` or `\\server\share` was not found.
2113 NetworkNotFound,
2114 } || os.UnexpectedError;
2115
2116 /// Whether `full_path` describes a symlink, file, or directory, this function
2117 /// removes it. If it cannot be removed because it is a non-empty directory,
2118 /// this function recursively removes its entries and then tries again.
2119 /// This operation is not atomic on most file systems.
2120 pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void {
2121 var initial_iterable_dir = (try self.deleteTreeOpenInitialSubpath(sub_path, .file)) orelse return;
2122
2123 const StackItem = struct {
2124 name: []const u8,
2125 parent_dir: Dir,
2126 iter: Dir.Iterator,
2127
2128 fn closeAll(items: []@This()) void {
2129 for (items) |*item| item.iter.dir.close();
2130 }
2131 };
2132
2133 var stack_buffer: [16]StackItem = undefined;
2134 var stack = std.ArrayListUnmanaged(StackItem).initBuffer(&stack_buffer);
2135 defer StackItem.closeAll(stack.items);
2136
2137 stack.appendAssumeCapacity(.{
2138 .name = sub_path,
2139 .parent_dir = self,
2140 .iter = initial_iterable_dir.iterateAssumeFirstIteration(),
2141 });
2142
2143 process_stack: while (stack.items.len != 0) {
2144 var top = &stack.items[stack.items.len - 1];
2145 while (try top.iter.next()) |entry| {
2146 var treat_as_dir = entry.kind == .directory;
2147 handle_entry: while (true) {
2148 if (treat_as_dir) {
2149 if (stack.unusedCapacitySlice().len >= 1) {
2150 var iterable_dir = top.iter.dir.openDir(entry.name, .{
2151 .no_follow = true,
2152 .iterate = true,
2153 }) catch |err| switch (err) {
2154 error.NotDir => {
2155 treat_as_dir = false;
2156 continue :handle_entry;
2157 },
2158 error.FileNotFound => {
2159 // That's fine, we were trying to remove this directory anyway.
2160 break :handle_entry;
2161 },
2162
2163 error.InvalidHandle,
2164 error.AccessDenied,
2165 error.SymLinkLoop,
2166 error.ProcessFdQuotaExceeded,
2167 error.NameTooLong,
2168 error.SystemFdQuotaExceeded,
2169 error.NoDevice,
2170 error.SystemResources,
2171 error.Unexpected,
2172 error.InvalidUtf8,
2173 error.BadPathName,
2174 error.NetworkNotFound,
2175 error.DeviceBusy,
2176 => |e| return e,
2177 };
2178 stack.appendAssumeCapacity(.{
2179 .name = entry.name,
2180 .parent_dir = top.iter.dir,
2181 .iter = iterable_dir.iterateAssumeFirstIteration(),
2182 });
2183 continue :process_stack;
2184 } else {
2185 try top.iter.dir.deleteTreeMinStackSizeWithKindHint(entry.name, entry.kind);
2186 break :handle_entry;
2187 }
2188 } else {
2189 if (top.iter.dir.deleteFile(entry.name)) {
2190 break :handle_entry;
2191 } else |err| switch (err) {
2192 error.FileNotFound => break :handle_entry,
2193
2194 // Impossible because we do not pass any path separators.
2195 error.NotDir => unreachable,
2196
2197 error.IsDir => {
2198 treat_as_dir = true;
2199 continue :handle_entry;
2200 },
2201
2202 error.AccessDenied,
2203 error.InvalidUtf8,
2204 error.SymLinkLoop,
2205 error.NameTooLong,
2206 error.SystemResources,
2207 error.ReadOnlyFileSystem,
2208 error.FileSystem,
2209 error.FileBusy,
2210 error.BadPathName,
2211 error.NetworkNotFound,
2212 error.Unexpected,
2213 => |e| return e,
2214 }
2215 }
2216 }
2217 }
2218
2219 // On Windows, we can't delete until the dir's handle has been closed, so
2220 // close it before we try to delete.
2221 top.iter.dir.close();
2222
2223 // In order to avoid double-closing the directory when cleaning up
2224 // the stack in the case of an error, we save the relevant portions and
2225 // pop the value from the stack.
2226 const parent_dir = top.parent_dir;
2227 const name = top.name;
2228 stack.items.len -= 1;
2229
2230 var need_to_retry: bool = false;
2231 parent_dir.deleteDir(name) catch |err| switch (err) {
2232 error.FileNotFound => {},
2233 error.DirNotEmpty => need_to_retry = true,
2234 else => |e| return e,
2235 };
2236
2237 if (need_to_retry) {
2238 // Since we closed the handle that the previous iterator used, we
2239 // need to re-open the dir and re-create the iterator.
2240 var iterable_dir = iterable_dir: {
2241 var treat_as_dir = true;
2242 handle_entry: while (true) {
2243 if (treat_as_dir) {
2244 break :iterable_dir parent_dir.openDir(name, .{
2245 .no_follow = true,
2246 .iterate = true,
2247 }) catch |err| switch (err) {
2248 error.NotDir => {
2249 treat_as_dir = false;
2250 continue :handle_entry;
2251 },
2252 error.FileNotFound => {
2253 // That's fine, we were trying to remove this directory anyway.
2254 continue :process_stack;
2255 },
2256
2257 error.InvalidHandle,
2258 error.AccessDenied,
2259 error.SymLinkLoop,
2260 error.ProcessFdQuotaExceeded,
2261 error.NameTooLong,
2262 error.SystemFdQuotaExceeded,
2263 error.NoDevice,
2264 error.SystemResources,
2265 error.Unexpected,
2266 error.InvalidUtf8,
2267 error.BadPathName,
2268 error.NetworkNotFound,
2269 error.DeviceBusy,
2270 => |e| return e,
2271 };
2272 } else {
2273 if (parent_dir.deleteFile(name)) {
2274 continue :process_stack;
2275 } else |err| switch (err) {
2276 error.FileNotFound => continue :process_stack,
2277
2278 // Impossible because we do not pass any path separators.
2279 error.NotDir => unreachable,
2280
2281 error.IsDir => {
2282 treat_as_dir = true;
2283 continue :handle_entry;
2284 },
2285
2286 error.AccessDenied,
2287 error.InvalidUtf8,
2288 error.SymLinkLoop,
2289 error.NameTooLong,
2290 error.SystemResources,
2291 error.ReadOnlyFileSystem,
2292 error.FileSystem,
2293 error.FileBusy,
2294 error.BadPathName,
2295 error.NetworkNotFound,
2296 error.Unexpected,
2297 => |e| return e,
2298 }
2299 }
2300 }
2301 };
2302 // We know there is room on the stack since we are just re-adding
2303 // the StackItem that we previously popped.
2304 stack.appendAssumeCapacity(.{
2305 .name = name,
2306 .parent_dir = parent_dir,
2307 .iter = iterable_dir.iterateAssumeFirstIteration(),
2308 });
2309 continue :process_stack;
2310 }
2311 }
2312 }
2313
2314 /// Like `deleteTree`, but only keeps one `Iterator` active at a time to minimize the function's stack size.
2315 /// This is slower than `deleteTree` but uses less stack space.
2316 pub fn deleteTreeMinStackSize(self: Dir, sub_path: []const u8) DeleteTreeError!void {
2317 return self.deleteTreeMinStackSizeWithKindHint(sub_path, .file);
2318 }
2319
2320 fn deleteTreeMinStackSizeWithKindHint(self: Dir, sub_path: []const u8, kind_hint: File.Kind) DeleteTreeError!void {
2321 start_over: while (true) {
2322 var dir = (try self.deleteTreeOpenInitialSubpath(sub_path, kind_hint)) orelse return;
2323 var cleanup_dir_parent: ?Dir = null;
2324 defer if (cleanup_dir_parent) |*d| d.close();
2325
2326 var cleanup_dir = true;
2327 defer if (cleanup_dir) dir.close();
2328
2329 // Valid use of MAX_PATH_BYTES because dir_name_buf will only
2330 // ever store a single path component that was returned from the
2331 // filesystem.
2332 var dir_name_buf: [MAX_PATH_BYTES]u8 = undefined;
2333 var dir_name: []const u8 = sub_path;
2334
2335 // Here we must avoid recursion, in order to provide O(1) memory guarantee of this function.
2336 // Go through each entry and if it is not a directory, delete it. If it is a directory,
2337 // open it, and close the original directory. Repeat. Then start the entire operation over.
2338
2339 scan_dir: while (true) {
2340 var dir_it = dir.iterateAssumeFirstIteration();
2341 dir_it: while (try dir_it.next()) |entry| {
2342 var treat_as_dir = entry.kind == .directory;
2343 handle_entry: while (true) {
2344 if (treat_as_dir) {
2345 const new_dir = dir.openDir(entry.name, .{
2346 .no_follow = true,
2347 .iterate = true,
2348 }) catch |err| switch (err) {
2349 error.NotDir => {
2350 treat_as_dir = false;
2351 continue :handle_entry;
2352 },
2353 error.FileNotFound => {
2354 // That's fine, we were trying to remove this directory anyway.
2355 continue :dir_it;
2356 },
2357
2358 error.InvalidHandle,
2359 error.AccessDenied,
2360 error.SymLinkLoop,
2361 error.ProcessFdQuotaExceeded,
2362 error.NameTooLong,
2363 error.SystemFdQuotaExceeded,
2364 error.NoDevice,
2365 error.SystemResources,
2366 error.Unexpected,
2367 error.InvalidUtf8,
2368 error.BadPathName,
2369 error.NetworkNotFound,
2370 error.DeviceBusy,
2371 => |e| return e,
2372 };
2373 if (cleanup_dir_parent) |*d| d.close();
2374 cleanup_dir_parent = dir;
2375 dir = new_dir;
2376 const result = dir_name_buf[0..entry.name.len];
2377 @memcpy(result, entry.name);
2378 dir_name = result;
2379 continue :scan_dir;
2380 } else {
2381 if (dir.deleteFile(entry.name)) {
2382 continue :dir_it;
2383 } else |err| switch (err) {
2384 error.FileNotFound => continue :dir_it,
2385
2386 // Impossible because we do not pass any path separators.
2387 error.NotDir => unreachable,
2388
2389 error.IsDir => {
2390 treat_as_dir = true;
2391 continue :handle_entry;
2392 },
2393
2394 error.AccessDenied,
2395 error.InvalidUtf8,
2396 error.SymLinkLoop,
2397 error.NameTooLong,
2398 error.SystemResources,
2399 error.ReadOnlyFileSystem,
2400 error.FileSystem,
2401 error.FileBusy,
2402 error.BadPathName,
2403 error.NetworkNotFound,
2404 error.Unexpected,
2405 => |e| return e,
2406 }
2407 }
2408 }
2409 }
2410 // Reached the end of the directory entries, which means we successfully deleted all of them.
2411 // Now to remove the directory itself.
2412 dir.close();
2413 cleanup_dir = false;
2414
2415 if (cleanup_dir_parent) |d| {
2416 d.deleteDir(dir_name) catch |err| switch (err) {
2417 // These two things can happen due to file system race conditions.
2418 error.FileNotFound, error.DirNotEmpty => continue :start_over,
2419 else => |e| return e,
2420 };
2421 continue :start_over;
2422 } else {
2423 self.deleteDir(sub_path) catch |err| switch (err) {
2424 error.FileNotFound => return,
2425 error.DirNotEmpty => continue :start_over,
2426 else => |e| return e,
2427 };
2428 return;
2429 }
2430 }
2431 }
2432 }
2433
2434 /// On successful delete, returns null.
2435 fn deleteTreeOpenInitialSubpath(self: Dir, sub_path: []const u8, kind_hint: File.Kind) !?Dir {
2436 return iterable_dir: {
2437 // Treat as a file by default
2438 var treat_as_dir = kind_hint == .directory;
2439
2440 handle_entry: while (true) {
2441 if (treat_as_dir) {
2442 break :iterable_dir self.openDir(sub_path, .{
2443 .no_follow = true,
2444 .iterate = true,
2445 }) catch |err| switch (err) {
2446 error.NotDir => {
2447 treat_as_dir = false;
2448 continue :handle_entry;
2449 },
2450 error.FileNotFound => {
2451 // That's fine, we were trying to remove this directory anyway.
2452 return null;
2453 },
2454
2455 error.InvalidHandle,
2456 error.AccessDenied,
2457 error.SymLinkLoop,
2458 error.ProcessFdQuotaExceeded,
2459 error.NameTooLong,
2460 error.SystemFdQuotaExceeded,
2461 error.NoDevice,
2462 error.SystemResources,
2463 error.Unexpected,
2464 error.InvalidUtf8,
2465 error.BadPathName,
2466 error.DeviceBusy,
2467 error.NetworkNotFound,
2468 => |e| return e,
2469 };
2470 } else {
2471 if (self.deleteFile(sub_path)) {
2472 return null;
2473 } else |err| switch (err) {
2474 error.FileNotFound => return null,
2475
2476 error.IsDir => {
2477 treat_as_dir = true;
2478 continue :handle_entry;
2479 },
2480
2481 error.AccessDenied,
2482 error.InvalidUtf8,
2483 error.SymLinkLoop,
2484 error.NameTooLong,
2485 error.SystemResources,
2486 error.ReadOnlyFileSystem,
2487 error.NotDir,
2488 error.FileSystem,
2489 error.FileBusy,
2490 error.BadPathName,
2491 error.NetworkNotFound,
2492 error.Unexpected,
2493 => |e| return e,
2494 }
2495 }
2496 }
2497 };
2498 }
2499
2500 pub const WriteFileError = File.WriteError || File.OpenError;
2501
2502 /// Deprecated: use `writeFile2`.
2503 pub fn writeFile(self: Dir, sub_path: []const u8, data: []const u8) WriteFileError!void {
2504 return writeFile2(self, .{
2505 .sub_path = sub_path,
2506 .data = data,
2507 .flags = .{},
2508 });
2509 }
2510
2511 pub const WriteFileOptions = struct {
2512 sub_path: []const u8,
2513 data: []const u8,
2514 flags: File.CreateFlags = .{},
2515 };
2516
2517 /// Writes content to the file system, using the file creation flags provided.
2518 pub fn writeFile2(self: Dir, options: WriteFileOptions) WriteFileError!void {
2519 var file = try self.createFile(options.sub_path, options.flags);
2520 defer file.close();
2521 try file.writeAll(options.data);
2522 }
2523
2524 pub const AccessError = os.AccessError;
2525
2526 /// Test accessing `path`.
2527 /// `path` is UTF-8-encoded.
2528 /// Be careful of Time-Of-Check-Time-Of-Use race conditions when using this function.
2529 /// For example, instead of testing if a file exists and then opening it, just
2530 /// open it and handle the error for file not found.
2531 pub fn access(self: Dir, sub_path: []const u8, flags: File.OpenFlags) AccessError!void {
2532 if (builtin.os.tag == .windows) {
2533 const sub_path_w = os.windows.sliceToPrefixedFileW(self.fd, sub_path) catch |err| switch (err) {
2534 error.AccessDenied => return error.PermissionDenied,
2535 else => |e| return e,
2536 };
2537 return self.accessW(sub_path_w.span().ptr, flags);
2538 }
2539 const path_c = try os.toPosixPath(sub_path);
2540 return self.accessZ(&path_c, flags);
2541 }
2542
2543 /// Same as `access` except the path parameter is null-terminated.
2544 pub fn accessZ(self: Dir, sub_path: [*:0]const u8, flags: File.OpenFlags) AccessError!void {
2545 if (builtin.os.tag == .windows) {
2546 const sub_path_w = os.windows.cStrToPrefixedFileW(self.fd, sub_path) catch |err| switch (err) {
2547 error.AccessDenied => return error.PermissionDenied,
2548 else => |e| return e,
2549 };
2550 return self.accessW(sub_path_w.span().ptr, flags);
2551 }
2552 const os_mode = switch (flags.mode) {
2553 .read_only => @as(u32, os.F_OK),
2554 .write_only => @as(u32, os.W_OK),
2555 .read_write => @as(u32, os.R_OK | os.W_OK),
2556 };
2557 const result = if (need_async_thread and flags.intended_io_mode != .blocking)
2558 std.event.Loop.instance.?.faccessatZ(self.fd, sub_path, os_mode, 0)
2559 else
2560 os.faccessatZ(self.fd, sub_path, os_mode, 0);
2561 return result;
2562 }
2563
2564 /// Same as `access` except asserts the target OS is Windows and the path parameter is
2565 /// * WTF-16 encoded
2566 /// * null-terminated
2567 /// * NtDll prefixed
2568 /// TODO currently this ignores `flags`.
2569 pub fn accessW(self: Dir, sub_path_w: [*:0]const u16, flags: File.OpenFlags) AccessError!void {
2570 _ = flags;
2571 return os.faccessatW(self.fd, sub_path_w, 0, 0);
2572 }
2573
2574 /// Check the file size, mtime, and mode of `source_path` and `dest_path`. If they are equal, does nothing.
2575 /// Otherwise, atomically copies `source_path` to `dest_path`. The destination file gains the mtime,
2576 /// atime, and mode of the source file so that the next call to `updateFile` will not need a copy.
2577 /// Returns the previous status of the file before updating.
2578 /// If any of the directories do not exist for dest_path, they are created.
2579 pub fn updateFile(
2580 source_dir: Dir,
2581 source_path: []const u8,
2582 dest_dir: Dir,
2583 dest_path: []const u8,
2584 options: CopyFileOptions,
2585 ) !PrevStatus {
2586 var src_file = try source_dir.openFile(source_path, .{});
2587 defer src_file.close();
2588
2589 const src_stat = try src_file.stat();
2590 const actual_mode = options.override_mode orelse src_stat.mode;
2591 check_dest_stat: {
2592 const dest_stat = blk: {
2593 var dest_file = dest_dir.openFile(dest_path, .{}) catch |err| switch (err) {
2594 error.FileNotFound => break :check_dest_stat,
2595 else => |e| return e,
2596 };
2597 defer dest_file.close();
2598
2599 break :blk try dest_file.stat();
2600 };
2601
2602 if (src_stat.size == dest_stat.size and
2603 src_stat.mtime == dest_stat.mtime and
2604 actual_mode == dest_stat.mode)
2605 {
2606 return PrevStatus.fresh;
2607 }
2608 }
2609
2610 if (path.dirname(dest_path)) |dirname| {
2611 try dest_dir.makePath(dirname);
2612 }
2613
2614 var atomic_file = try dest_dir.atomicFile(dest_path, .{ .mode = actual_mode });
2615 defer atomic_file.deinit();
2616
2617 try atomic_file.file.writeFileAll(src_file, .{ .in_len = src_stat.size });
2618 try atomic_file.file.updateTimes(src_stat.atime, src_stat.mtime);
2619 try atomic_file.finish();
2620 return PrevStatus.stale;
2621 }
2622
2623 pub const CopyFileError = File.OpenError || File.StatError || AtomicFile.InitError || CopyFileRawError || AtomicFile.FinishError;
2624
2625 /// Guaranteed to be atomic.
2626 /// On Linux, until https://patchwork.kernel.org/patch/9636735/ is merged and readily available,
2627 /// there is a possibility of power loss or application termination leaving temporary files present
2628 /// in the same directory as dest_path.
2629 pub fn copyFile(source_dir: Dir, source_path: []const u8, dest_dir: Dir, dest_path: []const u8, options: CopyFileOptions) CopyFileError!void {
2630 var in_file = try source_dir.openFile(source_path, .{});
2631 defer in_file.close();
2632
2633 var size: ?u64 = null;
2634 const mode = options.override_mode orelse blk: {
2635 const st = try in_file.stat();
2636 size = st.size;
2637 break :blk st.mode;
2638 };
2639
2640 var atomic_file = try dest_dir.atomicFile(dest_path, .{ .mode = mode });
2641 defer atomic_file.deinit();
2642
2643 try copy_file(in_file.handle, atomic_file.file.handle, size);
2644 try atomic_file.finish();
2645 }
2646
2647 pub const AtomicFileOptions = struct {
2648 mode: File.Mode = File.default_mode,
2649 };
2650
2651 /// Directly access the `.file` field, and then call `AtomicFile.finish`
2652 /// to atomically replace `dest_path` with contents.
2653 /// Always call `AtomicFile.deinit` to clean up, regardless of whether `AtomicFile.finish` succeeded.
2654 /// `dest_path` must remain valid until `AtomicFile.deinit` is called.
2655 pub fn atomicFile(self: Dir, dest_path: []const u8, options: AtomicFileOptions) !AtomicFile {
2656 if (path.dirname(dest_path)) |dirname| {
2657 const dir = try self.openDir(dirname, .{});
2658 return AtomicFile.init(path.basename(dest_path), options.mode, dir, true);
2659 } else {
2660 return AtomicFile.init(dest_path, options.mode, self, false);
2661 }
2662 }
2663
2664 pub const Stat = File.Stat;
2665 pub const StatError = File.StatError;
2666
2667 pub fn stat(self: Dir) StatError!Stat {
2668 const file: File = .{
2669 .handle = self.fd,
2670 .capable_io_mode = .blocking,
2671 };
2672 return file.stat();
2673 }
2674
2675 pub const StatFileError = File.OpenError || File.StatError || os.FStatAtError;
2676
2677 /// Returns metadata for a file inside the directory.
2678 ///
2679 /// On Windows, this requires three syscalls. On other operating systems, it
2680 /// only takes one.
2681 ///
2682 /// Symlinks are followed.
2683 ///
2684 /// `sub_path` may be absolute, in which case `self` is ignored.
2685 pub fn statFile(self: Dir, sub_path: []const u8) StatFileError!Stat {
2686 if (builtin.os.tag == .windows) {
2687 var file = try self.openFile(sub_path, .{});
2688 defer file.close();
2689 return file.stat();
2690 }
2691 if (builtin.os.tag == .wasi and !builtin.link_libc) {
2692 const st = try os.fstatatWasi(self.fd, sub_path, os.wasi.LOOKUP_SYMLINK_FOLLOW);
2693 return Stat.fromSystem(st);
2694 }
2695 const st = try os.fstatat(self.fd, sub_path, 0);
2696 return Stat.fromSystem(st);
2697 }
2698
2699 pub const ChmodError = File.ChmodError;
2700
2701 /// Changes the mode of the directory.
2702 /// The process must have the correct privileges in order to do this
2703 /// successfully, or must have the effective user ID matching the owner
2704 /// of the directory. Additionally, the directory must have been opened
2705 /// with `OpenDirOptions{ .iterate = true }`.
2706 pub fn chmod(self: Dir, new_mode: File.Mode) ChmodError!void {
2707 const file: File = .{
2708 .handle = self.fd,
2709 .capable_io_mode = .blocking,
2710 };
2711 try file.chmod(new_mode);
2712 }
2713
2714 /// Changes the owner and group of the directory.
2715 /// The process must have the correct privileges in order to do this
2716 /// successfully. The group may be changed by the owner of the directory to
2717 /// any group of which the owner is a member. Additionally, the directory
2718 /// must have been opened with `OpenDirOptions{ .iterate = true }`. If the
2719 /// owner or group is specified as `null`, the ID is not changed.
2720 pub fn chown(self: Dir, owner: ?File.Uid, group: ?File.Gid) ChownError!void {
2721 const file: File = .{
2722 .handle = self.fd,
2723 .capable_io_mode = .blocking,
2724 };
2725 try file.chown(owner, group);
2726 }
2727
2728 pub const ChownError = File.ChownError;
2729
2730 const Permissions = File.Permissions;
2731 pub const SetPermissionsError = File.SetPermissionsError;
2732
2733 /// Sets permissions according to the provided `Permissions` struct.
2734 /// This method is *NOT* available on WASI
2735 pub fn setPermissions(self: Dir, permissions: Permissions) SetPermissionsError!void {
2736 const file: File = .{
2737 .handle = self.fd,
2738 .capable_io_mode = .blocking,
2739 };
2740 try file.setPermissions(permissions);
2741 }
2742
2743 const Metadata = File.Metadata;
2744 pub const MetadataError = File.MetadataError;
2745
2746 /// Returns a `Metadata` struct, representing the permissions on the directory
2747 pub fn metadata(self: Dir) MetadataError!Metadata {
2748 const file: File = .{
2749 .handle = self.fd,
2750 .capable_io_mode = .blocking,
2751 };
2752 return try file.metadata();
2753 }
2754};
2755
2756307/// Returns a handle to the current working directory. It is not opened with iteration capability.
2757308/// Closing the returned `Dir` is checked illegal behavior. Iterating over the result is illegal behavior.
2758309/// On POSIX targets, this function is comptime-callable.
......@@ -2918,20 +469,16 @@ pub fn readLinkAbsoluteZ(pathname_c: [*:0]const u8, buffer: *[MAX_PATH_BYTES]u8)
2918469 return os.readlinkZ(pathname_c, buffer);
2919470}
2920471
2921/// Use with `Dir.symLink` and `symLinkAbsolute` to specify whether the symlink
2922/// will point to a file or a directory. This value is ignored on all hosts
2923/// except Windows where creating symlinks to different resource types, requires
2924/// different flags. By default, `symLinkAbsolute` is assumed to point to a file.
2925pub const SymLinkFlags = struct {
2926 is_directory: bool = false,
2927};
2928
2929472/// Creates a symbolic link named `sym_link_path` which contains the string `target_path`.
2930473/// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent
2931474/// one; the latter case is known as a dangling link.
2932475/// If `sym_link_path` exists, it will not be overwritten.
2933476/// See also `symLinkAbsoluteZ` and `symLinkAbsoluteW`.
2934pub fn symLinkAbsolute(target_path: []const u8, sym_link_path: []const u8, flags: SymLinkFlags) !void {
477pub fn symLinkAbsolute(
478 target_path: []const u8,
479 sym_link_path: []const u8,
480 flags: Dir.SymLinkFlags,
481) !void {
2935482 assert(path.isAbsolute(target_path));
2936483 assert(path.isAbsolute(sym_link_path));
2937484 if (builtin.os.tag == .windows) {
......@@ -2946,7 +493,11 @@ pub fn symLinkAbsolute(target_path: []const u8, sym_link_path: []const u8, flags
2946493/// Note that this function will by default try creating a symbolic link to a file. If you would
2947494/// like to create a symbolic link to a directory, specify this with `SymLinkFlags{ .is_directory = true }`.
2948495/// See also `symLinkAbsolute`, `symLinkAbsoluteZ`.
2949pub fn symLinkAbsoluteW(target_path_w: []const u16, sym_link_path_w: []const u16, flags: SymLinkFlags) !void {
496pub fn symLinkAbsoluteW(
497 target_path_w: []const u16,
498 sym_link_path_w: []const u16,
499 flags: Dir.SymLinkFlags,
500) !void {
2950501 assert(path.isAbsoluteWindowsWTF16(target_path_w));
2951502 assert(path.isAbsoluteWindowsWTF16(sym_link_path_w));
2952503 return os.windows.CreateSymbolicLink(null, sym_link_path_w, target_path_w, flags.is_directory);
......@@ -2954,7 +505,11 @@ pub fn symLinkAbsoluteW(target_path_w: []const u16, sym_link_path_w: []const u16
2954505
2955506/// Same as `symLinkAbsolute` except the parameters are null-terminated pointers.
2956507/// See also `symLinkAbsolute`.
2957pub fn symLinkAbsoluteZ(target_path_c: [*:0]const u8, sym_link_path_c: [*:0]const u8, flags: SymLinkFlags) !void {
508pub fn symLinkAbsoluteZ(
509 target_path_c: [*:0]const u8,
510 sym_link_path_c: [*:0]const u8,
511 flags: Dir.SymLinkFlags,
512) !void {
2958513 assert(path.isAbsoluteZ(target_path_c));
2959514 assert(path.isAbsoluteZ(sym_link_path_c));
2960515 if (builtin.os.tag == .windows) {
......@@ -3151,59 +706,6 @@ pub fn realpathAlloc(allocator: Allocator, pathname: []const u8) ![]u8 {
3151706 return allocator.dupe(u8, try os.realpath(pathname, &buf));
3152707}
3153708
3154const CopyFileRawError = error{SystemResources} || os.CopyFileRangeError || os.SendFileError;
3155
3156// Transfer all the data between two file descriptors in the most efficient way.
3157// The copy starts at offset 0, the initial offsets are preserved.
3158// No metadata is transferred over.
3159fn copy_file(fd_in: os.fd_t, fd_out: os.fd_t, maybe_size: ?u64) CopyFileRawError!void {
3160 if (comptime builtin.target.isDarwin()) {
3161 const rc = os.system.fcopyfile(fd_in, fd_out, null, os.system.COPYFILE_DATA);
3162 switch (os.errno(rc)) {
3163 .SUCCESS => return,
3164 .INVAL => unreachable,
3165 .NOMEM => return error.SystemResources,
3166 // The source file is not a directory, symbolic link, or regular file.
3167 // Try with the fallback path before giving up.
3168 .OPNOTSUPP => {},
3169 else => |err| return os.unexpectedErrno(err),
3170 }
3171 }
3172
3173 if (builtin.os.tag == .linux) {
3174 // Try copy_file_range first as that works at the FS level and is the
3175 // most efficient method (if available).
3176 var offset: u64 = 0;
3177 cfr_loop: while (true) {
3178 // The kernel checks the u64 value `offset+count` for overflow, use
3179 // a 32 bit value so that the syscall won't return EINVAL except for
3180 // impossibly large files (> 2^64-1 - 2^32-1).
3181 const amt = try os.copy_file_range(fd_in, offset, fd_out, offset, math.maxInt(u32), 0);
3182 // Terminate as soon as we have copied size bytes or no bytes
3183 if (maybe_size) |s| {
3184 if (s == amt) break :cfr_loop;
3185 }
3186 if (amt == 0) break :cfr_loop;
3187 offset += amt;
3188 }
3189 return;
3190 }
3191
3192 // Sendfile is a zero-copy mechanism iff the OS supports it, otherwise the
3193 // fallback code will copy the contents chunk by chunk.
3194 const empty_iovec = [0]os.iovec_const{};
3195 var offset: u64 = 0;
3196 sendfile_loop: while (true) {
3197 const amt = try os.sendfile(fd_out, fd_in, offset, 0, &empty_iovec, &empty_iovec, 0);
3198 // Terminate as soon as we have copied size bytes or no bytes
3199 if (maybe_size) |s| {
3200 if (s == amt) break :sendfile_loop;
3201 }
3202 if (amt == 0) break :sendfile_loop;
3203 offset += amt;
3204 }
3205}
3206
3207709test {
3208710 if (builtin.os.tag != .wasi) {
3209711 _ = &makeDirAbsolute;
......@@ -3211,10 +713,10 @@ test {
3211713 _ = &copyFileAbsolute;
3212714 _ = &updateFileAbsolute;
3213715 }
3214 _ = &Dir.copyFile;
716 _ = &File;
717 _ = &Dir;
718 _ = &path;
3215719 _ = @import("fs/test.zig");
3216 _ = @import("fs/path.zig");
3217 _ = @import("fs/file.zig");
3218720 _ = @import("fs/get_app_data_dir.zig");
3219721 _ = @import("fs/watch.zig");
3220722}
lib/std/fs/Dir.zig created+2533
......@@ -0,0 +1,2533 @@
1fd: posix.fd_t,
2
3pub const default_mode = 0o755;
4
5pub const Entry = struct {
6 name: []const u8,
7 kind: Kind,
8
9 pub const Kind = File.Kind;
10};
11
12const IteratorError = error{ AccessDenied, SystemResources } || posix.UnexpectedError;
13
14pub const Iterator = switch (builtin.os.tag) {
15 .macos, .ios, .freebsd, .netbsd, .dragonfly, .openbsd, .solaris, .illumos => struct {
16 dir: Dir,
17 seek: i64,
18 buf: [1024]u8, // TODO align(@alignOf(posix.system.dirent)),
19 index: usize,
20 end_index: usize,
21 first_iter: bool,
22
23 const Self = @This();
24
25 pub const Error = IteratorError;
26
27 /// Memory such as file names referenced in this returned entry becomes invalid
28 /// with subsequent calls to `next`, as well as when this `Dir` is deinitialized.
29 pub fn next(self: *Self) Error!?Entry {
30 switch (builtin.os.tag) {
31 .macos, .ios => return self.nextDarwin(),
32 .freebsd, .netbsd, .dragonfly, .openbsd => return self.nextBsd(),
33 .solaris, .illumos => return self.nextSolaris(),
34 else => @compileError("unimplemented"),
35 }
36 }
37
38 fn nextDarwin(self: *Self) !?Entry {
39 start_over: while (true) {
40 if (self.index >= self.end_index) {
41 if (self.first_iter) {
42 posix.lseek_SET(self.dir.fd, 0) catch unreachable; // EBADF here likely means that the Dir was not opened with iteration permissions
43 self.first_iter = false;
44 }
45 const rc = posix.system.__getdirentries64(
46 self.dir.fd,
47 &self.buf,
48 self.buf.len,
49 &self.seek,
50 );
51 if (rc == 0) return null;
52 if (rc < 0) {
53 switch (posix.errno(rc)) {
54 .BADF => unreachable, // Dir is invalid or was opened without iteration ability
55 .FAULT => unreachable,
56 .NOTDIR => unreachable,
57 .INVAL => unreachable,
58 else => |err| return posix.unexpectedErrno(err),
59 }
60 }
61 self.index = 0;
62 self.end_index = @as(usize, @intCast(rc));
63 }
64 const darwin_entry = @as(*align(1) posix.system.dirent, @ptrCast(&self.buf[self.index]));
65 const next_index = self.index + darwin_entry.reclen();
66 self.index = next_index;
67
68 const name = @as([*]u8, @ptrCast(&darwin_entry.d_name))[0..darwin_entry.d_namlen];
69
70 if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..") or (darwin_entry.d_ino == 0)) {
71 continue :start_over;
72 }
73
74 const entry_kind: Entry.Kind = switch (darwin_entry.d_type) {
75 posix.DT.BLK => .block_device,
76 posix.DT.CHR => .character_device,
77 posix.DT.DIR => .directory,
78 posix.DT.FIFO => .named_pipe,
79 posix.DT.LNK => .sym_link,
80 posix.DT.REG => .file,
81 posix.DT.SOCK => .unix_domain_socket,
82 posix.DT.WHT => .whiteout,
83 else => .unknown,
84 };
85 return Entry{
86 .name = name,
87 .kind = entry_kind,
88 };
89 }
90 }
91
92 fn nextSolaris(self: *Self) !?Entry {
93 start_over: while (true) {
94 if (self.index >= self.end_index) {
95 if (self.first_iter) {
96 posix.lseek_SET(self.dir.fd, 0) catch unreachable; // EBADF here likely means that the Dir was not opened with iteration permissions
97 self.first_iter = false;
98 }
99 const rc = posix.system.getdents(self.dir.fd, &self.buf, self.buf.len);
100 switch (posix.errno(rc)) {
101 .SUCCESS => {},
102 .BADF => unreachable, // Dir is invalid or was opened without iteration ability
103 .FAULT => unreachable,
104 .NOTDIR => unreachable,
105 .INVAL => unreachable,
106 else => |err| return posix.unexpectedErrno(err),
107 }
108 if (rc == 0) return null;
109 self.index = 0;
110 self.end_index = @as(usize, @intCast(rc));
111 }
112 const entry = @as(*align(1) posix.system.dirent, @ptrCast(&self.buf[self.index]));
113 const next_index = self.index + entry.reclen();
114 self.index = next_index;
115
116 const name = mem.sliceTo(@as([*:0]u8, @ptrCast(&entry.d_name)), 0);
117 if (mem.eql(u8, name, ".") or mem.eql(u8, name, ".."))
118 continue :start_over;
119
120 // Solaris dirent doesn't expose d_type, so we have to call stat to get it.
121 const stat_info = posix.fstatat(
122 self.dir.fd,
123 name,
124 posix.AT.SYMLINK_NOFOLLOW,
125 ) catch |err| switch (err) {
126 error.NameTooLong => unreachable,
127 error.SymLinkLoop => unreachable,
128 error.FileNotFound => unreachable, // lost the race
129 else => |e| return e,
130 };
131 const entry_kind: Entry.Kind = switch (stat_info.mode & posix.S.IFMT) {
132 posix.S.IFIFO => .named_pipe,
133 posix.S.IFCHR => .character_device,
134 posix.S.IFDIR => .directory,
135 posix.S.IFBLK => .block_device,
136 posix.S.IFREG => .file,
137 posix.S.IFLNK => .sym_link,
138 posix.S.IFSOCK => .unix_domain_socket,
139 posix.S.IFDOOR => .door,
140 posix.S.IFPORT => .event_port,
141 else => .unknown,
142 };
143 return Entry{
144 .name = name,
145 .kind = entry_kind,
146 };
147 }
148 }
149
150 fn nextBsd(self: *Self) !?Entry {
151 start_over: while (true) {
152 if (self.index >= self.end_index) {
153 if (self.first_iter) {
154 posix.lseek_SET(self.dir.fd, 0) catch unreachable; // EBADF here likely means that the Dir was not opened with iteration permissions
155 self.first_iter = false;
156 }
157 const rc = if (builtin.os.tag == .netbsd)
158 posix.system.__getdents30(self.dir.fd, &self.buf, self.buf.len)
159 else
160 posix.system.getdents(self.dir.fd, &self.buf, self.buf.len);
161 switch (posix.errno(rc)) {
162 .SUCCESS => {},
163 .BADF => unreachable, // Dir is invalid or was opened without iteration ability
164 .FAULT => unreachable,
165 .NOTDIR => unreachable,
166 .INVAL => unreachable,
167 // Introduced in freebsd 13.2: directory unlinked but still open.
168 // To be consistent, iteration ends if the directory being iterated is deleted during iteration.
169 .NOENT => return null,
170 else => |err| return posix.unexpectedErrno(err),
171 }
172 if (rc == 0) return null;
173 self.index = 0;
174 self.end_index = @as(usize, @intCast(rc));
175 }
176 const bsd_entry = @as(*align(1) posix.system.dirent, @ptrCast(&self.buf[self.index]));
177 const next_index = self.index + bsd_entry.reclen();
178 self.index = next_index;
179
180 const name = @as([*]u8, @ptrCast(&bsd_entry.d_name))[0..bsd_entry.d_namlen];
181
182 const skip_zero_fileno = switch (builtin.os.tag) {
183 // d_fileno=0 is used to mark invalid entries or deleted files.
184 .openbsd, .netbsd => true,
185 else => false,
186 };
187 if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..") or
188 (skip_zero_fileno and bsd_entry.d_fileno == 0))
189 {
190 continue :start_over;
191 }
192
193 const entry_kind: Entry.Kind = switch (bsd_entry.d_type) {
194 posix.DT.BLK => .block_device,
195 posix.DT.CHR => .character_device,
196 posix.DT.DIR => .directory,
197 posix.DT.FIFO => .named_pipe,
198 posix.DT.LNK => .sym_link,
199 posix.DT.REG => .file,
200 posix.DT.SOCK => .unix_domain_socket,
201 posix.DT.WHT => .whiteout,
202 else => .unknown,
203 };
204 return Entry{
205 .name = name,
206 .kind = entry_kind,
207 };
208 }
209 }
210
211 pub fn reset(self: *Self) void {
212 self.index = 0;
213 self.end_index = 0;
214 self.first_iter = true;
215 }
216 },
217 .haiku => struct {
218 dir: Dir,
219 buf: [1024]u8, // TODO align(@alignOf(posix.dirent64)),
220 index: usize,
221 end_index: usize,
222 first_iter: bool,
223
224 const Self = @This();
225
226 pub const Error = IteratorError;
227
228 /// Memory such as file names referenced in this returned entry becomes invalid
229 /// with subsequent calls to `next`, as well as when this `Dir` is deinitialized.
230 pub fn next(self: *Self) Error!?Entry {
231 start_over: while (true) {
232 // TODO: find a better max
233 const HAIKU_MAX_COUNT = 10000;
234 if (self.index >= self.end_index) {
235 if (self.first_iter) {
236 posix.lseek_SET(self.dir.fd, 0) catch unreachable; // EBADF here likely means that the Dir was not opened with iteration permissions
237 self.first_iter = false;
238 }
239 const rc = posix.system._kern_read_dir(
240 self.dir.fd,
241 &self.buf,
242 self.buf.len,
243 HAIKU_MAX_COUNT,
244 );
245 if (rc == 0) return null;
246 if (rc < 0) {
247 switch (posix.errno(rc)) {
248 .BADF => unreachable, // Dir is invalid or was opened without iteration ability
249 .FAULT => unreachable,
250 .NOTDIR => unreachable,
251 .INVAL => unreachable,
252 else => |err| return posix.unexpectedErrno(err),
253 }
254 }
255 self.index = 0;
256 self.end_index = @as(usize, @intCast(rc));
257 }
258 const haiku_entry = @as(*align(1) posix.system.dirent, @ptrCast(&self.buf[self.index]));
259 const next_index = self.index + haiku_entry.reclen();
260 self.index = next_index;
261 const name = mem.sliceTo(@as([*:0]u8, @ptrCast(&haiku_entry.d_name)), 0);
262
263 if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..") or (haiku_entry.d_ino == 0)) {
264 continue :start_over;
265 }
266
267 var stat_info: posix.Stat = undefined;
268 const rc = posix.system._kern_read_stat(
269 self.dir.fd,
270 &haiku_entry.d_name,
271 false,
272 &stat_info,
273 0,
274 );
275 if (rc != 0) {
276 switch (posix.errno(rc)) {
277 .SUCCESS => {},
278 .BADF => unreachable, // Dir is invalid or was opened without iteration ability
279 .FAULT => unreachable,
280 .NOTDIR => unreachable,
281 .INVAL => unreachable,
282 else => |err| return posix.unexpectedErrno(err),
283 }
284 }
285 const statmode = stat_info.mode & posix.S.IFMT;
286
287 const entry_kind: Entry.Kind = switch (statmode) {
288 posix.S.IFDIR => .directory,
289 posix.S.IFBLK => .block_device,
290 posix.S.IFCHR => .character_device,
291 posix.S.IFLNK => .sym_link,
292 posix.S.IFREG => .file,
293 posix.S.IFIFO => .named_pipe,
294 else => .unknown,
295 };
296
297 return Entry{
298 .name = name,
299 .kind = entry_kind,
300 };
301 }
302 }
303
304 pub fn reset(self: *Self) void {
305 self.index = 0;
306 self.end_index = 0;
307 self.first_iter = true;
308 }
309 },
310 .linux => struct {
311 dir: Dir,
312 // The if guard is solely there to prevent compile errors from missing `linux.dirent64`
313 // definition when compiling for other OSes. It doesn't do anything when compiling for Linux.
314 buf: [1024]u8 align(if (builtin.os.tag != .linux) 1 else @alignOf(linux.dirent64)),
315 index: usize,
316 end_index: usize,
317 first_iter: bool,
318
319 const Self = @This();
320 const linux = std.os.linux;
321
322 pub const Error = IteratorError;
323
324 /// Memory such as file names referenced in this returned entry becomes invalid
325 /// with subsequent calls to `next`, as well as when this `Dir` is deinitialized.
326 pub fn next(self: *Self) Error!?Entry {
327 return self.nextLinux() catch |err| switch (err) {
328 // To be consistent across platforms, iteration ends if the directory being iterated is deleted during iteration.
329 // This matches the behavior of non-Linux UNIX platforms.
330 error.DirNotFound => null,
331 else => |e| return e,
332 };
333 }
334
335 pub const ErrorLinux = error{DirNotFound} || IteratorError;
336
337 /// Implementation of `next` that can return `error.DirNotFound` if the directory being
338 /// iterated was deleted during iteration (this error is Linux specific).
339 pub fn nextLinux(self: *Self) ErrorLinux!?Entry {
340 start_over: while (true) {
341 if (self.index >= self.end_index) {
342 if (self.first_iter) {
343 posix.lseek_SET(self.dir.fd, 0) catch unreachable; // EBADF here likely means that the Dir was not opened with iteration permissions
344 self.first_iter = false;
345 }
346 const rc = linux.getdents64(self.dir.fd, &self.buf, self.buf.len);
347 switch (linux.getErrno(rc)) {
348 .SUCCESS => {},
349 .BADF => unreachable, // Dir is invalid or was opened without iteration ability
350 .FAULT => unreachable,
351 .NOTDIR => unreachable,
352 .NOENT => return error.DirNotFound, // The directory being iterated was deleted during iteration.
353 .INVAL => return error.Unexpected, // Linux may in some cases return EINVAL when reading /proc/$PID/net.
354 .ACCES => return error.AccessDenied, // Do not have permission to iterate this directory.
355 else => |err| return posix.unexpectedErrno(err),
356 }
357 if (rc == 0) return null;
358 self.index = 0;
359 self.end_index = rc;
360 }
361 const linux_entry = @as(*align(1) linux.dirent64, @ptrCast(&self.buf[self.index]));
362 const next_index = self.index + linux_entry.reclen();
363 self.index = next_index;
364
365 const name = mem.sliceTo(@as([*:0]u8, @ptrCast(&linux_entry.d_name)), 0);
366
367 // skip . and .. entries
368 if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) {
369 continue :start_over;
370 }
371
372 const entry_kind: Entry.Kind = switch (linux_entry.d_type) {
373 linux.DT.BLK => .block_device,
374 linux.DT.CHR => .character_device,
375 linux.DT.DIR => .directory,
376 linux.DT.FIFO => .named_pipe,
377 linux.DT.LNK => .sym_link,
378 linux.DT.REG => .file,
379 linux.DT.SOCK => .unix_domain_socket,
380 else => .unknown,
381 };
382 return Entry{
383 .name = name,
384 .kind = entry_kind,
385 };
386 }
387 }
388
389 pub fn reset(self: *Self) void {
390 self.index = 0;
391 self.end_index = 0;
392 self.first_iter = true;
393 }
394 },
395 .windows => struct {
396 dir: Dir,
397 buf: [1024]u8 align(@alignOf(std.os.windows.FILE_BOTH_DIR_INFORMATION)),
398 index: usize,
399 end_index: usize,
400 first_iter: bool,
401 name_data: [fs.MAX_NAME_BYTES]u8,
402
403 const Self = @This();
404
405 pub const Error = IteratorError;
406
407 /// Memory such as file names referenced in this returned entry becomes invalid
408 /// with subsequent calls to `next`, as well as when this `Dir` is deinitialized.
409 pub fn next(self: *Self) Error!?Entry {
410 while (true) {
411 const w = std.os.windows;
412 if (self.index >= self.end_index) {
413 var io: w.IO_STATUS_BLOCK = undefined;
414 const rc = w.ntdll.NtQueryDirectoryFile(
415 self.dir.fd,
416 null,
417 null,
418 null,
419 &io,
420 &self.buf,
421 self.buf.len,
422 .FileBothDirectoryInformation,
423 w.FALSE,
424 null,
425 if (self.first_iter) @as(w.BOOLEAN, w.TRUE) else @as(w.BOOLEAN, w.FALSE),
426 );
427 self.first_iter = false;
428 if (io.Information == 0) return null;
429 self.index = 0;
430 self.end_index = io.Information;
431 switch (rc) {
432 .SUCCESS => {},
433 .ACCESS_DENIED => return error.AccessDenied, // Double-check that the Dir was opened with iteration ability
434
435 else => return w.unexpectedStatus(rc),
436 }
437 }
438
439 // While the official api docs guarantee FILE_BOTH_DIR_INFORMATION to be aligned properly
440 // this may not always be the case (e.g. due to faulty VM/Sandboxing tools)
441 const dir_info: *align(2) w.FILE_BOTH_DIR_INFORMATION = @ptrCast(@alignCast(&self.buf[self.index]));
442 if (dir_info.NextEntryOffset != 0) {
443 self.index += dir_info.NextEntryOffset;
444 } else {
445 self.index = self.buf.len;
446 }
447
448 const name_utf16le = @as([*]u16, @ptrCast(&dir_info.FileName))[0 .. dir_info.FileNameLength / 2];
449
450 if (mem.eql(u16, name_utf16le, &[_]u16{'.'}) or mem.eql(u16, name_utf16le, &[_]u16{ '.', '.' }))
451 continue;
452 // Trust that Windows gives us valid UTF-16LE
453 const name_utf8_len = std.unicode.utf16leToUtf8(self.name_data[0..], name_utf16le) catch unreachable;
454 const name_utf8 = self.name_data[0..name_utf8_len];
455 const kind: Entry.Kind = blk: {
456 const attrs = dir_info.FileAttributes;
457 if (attrs & w.FILE_ATTRIBUTE_DIRECTORY != 0) break :blk .directory;
458 if (attrs & w.FILE_ATTRIBUTE_REPARSE_POINT != 0) break :blk .sym_link;
459 break :blk .file;
460 };
461 return Entry{
462 .name = name_utf8,
463 .kind = kind,
464 };
465 }
466 }
467
468 pub fn reset(self: *Self) void {
469 self.index = 0;
470 self.end_index = 0;
471 self.first_iter = true;
472 }
473 },
474 .wasi => struct {
475 dir: Dir,
476 buf: [1024]u8, // TODO align(@alignOf(posix.wasi.dirent_t)),
477 cookie: u64,
478 index: usize,
479 end_index: usize,
480
481 const Self = @This();
482
483 pub const Error = IteratorError;
484
485 /// Memory such as file names referenced in this returned entry becomes invalid
486 /// with subsequent calls to `next`, as well as when this `Dir` is deinitialized.
487 pub fn next(self: *Self) Error!?Entry {
488 return self.nextWasi() catch |err| switch (err) {
489 // To be consistent across platforms, iteration ends if the directory being iterated is deleted during iteration.
490 // This matches the behavior of non-Linux UNIX platforms.
491 error.DirNotFound => null,
492 else => |e| return e,
493 };
494 }
495
496 pub const ErrorWasi = error{DirNotFound} || IteratorError;
497
498 /// Implementation of `next` that can return platform-dependent errors depending on the host platform.
499 /// When the host platform is Linux, `error.DirNotFound` can be returned if the directory being
500 /// iterated was deleted during iteration.
501 pub fn nextWasi(self: *Self) ErrorWasi!?Entry {
502 // We intentinally use fd_readdir even when linked with libc,
503 // since its implementation is exactly the same as below,
504 // and we avoid the code complexity here.
505 const w = std.os.wasi;
506 start_over: while (true) {
507 // According to the WASI spec, the last entry might be truncated,
508 // so we need to check if the left buffer contains the whole dirent.
509 if (self.end_index - self.index < @sizeOf(w.dirent_t)) {
510 var bufused: usize = undefined;
511 switch (w.fd_readdir(self.dir.fd, &self.buf, self.buf.len, self.cookie, &bufused)) {
512 .SUCCESS => {},
513 .BADF => unreachable, // Dir is invalid or was opened without iteration ability
514 .FAULT => unreachable,
515 .NOTDIR => unreachable,
516 .INVAL => unreachable,
517 .NOENT => return error.DirNotFound, // The directory being iterated was deleted during iteration.
518 .NOTCAPABLE => return error.AccessDenied,
519 else => |err| return posix.unexpectedErrno(err),
520 }
521 if (bufused == 0) return null;
522 self.index = 0;
523 self.end_index = bufused;
524 }
525 const entry = @as(*align(1) w.dirent_t, @ptrCast(&self.buf[self.index]));
526 const entry_size = @sizeOf(w.dirent_t);
527 const name_index = self.index + entry_size;
528 if (name_index + entry.d_namlen > self.end_index) {
529 // This case, the name is truncated, so we need to call readdir to store the entire name.
530 self.end_index = self.index; // Force fd_readdir in the next loop.
531 continue :start_over;
532 }
533 const name = self.buf[name_index .. name_index + entry.d_namlen];
534
535 const next_index = name_index + entry.d_namlen;
536 self.index = next_index;
537 self.cookie = entry.d_next;
538
539 // skip . and .. entries
540 if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) {
541 continue :start_over;
542 }
543
544 const entry_kind: Entry.Kind = switch (entry.d_type) {
545 .BLOCK_DEVICE => .block_device,
546 .CHARACTER_DEVICE => .character_device,
547 .DIRECTORY => .directory,
548 .SYMBOLIC_LINK => .sym_link,
549 .REGULAR_FILE => .file,
550 .SOCKET_STREAM, .SOCKET_DGRAM => .unix_domain_socket,
551 else => .unknown,
552 };
553 return Entry{
554 .name = name,
555 .kind = entry_kind,
556 };
557 }
558 }
559
560 pub fn reset(self: *Self) void {
561 self.index = 0;
562 self.end_index = 0;
563 self.cookie = std.os.wasi.DIRCOOKIE_START;
564 }
565 },
566 else => @compileError("unimplemented"),
567};
568
569pub fn iterate(self: Dir) Iterator {
570 return self.iterateImpl(true);
571}
572
573/// Like `iterate`, but will not reset the directory cursor before the first
574/// iteration. This should only be used in cases where it is known that the
575/// `Dir` has not had its cursor modified yet (e.g. it was just opened).
576pub fn iterateAssumeFirstIteration(self: Dir) Iterator {
577 return self.iterateImpl(false);
578}
579
580fn iterateImpl(self: Dir, first_iter_start_value: bool) Iterator {
581 switch (builtin.os.tag) {
582 .macos,
583 .ios,
584 .freebsd,
585 .netbsd,
586 .dragonfly,
587 .openbsd,
588 .solaris,
589 .illumos,
590 => return Iterator{
591 .dir = self,
592 .seek = 0,
593 .index = 0,
594 .end_index = 0,
595 .buf = undefined,
596 .first_iter = first_iter_start_value,
597 },
598 .linux, .haiku => return Iterator{
599 .dir = self,
600 .index = 0,
601 .end_index = 0,
602 .buf = undefined,
603 .first_iter = first_iter_start_value,
604 },
605 .windows => return Iterator{
606 .dir = self,
607 .index = 0,
608 .end_index = 0,
609 .first_iter = first_iter_start_value,
610 .buf = undefined,
611 .name_data = undefined,
612 },
613 .wasi => return Iterator{
614 .dir = self,
615 .cookie = std.os.wasi.DIRCOOKIE_START,
616 .index = 0,
617 .end_index = 0,
618 .buf = undefined,
619 },
620 else => @compileError("unimplemented"),
621 }
622}
623
624pub const Walker = struct {
625 stack: std.ArrayList(StackItem),
626 name_buffer: std.ArrayList(u8),
627
628 pub const WalkerEntry = struct {
629 /// The containing directory. This can be used to operate directly on `basename`
630 /// rather than `path`, avoiding `error.NameTooLong` for deeply nested paths.
631 /// The directory remains open until `next` or `deinit` is called.
632 dir: Dir,
633 basename: []const u8,
634 path: []const u8,
635 kind: Dir.Entry.Kind,
636 };
637
638 const StackItem = struct {
639 iter: Dir.Iterator,
640 dirname_len: usize,
641 };
642
643 /// After each call to this function, and on deinit(), the memory returned
644 /// from this function becomes invalid. A copy must be made in order to keep
645 /// a reference to the path.
646 pub fn next(self: *Walker) !?WalkerEntry {
647 while (self.stack.items.len != 0) {
648 // `top` and `containing` become invalid after appending to `self.stack`
649 var top = &self.stack.items[self.stack.items.len - 1];
650 var containing = top;
651 var dirname_len = top.dirname_len;
652 if (top.iter.next() catch |err| {
653 // If we get an error, then we want the user to be able to continue
654 // walking if they want, which means that we need to pop the directory
655 // that errored from the stack. Otherwise, all future `next` calls would
656 // likely just fail with the same error.
657 var item = self.stack.pop();
658 if (self.stack.items.len != 0) {
659 item.iter.dir.close();
660 }
661 return err;
662 }) |base| {
663 self.name_buffer.shrinkRetainingCapacity(dirname_len);
664 if (self.name_buffer.items.len != 0) {
665 try self.name_buffer.append(fs.path.sep);
666 dirname_len += 1;
667 }
668 try self.name_buffer.appendSlice(base.name);
669 if (base.kind == .directory) {
670 var new_dir = top.iter.dir.openDir(base.name, .{ .iterate = true }) catch |err| switch (err) {
671 error.NameTooLong => unreachable, // no path sep in base.name
672 else => |e| return e,
673 };
674 {
675 errdefer new_dir.close();
676 try self.stack.append(StackItem{
677 .iter = new_dir.iterateAssumeFirstIteration(),
678 .dirname_len = self.name_buffer.items.len,
679 });
680 top = &self.stack.items[self.stack.items.len - 1];
681 containing = &self.stack.items[self.stack.items.len - 2];
682 }
683 }
684 return WalkerEntry{
685 .dir = containing.iter.dir,
686 .basename = self.name_buffer.items[dirname_len..],
687 .path = self.name_buffer.items,
688 .kind = base.kind,
689 };
690 } else {
691 var item = self.stack.pop();
692 if (self.stack.items.len != 0) {
693 item.iter.dir.close();
694 }
695 }
696 }
697 return null;
698 }
699
700 pub fn deinit(self: *Walker) void {
701 // Close any remaining directories except the initial one (which is always at index 0)
702 if (self.stack.items.len > 1) {
703 for (self.stack.items[1..]) |*item| {
704 item.iter.dir.close();
705 }
706 }
707 self.stack.deinit();
708 self.name_buffer.deinit();
709 }
710};
711
712/// Recursively iterates over a directory.
713/// `self` must have been opened with `OpenDirOptions{.iterate = true}`.
714/// Must call `Walker.deinit` when done.
715/// The order of returned file system entries is undefined.
716/// `self` will not be closed after walking it.
717pub fn walk(self: Dir, allocator: Allocator) !Walker {
718 var name_buffer = std.ArrayList(u8).init(allocator);
719 errdefer name_buffer.deinit();
720
721 var stack = std.ArrayList(Walker.StackItem).init(allocator);
722 errdefer stack.deinit();
723
724 try stack.append(Walker.StackItem{
725 .iter = self.iterate(),
726 .dirname_len = 0,
727 });
728
729 return Walker{
730 .stack = stack,
731 .name_buffer = name_buffer,
732 };
733}
734
735pub const OpenError = error{
736 FileNotFound,
737 NotDir,
738 InvalidHandle,
739 AccessDenied,
740 SymLinkLoop,
741 ProcessFdQuotaExceeded,
742 NameTooLong,
743 SystemFdQuotaExceeded,
744 NoDevice,
745 SystemResources,
746 InvalidUtf8,
747 BadPathName,
748 DeviceBusy,
749 /// On Windows, `\\server` or `\\server\share` was not found.
750 NetworkNotFound,
751} || posix.UnexpectedError;
752
753pub fn close(self: *Dir) void {
754 if (fs.need_async_thread) {
755 std.event.Loop.instance.?.close(self.fd);
756 } else {
757 posix.close(self.fd);
758 }
759 self.* = undefined;
760}
761
762/// Opens a file for reading or writing, without attempting to create a new file.
763/// To create a new file, see `createFile`.
764/// Call `File.close` to release the resource.
765/// Asserts that the path parameter has no null bytes.
766pub fn openFile(self: Dir, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File {
767 if (builtin.os.tag == .windows) {
768 const path_w = try std.os.windows.sliceToPrefixedFileW(self.fd, sub_path);
769 return self.openFileW(path_w.span(), flags);
770 }
771 if (builtin.os.tag == .wasi and !builtin.link_libc) {
772 return self.openFileWasi(sub_path, flags);
773 }
774 const path_c = try posix.toPosixPath(sub_path);
775 return self.openFileZ(&path_c, flags);
776}
777
778/// Same as `openFile` but WASI only.
779pub fn openFileWasi(self: Dir, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File {
780 const w = std.os.wasi;
781 var fdflags: w.fdflags_t = 0x0;
782 var base: w.rights_t = 0x0;
783 if (flags.isRead()) {
784 base |= w.RIGHT.FD_READ | w.RIGHT.FD_TELL | w.RIGHT.FD_SEEK | w.RIGHT.FD_FILESTAT_GET;
785 }
786 if (flags.isWrite()) {
787 fdflags |= w.FDFLAG.APPEND;
788 base |= w.RIGHT.FD_WRITE |
789 w.RIGHT.FD_TELL |
790 w.RIGHT.FD_SEEK |
791 w.RIGHT.FD_DATASYNC |
792 w.RIGHT.FD_FDSTAT_SET_FLAGS |
793 w.RIGHT.FD_SYNC |
794 w.RIGHT.FD_ALLOCATE |
795 w.RIGHT.FD_ADVISE |
796 w.RIGHT.FD_FILESTAT_SET_TIMES |
797 w.RIGHT.FD_FILESTAT_SET_SIZE;
798 }
799 const fd = try posix.openatWasi(self.fd, sub_path, 0x0, 0x0, fdflags, base, 0x0);
800 return File{ .handle = fd };
801}
802
803/// Same as `openFile` but the path parameter is null-terminated.
804pub fn openFileZ(self: Dir, sub_path: [*:0]const u8, flags: File.OpenFlags) File.OpenError!File {
805 if (builtin.os.tag == .windows) {
806 const path_w = try std.os.windows.cStrToPrefixedFileW(self.fd, sub_path);
807 return self.openFileW(path_w.span(), flags);
808 }
809
810 var os_flags: u32 = 0;
811 if (@hasDecl(posix.O, "CLOEXEC")) os_flags = posix.O.CLOEXEC;
812
813 // Use the O locking flags if the os supports them to acquire the lock
814 // atomically.
815 const has_flock_open_flags = @hasDecl(posix.O, "EXLOCK");
816 if (has_flock_open_flags) {
817 // Note that the O.NONBLOCK flag is removed after the openat() call
818 // is successful.
819 const nonblocking_lock_flag: u32 = if (flags.lock_nonblocking)
820 posix.O.NONBLOCK
821 else
822 0;
823 os_flags |= switch (flags.lock) {
824 .none => @as(u32, 0),
825 .shared => posix.O.SHLOCK | nonblocking_lock_flag,
826 .exclusive => posix.O.EXLOCK | nonblocking_lock_flag,
827 };
828 }
829 if (@hasDecl(posix.O, "LARGEFILE")) {
830 os_flags |= posix.O.LARGEFILE;
831 }
832 if (@hasDecl(posix.O, "NOCTTY") and !flags.allow_ctty) {
833 os_flags |= posix.O.NOCTTY;
834 }
835 os_flags |= switch (flags.mode) {
836 .read_only => @as(u32, posix.O.RDONLY),
837 .write_only => @as(u32, posix.O.WRONLY),
838 .read_write => @as(u32, posix.O.RDWR),
839 };
840 const fd = if (flags.intended_io_mode != .blocking)
841 try std.event.Loop.instance.?.openatZ(self.fd, sub_path, os_flags, 0)
842 else
843 try posix.openatZ(self.fd, sub_path, os_flags, 0);
844 errdefer posix.close(fd);
845
846 // WASI doesn't have posix.flock so we intetinally check OS prior to the inner if block
847 // since it is not compiltime-known and we need to avoid undefined symbol in Wasm.
848 if (@hasDecl(posix.system, "LOCK") and builtin.target.os.tag != .wasi) {
849 if (!has_flock_open_flags and flags.lock != .none) {
850 // TODO: integrate async I/O
851 const lock_nonblocking: i32 = if (flags.lock_nonblocking) posix.LOCK.NB else 0;
852 try posix.flock(fd, switch (flags.lock) {
853 .none => unreachable,
854 .shared => posix.LOCK.SH | lock_nonblocking,
855 .exclusive => posix.LOCK.EX | lock_nonblocking,
856 });
857 }
858 }
859
860 if (has_flock_open_flags and flags.lock_nonblocking) {
861 var fl_flags = posix.fcntl(fd, posix.F.GETFL, 0) catch |err| switch (err) {
862 error.FileBusy => unreachable,
863 error.Locked => unreachable,
864 error.PermissionDenied => unreachable,
865 error.DeadLock => unreachable,
866 error.LockedRegionLimitExceeded => unreachable,
867 else => |e| return e,
868 };
869 fl_flags &= ~@as(usize, posix.O.NONBLOCK);
870 _ = posix.fcntl(fd, posix.F.SETFL, fl_flags) catch |err| switch (err) {
871 error.FileBusy => unreachable,
872 error.Locked => unreachable,
873 error.PermissionDenied => unreachable,
874 error.DeadLock => unreachable,
875 error.LockedRegionLimitExceeded => unreachable,
876 else => |e| return e,
877 };
878 }
879
880 return File{
881 .handle = fd,
882 .capable_io_mode = .blocking,
883 .intended_io_mode = flags.intended_io_mode,
884 };
885}
886
887/// Same as `openFile` but Windows-only and the path parameter is
888/// [WTF-16](https://simonsapin.github.io/wtf-8/#potentially-ill-formed-utf-16) encoded.
889pub fn openFileW(self: Dir, sub_path_w: []const u16, flags: File.OpenFlags) File.OpenError!File {
890 const w = std.os.windows;
891 const file: File = .{
892 .handle = try w.OpenFile(sub_path_w, .{
893 .dir = self.fd,
894 .access_mask = w.SYNCHRONIZE |
895 (if (flags.isRead()) @as(u32, w.GENERIC_READ) else 0) |
896 (if (flags.isWrite()) @as(u32, w.GENERIC_WRITE) else 0),
897 .creation = w.FILE_OPEN,
898 .io_mode = flags.intended_io_mode,
899 }),
900 .capable_io_mode = std.io.default_mode,
901 .intended_io_mode = flags.intended_io_mode,
902 };
903 errdefer file.close();
904 var io: w.IO_STATUS_BLOCK = undefined;
905 const range_off: w.LARGE_INTEGER = 0;
906 const range_len: w.LARGE_INTEGER = 1;
907 const exclusive = switch (flags.lock) {
908 .none => return file,
909 .shared => false,
910 .exclusive => true,
911 };
912 try w.LockFile(
913 file.handle,
914 null,
915 null,
916 null,
917 &io,
918 &range_off,
919 &range_len,
920 null,
921 @intFromBool(flags.lock_nonblocking),
922 @intFromBool(exclusive),
923 );
924 return file;
925}
926
927/// Creates, opens, or overwrites a file with write access.
928/// Call `File.close` on the result when done.
929/// Asserts that the path parameter has no null bytes.
930pub fn createFile(self: Dir, sub_path: []const u8, flags: File.CreateFlags) File.OpenError!File {
931 if (builtin.os.tag == .windows) {
932 const path_w = try std.os.windows.sliceToPrefixedFileW(self.fd, sub_path);
933 return self.createFileW(path_w.span(), flags);
934 }
935 if (builtin.os.tag == .wasi and !builtin.link_libc) {
936 return self.createFileWasi(sub_path, flags);
937 }
938 const path_c = try posix.toPosixPath(sub_path);
939 return self.createFileZ(&path_c, flags);
940}
941
942/// Same as `createFile` but WASI only.
943pub fn createFileWasi(self: Dir, sub_path: []const u8, flags: File.CreateFlags) File.OpenError!File {
944 const w = std.os.wasi;
945 var oflags = w.O.CREAT;
946 var base: w.rights_t = w.RIGHT.FD_WRITE |
947 w.RIGHT.FD_DATASYNC |
948 w.RIGHT.FD_SEEK |
949 w.RIGHT.FD_TELL |
950 w.RIGHT.FD_FDSTAT_SET_FLAGS |
951 w.RIGHT.FD_SYNC |
952 w.RIGHT.FD_ALLOCATE |
953 w.RIGHT.FD_ADVISE |
954 w.RIGHT.FD_FILESTAT_SET_TIMES |
955 w.RIGHT.FD_FILESTAT_SET_SIZE |
956 w.RIGHT.FD_FILESTAT_GET;
957 if (flags.read) {
958 base |= w.RIGHT.FD_READ;
959 }
960 if (flags.truncate) {
961 oflags |= w.O.TRUNC;
962 }
963 if (flags.exclusive) {
964 oflags |= w.O.EXCL;
965 }
966 const fd = try posix.openatWasi(self.fd, sub_path, 0x0, oflags, 0x0, base, 0x0);
967 return File{ .handle = fd };
968}
969
970/// Same as `createFile` but the path parameter is null-terminated.
971pub fn createFileZ(self: Dir, sub_path_c: [*:0]const u8, flags: File.CreateFlags) File.OpenError!File {
972 if (builtin.os.tag == .windows) {
973 const path_w = try std.os.windows.cStrToPrefixedFileW(self.fd, sub_path_c);
974 return self.createFileW(path_w.span(), flags);
975 }
976
977 // Use the O locking flags if the os supports them to acquire the lock
978 // atomically.
979 const has_flock_open_flags = @hasDecl(posix.O, "EXLOCK");
980 // Note that the O.NONBLOCK flag is removed after the openat() call
981 // is successful.
982 const nonblocking_lock_flag: u32 = if (has_flock_open_flags and flags.lock_nonblocking)
983 posix.O.NONBLOCK
984 else
985 0;
986 const lock_flag: u32 = if (has_flock_open_flags) switch (flags.lock) {
987 .none => @as(u32, 0),
988 .shared => posix.O.SHLOCK | nonblocking_lock_flag,
989 .exclusive => posix.O.EXLOCK | nonblocking_lock_flag,
990 } else 0;
991
992 const O_LARGEFILE = if (@hasDecl(posix.O, "LARGEFILE")) posix.O.LARGEFILE else 0;
993 const os_flags = lock_flag | O_LARGEFILE | posix.O.CREAT | posix.O.CLOEXEC |
994 (if (flags.truncate) @as(u32, posix.O.TRUNC) else 0) |
995 (if (flags.read) @as(u32, posix.O.RDWR) else posix.O.WRONLY) |
996 (if (flags.exclusive) @as(u32, posix.O.EXCL) else 0);
997 const fd = if (flags.intended_io_mode != .blocking)
998 try std.event.Loop.instance.?.openatZ(self.fd, sub_path_c, os_flags, flags.mode)
999 else
1000 try posix.openatZ(self.fd, sub_path_c, os_flags, flags.mode);
1001 errdefer posix.close(fd);
1002
1003 // WASI doesn't have posix.flock so we intetinally check OS prior to the inner if block
1004 // since it is not compiltime-known and we need to avoid undefined symbol in Wasm.
1005 if (builtin.target.os.tag != .wasi) {
1006 if (!has_flock_open_flags and flags.lock != .none) {
1007 // TODO: integrate async I/O
1008 const lock_nonblocking: i32 = if (flags.lock_nonblocking) posix.LOCK.NB else 0;
1009 try posix.flock(fd, switch (flags.lock) {
1010 .none => unreachable,
1011 .shared => posix.LOCK.SH | lock_nonblocking,
1012 .exclusive => posix.LOCK.EX | lock_nonblocking,
1013 });
1014 }
1015 }
1016
1017 if (has_flock_open_flags and flags.lock_nonblocking) {
1018 var fl_flags = posix.fcntl(fd, posix.F.GETFL, 0) catch |err| switch (err) {
1019 error.FileBusy => unreachable,
1020 error.Locked => unreachable,
1021 error.PermissionDenied => unreachable,
1022 error.DeadLock => unreachable,
1023 error.LockedRegionLimitExceeded => unreachable,
1024 else => |e| return e,
1025 };
1026 fl_flags &= ~@as(usize, posix.O.NONBLOCK);
1027 _ = posix.fcntl(fd, posix.F.SETFL, fl_flags) catch |err| switch (err) {
1028 error.FileBusy => unreachable,
1029 error.Locked => unreachable,
1030 error.PermissionDenied => unreachable,
1031 error.DeadLock => unreachable,
1032 error.LockedRegionLimitExceeded => unreachable,
1033 else => |e| return e,
1034 };
1035 }
1036
1037 return File{
1038 .handle = fd,
1039 .capable_io_mode = .blocking,
1040 .intended_io_mode = flags.intended_io_mode,
1041 };
1042}
1043
1044/// Same as `createFile` but Windows-only and the path parameter is
1045/// [WTF-16](https://simonsapin.github.io/wtf-8/#potentially-ill-formed-utf-16) encoded.
1046pub fn createFileW(self: Dir, sub_path_w: []const u16, flags: File.CreateFlags) File.OpenError!File {
1047 const w = std.os.windows;
1048 const read_flag = if (flags.read) @as(u32, w.GENERIC_READ) else 0;
1049 const file: File = .{
1050 .handle = try w.OpenFile(sub_path_w, .{
1051 .dir = self.fd,
1052 .access_mask = w.SYNCHRONIZE | w.GENERIC_WRITE | read_flag,
1053 .creation = if (flags.exclusive)
1054 @as(u32, w.FILE_CREATE)
1055 else if (flags.truncate)
1056 @as(u32, w.FILE_OVERWRITE_IF)
1057 else
1058 @as(u32, w.FILE_OPEN_IF),
1059 .io_mode = flags.intended_io_mode,
1060 }),
1061 .capable_io_mode = std.io.default_mode,
1062 .intended_io_mode = flags.intended_io_mode,
1063 };
1064 errdefer file.close();
1065 var io: w.IO_STATUS_BLOCK = undefined;
1066 const range_off: w.LARGE_INTEGER = 0;
1067 const range_len: w.LARGE_INTEGER = 1;
1068 const exclusive = switch (flags.lock) {
1069 .none => return file,
1070 .shared => false,
1071 .exclusive => true,
1072 };
1073 try w.LockFile(
1074 file.handle,
1075 null,
1076 null,
1077 null,
1078 &io,
1079 &range_off,
1080 &range_len,
1081 null,
1082 @intFromBool(flags.lock_nonblocking),
1083 @intFromBool(exclusive),
1084 );
1085 return file;
1086}
1087
1088/// Creates a single directory with a relative or absolute path.
1089/// To create multiple directories to make an entire path, see `makePath`.
1090/// To operate on only absolute paths, see `makeDirAbsolute`.
1091pub fn makeDir(self: Dir, sub_path: []const u8) !void {
1092 try posix.mkdirat(self.fd, sub_path, default_mode);
1093}
1094
1095/// Creates a single directory with a relative or absolute null-terminated UTF-8-encoded path.
1096/// To create multiple directories to make an entire path, see `makePath`.
1097/// To operate on only absolute paths, see `makeDirAbsoluteZ`.
1098pub fn makeDirZ(self: Dir, sub_path: [*:0]const u8) !void {
1099 try posix.mkdiratZ(self.fd, sub_path, default_mode);
1100}
1101
1102/// Creates a single directory with a relative or absolute null-terminated WTF-16-encoded path.
1103/// To create multiple directories to make an entire path, see `makePath`.
1104/// To operate on only absolute paths, see `makeDirAbsoluteW`.
1105pub fn makeDirW(self: Dir, sub_path: [*:0]const u16) !void {
1106 try posix.mkdiratW(self.fd, sub_path, default_mode);
1107}
1108
1109/// Calls makeDir iteratively to make an entire path
1110/// (i.e. creating any parent directories that do not exist).
1111/// Returns success if the path already exists and is a directory.
1112/// This function is not atomic, and if it returns an error, the file system may
1113/// have been modified regardless.
1114pub fn makePath(self: Dir, sub_path: []const u8) !void {
1115 var it = try fs.path.componentIterator(sub_path);
1116 var component = it.last() orelse return;
1117 while (true) {
1118 self.makeDir(component.path) catch |err| switch (err) {
1119 error.PathAlreadyExists => {
1120 // TODO stat the file and return an error if it's not a directory
1121 // this is important because otherwise a dangling symlink
1122 // could cause an infinite loop
1123 },
1124 error.FileNotFound => |e| {
1125 component = it.previous() orelse return e;
1126 continue;
1127 },
1128 else => |e| return e,
1129 };
1130 component = it.next() orelse return;
1131 }
1132}
1133
1134/// Calls makeOpenDirAccessMaskW iteratively to make an entire path
1135/// (i.e. creating any parent directories that do not exist).
1136/// Opens the dir if the path already exists and is a directory.
1137/// This function is not atomic, and if it returns an error, the file system may
1138/// have been modified regardless.
1139fn makeOpenPathAccessMaskW(self: Dir, sub_path: []const u8, access_mask: u32, no_follow: bool) OpenError!Dir {
1140 const w = std.os.windows;
1141 var it = try fs.path.componentIterator(sub_path);
1142 // If there are no components in the path, then create a dummy component with the full path.
1143 var component = it.last() orelse fs.path.NativeUtf8ComponentIterator.Component{
1144 .name = "",
1145 .path = sub_path,
1146 };
1147
1148 while (true) {
1149 const sub_path_w = try w.sliceToPrefixedFileW(self.fd, component.path);
1150 const is_last = it.peekNext() == null;
1151 var result = self.makeOpenDirAccessMaskW(sub_path_w.span().ptr, access_mask, .{
1152 .no_follow = no_follow,
1153 .create_disposition = if (is_last) w.FILE_OPEN_IF else w.FILE_CREATE,
1154 }) catch |err| switch (err) {
1155 error.FileNotFound => |e| {
1156 component = it.previous() orelse return e;
1157 continue;
1158 },
1159 else => |e| return e,
1160 };
1161
1162 component = it.next() orelse return result;
1163 // Don't leak the intermediate file handles
1164 result.close();
1165 }
1166}
1167
1168/// This function performs `makePath`, followed by `openDir`.
1169/// If supported by the OS, this operation is atomic. It is not atomic on
1170/// all operating systems.
1171/// On Windows, this function performs `makeOpenPathAccessMaskW`.
1172pub fn makeOpenPath(self: Dir, sub_path: []const u8, open_dir_options: OpenDirOptions) !Dir {
1173 return switch (builtin.os.tag) {
1174 .windows => {
1175 const w = std.os.windows;
1176 const base_flags = w.STANDARD_RIGHTS_READ | w.FILE_READ_ATTRIBUTES | w.FILE_READ_EA |
1177 w.SYNCHRONIZE | w.FILE_TRAVERSE |
1178 (if (open_dir_options.iterate) w.FILE_LIST_DIRECTORY else @as(u32, 0));
1179
1180 return self.makeOpenPathAccessMaskW(sub_path, base_flags, open_dir_options.no_follow);
1181 },
1182 else => {
1183 return self.openDir(sub_path, open_dir_options) catch |err| switch (err) {
1184 error.FileNotFound => {
1185 try self.makePath(sub_path);
1186 return self.openDir(sub_path, open_dir_options);
1187 },
1188 else => |e| return e,
1189 };
1190 },
1191 };
1192}
1193
1194/// This function returns the canonicalized absolute pathname of
1195/// `pathname` relative to this `Dir`. If `pathname` is absolute, ignores this
1196/// `Dir` handle and returns the canonicalized absolute pathname of `pathname`
1197/// argument.
1198/// This function is not universally supported by all platforms.
1199/// Currently supported hosts are: Linux, macOS, and Windows.
1200/// See also `Dir.realpathZ`, `Dir.realpathW`, and `Dir.realpathAlloc`.
1201pub fn realpath(self: Dir, pathname: []const u8, out_buffer: []u8) ![]u8 {
1202 if (builtin.os.tag == .wasi) {
1203 @compileError("realpath is not available on WASI");
1204 }
1205 if (builtin.os.tag == .windows) {
1206 const pathname_w = try std.os.windows.sliceToPrefixedFileW(self.fd, pathname);
1207 return self.realpathW(pathname_w.span(), out_buffer);
1208 }
1209 const pathname_c = try posix.toPosixPath(pathname);
1210 return self.realpathZ(&pathname_c, out_buffer);
1211}
1212
1213/// Same as `Dir.realpath` except `pathname` is null-terminated.
1214/// See also `Dir.realpath`, `realpathZ`.
1215pub fn realpathZ(self: Dir, pathname: [*:0]const u8, out_buffer: []u8) ![]u8 {
1216 if (builtin.os.tag == .windows) {
1217 const pathname_w = try posix.windows.cStrToPrefixedFileW(self.fd, pathname);
1218 return self.realpathW(pathname_w.span(), out_buffer);
1219 }
1220
1221 const flags = if (builtin.os.tag == .linux)
1222 posix.O.PATH | posix.O.NONBLOCK | posix.O.CLOEXEC
1223 else
1224 posix.O.NONBLOCK | posix.O.CLOEXEC;
1225 const fd = posix.openatZ(self.fd, pathname, flags, 0) catch |err| switch (err) {
1226 error.FileLocksNotSupported => unreachable,
1227 else => |e| return e,
1228 };
1229 defer posix.close(fd);
1230
1231 // Use of MAX_PATH_BYTES here is valid as the realpath function does not
1232 // have a variant that takes an arbitrary-size buffer.
1233 // TODO(#4812): Consider reimplementing realpath or using the POSIX.1-2008
1234 // NULL out parameter (GNU's canonicalize_file_name) to handle overelong
1235 // paths. musl supports passing NULL but restricts the output to PATH_MAX
1236 // anyway.
1237 var buffer: [fs.MAX_PATH_BYTES]u8 = undefined;
1238 const out_path = try posix.getFdPath(fd, &buffer);
1239
1240 if (out_path.len > out_buffer.len) {
1241 return error.NameTooLong;
1242 }
1243
1244 const result = out_buffer[0..out_path.len];
1245 @memcpy(result, out_path);
1246 return result;
1247}
1248
1249/// Windows-only. Same as `Dir.realpath` except `pathname` is WTF16 encoded.
1250/// See also `Dir.realpath`, `realpathW`.
1251pub fn realpathW(self: Dir, pathname: []const u16, out_buffer: []u8) ![]u8 {
1252 const w = std.os.windows;
1253
1254 const access_mask = w.GENERIC_READ | w.SYNCHRONIZE;
1255 const share_access = w.FILE_SHARE_READ;
1256 const creation = w.FILE_OPEN;
1257 const h_file = blk: {
1258 const res = w.OpenFile(pathname, .{
1259 .dir = self.fd,
1260 .access_mask = access_mask,
1261 .share_access = share_access,
1262 .creation = creation,
1263 .io_mode = .blocking,
1264 .filter = .any,
1265 }) catch |err| switch (err) {
1266 error.WouldBlock => unreachable,
1267 else => |e| return e,
1268 };
1269 break :blk res;
1270 };
1271 defer w.CloseHandle(h_file);
1272
1273 // Use of MAX_PATH_BYTES here is valid as the realpath function does not
1274 // have a variant that takes an arbitrary-size buffer.
1275 // TODO(#4812): Consider reimplementing realpath or using the POSIX.1-2008
1276 // NULL out parameter (GNU's canonicalize_file_name) to handle overelong
1277 // paths. musl supports passing NULL but restricts the output to PATH_MAX
1278 // anyway.
1279 var buffer: [fs.MAX_PATH_BYTES]u8 = undefined;
1280 const out_path = try posix.getFdPath(h_file, &buffer);
1281
1282 if (out_path.len > out_buffer.len) {
1283 return error.NameTooLong;
1284 }
1285
1286 const result = out_buffer[0..out_path.len];
1287 @memcpy(result, out_path);
1288 return result;
1289}
1290
1291/// Same as `Dir.realpath` except caller must free the returned memory.
1292/// See also `Dir.realpath`.
1293pub fn realpathAlloc(self: Dir, allocator: Allocator, pathname: []const u8) ![]u8 {
1294 // Use of MAX_PATH_BYTES here is valid as the realpath function does not
1295 // have a variant that takes an arbitrary-size buffer.
1296 // TODO(#4812): Consider reimplementing realpath or using the POSIX.1-2008
1297 // NULL out parameter (GNU's canonicalize_file_name) to handle overelong
1298 // paths. musl supports passing NULL but restricts the output to PATH_MAX
1299 // anyway.
1300 var buf: [fs.MAX_PATH_BYTES]u8 = undefined;
1301 return allocator.dupe(u8, try self.realpath(pathname, buf[0..]));
1302}
1303
1304/// Changes the current working directory to the open directory handle.
1305/// This modifies global state and can have surprising effects in multi-
1306/// threaded applications. Most applications and especially libraries should
1307/// not call this function as a general rule, however it can have use cases
1308/// in, for example, implementing a shell, or child process execution.
1309/// Not all targets support this. For example, WASI does not have the concept
1310/// of a current working directory.
1311pub fn setAsCwd(self: Dir) !void {
1312 if (builtin.os.tag == .wasi) {
1313 @compileError("changing cwd is not currently possible in WASI");
1314 }
1315 if (builtin.os.tag == .windows) {
1316 var dir_path_buffer: [std.os.windows.PATH_MAX_WIDE]u16 = undefined;
1317 const dir_path = try std.os.windows.GetFinalPathNameByHandle(self.fd, .{}, &dir_path_buffer);
1318 if (builtin.link_libc) {
1319 return posix.chdirW(dir_path);
1320 }
1321 return std.os.windows.SetCurrentDirectory(dir_path);
1322 }
1323 try posix.fchdir(self.fd);
1324}
1325
1326pub const OpenDirOptions = struct {
1327 /// `true` means the opened directory can be used as the `Dir` parameter
1328 /// for functions which operate based on an open directory handle. When `false`,
1329 /// such operations are Illegal Behavior.
1330 access_sub_paths: bool = true,
1331
1332 /// `true` means the opened directory can be scanned for the files and sub-directories
1333 /// of the result. It means the `iterate` function can be called.
1334 iterate: bool = false,
1335
1336 /// `true` means it won't dereference the symlinks.
1337 no_follow: bool = false,
1338};
1339
1340/// Opens a directory at the given path. The directory is a system resource that remains
1341/// open until `close` is called on the result.
1342/// The directory cannot be iterated unless the `iterate` option is set to `true`.
1343///
1344/// Asserts that the path parameter has no null bytes.
1345pub fn openDir(self: Dir, sub_path: []const u8, args: OpenDirOptions) OpenError!Dir {
1346 if (builtin.os.tag == .windows) {
1347 const sub_path_w = try posix.windows.sliceToPrefixedFileW(self.fd, sub_path);
1348 return self.openDirW(sub_path_w.span().ptr, args);
1349 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
1350 return self.openDirWasi(sub_path, args);
1351 } else {
1352 const sub_path_c = try posix.toPosixPath(sub_path);
1353 return self.openDirZ(&sub_path_c, args);
1354 }
1355}
1356
1357/// Same as `openDir` except only WASI.
1358pub fn openDirWasi(self: Dir, sub_path: []const u8, args: OpenDirOptions) OpenError!Dir {
1359 const w = std.os.wasi;
1360 var base: w.rights_t = w.RIGHT.FD_FILESTAT_GET | w.RIGHT.FD_FDSTAT_SET_FLAGS | w.RIGHT.FD_FILESTAT_SET_TIMES;
1361 if (args.access_sub_paths) {
1362 base |= w.RIGHT.FD_READDIR |
1363 w.RIGHT.PATH_CREATE_DIRECTORY |
1364 w.RIGHT.PATH_CREATE_FILE |
1365 w.RIGHT.PATH_LINK_SOURCE |
1366 w.RIGHT.PATH_LINK_TARGET |
1367 w.RIGHT.PATH_OPEN |
1368 w.RIGHT.PATH_READLINK |
1369 w.RIGHT.PATH_RENAME_SOURCE |
1370 w.RIGHT.PATH_RENAME_TARGET |
1371 w.RIGHT.PATH_FILESTAT_GET |
1372 w.RIGHT.PATH_FILESTAT_SET_SIZE |
1373 w.RIGHT.PATH_FILESTAT_SET_TIMES |
1374 w.RIGHT.PATH_SYMLINK |
1375 w.RIGHT.PATH_REMOVE_DIRECTORY |
1376 w.RIGHT.PATH_UNLINK_FILE;
1377 }
1378 const symlink_flags: w.lookupflags_t = if (args.no_follow) 0x0 else w.LOOKUP_SYMLINK_FOLLOW;
1379 // TODO do we really need all the rights here?
1380 const inheriting: w.rights_t = w.RIGHT.ALL ^ w.RIGHT.SOCK_SHUTDOWN;
1381
1382 const result = posix.openatWasi(
1383 self.fd,
1384 sub_path,
1385 symlink_flags,
1386 w.O.DIRECTORY,
1387 0x0,
1388 base,
1389 inheriting,
1390 );
1391 const fd = result catch |err| switch (err) {
1392 error.FileTooBig => unreachable, // can't happen for directories
1393 error.IsDir => unreachable, // we're providing O.DIRECTORY
1394 error.NoSpaceLeft => unreachable, // not providing O.CREAT
1395 error.PathAlreadyExists => unreachable, // not providing O.CREAT
1396 error.FileLocksNotSupported => unreachable, // locking folders is not supported
1397 error.WouldBlock => unreachable, // can't happen for directories
1398 error.FileBusy => unreachable, // can't happen for directories
1399 else => |e| return e,
1400 };
1401 return Dir{ .fd = fd };
1402}
1403
1404/// Same as `openDir` except the parameter is null-terminated.
1405pub fn openDirZ(self: Dir, sub_path_c: [*:0]const u8, args: OpenDirOptions) OpenError!Dir {
1406 if (builtin.os.tag == .windows) {
1407 const sub_path_w = try std.os.windows.cStrToPrefixedFileW(self.fd, sub_path_c);
1408 return self.openDirW(sub_path_w.span().ptr, args);
1409 }
1410 const symlink_flags: u32 = if (args.no_follow) posix.O.NOFOLLOW else 0x0;
1411 if (!args.iterate) {
1412 const O_PATH = if (@hasDecl(posix.O, "PATH")) posix.O.PATH else 0;
1413 return self.openDirFlagsZ(sub_path_c, posix.O.DIRECTORY | posix.O.RDONLY | posix.O.CLOEXEC | O_PATH | symlink_flags);
1414 } else {
1415 return self.openDirFlagsZ(sub_path_c, posix.O.DIRECTORY | posix.O.RDONLY | posix.O.CLOEXEC | symlink_flags);
1416 }
1417}
1418
1419/// Same as `openDir` except the path parameter is WTF-16 encoded, NT-prefixed.
1420/// This function asserts the target OS is Windows.
1421pub fn openDirW(self: Dir, sub_path_w: [*:0]const u16, args: OpenDirOptions) OpenError!Dir {
1422 const w = std.os.windows;
1423 // TODO remove some of these flags if args.access_sub_paths is false
1424 const base_flags = w.STANDARD_RIGHTS_READ | w.FILE_READ_ATTRIBUTES | w.FILE_READ_EA |
1425 w.SYNCHRONIZE | w.FILE_TRAVERSE;
1426 const flags: u32 = if (args.iterate) base_flags | w.FILE_LIST_DIRECTORY else base_flags;
1427 const dir = try self.makeOpenDirAccessMaskW(sub_path_w, flags, .{
1428 .no_follow = args.no_follow,
1429 .create_disposition = w.FILE_OPEN,
1430 });
1431 return dir;
1432}
1433
1434/// `flags` must contain `posix.O.DIRECTORY`.
1435fn openDirFlagsZ(self: Dir, sub_path_c: [*:0]const u8, flags: u32) OpenError!Dir {
1436 const result = if (fs.need_async_thread)
1437 std.event.Loop.instance.?.openatZ(self.fd, sub_path_c, flags, 0)
1438 else
1439 posix.openatZ(self.fd, sub_path_c, flags, 0);
1440 const fd = result catch |err| switch (err) {
1441 error.FileTooBig => unreachable, // can't happen for directories
1442 error.IsDir => unreachable, // we're providing O.DIRECTORY
1443 error.NoSpaceLeft => unreachable, // not providing O.CREAT
1444 error.PathAlreadyExists => unreachable, // not providing O.CREAT
1445 error.FileLocksNotSupported => unreachable, // locking folders is not supported
1446 error.WouldBlock => unreachable, // can't happen for directories
1447 error.FileBusy => unreachable, // can't happen for directories
1448 else => |e| return e,
1449 };
1450 return Dir{ .fd = fd };
1451}
1452
1453const MakeOpenDirAccessMaskWOptions = struct {
1454 no_follow: bool,
1455 create_disposition: u32,
1456};
1457
1458fn makeOpenDirAccessMaskW(self: Dir, sub_path_w: [*:0]const u16, access_mask: u32, flags: MakeOpenDirAccessMaskWOptions) OpenError!Dir {
1459 const w = std.os.windows;
1460
1461 var result = Dir{
1462 .fd = undefined,
1463 };
1464
1465 const path_len_bytes = @as(u16, @intCast(mem.sliceTo(sub_path_w, 0).len * 2));
1466 var nt_name = w.UNICODE_STRING{
1467 .Length = path_len_bytes,
1468 .MaximumLength = path_len_bytes,
1469 .Buffer = @constCast(sub_path_w),
1470 };
1471 var attr = w.OBJECT_ATTRIBUTES{
1472 .Length = @sizeOf(w.OBJECT_ATTRIBUTES),
1473 .RootDirectory = if (fs.path.isAbsoluteWindowsW(sub_path_w)) null else self.fd,
1474 .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
1475 .ObjectName = &nt_name,
1476 .SecurityDescriptor = null,
1477 .SecurityQualityOfService = null,
1478 };
1479 const open_reparse_point: w.DWORD = if (flags.no_follow) w.FILE_OPEN_REPARSE_POINT else 0x0;
1480 var io: w.IO_STATUS_BLOCK = undefined;
1481 const rc = w.ntdll.NtCreateFile(
1482 &result.fd,
1483 access_mask,
1484 &attr,
1485 &io,
1486 null,
1487 w.FILE_ATTRIBUTE_NORMAL,
1488 w.FILE_SHARE_READ | w.FILE_SHARE_WRITE,
1489 flags.create_disposition,
1490 w.FILE_DIRECTORY_FILE | w.FILE_SYNCHRONOUS_IO_NONALERT | w.FILE_OPEN_FOR_BACKUP_INTENT | open_reparse_point,
1491 null,
1492 0,
1493 );
1494
1495 switch (rc) {
1496 .SUCCESS => return result,
1497 .OBJECT_NAME_INVALID => return error.BadPathName,
1498 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
1499 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
1500 .NOT_A_DIRECTORY => return error.NotDir,
1501 // This can happen if the directory has 'List folder contents' permission set to 'Deny'
1502 // and the directory is trying to be opened for iteration.
1503 .ACCESS_DENIED => return error.AccessDenied,
1504 .INVALID_PARAMETER => unreachable,
1505 else => return w.unexpectedStatus(rc),
1506 }
1507}
1508
1509pub const DeleteFileError = posix.UnlinkError;
1510
1511/// Delete a file name and possibly the file it refers to, based on an open directory handle.
1512/// Asserts that the path parameter has no null bytes.
1513pub fn deleteFile(self: Dir, sub_path: []const u8) DeleteFileError!void {
1514 if (builtin.os.tag == .windows) {
1515 const sub_path_w = try std.os.windows.sliceToPrefixedFileW(self.fd, sub_path);
1516 return self.deleteFileW(sub_path_w.span());
1517 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
1518 posix.unlinkat(self.fd, sub_path, 0) catch |err| switch (err) {
1519 error.DirNotEmpty => unreachable, // not passing AT.REMOVEDIR
1520 else => |e| return e,
1521 };
1522 } else {
1523 const sub_path_c = try posix.toPosixPath(sub_path);
1524 return self.deleteFileZ(&sub_path_c);
1525 }
1526}
1527
1528/// Same as `deleteFile` except the parameter is null-terminated.
1529pub fn deleteFileZ(self: Dir, sub_path_c: [*:0]const u8) DeleteFileError!void {
1530 posix.unlinkatZ(self.fd, sub_path_c, 0) catch |err| switch (err) {
1531 error.DirNotEmpty => unreachable, // not passing AT.REMOVEDIR
1532 error.AccessDenied => |e| switch (builtin.os.tag) {
1533 // non-Linux POSIX systems return EPERM when trying to delete a directory, so
1534 // we need to handle that case specifically and translate the error
1535 .macos, .ios, .freebsd, .netbsd, .dragonfly, .openbsd, .solaris, .illumos => {
1536 // Don't follow symlinks to match unlinkat (which acts on symlinks rather than follows them)
1537 const fstat = posix.fstatatZ(self.fd, sub_path_c, posix.AT.SYMLINK_NOFOLLOW) catch return e;
1538 const is_dir = fstat.mode & posix.S.IFMT == posix.S.IFDIR;
1539 return if (is_dir) error.IsDir else e;
1540 },
1541 else => return e,
1542 },
1543 else => |e| return e,
1544 };
1545}
1546
1547/// Same as `deleteFile` except the parameter is WTF-16 encoded.
1548pub fn deleteFileW(self: Dir, sub_path_w: []const u16) DeleteFileError!void {
1549 posix.unlinkatW(self.fd, sub_path_w, 0) catch |err| switch (err) {
1550 error.DirNotEmpty => unreachable, // not passing AT.REMOVEDIR
1551 else => |e| return e,
1552 };
1553}
1554
1555pub const DeleteDirError = error{
1556 DirNotEmpty,
1557 FileNotFound,
1558 AccessDenied,
1559 FileBusy,
1560 FileSystem,
1561 SymLinkLoop,
1562 NameTooLong,
1563 NotDir,
1564 SystemResources,
1565 ReadOnlyFileSystem,
1566 InvalidUtf8,
1567 BadPathName,
1568 /// On Windows, `\\server` or `\\server\share` was not found.
1569 NetworkNotFound,
1570 Unexpected,
1571};
1572
1573/// Returns `error.DirNotEmpty` if the directory is not empty.
1574/// To delete a directory recursively, see `deleteTree`.
1575/// Asserts that the path parameter has no null bytes.
1576pub fn deleteDir(self: Dir, sub_path: []const u8) DeleteDirError!void {
1577 if (builtin.os.tag == .windows) {
1578 const sub_path_w = try std.os.windows.sliceToPrefixedFileW(self.fd, sub_path);
1579 return self.deleteDirW(sub_path_w.span());
1580 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
1581 posix.unlinkat(self.fd, sub_path, posix.AT.REMOVEDIR) catch |err| switch (err) {
1582 error.IsDir => unreachable, // not possible since we pass AT.REMOVEDIR
1583 else => |e| return e,
1584 };
1585 } else {
1586 const sub_path_c = try posix.toPosixPath(sub_path);
1587 return self.deleteDirZ(&sub_path_c);
1588 }
1589}
1590
1591/// Same as `deleteDir` except the parameter is null-terminated.
1592pub fn deleteDirZ(self: Dir, sub_path_c: [*:0]const u8) DeleteDirError!void {
1593 posix.unlinkatZ(self.fd, sub_path_c, posix.AT.REMOVEDIR) catch |err| switch (err) {
1594 error.IsDir => unreachable, // not possible since we pass AT.REMOVEDIR
1595 else => |e| return e,
1596 };
1597}
1598
1599/// Same as `deleteDir` except the parameter is UTF16LE, NT prefixed.
1600/// This function is Windows-only.
1601pub fn deleteDirW(self: Dir, sub_path_w: []const u16) DeleteDirError!void {
1602 posix.unlinkatW(self.fd, sub_path_w, posix.AT.REMOVEDIR) catch |err| switch (err) {
1603 error.IsDir => unreachable, // not possible since we pass AT.REMOVEDIR
1604 else => |e| return e,
1605 };
1606}
1607
1608pub const RenameError = posix.RenameError;
1609
1610/// Change the name or location of a file or directory.
1611/// If new_sub_path already exists, it will be replaced.
1612/// Renaming a file over an existing directory or a directory
1613/// over an existing file will fail with `error.IsDir` or `error.NotDir`
1614pub fn rename(self: Dir, old_sub_path: []const u8, new_sub_path: []const u8) RenameError!void {
1615 return posix.renameat(self.fd, old_sub_path, self.fd, new_sub_path);
1616}
1617
1618/// Same as `rename` except the parameters are null-terminated.
1619pub fn renameZ(self: Dir, old_sub_path_z: [*:0]const u8, new_sub_path_z: [*:0]const u8) RenameError!void {
1620 return posix.renameatZ(self.fd, old_sub_path_z, self.fd, new_sub_path_z);
1621}
1622
1623/// Same as `rename` except the parameters are UTF16LE, NT prefixed.
1624/// This function is Windows-only.
1625pub fn renameW(self: Dir, old_sub_path_w: []const u16, new_sub_path_w: []const u16) RenameError!void {
1626 return posix.renameatW(self.fd, old_sub_path_w, self.fd, new_sub_path_w);
1627}
1628
1629/// Use with `Dir.symLink` and `symLinkAbsolute` to specify whether the symlink
1630/// will point to a file or a directory. This value is ignored on all hosts
1631/// except Windows where creating symlinks to different resource types, requires
1632/// different flags. By default, `symLinkAbsolute` is assumed to point to a file.
1633pub const SymLinkFlags = struct {
1634 is_directory: bool = false,
1635};
1636
1637/// Creates a symbolic link named `sym_link_path` which contains the string `target_path`.
1638/// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent
1639/// one; the latter case is known as a dangling link.
1640/// If `sym_link_path` exists, it will not be overwritten.
1641pub fn symLink(
1642 self: Dir,
1643 target_path: []const u8,
1644 sym_link_path: []const u8,
1645 flags: SymLinkFlags,
1646) !void {
1647 if (builtin.os.tag == .wasi and !builtin.link_libc) {
1648 return self.symLinkWasi(target_path, sym_link_path, flags);
1649 }
1650 if (builtin.os.tag == .windows) {
1651 // Target path does not use sliceToPrefixedFileW because certain paths
1652 // are handled differently when creating a symlink than they would be
1653 // when converting to an NT namespaced path. CreateSymbolicLink in
1654 // symLinkW will handle the necessary conversion.
1655 var target_path_w: std.os.windows.PathSpace = undefined;
1656 target_path_w.len = try std.unicode.utf8ToUtf16Le(&target_path_w.data, target_path);
1657 target_path_w.data[target_path_w.len] = 0;
1658 const sym_link_path_w = try std.os.windows.sliceToPrefixedFileW(self.fd, sym_link_path);
1659 return self.symLinkW(target_path_w.span(), sym_link_path_w.span(), flags);
1660 }
1661 const target_path_c = try posix.toPosixPath(target_path);
1662 const sym_link_path_c = try posix.toPosixPath(sym_link_path);
1663 return self.symLinkZ(&target_path_c, &sym_link_path_c, flags);
1664}
1665
1666/// WASI-only. Same as `symLink` except targeting WASI.
1667pub fn symLinkWasi(
1668 self: Dir,
1669 target_path: []const u8,
1670 sym_link_path: []const u8,
1671 _: SymLinkFlags,
1672) !void {
1673 return posix.symlinkat(target_path, self.fd, sym_link_path);
1674}
1675
1676/// Same as `symLink`, except the pathname parameters are null-terminated.
1677pub fn symLinkZ(
1678 self: Dir,
1679 target_path_c: [*:0]const u8,
1680 sym_link_path_c: [*:0]const u8,
1681 flags: SymLinkFlags,
1682) !void {
1683 if (builtin.os.tag == .windows) {
1684 const target_path_w = try std.os.windows.cStrToPrefixedFileW(self.fd, target_path_c);
1685 const sym_link_path_w = try std.os.windows.cStrToPrefixedFileW(self.fd, sym_link_path_c);
1686 return self.symLinkW(target_path_w.span(), sym_link_path_w.span(), flags);
1687 }
1688 return posix.symlinkatZ(target_path_c, self.fd, sym_link_path_c);
1689}
1690
1691/// Windows-only. Same as `symLink` except the pathname parameters
1692/// are null-terminated, WTF16 encoded.
1693pub fn symLinkW(
1694 self: Dir,
1695 /// WTF-16, does not need to be NT-prefixed. The NT-prefixing
1696 /// of this path is handled by CreateSymbolicLink.
1697 target_path_w: [:0]const u16,
1698 /// WTF-16, must be NT-prefixed or relative
1699 sym_link_path_w: []const u16,
1700 flags: SymLinkFlags,
1701) !void {
1702 return std.os.windows.CreateSymbolicLink(self.fd, sym_link_path_w, target_path_w, flags.is_directory);
1703}
1704
1705pub const ReadLinkError = posix.ReadLinkError;
1706
1707/// Read value of a symbolic link.
1708/// The return value is a slice of `buffer`, from index `0`.
1709/// Asserts that the path parameter has no null bytes.
1710pub fn readLink(self: Dir, sub_path: []const u8, buffer: []u8) ReadLinkError![]u8 {
1711 if (builtin.os.tag == .wasi and !builtin.link_libc) {
1712 return self.readLinkWasi(sub_path, buffer);
1713 }
1714 if (builtin.os.tag == .windows) {
1715 const sub_path_w = try std.os.windows.sliceToPrefixedFileW(self.fd, sub_path);
1716 return self.readLinkW(sub_path_w.span(), buffer);
1717 }
1718 const sub_path_c = try posix.toPosixPath(sub_path);
1719 return self.readLinkZ(&sub_path_c, buffer);
1720}
1721
1722/// WASI-only. Same as `readLink` except targeting WASI.
1723pub fn readLinkWasi(self: Dir, sub_path: []const u8, buffer: []u8) ![]u8 {
1724 return posix.readlinkat(self.fd, sub_path, buffer);
1725}
1726
1727/// Same as `readLink`, except the `pathname` parameter is null-terminated.
1728pub fn readLinkZ(self: Dir, sub_path_c: [*:0]const u8, buffer: []u8) ![]u8 {
1729 if (builtin.os.tag == .windows) {
1730 const sub_path_w = try std.os.windows.cStrToPrefixedFileW(self.fd, sub_path_c);
1731 return self.readLinkW(sub_path_w.span(), buffer);
1732 }
1733 return posix.readlinkatZ(self.fd, sub_path_c, buffer);
1734}
1735
1736/// Windows-only. Same as `readLink` except the pathname parameter
1737/// is null-terminated, WTF16 encoded.
1738pub fn readLinkW(self: Dir, sub_path_w: []const u16, buffer: []u8) ![]u8 {
1739 return std.os.windows.ReadLink(self.fd, sub_path_w, buffer);
1740}
1741
1742/// Read all of file contents using a preallocated buffer.
1743/// The returned slice has the same pointer as `buffer`. If the length matches `buffer.len`
1744/// the situation is ambiguous. It could either mean that the entire file was read, and
1745/// it exactly fits the buffer, or it could mean the buffer was not big enough for the
1746/// entire file.
1747pub fn readFile(self: Dir, file_path: []const u8, buffer: []u8) ![]u8 {
1748 var file = try self.openFile(file_path, .{});
1749 defer file.close();
1750
1751 const end_index = try file.readAll(buffer);
1752 return buffer[0..end_index];
1753}
1754
1755/// On success, caller owns returned buffer.
1756/// If the file is larger than `max_bytes`, returns `error.FileTooBig`.
1757pub fn readFileAlloc(self: Dir, allocator: mem.Allocator, file_path: []const u8, max_bytes: usize) ![]u8 {
1758 return self.readFileAllocOptions(allocator, file_path, max_bytes, null, @alignOf(u8), null);
1759}
1760
1761/// On success, caller owns returned buffer.
1762/// If the file is larger than `max_bytes`, returns `error.FileTooBig`.
1763/// If `size_hint` is specified the initial buffer size is calculated using
1764/// that value, otherwise the effective file size is used instead.
1765/// Allows specifying alignment and a sentinel value.
1766pub fn readFileAllocOptions(
1767 self: Dir,
1768 allocator: mem.Allocator,
1769 file_path: []const u8,
1770 max_bytes: usize,
1771 size_hint: ?usize,
1772 comptime alignment: u29,
1773 comptime optional_sentinel: ?u8,
1774) !(if (optional_sentinel) |s| [:s]align(alignment) u8 else []align(alignment) u8) {
1775 var file = try self.openFile(file_path, .{});
1776 defer file.close();
1777
1778 // If the file size doesn't fit a usize it'll be certainly greater than
1779 // `max_bytes`
1780 const stat_size = size_hint orelse std.math.cast(usize, try file.getEndPos()) orelse
1781 return error.FileTooBig;
1782
1783 return file.readToEndAllocOptions(allocator, max_bytes, stat_size, alignment, optional_sentinel);
1784}
1785
1786pub const DeleteTreeError = error{
1787 InvalidHandle,
1788 AccessDenied,
1789 FileTooBig,
1790 SymLinkLoop,
1791 ProcessFdQuotaExceeded,
1792 NameTooLong,
1793 SystemFdQuotaExceeded,
1794 NoDevice,
1795 SystemResources,
1796 ReadOnlyFileSystem,
1797 FileSystem,
1798 FileBusy,
1799 DeviceBusy,
1800
1801 /// One of the path components was not a directory.
1802 /// This error is unreachable if `sub_path` does not contain a path separator.
1803 NotDir,
1804
1805 /// On Windows, file paths must be valid Unicode.
1806 InvalidUtf8,
1807
1808 /// On Windows, file paths cannot contain these characters:
1809 /// '/', '*', '?', '"', '<', '>', '|'
1810 BadPathName,
1811
1812 /// On Windows, `\\server` or `\\server\share` was not found.
1813 NetworkNotFound,
1814} || posix.UnexpectedError;
1815
1816/// Whether `full_path` describes a symlink, file, or directory, this function
1817/// removes it. If it cannot be removed because it is a non-empty directory,
1818/// this function recursively removes its entries and then tries again.
1819/// This operation is not atomic on most file systems.
1820pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void {
1821 var initial_iterable_dir = (try self.deleteTreeOpenInitialSubpath(sub_path, .file)) orelse return;
1822
1823 const StackItem = struct {
1824 name: []const u8,
1825 parent_dir: Dir,
1826 iter: Dir.Iterator,
1827
1828 fn closeAll(items: []@This()) void {
1829 for (items) |*item| item.iter.dir.close();
1830 }
1831 };
1832
1833 var stack_buffer: [16]StackItem = undefined;
1834 var stack = std.ArrayListUnmanaged(StackItem).initBuffer(&stack_buffer);
1835 defer StackItem.closeAll(stack.items);
1836
1837 stack.appendAssumeCapacity(.{
1838 .name = sub_path,
1839 .parent_dir = self,
1840 .iter = initial_iterable_dir.iterateAssumeFirstIteration(),
1841 });
1842
1843 process_stack: while (stack.items.len != 0) {
1844 var top = &stack.items[stack.items.len - 1];
1845 while (try top.iter.next()) |entry| {
1846 var treat_as_dir = entry.kind == .directory;
1847 handle_entry: while (true) {
1848 if (treat_as_dir) {
1849 if (stack.unusedCapacitySlice().len >= 1) {
1850 var iterable_dir = top.iter.dir.openDir(entry.name, .{
1851 .no_follow = true,
1852 .iterate = true,
1853 }) catch |err| switch (err) {
1854 error.NotDir => {
1855 treat_as_dir = false;
1856 continue :handle_entry;
1857 },
1858 error.FileNotFound => {
1859 // That's fine, we were trying to remove this directory anyway.
1860 break :handle_entry;
1861 },
1862
1863 error.InvalidHandle,
1864 error.AccessDenied,
1865 error.SymLinkLoop,
1866 error.ProcessFdQuotaExceeded,
1867 error.NameTooLong,
1868 error.SystemFdQuotaExceeded,
1869 error.NoDevice,
1870 error.SystemResources,
1871 error.Unexpected,
1872 error.InvalidUtf8,
1873 error.BadPathName,
1874 error.NetworkNotFound,
1875 error.DeviceBusy,
1876 => |e| return e,
1877 };
1878 stack.appendAssumeCapacity(.{
1879 .name = entry.name,
1880 .parent_dir = top.iter.dir,
1881 .iter = iterable_dir.iterateAssumeFirstIteration(),
1882 });
1883 continue :process_stack;
1884 } else {
1885 try top.iter.dir.deleteTreeMinStackSizeWithKindHint(entry.name, entry.kind);
1886 break :handle_entry;
1887 }
1888 } else {
1889 if (top.iter.dir.deleteFile(entry.name)) {
1890 break :handle_entry;
1891 } else |err| switch (err) {
1892 error.FileNotFound => break :handle_entry,
1893
1894 // Impossible because we do not pass any path separators.
1895 error.NotDir => unreachable,
1896
1897 error.IsDir => {
1898 treat_as_dir = true;
1899 continue :handle_entry;
1900 },
1901
1902 error.AccessDenied,
1903 error.InvalidUtf8,
1904 error.SymLinkLoop,
1905 error.NameTooLong,
1906 error.SystemResources,
1907 error.ReadOnlyFileSystem,
1908 error.FileSystem,
1909 error.FileBusy,
1910 error.BadPathName,
1911 error.NetworkNotFound,
1912 error.Unexpected,
1913 => |e| return e,
1914 }
1915 }
1916 }
1917 }
1918
1919 // On Windows, we can't delete until the dir's handle has been closed, so
1920 // close it before we try to delete.
1921 top.iter.dir.close();
1922
1923 // In order to avoid double-closing the directory when cleaning up
1924 // the stack in the case of an error, we save the relevant portions and
1925 // pop the value from the stack.
1926 const parent_dir = top.parent_dir;
1927 const name = top.name;
1928 stack.items.len -= 1;
1929
1930 var need_to_retry: bool = false;
1931 parent_dir.deleteDir(name) catch |err| switch (err) {
1932 error.FileNotFound => {},
1933 error.DirNotEmpty => need_to_retry = true,
1934 else => |e| return e,
1935 };
1936
1937 if (need_to_retry) {
1938 // Since we closed the handle that the previous iterator used, we
1939 // need to re-open the dir and re-create the iterator.
1940 var iterable_dir = iterable_dir: {
1941 var treat_as_dir = true;
1942 handle_entry: while (true) {
1943 if (treat_as_dir) {
1944 break :iterable_dir parent_dir.openDir(name, .{
1945 .no_follow = true,
1946 .iterate = true,
1947 }) catch |err| switch (err) {
1948 error.NotDir => {
1949 treat_as_dir = false;
1950 continue :handle_entry;
1951 },
1952 error.FileNotFound => {
1953 // That's fine, we were trying to remove this directory anyway.
1954 continue :process_stack;
1955 },
1956
1957 error.InvalidHandle,
1958 error.AccessDenied,
1959 error.SymLinkLoop,
1960 error.ProcessFdQuotaExceeded,
1961 error.NameTooLong,
1962 error.SystemFdQuotaExceeded,
1963 error.NoDevice,
1964 error.SystemResources,
1965 error.Unexpected,
1966 error.InvalidUtf8,
1967 error.BadPathName,
1968 error.NetworkNotFound,
1969 error.DeviceBusy,
1970 => |e| return e,
1971 };
1972 } else {
1973 if (parent_dir.deleteFile(name)) {
1974 continue :process_stack;
1975 } else |err| switch (err) {
1976 error.FileNotFound => continue :process_stack,
1977
1978 // Impossible because we do not pass any path separators.
1979 error.NotDir => unreachable,
1980
1981 error.IsDir => {
1982 treat_as_dir = true;
1983 continue :handle_entry;
1984 },
1985
1986 error.AccessDenied,
1987 error.InvalidUtf8,
1988 error.SymLinkLoop,
1989 error.NameTooLong,
1990 error.SystemResources,
1991 error.ReadOnlyFileSystem,
1992 error.FileSystem,
1993 error.FileBusy,
1994 error.BadPathName,
1995 error.NetworkNotFound,
1996 error.Unexpected,
1997 => |e| return e,
1998 }
1999 }
2000 }
2001 };
2002 // We know there is room on the stack since we are just re-adding
2003 // the StackItem that we previously popped.
2004 stack.appendAssumeCapacity(.{
2005 .name = name,
2006 .parent_dir = parent_dir,
2007 .iter = iterable_dir.iterateAssumeFirstIteration(),
2008 });
2009 continue :process_stack;
2010 }
2011 }
2012}
2013
2014/// Like `deleteTree`, but only keeps one `Iterator` active at a time to minimize the function's stack size.
2015/// This is slower than `deleteTree` but uses less stack space.
2016pub fn deleteTreeMinStackSize(self: Dir, sub_path: []const u8) DeleteTreeError!void {
2017 return self.deleteTreeMinStackSizeWithKindHint(sub_path, .file);
2018}
2019
2020fn deleteTreeMinStackSizeWithKindHint(self: Dir, sub_path: []const u8, kind_hint: File.Kind) DeleteTreeError!void {
2021 start_over: while (true) {
2022 var dir = (try self.deleteTreeOpenInitialSubpath(sub_path, kind_hint)) orelse return;
2023 var cleanup_dir_parent: ?Dir = null;
2024 defer if (cleanup_dir_parent) |*d| d.close();
2025
2026 var cleanup_dir = true;
2027 defer if (cleanup_dir) dir.close();
2028
2029 // Valid use of MAX_PATH_BYTES because dir_name_buf will only
2030 // ever store a single path component that was returned from the
2031 // filesystem.
2032 var dir_name_buf: [fs.MAX_PATH_BYTES]u8 = undefined;
2033 var dir_name: []const u8 = sub_path;
2034
2035 // Here we must avoid recursion, in order to provide O(1) memory guarantee of this function.
2036 // Go through each entry and if it is not a directory, delete it. If it is a directory,
2037 // open it, and close the original directory. Repeat. Then start the entire operation over.
2038
2039 scan_dir: while (true) {
2040 var dir_it = dir.iterateAssumeFirstIteration();
2041 dir_it: while (try dir_it.next()) |entry| {
2042 var treat_as_dir = entry.kind == .directory;
2043 handle_entry: while (true) {
2044 if (treat_as_dir) {
2045 const new_dir = dir.openDir(entry.name, .{
2046 .no_follow = true,
2047 .iterate = true,
2048 }) catch |err| switch (err) {
2049 error.NotDir => {
2050 treat_as_dir = false;
2051 continue :handle_entry;
2052 },
2053 error.FileNotFound => {
2054 // That's fine, we were trying to remove this directory anyway.
2055 continue :dir_it;
2056 },
2057
2058 error.InvalidHandle,
2059 error.AccessDenied,
2060 error.SymLinkLoop,
2061 error.ProcessFdQuotaExceeded,
2062 error.NameTooLong,
2063 error.SystemFdQuotaExceeded,
2064 error.NoDevice,
2065 error.SystemResources,
2066 error.Unexpected,
2067 error.InvalidUtf8,
2068 error.BadPathName,
2069 error.NetworkNotFound,
2070 error.DeviceBusy,
2071 => |e| return e,
2072 };
2073 if (cleanup_dir_parent) |*d| d.close();
2074 cleanup_dir_parent = dir;
2075 dir = new_dir;
2076 const result = dir_name_buf[0..entry.name.len];
2077 @memcpy(result, entry.name);
2078 dir_name = result;
2079 continue :scan_dir;
2080 } else {
2081 if (dir.deleteFile(entry.name)) {
2082 continue :dir_it;
2083 } else |err| switch (err) {
2084 error.FileNotFound => continue :dir_it,
2085
2086 // Impossible because we do not pass any path separators.
2087 error.NotDir => unreachable,
2088
2089 error.IsDir => {
2090 treat_as_dir = true;
2091 continue :handle_entry;
2092 },
2093
2094 error.AccessDenied,
2095 error.InvalidUtf8,
2096 error.SymLinkLoop,
2097 error.NameTooLong,
2098 error.SystemResources,
2099 error.ReadOnlyFileSystem,
2100 error.FileSystem,
2101 error.FileBusy,
2102 error.BadPathName,
2103 error.NetworkNotFound,
2104 error.Unexpected,
2105 => |e| return e,
2106 }
2107 }
2108 }
2109 }
2110 // Reached the end of the directory entries, which means we successfully deleted all of them.
2111 // Now to remove the directory itself.
2112 dir.close();
2113 cleanup_dir = false;
2114
2115 if (cleanup_dir_parent) |d| {
2116 d.deleteDir(dir_name) catch |err| switch (err) {
2117 // These two things can happen due to file system race conditions.
2118 error.FileNotFound, error.DirNotEmpty => continue :start_over,
2119 else => |e| return e,
2120 };
2121 continue :start_over;
2122 } else {
2123 self.deleteDir(sub_path) catch |err| switch (err) {
2124 error.FileNotFound => return,
2125 error.DirNotEmpty => continue :start_over,
2126 else => |e| return e,
2127 };
2128 return;
2129 }
2130 }
2131 }
2132}
2133
2134/// On successful delete, returns null.
2135fn deleteTreeOpenInitialSubpath(self: Dir, sub_path: []const u8, kind_hint: File.Kind) !?Dir {
2136 return iterable_dir: {
2137 // Treat as a file by default
2138 var treat_as_dir = kind_hint == .directory;
2139
2140 handle_entry: while (true) {
2141 if (treat_as_dir) {
2142 break :iterable_dir self.openDir(sub_path, .{
2143 .no_follow = true,
2144 .iterate = true,
2145 }) catch |err| switch (err) {
2146 error.NotDir => {
2147 treat_as_dir = false;
2148 continue :handle_entry;
2149 },
2150 error.FileNotFound => {
2151 // That's fine, we were trying to remove this directory anyway.
2152 return null;
2153 },
2154
2155 error.InvalidHandle,
2156 error.AccessDenied,
2157 error.SymLinkLoop,
2158 error.ProcessFdQuotaExceeded,
2159 error.NameTooLong,
2160 error.SystemFdQuotaExceeded,
2161 error.NoDevice,
2162 error.SystemResources,
2163 error.Unexpected,
2164 error.InvalidUtf8,
2165 error.BadPathName,
2166 error.DeviceBusy,
2167 error.NetworkNotFound,
2168 => |e| return e,
2169 };
2170 } else {
2171 if (self.deleteFile(sub_path)) {
2172 return null;
2173 } else |err| switch (err) {
2174 error.FileNotFound => return null,
2175
2176 error.IsDir => {
2177 treat_as_dir = true;
2178 continue :handle_entry;
2179 },
2180
2181 error.AccessDenied,
2182 error.InvalidUtf8,
2183 error.SymLinkLoop,
2184 error.NameTooLong,
2185 error.SystemResources,
2186 error.ReadOnlyFileSystem,
2187 error.NotDir,
2188 error.FileSystem,
2189 error.FileBusy,
2190 error.BadPathName,
2191 error.NetworkNotFound,
2192 error.Unexpected,
2193 => |e| return e,
2194 }
2195 }
2196 }
2197 };
2198}
2199
2200pub const WriteFileError = File.WriteError || File.OpenError;
2201
2202/// Deprecated: use `writeFile2`.
2203pub fn writeFile(self: Dir, sub_path: []const u8, data: []const u8) WriteFileError!void {
2204 return writeFile2(self, .{
2205 .sub_path = sub_path,
2206 .data = data,
2207 .flags = .{},
2208 });
2209}
2210
2211pub const WriteFileOptions = struct {
2212 sub_path: []const u8,
2213 data: []const u8,
2214 flags: File.CreateFlags = .{},
2215};
2216
2217/// Writes content to the file system, using the file creation flags provided.
2218pub fn writeFile2(self: Dir, options: WriteFileOptions) WriteFileError!void {
2219 var file = try self.createFile(options.sub_path, options.flags);
2220 defer file.close();
2221 try file.writeAll(options.data);
2222}
2223
2224pub const AccessError = posix.AccessError;
2225
2226/// Test accessing `path`.
2227/// `path` is UTF-8-encoded.
2228/// Be careful of Time-Of-Check-Time-Of-Use race conditions when using this function.
2229/// For example, instead of testing if a file exists and then opening it, just
2230/// open it and handle the error for file not found.
2231pub fn access(self: Dir, sub_path: []const u8, flags: File.OpenFlags) AccessError!void {
2232 if (builtin.os.tag == .windows) {
2233 const sub_path_w = std.os.windows.sliceToPrefixedFileW(self.fd, sub_path) catch |err| switch (err) {
2234 error.AccessDenied => return error.PermissionDenied,
2235 else => |e| return e,
2236 };
2237 return self.accessW(sub_path_w.span().ptr, flags);
2238 }
2239 const path_c = try posix.toPosixPath(sub_path);
2240 return self.accessZ(&path_c, flags);
2241}
2242
2243/// Same as `access` except the path parameter is null-terminated.
2244pub fn accessZ(self: Dir, sub_path: [*:0]const u8, flags: File.OpenFlags) AccessError!void {
2245 if (builtin.os.tag == .windows) {
2246 const sub_path_w = std.os.windows.cStrToPrefixedFileW(self.fd, sub_path) catch |err| switch (err) {
2247 error.AccessDenied => return error.PermissionDenied,
2248 else => |e| return e,
2249 };
2250 return self.accessW(sub_path_w.span().ptr, flags);
2251 }
2252 const os_mode = switch (flags.mode) {
2253 .read_only => @as(u32, posix.F_OK),
2254 .write_only => @as(u32, posix.W_OK),
2255 .read_write => @as(u32, posix.R_OK | posix.W_OK),
2256 };
2257 const result = if (fs.need_async_thread and flags.intended_io_mode != .blocking)
2258 std.event.Loop.instance.?.faccessatZ(self.fd, sub_path, os_mode, 0)
2259 else
2260 posix.faccessatZ(self.fd, sub_path, os_mode, 0);
2261 return result;
2262}
2263
2264/// Same as `access` except asserts the target OS is Windows and the path parameter is
2265/// * WTF-16 encoded
2266/// * null-terminated
2267/// * NtDll prefixed
2268/// TODO currently this ignores `flags`.
2269pub fn accessW(self: Dir, sub_path_w: [*:0]const u16, flags: File.OpenFlags) AccessError!void {
2270 _ = flags;
2271 return posix.faccessatW(self.fd, sub_path_w, 0, 0);
2272}
2273
2274pub const CopyFileOptions = struct {
2275 /// When this is `null` the mode is copied from the source file.
2276 override_mode: ?File.Mode = null,
2277};
2278
2279pub const PrevStatus = enum {
2280 stale,
2281 fresh,
2282};
2283
2284/// Check the file size, mtime, and mode of `source_path` and `dest_path`. If they are equal, does nothing.
2285/// Otherwise, atomically copies `source_path` to `dest_path`. The destination file gains the mtime,
2286/// atime, and mode of the source file so that the next call to `updateFile` will not need a copy.
2287/// Returns the previous status of the file before updating.
2288/// If any of the directories do not exist for dest_path, they are created.
2289pub fn updateFile(
2290 source_dir: Dir,
2291 source_path: []const u8,
2292 dest_dir: Dir,
2293 dest_path: []const u8,
2294 options: CopyFileOptions,
2295) !PrevStatus {
2296 var src_file = try source_dir.openFile(source_path, .{});
2297 defer src_file.close();
2298
2299 const src_stat = try src_file.stat();
2300 const actual_mode = options.override_mode orelse src_stat.mode;
2301 check_dest_stat: {
2302 const dest_stat = blk: {
2303 var dest_file = dest_dir.openFile(dest_path, .{}) catch |err| switch (err) {
2304 error.FileNotFound => break :check_dest_stat,
2305 else => |e| return e,
2306 };
2307 defer dest_file.close();
2308
2309 break :blk try dest_file.stat();
2310 };
2311
2312 if (src_stat.size == dest_stat.size and
2313 src_stat.mtime == dest_stat.mtime and
2314 actual_mode == dest_stat.mode)
2315 {
2316 return PrevStatus.fresh;
2317 }
2318 }
2319
2320 if (fs.path.dirname(dest_path)) |dirname| {
2321 try dest_dir.makePath(dirname);
2322 }
2323
2324 var atomic_file = try dest_dir.atomicFile(dest_path, .{ .mode = actual_mode });
2325 defer atomic_file.deinit();
2326
2327 try atomic_file.file.writeFileAll(src_file, .{ .in_len = src_stat.size });
2328 try atomic_file.file.updateTimes(src_stat.atime, src_stat.mtime);
2329 try atomic_file.finish();
2330 return PrevStatus.stale;
2331}
2332
2333pub const CopyFileError = File.OpenError || File.StatError ||
2334 AtomicFile.InitError || CopyFileRawError || AtomicFile.FinishError;
2335
2336/// Guaranteed to be atomic.
2337/// On Linux, until https://patchwork.kernel.org/patch/9636735/ is merged and readily available,
2338/// there is a possibility of power loss or application termination leaving temporary files present
2339/// in the same directory as dest_path.
2340pub fn copyFile(
2341 source_dir: Dir,
2342 source_path: []const u8,
2343 dest_dir: Dir,
2344 dest_path: []const u8,
2345 options: CopyFileOptions,
2346) CopyFileError!void {
2347 var in_file = try source_dir.openFile(source_path, .{});
2348 defer in_file.close();
2349
2350 var size: ?u64 = null;
2351 const mode = options.override_mode orelse blk: {
2352 const st = try in_file.stat();
2353 size = st.size;
2354 break :blk st.mode;
2355 };
2356
2357 var atomic_file = try dest_dir.atomicFile(dest_path, .{ .mode = mode });
2358 defer atomic_file.deinit();
2359
2360 try copy_file(in_file.handle, atomic_file.file.handle, size);
2361 try atomic_file.finish();
2362}
2363
2364const CopyFileRawError = error{SystemResources} || posix.CopyFileRangeError || posix.SendFileError;
2365
2366// Transfer all the data between two file descriptors in the most efficient way.
2367// The copy starts at offset 0, the initial offsets are preserved.
2368// No metadata is transferred over.
2369fn copy_file(fd_in: posix.fd_t, fd_out: posix.fd_t, maybe_size: ?u64) CopyFileRawError!void {
2370 if (comptime builtin.target.isDarwin()) {
2371 const rc = posix.system.fcopyfile(fd_in, fd_out, null, posix.system.COPYFILE_DATA);
2372 switch (posix.errno(rc)) {
2373 .SUCCESS => return,
2374 .INVAL => unreachable,
2375 .NOMEM => return error.SystemResources,
2376 // The source file is not a directory, symbolic link, or regular file.
2377 // Try with the fallback path before giving up.
2378 .OPNOTSUPP => {},
2379 else => |err| return posix.unexpectedErrno(err),
2380 }
2381 }
2382
2383 if (builtin.os.tag == .linux) {
2384 // Try copy_file_range first as that works at the FS level and is the
2385 // most efficient method (if available).
2386 var offset: u64 = 0;
2387 cfr_loop: while (true) {
2388 // The kernel checks the u64 value `offset+count` for overflow, use
2389 // a 32 bit value so that the syscall won't return EINVAL except for
2390 // impossibly large files (> 2^64-1 - 2^32-1).
2391 const amt = try posix.copy_file_range(fd_in, offset, fd_out, offset, std.math.maxInt(u32), 0);
2392 // Terminate as soon as we have copied size bytes or no bytes
2393 if (maybe_size) |s| {
2394 if (s == amt) break :cfr_loop;
2395 }
2396 if (amt == 0) break :cfr_loop;
2397 offset += amt;
2398 }
2399 return;
2400 }
2401
2402 // Sendfile is a zero-copy mechanism iff the OS supports it, otherwise the
2403 // fallback code will copy the contents chunk by chunk.
2404 const empty_iovec = [0]posix.iovec_const{};
2405 var offset: u64 = 0;
2406 sendfile_loop: while (true) {
2407 const amt = try posix.sendfile(fd_out, fd_in, offset, 0, &empty_iovec, &empty_iovec, 0);
2408 // Terminate as soon as we have copied size bytes or no bytes
2409 if (maybe_size) |s| {
2410 if (s == amt) break :sendfile_loop;
2411 }
2412 if (amt == 0) break :sendfile_loop;
2413 offset += amt;
2414 }
2415}
2416
2417pub const AtomicFileOptions = struct {
2418 mode: File.Mode = File.default_mode,
2419};
2420
2421/// Directly access the `.file` field, and then call `AtomicFile.finish`
2422/// to atomically replace `dest_path` with contents.
2423/// Always call `AtomicFile.deinit` to clean up, regardless of whether `AtomicFile.finish` succeeded.
2424/// `dest_path` must remain valid until `AtomicFile.deinit` is called.
2425pub fn atomicFile(self: Dir, dest_path: []const u8, options: AtomicFileOptions) !AtomicFile {
2426 if (fs.path.dirname(dest_path)) |dirname| {
2427 const dir = try self.openDir(dirname, .{});
2428 return AtomicFile.init(fs.path.basename(dest_path), options.mode, dir, true);
2429 } else {
2430 return AtomicFile.init(dest_path, options.mode, self, false);
2431 }
2432}
2433
2434pub const Stat = File.Stat;
2435pub const StatError = File.StatError;
2436
2437pub fn stat(self: Dir) StatError!Stat {
2438 const file: File = .{
2439 .handle = self.fd,
2440 .capable_io_mode = .blocking,
2441 };
2442 return file.stat();
2443}
2444
2445pub const StatFileError = File.OpenError || File.StatError || posix.FStatAtError;
2446
2447/// Returns metadata for a file inside the directory.
2448///
2449/// On Windows, this requires three syscalls. On other operating systems, it
2450/// only takes one.
2451///
2452/// Symlinks are followed.
2453///
2454/// `sub_path` may be absolute, in which case `self` is ignored.
2455pub fn statFile(self: Dir, sub_path: []const u8) StatFileError!Stat {
2456 if (builtin.os.tag == .windows) {
2457 var file = try self.openFile(sub_path, .{});
2458 defer file.close();
2459 return file.stat();
2460 }
2461 if (builtin.os.tag == .wasi and !builtin.link_libc) {
2462 const st = try posix.fstatatWasi(self.fd, sub_path, posix.wasi.LOOKUP_SYMLINK_FOLLOW);
2463 return Stat.fromSystem(st);
2464 }
2465 const st = try posix.fstatat(self.fd, sub_path, 0);
2466 return Stat.fromSystem(st);
2467}
2468
2469pub const ChmodError = File.ChmodError;
2470
2471/// Changes the mode of the directory.
2472/// The process must have the correct privileges in order to do this
2473/// successfully, or must have the effective user ID matching the owner
2474/// of the directory. Additionally, the directory must have been opened
2475/// with `OpenDirOptions{ .iterate = true }`.
2476pub fn chmod(self: Dir, new_mode: File.Mode) ChmodError!void {
2477 const file: File = .{
2478 .handle = self.fd,
2479 .capable_io_mode = .blocking,
2480 };
2481 try file.chmod(new_mode);
2482}
2483
2484/// Changes the owner and group of the directory.
2485/// The process must have the correct privileges in order to do this
2486/// successfully. The group may be changed by the owner of the directory to
2487/// any group of which the owner is a member. Additionally, the directory
2488/// must have been opened with `OpenDirOptions{ .iterate = true }`. If the
2489/// owner or group is specified as `null`, the ID is not changed.
2490pub fn chown(self: Dir, owner: ?File.Uid, group: ?File.Gid) ChownError!void {
2491 const file: File = .{
2492 .handle = self.fd,
2493 .capable_io_mode = .blocking,
2494 };
2495 try file.chown(owner, group);
2496}
2497
2498pub const ChownError = File.ChownError;
2499
2500const Permissions = File.Permissions;
2501pub const SetPermissionsError = File.SetPermissionsError;
2502
2503/// Sets permissions according to the provided `Permissions` struct.
2504/// This method is *NOT* available on WASI
2505pub fn setPermissions(self: Dir, permissions: Permissions) SetPermissionsError!void {
2506 const file: File = .{
2507 .handle = self.fd,
2508 .capable_io_mode = .blocking,
2509 };
2510 try file.setPermissions(permissions);
2511}
2512
2513const Metadata = File.Metadata;
2514pub const MetadataError = File.MetadataError;
2515
2516/// Returns a `Metadata` struct, representing the permissions on the directory
2517pub fn metadata(self: Dir) MetadataError!Metadata {
2518 const file: File = .{
2519 .handle = self.fd,
2520 .capable_io_mode = .blocking,
2521 };
2522 return try file.metadata();
2523}
2524
2525const Dir = @This();
2526const builtin = @import("builtin");
2527const std = @import("../std.zig");
2528const File = std.fs.File;
2529const AtomicFile = std.fs.AtomicFile;
2530const posix = std.os;
2531const mem = std.mem;
2532const fs = std.fs;
2533const Allocator = std.mem.Allocator;
lib/std/fs/test.zig+2-2
......@@ -1486,7 +1486,7 @@ test ". and .. in fs.Dir functions" {
14861486
14871487 try ctx.dir.writeFile(update_path, "something");
14881488 const prev_status = try ctx.dir.updateFile(file_path, ctx.dir, update_path, .{});
1489 try testing.expectEqual(fs.PrevStatus.stale, prev_status);
1489 try testing.expectEqual(fs.Dir.PrevStatus.stale, prev_status);
14901490
14911491 try ctx.dir.deleteDir(subdir_path);
14921492 }
......@@ -1532,7 +1532,7 @@ test ". and .. in absolute functions" {
15321532 try update_file.writeAll("something");
15331533 update_file.close();
15341534 const prev_status = try fs.updateFileAbsolute(created_file_path, update_file_path, .{});
1535 try testing.expectEqual(fs.PrevStatus.stale, prev_status);
1535 try testing.expectEqual(fs.Dir.PrevStatus.stale, prev_status);
15361536
15371537 try fs.deleteDirAbsolute(subdir_path);
15381538}