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