const Iterator: type = switch (builtin.os.tag) { .macos, .ios, .freebsd, .netbsd, .dragonfly, .openbsd, .solaris => struct { dir: Dir, seek: i64, buf: [1024]u8, // TODO align(@alignOf(os.system.dirent)), index: usize, end_index: usize, first_iter: bool, const Self = @This(); pub const Error = IteratorError; /// Memory such as file names referenced in this returned entry becomes invalid /// with subsequent calls to `next`, as well as when this `Dir` is deinitialized. pub fn next(self: *Self) Error!?Entry { switch (builtin.os.tag) { .macos, .ios => return self.nextDarwin(), .freebsd, .netbsd, .dragonfly, .openbsd => return self.nextBsd(), .solaris => return self.nextSolaris(), else => @compileError("unimplemented"), } } fn nextDarwin(self: *Self) !?Entry { start_over: while (true) { if (self.index >= self.end_index) { if (self.first_iter) { std.os.lseek_SET(self.dir.fd, 0) catch unreachable; // EBADF here likely means that the Dir was not opened with iteration permissions self.first_iter = false; } const rc = os.system.__getdirentries64( self.dir.fd, &self.buf, self.buf.len, &self.seek, ); if (rc == 0) return null; if (rc < 0) { switch (os.errno(rc)) { .BADF => unreachable, // Dir is invalid or was opened without iteration ability .FAULT => unreachable, .NOTDIR => unreachable, .INVAL => unreachable, else => |err| return os.unexpectedErrno(err), } } self.index = 0; self.end_index = @as(usize, @intCast(rc)); } const darwin_entry = @as(*align(1) os.system.dirent, @ptrCast(&self.buf[self.index])); const next_index = self.index + darwin_entry.reclen(); self.index = next_index; const name = @as([*]u8, @ptrCast(&darwin_entry.d_name))[0..darwin_entry.d_namlen]; if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..") or (darwin_entry.d_ino == 0)) { continue :start_over; } const entry_kind: Entry.Kind = switch (darwin_entry.d_type) { os.DT.BLK => .block_device, os.DT.CHR => .character_device, os.DT.DIR => .directory, os.DT.FIFO => .named_pipe, os.DT.LNK => .sym_link, os.DT.REG => .file, os.DT.SOCK => .unix_domain_socket, os.DT.WHT => .whiteout, else => .unknown, }; return Entry{ .name = name, .kind = entry_kind, }; } } fn nextSolaris(self: *Self) !?Entry { start_over: while (true) { if (self.index >= self.end_index) { if (self.first_iter) { std.os.lseek_SET(self.dir.fd, 0) catch unreachable; // EBADF here likely means that the Dir was not opened with iteration permissions self.first_iter = false; } const rc = os.system.getdents(self.dir.fd, &self.buf, self.buf.len); switch (os.errno(rc)) { .SUCCESS => {}, .BADF => unreachable, // Dir is invalid or was opened without iteration ability .FAULT => unreachable, .NOTDIR => unreachable, .INVAL => unreachable, else => |err| return os.unexpectedErrno(err), } if (rc == 0) return null; self.index = 0; self.end_index = @as(usize, @intCast(rc)); } const entry = @as(*align(1) os.system.dirent, @ptrCast(&self.buf[self.index])); const next_index = self.index + entry.reclen(); self.index = next_index; const name = mem.sliceTo(@as([*:0]u8, @ptrCast(&entry.d_name)), 0); if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) continue :start_over; // Solaris dirent doesn't expose d_type, so we have to call stat to get it. const stat_info = os.fstatat( self.dir.fd, name, os.AT.SYMLINK_NOFOLLOW, ) catch |err| switch (err) { error.NameTooLong => unreachable, error.SymLinkLoop => unreachable, error.FileNotFound => unreachable, // lost the race else => |e| return e, }; const entry_kind: Entry.Kind = switch (stat_info.mode & os.S.IFMT) { os.S.IFIFO => .named_pipe, os.S.IFCHR => .character_device, os.S.IFDIR => .directory, os.S.IFBLK => .block_device, os.S.IFREG => .file, os.S.IFLNK => .sym_link, os.S.IFSOCK => .unix_domain_socket, os.S.IFDOOR => .door, os.S.IFPORT => .event_port, else => .unknown, }; return Entry{ .name = name, .kind = entry_kind, }; } } fn nextBsd(self: *Self) !?Entry { start_over: while (true) { if (self.index >= self.end_index) { if (self.first_iter) { std.os.lseek_SET(self.dir.fd, 0) catch unreachable; // EBADF here likely means that the Dir was not opened with iteration permissions self.first_iter = false; } const rc = if (builtin.os.tag == .netbsd) os.system.__getdents30(self.dir.fd, &self.buf, self.buf.len) else os.system.getdents(self.dir.fd, &self.buf, self.buf.len); switch (os.errno(rc)) { .SUCCESS => {}, .BADF => unreachable, // Dir is invalid or was opened without iteration ability .FAULT => unreachable, .NOTDIR => unreachable, .INVAL => unreachable, // Introduced in freebsd 13.2: directory unlinked but still open. // To be consistent, iteration ends if the directory being iterated is deleted during iteration. .NOENT => return null, else => |err| return os.unexpectedErrno(err), } if (rc == 0) return null; self.index = 0; self.end_index = @as(usize, @intCast(rc)); } const bsd_entry = @as(*align(1) os.system.dirent, @ptrCast(&self.buf[self.index])); const next_index = self.index + bsd_entry.reclen(); self.index = next_index; const name = @as([*]u8, @ptrCast(&bsd_entry.d_name))[0..bsd_entry.d_namlen]; const skip_zero_fileno = switch (builtin.os.tag) { // d_fileno=0 is used to mark invalid entries or deleted files. .openbsd, .netbsd => true, else => false, }; if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..") or (skip_zero_fileno and bsd_entry.d_fileno == 0)) { continue :start_over; } const entry_kind: Entry.Kind = switch (bsd_entry.d_type) { os.DT.BLK => .block_device, os.DT.CHR => .character_device, os.DT.DIR => .directory, os.DT.FIFO => .named_pipe, os.DT.LNK => .sym_link, os.DT.REG => .file, os.DT.SOCK => .unix_domain_socket, os.DT.WHT => .whiteout, else => .unknown, }; return Entry{ .name = name, .kind = entry_kind, }; } } pub fn reset(self: *Self) void { self.index = 0; self.end_index = 0; self.first_iter = true; } }, .haiku => struct { dir: Dir, buf: [1024]u8, // TODO align(@alignOf(os.dirent64)), index: usize, end_index: usize, first_iter: bool, const Self = @This(); pub const Error = IteratorError; /// Memory such as file names referenced in this returned entry becomes invalid /// with subsequent calls to `next`, as well as when this `Dir` is deinitialized. pub fn next(self: *Self) Error!?Entry { start_over: while (true) { // TODO: find a better max const HAIKU_MAX_COUNT = 10000; if (self.index >= self.end_index) { if (self.first_iter) { std.os.lseek_SET(self.dir.fd, 0) catch unreachable; // EBADF here likely means that the Dir was not opened with iteration permissions self.first_iter = false; } const rc = os.system._kern_read_dir( self.dir.fd, &self.buf, self.buf.len, HAIKU_MAX_COUNT, ); if (rc == 0) return null; if (rc < 0) { switch (os.errno(rc)) { .BADF => unreachable, // Dir is invalid or was opened without iteration ability .FAULT => unreachable, .NOTDIR => unreachable, .INVAL => unreachable, else => |err| return os.unexpectedErrno(err), } } self.index = 0; self.end_index = @as(usize, @intCast(rc)); } const haiku_entry = @as(*align(1) os.system.dirent, @ptrCast(&self.buf[self.index])); const next_index = self.index + haiku_entry.reclen(); self.index = next_index; const name = mem.sliceTo(@as([*:0]u8, @ptrCast(&haiku_entry.d_name)), 0); if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..") or (haiku_entry.d_ino == 0)) { continue :start_over; } var stat_info: os.Stat = undefined; const rc = os.system._kern_read_stat( self.dir.fd, &haiku_entry.d_name, false, &stat_info, 0, ); if (rc != 0) { switch (os.errno(rc)) { .SUCCESS => {}, .BADF => unreachable, // Dir is invalid or was opened without iteration ability .FAULT => unreachable, .NOTDIR => unreachable, .INVAL => unreachable, else => |err| return os.unexpectedErrno(err), } } const statmode = stat_info.mode & os.S.IFMT; const entry_kind: Entry.Kind = switch (statmode) { os.S.IFDIR => .directory, os.S.IFBLK => .block_device, os.S.IFCHR => .character_device, os.S.IFLNK => .sym_link, os.S.IFREG => .file, os.S.IFIFO => .named_pipe, else => .unknown, }; return Entry{ .name = name, .kind = entry_kind, }; } } pub fn reset(self: *Self) void { self.index = 0; self.end_index = 0; self.first_iter = true; } }, .linux => struct { dir: Dir, // The if guard is solely there to prevent compile errors from missing `linux.dirent64` // definition when compiling for other OSes. It doesn't do anything when compiling for Linux. buf: [1024]u8 align(if (builtin.os.tag != .linux) 1 else @alignOf(linux.dirent64)), index: usize, end_index: usize, first_iter: bool, const Self = @This(); const linux = os.linux; pub const Error = IteratorError; /// Memory such as file names referenced in this returned entry becomes invalid /// with subsequent calls to `next`, as well as when this `Dir` is deinitialized. pub fn next(self: *Self) Error!?Entry { return self.nextLinux() catch |err| switch (err) { // To be consistent across platforms, iteration ends if the directory being iterated is deleted during iteration. // This matches the behavior of non-Linux UNIX platforms. error.DirNotFound => null, else => |e| return e, }; } pub const ErrorLinux = error{DirNotFound} || IteratorError; /// Implementation of `next` that can return `error.DirNotFound` if the directory being /// iterated was deleted during iteration (this error is Linux specific). pub fn nextLinux(self: *Self) ErrorLinux!?Entry { start_over: while (true) { if (self.index >= self.end_index) { if (self.first_iter) { std.os.lseek_SET(self.dir.fd, 0) catch unreachable; // EBADF here likely means that the Dir was not opened with iteration permissions self.first_iter = false; } const rc = linux.getdents64(self.dir.fd, &self.buf, self.buf.len); switch (linux.getErrno(rc)) { .SUCCESS => {}, .BADF => unreachable, // Dir is invalid or was opened without iteration ability .FAULT => unreachable, .NOTDIR => unreachable, .NOENT => return error.DirNotFound, // The directory being iterated was deleted during iteration. .INVAL => return error.Unexpected, // Linux may in some cases return EINVAL when reading /proc/$PID/net. .ACCES => return error.AccessDenied, // Do not have permission to iterate this directory. else => |err| return os.unexpectedErrno(err), } if (rc == 0) return null; self.index = 0; self.end_index = rc; } const linux_entry = @as(*align(1) linux.dirent64, @ptrCast(&self.buf[self.index])); const next_index = self.index + linux_entry.reclen(); self.index = next_index; const name = mem.sliceTo(@as([*:0]u8, @ptrCast(&linux_entry.d_name)), 0); // skip . and .. entries if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) { continue :start_over; } const entry_kind: Entry.Kind = switch (linux_entry.d_type) { linux.DT.BLK => .block_device, linux.DT.CHR => .character_device, linux.DT.DIR => .directory, linux.DT.FIFO => .named_pipe, linux.DT.LNK => .sym_link, linux.DT.REG => .file, linux.DT.SOCK => .unix_domain_socket, else => .unknown, }; return Entry{ .name = name, .kind = entry_kind, }; } } pub fn reset(self: *Self) void { self.index = 0; self.end_index = 0; self.first_iter = true; } }, .windows => struct { dir: Dir, buf: [1024]u8 align(@alignOf(os.windows.FILE_BOTH_DIR_INFORMATION)), index: usize, end_index: usize, first_iter: bool, name_data: [MAX_NAME_BYTES]u8, const Self = @This(); pub const Error = IteratorError; /// Memory such as file names referenced in this returned entry becomes invalid /// with subsequent calls to `next`, as well as when this `Dir` is deinitialized. pub fn next(self: *Self) Error!?Entry { while (true) { const w = os.windows; if (self.index >= self.end_index) { var io: w.IO_STATUS_BLOCK = undefined; const rc = w.ntdll.NtQueryDirectoryFile( self.dir.fd, null, null, null, &io, &self.buf, self.buf.len, .FileBothDirectoryInformation, w.FALSE, null, if (self.first_iter) @as(w.BOOLEAN, w.TRUE) else @as(w.BOOLEAN, w.FALSE), ); self.first_iter = false; if (io.Information == 0) return null; self.index = 0; self.end_index = io.Information; switch (rc) { .SUCCESS => {}, .ACCESS_DENIED => return error.AccessDenied, // Double-check that the Dir was opened with iteration ability else => return w.unexpectedStatus(rc), } } const dir_info: *w.FILE_BOTH_DIR_INFORMATION = @ptrCast(@alignCast(&self.buf[self.index])); if (dir_info.NextEntryOffset != 0) { self.index += dir_info.NextEntryOffset; } else { self.index = self.buf.len; } const name_utf16le = @as([*]u16, @ptrCast(&dir_info.FileName))[0 .. dir_info.FileNameLength / 2]; if (mem.eql(u16, name_utf16le, &[_]u16{'.'}) or mem.eql(u16, name_utf16le, &[_]u16{ '.', '.' })) continue; // Trust that Windows gives us valid UTF-16LE const name_utf8_len = std.unicode.utf16leToUtf8(self.name_data[0..], name_utf16le) catch unreachable; const name_utf8 = self.name_data[0..name_utf8_len]; const kind: Entry.Kind = blk: { const attrs = dir_info.FileAttributes; if (attrs & w.FILE_ATTRIBUTE_DIRECTORY != 0) break :blk .directory; if (attrs & w.FILE_ATTRIBUTE_REPARSE_POINT != 0) break :blk .sym_link; break :blk .file; }; return Entry{ .name = name_utf8, .kind = kind, }; } } pub fn reset(self: *Self) void { self.index = 0; self.end_index = 0; self.first_iter = true; } }, .wasi => struct { dir: Dir, buf: [1024]u8, // TODO align(@alignOf(os.wasi.dirent_t)), cookie: u64, index: usize, end_index: usize, const Self = @This(); pub const Error = IteratorError; /// Memory such as file names referenced in this returned entry becomes invalid /// with subsequent calls to `next`, as well as when this `Dir` is deinitialized. pub fn next(self: *Self) Error!?Entry { return self.nextWasi() catch |err| switch (err) { // To be consistent across platforms, iteration ends if the directory being iterated is deleted during iteration. // This matches the behavior of non-Linux UNIX platforms. error.DirNotFound => null, else => |e| return e, }; } pub const ErrorWasi = error{DirNotFound} || IteratorError; /// Implementation of `next` that can return platform-dependent errors depending on the host platform. /// When the host platform is Linux, `error.DirNotFound` can be returned if the directory being /// iterated was deleted during iteration. pub fn nextWasi(self: *Self) ErrorWasi!?Entry { // We intentinally use fd_readdir even when linked with libc, // since its implementation is exactly the same as below, // and we avoid the code complexity here. const w = os.wasi; start_over: while (true) { // According to the WASI spec, the last entry might be truncated, // so we need to check if the left buffer contains the whole dirent. if (self.end_index - self.index < @sizeOf(w.dirent_t)) { var bufused: usize = undefined; switch (w.fd_readdir(self.dir.fd, &self.buf, self.buf.len, self.cookie, &bufused)) { .SUCCESS => {}, .BADF => unreachable, // Dir is invalid or was opened without iteration ability .FAULT => unreachable, .NOTDIR => unreachable, .INVAL => unreachable, .NOENT => return error.DirNotFound, // The directory being iterated was deleted during iteration. .NOTCAPABLE => return error.AccessDenied, else => |err| return os.unexpectedErrno(err), } if (bufused == 0) return null; self.index = 0; self.end_index = bufused; } const entry = @as(*align(1) w.dirent_t, @ptrCast(&self.buf[self.index])); const entry_size = @sizeOf(w.dirent_t); const name_index = self.index + entry_size; if (name_index + entry.d_namlen > self.end_index) { // This case, the name is truncated, so we need to call readdir to store the entire name. self.end_index = self.index; // Force fd_readdir in the next loop. continue :start_over; } const name = self.buf[name_index .. name_index + entry.d_namlen]; const next_index = name_index + entry.d_namlen; self.index = next_index; self.cookie = entry.d_next; // skip . and .. entries if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) { continue :start_over; } const entry_kind: Entry.Kind = switch (entry.d_type) { .BLOCK_DEVICE => .block_device, .CHARACTER_DEVICE => .character_device, .DIRECTORY => .directory, .SYMBOLIC_LINK => .sym_link, .REGULAR_FILE => .file, .SOCKET_STREAM, .SOCKET_DGRAM => .unix_domain_socket, else => .unknown, }; return Entry{ .name = name, .kind = entry_kind, }; } } pub fn reset(self: *Self) void { self.index = 0; self.end_index = 0; self.cookie = os.wasi.DIRCOOKIE_START; } }, else => @compileError("unimplemented"), };
[src]