Type Iterator [src]

Members

Source

pub const Iterator = switch (native_os) { .macos, .ios, .freebsd, .netbsd, .dragonfly, .openbsd, .solaris, .illumos => struct { dir: Dir, seek: i64, buf: [1024]u8, // TODO align(@alignOf(posix.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 (native_os) { .macos, .ios => return self.nextDarwin(), .freebsd, .netbsd, .dragonfly, .openbsd => return self.nextBsd(), .solaris, .illumos => 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) { posix.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 = posix.system.getdirentries( self.dir.fd, &self.buf, self.buf.len, &self.seek, ); if (rc == 0) return null; if (rc < 0) { switch (posix.errno(rc)) { .BADF => unreachable, // Dir is invalid or was opened without iteration ability .FAULT => unreachable, .NOTDIR => unreachable, .INVAL => unreachable, else => |err| return posix.unexpectedErrno(err), } } self.index = 0; self.end_index = @as(usize, @intCast(rc)); } const darwin_entry = @as(*align(1) posix.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.name))[0..darwin_entry.namlen]; if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..") or (darwin_entry.ino == 0)) { continue :start_over; } const entry_kind: Entry.Kind = switch (darwin_entry.type) { posix.DT.BLK => .block_device, posix.DT.CHR => .character_device, posix.DT.DIR => .directory, posix.DT.FIFO => .named_pipe, posix.DT.LNK => .sym_link, posix.DT.REG => .file, posix.DT.SOCK => .unix_domain_socket, posix.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) { posix.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 = posix.system.getdents(self.dir.fd, &self.buf, self.buf.len); switch (posix.errno(rc)) { .SUCCESS => {}, .BADF => unreachable, // Dir is invalid or was opened without iteration ability .FAULT => unreachable, .NOTDIR => unreachable, .INVAL => unreachable, else => |err| return posix.unexpectedErrno(err), } if (rc == 0) return null; self.index = 0; self.end_index = @as(usize, @intCast(rc)); } const entry = @as(*align(1) posix.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.name)), 0); if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) continue :start_over; // Solaris dirent doesn't expose type, so we have to call stat to get it. const stat_info = posix.fstatat( self.dir.fd, name, posix.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 & posix.S.IFMT) { posix.S.IFIFO => .named_pipe, posix.S.IFCHR => .character_device, posix.S.IFDIR => .directory, posix.S.IFBLK => .block_device, posix.S.IFREG => .file, posix.S.IFLNK => .sym_link, posix.S.IFSOCK => .unix_domain_socket, posix.S.IFDOOR => .door, posix.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) { posix.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 = posix.system.getdents(self.dir.fd, &self.buf, self.buf.len); switch (posix.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 posix.unexpectedErrno(err), } if (rc == 0) return null; self.index = 0; self.end_index = @as(usize, @intCast(rc)); } const bsd_entry = @as(*align(1) posix.system.dirent, @ptrCast(&self.buf[self.index])); const next_index = self.index + if (@hasField(posix.system.dirent, "reclen")) bsd_entry.reclen else bsd_entry.reclen(); self.index = next_index; const name = @as([*]u8, @ptrCast(&bsd_entry.name))[0..bsd_entry.namlen]; const skip_zero_fileno = switch (native_os) { // 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.fileno == 0)) { continue :start_over; } const entry_kind: Entry.Kind = switch (bsd_entry.type) { posix.DT.BLK => .block_device, posix.DT.CHR => .character_device, posix.DT.DIR => .directory, posix.DT.FIFO => .named_pipe, posix.DT.LNK => .sym_link, posix.DT.REG => .file, posix.DT.SOCK => .unix_domain_socket, posix.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: [@sizeOf(DirEnt) + posix.PATH_MAX]u8 align(@alignOf(DirEnt)), offset: usize, index: usize, end_index: usize, first_iter: bool, const Self = @This(); const DirEnt = posix.system.DirEnt; 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) { if (self.index >= self.end_index) { if (self.first_iter) { switch (@as(posix.E, @enumFromInt(posix.system._kern_rewind_dir(self.dir.fd)))) { .SUCCESS => {}, .BADF => unreachable, // Dir is invalid .FAULT => unreachable, .NOTDIR => unreachable, .INVAL => unreachable, .ACCES => return error.AccessDenied, .PERM => return error.PermissionDenied, else => |err| return posix.unexpectedErrno(err), } self.first_iter = false; } const rc = posix.system._kern_read_dir( self.dir.fd, &self.buf, self.buf.len, self.buf.len / @sizeOf(DirEnt), ); if (rc == 0) return null; if (rc < 0) { switch (@as(posix.E, @enumFromInt(rc))) { .BADF => unreachable, // Dir is invalid .FAULT => unreachable, .NOTDIR => unreachable, .INVAL => unreachable, .OVERFLOW => unreachable, .ACCES => return error.AccessDenied, .PERM => return error.PermissionDenied, else => |err| return posix.unexpectedErrno(err), } } self.offset = 0; self.index = 0; self.end_index = @intCast(rc); } const dirent: *DirEnt = @ptrCast(@alignCast(&self.buf[self.offset])); self.offset += dirent.reclen; self.index += 1; const name = mem.span(dirent.getName()); if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..") or dirent.ino == 0) continue; var stat_info: posix.Stat = undefined; switch (@as(posix.E, @enumFromInt(posix.system._kern_read_stat( self.dir.fd, name, false, &stat_info, 0, )))) { .SUCCESS => {}, .INVAL => unreachable, .BADF => unreachable, // Dir is invalid .NOMEM => return error.SystemResources, .ACCES => return error.AccessDenied, .PERM => return error.PermissionDenied, .FAULT => unreachable, .NAMETOOLONG => unreachable, .LOOP => unreachable, .NOENT => continue, else => |err| return posix.unexpectedErrno(err), } const statmode = stat_info.mode & posix.S.IFMT; const entry_kind: Entry.Kind = switch (statmode) { posix.S.IFDIR => .directory, posix.S.IFBLK => .block_device, posix.S.IFCHR => .character_device, posix.S.IFLNK => .sym_link, posix.S.IFREG => .file, posix.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(@alignOf(linux.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 { 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) { posix.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.E.init(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 posix.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.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.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(windows.FILE_BOTH_DIR_INFORMATION)), index: usize, end_index: usize, first_iter: bool, name_data: [fs.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 { const w = windows; while (true) { 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), } } // While the official api docs guarantee FILE_BOTH_DIR_INFORMATION to be aligned properly // this may not always be the case (e.g. due to faulty VM/Sandboxing tools) const dir_info: *align(2) 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_wtf16le = @as([*]u16, @ptrCast(&dir_info.FileName))[0 .. dir_info.FileNameLength / 2]; if (mem.eql(u16, name_wtf16le, &[_]u16{'.'}) or mem.eql(u16, name_wtf16le, &[_]u16{ '.', '.' })) continue; const name_wtf8_len = std.unicode.wtf16LeToWtf8(self.name_data[0..], name_wtf16le); const name_wtf8 = self.name_data[0..name_wtf8_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_wtf8, .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(posix.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 = std.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, .ILSEQ => return error.InvalidUtf8, // An entry's name cannot be encoded as UTF-8. else => |err| return posix.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.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.namlen]; const next_index = name_index + entry.namlen; self.index = next_index; self.cookie = entry.next; // skip . and .. entries if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) { continue :start_over; } const entry_kind: Entry.Kind = switch (entry.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 = std.os.wasi.DIRCOOKIE_START; } }, else => @compileError("unimplemented"), }