Skip to content
16 changes: 5 additions & 11 deletions src/install/prune.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1343,24 +1343,17 @@ fn lstat_kind(dir: &Dir, name: &[u8]) -> EntryKind {
}
}

fn entry_kind(dir: &Dir, name: &[u8], kind: EntryKind) -> EntryKind {
if kind != EntryKind::Unknown {
return kind;
}
lstat_kind(dir, name)
}

fn read_entries(dir: &Dir) -> Vec<(Box<[u8]>, EntryKind)> {
let mut out = Vec::new();
let mut iter = sys::iterate_dir(dir.fd());
iter.resolve_unknown_entry_types = true;
while let Ok(Some(entry)) = iter.next() {
let name = entry.name.slice_u8();
if name.first() == Some(&b'.') {
continue;
}
let kind = entry_kind(dir, name, entry.kind);
if kind == EntryKind::Directory || kind == EntryKind::SymLink {
out.push((name.into(), kind));
if entry.kind == EntryKind::Directory || entry.kind == EntryKind::SymLink {
out.push((name.into(), entry.kind));
}
}
out
Expand Down Expand Up @@ -1780,9 +1773,10 @@ fn prune_bins(dir: &Dir) {
};
let mut dangling: Vec<Box<[u8]>> = Vec::new();
let mut iter = sys::iterate_dir(bin.fd());
iter.resolve_unknown_entry_types = true;
while let Ok(Some(entry)) = iter.next() {
let name = entry.name.slice_u8();
if entry_kind(&bin, name, entry.kind) == EntryKind::SymLink && is_dangling(&bin, name) {
if entry.kind == EntryKind::SymLink && is_dangling(&bin, name) {
dangling.push(name.into());
}
}
Expand Down
5 changes: 5 additions & 0 deletions src/runtime/cli/pack_command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,7 @@ fn iterate_included_project_tree(
});

let mut dir_iter = DirIterator::iterate(Fd::from_std_dir(&dir));
dir_iter.resolve_unknown_entry_types = true;
'next_entry: while let Some(entry) = dir_iter.next().ok().flatten() {
// On iterator error, treat as end of iteration.
if entry.kind != bun_sys::FileKind::File && entry.kind != bun_sys::FileKind::Directory {
Expand Down Expand Up @@ -713,6 +714,7 @@ fn add_entire_tree(
}

let mut iter = DirIterator::iterate(Fd::from_std_dir(&dir));
iter.resolve_unknown_entry_types = true;
'next_entry: while let Some(entry) = iter.next().ok().flatten() {
if entry.kind != bun_sys::FileKind::File && entry.kind != bun_sys::FileKind::Directory {
continue;
Expand Down Expand Up @@ -884,6 +886,7 @@ fn iterate_bundled_deps(
let mut additional_bundled_deps: Vec<DirInfo> = Vec::new();

let mut iter = DirIterator::iterate(Fd::from_std_dir(&dir));
iter.resolve_unknown_entry_types = true;
while let Some(entry) = iter.next().ok().flatten() {
if entry.kind != bun_sys::FileKind::Directory {
continue;
Expand Down Expand Up @@ -1022,6 +1025,7 @@ fn add_bundled_dep(
let DirInfo(dir, dir_subpath, dir_depth) = dir_info;

let mut iter = DirIterator::iterate(Fd::from_std_dir(&dir));
iter.resolve_unknown_entry_types = true;
while let Some(entry) = iter.next().ok().flatten() {
if entry.kind != bun_sys::FileKind::File && entry.kind != bun_sys::FileKind::Directory {
continue;
Expand Down Expand Up @@ -1284,6 +1288,7 @@ fn iterate_project_tree(
}

let mut dir_iter = DirIterator::iterate(Fd::from_std_dir(&dir));
dir_iter.resolve_unknown_entry_types = true;
'next_entry: while let Some(entry) = dir_iter.next().ok().flatten() {
if entry.kind != bun_sys::FileKind::File && entry.kind != bun_sys::FileKind::Directory {
continue;
Expand Down
2 changes: 2 additions & 0 deletions src/runtime/cli/publish_command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1588,6 +1588,7 @@ impl PublishCommand {
});

let mut iter = DirIterator::iterate(workspace_dir);
iter.resolve_unknown_entry_types = true;
while let Some(entry) = iter.next().ok().flatten() {
if entry.kind == bun_sys::EntryKind::Directory {
continue;
Expand Down Expand Up @@ -1810,6 +1811,7 @@ impl PublishCommand {
});

let mut iter = DirIterator::iterate(dir);
iter.resolve_unknown_entry_types = true;
while let Some(entry) = iter.next().ok().flatten() {
let (name, subpath): (&'static ZStr, &'static ZStr) = {
// Entry names are UTF-8 on every platform.
Expand Down
36 changes: 33 additions & 3 deletions src/sys/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,25 @@ pub mod dir_iterator {
pub kind: EntryKind,
}

impl IteratorResult {
/// See `WrappedIterator::resolve_unknown_entry_types`.
fn resolve_unknown_kind(&mut self, dir: Fd) {
#[cfg(not(windows))]
{
if self.kind == EntryKind::Unknown {
if let Ok(stat) = super::lstatat(dir, self.name.as_zstr()) {
self.kind = super::kind_from_mode(stat.st_mode as super::Mode);
}
}
}
#[cfg(windows)]
{
// The Windows iterator always knows the kind.
let _ = dir;
}
}
}

/// Length-known, NUL-terminated entry name in OS-native encoding.
///
/// **POSIX**: lifetime-erased borrow (raw pointer + length) into the
Expand Down Expand Up @@ -265,8 +284,7 @@ pub mod dir_iterator {
// literal matches <sys/dirent.h>.
#[cfg(any(target_os = "macos", target_os = "freebsd"))]
14 /* DT_WHT */ => EntryKind::Whiteout,
// DT_UNKNOWN: some filesystems (bind mounts, FUSE, NFS) don't
// provide d_type. Callers should lstatat() to resolve when needed.
// DT_UNKNOWN: see `WrappedIterator::resolve_unknown_entry_types`.
_ => EntryKind::Unknown,
}
}
Expand Down Expand Up @@ -731,6 +749,10 @@ pub mod dir_iterator {
#[cfg(not(windows))]
name_filter: Option<Vec<u16>>,
state: State,
/// `lstat` entries whose kind the filesystem did not report (`Unknown`:
/// FUSE, NFS, XFS with `ftype=0`), so that, as with `d_type`, a symlink is
/// still a symlink. Entries that cannot be stat'ed stay `Unknown`.
Comment thread
robobun marked this conversation as resolved.
pub resolve_unknown_entry_types: bool,
}
impl WrappedIterator {
#[inline]
Expand Down Expand Up @@ -759,7 +781,13 @@ pub mod dir_iterator {
/// Copy it out before pushing the iterator into a `Vec` etc.
#[inline]
pub fn next(&mut self) -> Result<Option<IteratorResult>> {
self.state.next(self.dir)
let mut entry = self.state.next(self.dir)?;
if self.resolve_unknown_entry_types {
if let Some(entry) = entry.as_mut() {
entry.resolve_unknown_kind(self.dir);
}
}
Ok(entry)
}
}

Expand All @@ -770,13 +798,15 @@ pub mod dir_iterator {
dir,
name_filter: None,
state: State::new(),
resolve_unknown_entry_types: false,
}
}
#[cfg(windows)]
{
WrappedIterator {
dir,
state: State::new(),
resolve_unknown_entry_types: false,
}
}
}
Expand Down
202 changes: 87 additions & 115 deletions src/sys/walker_skippable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ pub struct Walker {
skip_dirnames: Range<usize>,
skip_all: Box<[u64]>,
seed: u64,
/// See `dir_iterator::WrappedIterator::resolve_unknown_entry_types`.
pub resolve_unknown_entry_types: bool,
}

Expand Down Expand Up @@ -76,127 +77,98 @@ impl Walker {
// be invalidated by appending to `self.stack` below.
let top_idx = self.stack.len() - 1;
let mut dirname_len = self.stack[top_idx].dirname_len;
match self.stack[top_idx].iter.next() {
Err(err) => return Err(err),
Ok(res) => {
if let Some(base) = res {
// Some filesystems (NFS, FUSE, bind mounts) don't provide
// d_type and return DT_UNKNOWN. Optionally resolve via
// fstatat so callers get accurate types for recursion.
// This only affects POSIX; Windows always provides types.
#[cfg(not(windows))]
let kind: sys::EntryKind = if base.kind == sys::EntryKind::Unknown
&& self.resolve_unknown_entry_types
{
let dir_fd = self.stack[top_idx].iter.dir();
match sys::lstatat(dir_fd, base.name.as_zstr()) {
Ok(stat_buf) => sys::kind_from_mode(stat_buf.st_mode as sys::Mode),
Err(_) => continue, // skip entries we can't stat
}
// Per call: callers set the flag after `walk()` built the root iterator.
self.stack[top_idx].iter.resolve_unknown_entry_types = self.resolve_unknown_entry_types;
let Some(base) = self.stack[top_idx].iter.next()? else {
let item = self.stack.pop().unwrap();
if !self.stack.is_empty() {
item.iter.dir().close();
}
continue;
};
let kind = base.kind;

match kind {
sys::EntryKind::Directory => {
let skip = &self.skip_all[self.skip_dirnames.clone()];
if skip.contains(
// avoid hashing if there will be 0 results
&(if !skip.is_empty() {
hash_with_seed(self.seed, slice_as_bytes(base.name.as_slice()))
} else {
base.kind
};
#[cfg(windows)]
let kind: sys::EntryKind = base.kind;

match kind {
sys::EntryKind::Directory => {
let skip = &self.skip_all[self.skip_dirnames.clone()];
if skip.contains(
// avoid hashing if there will be 0 results
&(if !skip.is_empty() {
hash_with_seed(
self.seed,
slice_as_bytes(base.name.as_slice()),
)
} else {
0
}),
) {
continue;
}
}
sys::EntryKind::File => {
let skip = &self.skip_all[self.skip_filenames.clone()];
if skip.contains(
// avoid hashing if there will be 0 results
&(if !skip.is_empty() {
hash_with_seed(
self.seed,
slice_as_bytes(base.name.as_slice()),
)
} else {
0
}),
) {
continue;
}
}

// we don't know what it is for a symlink
sys::EntryKind::SymLink => {
let skip = &self.skip_all[..];
if skip.contains(
// avoid hashing if there will be 0 results
&(if !skip.is_empty() {
hash_with_seed(
self.seed,
slice_as_bytes(base.name.as_slice()),
)
} else {
0
}),
) {
continue;
}
}
0
}),
) {
continue;
}
}
sys::EntryKind::File => {
let skip = &self.skip_all[self.skip_filenames.clone()];
if skip.contains(
// avoid hashing if there will be 0 results
&(if !skip.is_empty() {
hash_with_seed(self.seed, slice_as_bytes(base.name.as_slice()))
} else {
0
}),
) {
continue;
}
}

_ => {}
}
// we don't know what it is for a symlink
sys::EntryKind::SymLink => {
let skip = &self.skip_all[..];
if skip.contains(
// avoid hashing if there will be 0 results
&(if !skip.is_empty() {
hash_with_seed(self.seed, slice_as_bytes(base.name.as_slice()))
} else {
0
}),
) {
continue;
}
}

self.name_buffer.truncate(dirname_len);
if !self.name_buffer.is_empty() {
self.name_buffer.push(SEP as OSPathChar);
dirname_len += 1;
}
self.name_buffer.extend_from_slice(base.name.as_slice());
let cur_len = self.name_buffer.len();
self.name_buffer.push(0);
_ => {}
}

let mut top_idx = top_idx;
if kind == sys::EntryKind::Directory {
let new_dir = sys::open_dir_for_iteration_os_path(
self.stack[top_idx].iter.dir(),
base.name.as_slice(),
)?;
{
self.stack.push(StackItem {
iter: dir_iterator::iterate(new_dir),
dirname_len: cur_len,
});
top_idx = self.stack.len() - 1;
}
}
// `name_buffer[cur_len] == 0` was written above; both views end at
// `cur_len` and are NUL-terminated by that sentinel char. `from_buf`
// ties the borrow to `&self.name_buffer` (no raw-pointer reslice).
return Ok(Some(WalkerEntry {
dir: self.stack[top_idx].iter.dir(),
basename: OSPathSliceZ::from_buf(
&self.name_buffer[dirname_len..],
cur_len - dirname_len,
),
path: OSPathSliceZ::from_buf(&self.name_buffer, cur_len),
kind,
}));
} else {
let item = self.stack.pop().unwrap();
if !self.stack.is_empty() {
item.iter.dir().close();
}
}
self.name_buffer.truncate(dirname_len);
if !self.name_buffer.is_empty() {
self.name_buffer.push(SEP as OSPathChar);
dirname_len += 1;
}
self.name_buffer.extend_from_slice(base.name.as_slice());
let cur_len = self.name_buffer.len();
self.name_buffer.push(0);

let mut top_idx = top_idx;
if kind == sys::EntryKind::Directory {
let new_dir = sys::open_dir_for_iteration_os_path(
self.stack[top_idx].iter.dir(),
base.name.as_slice(),
)?;
{
self.stack.push(StackItem {
iter: dir_iterator::iterate(new_dir),
dirname_len: cur_len,
});
top_idx = self.stack.len() - 1;
}
}
// `name_buffer[cur_len] == 0` was written above; both views end at
// `cur_len` and are NUL-terminated by that sentinel char. `from_buf`
// ties the borrow to `&self.name_buffer` (no raw-pointer reslice).
Comment thread
robobun marked this conversation as resolved.
Outdated
return Ok(Some(WalkerEntry {
dir: self.stack[top_idx].iter.dir(),
basename: OSPathSliceZ::from_buf(
&self.name_buffer[dirname_len..],
cur_len - dirname_len,
),
path: OSPathSliceZ::from_buf(&self.name_buffer, cur_len),
kind,
}));
}
Ok(None)
}
Expand Down
Loading
Loading