use core::cell::RefCell;
use core::ffi::c_void;
use std::borrow::Cow;
use std::io::Write as _;
use bstr::BStr;
use bun_alloc::{AllocError, allocators};
use bun_collections::VecExt as _;
use bun_core::{FeatureFlags, Generation, ZStr, env_var};
use bun_core::{MutableString, PathString};
use bun_paths::resolve_path::platform;
use bun_paths::strings;
use bun_paths::{MAX_PATH_BYTES, PathBuffer, SEP, resolve_path as path_handler};
use bun_sys::{self, Fd};
use bun_threading::Mutex;
bun_core::define_scoped_log!(debug, Fs, hidden);
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum BOM {
Utf8,
Utf16Le,
Utf16Be,
Utf32Le,
Utf32Be,
}
impl BOM {
pub const UTF8_BYTES: [u8; 3] = [0xef, 0xbb, 0xbf];
pub const UTF16_LE_BYTES: [u8; 2] = [0xff, 0xfe];
pub const UTF16_BE_BYTES: [u8; 2] = [0xfe, 0xff];
pub const UTF32_LE_BYTES: [u8; 4] = [0xff, 0xfe, 0x00, 0x00];
pub const UTF32_BE_BYTES: [u8; 4] = [0x00, 0x00, 0xfe, 0xff];
pub fn detect(bytes: &[u8]) -> Option<BOM> {
if bytes.len() < 3 {
return None;
}
if bytes.starts_with(&Self::UTF8_BYTES) {
return Some(BOM::Utf8);
}
if bytes.starts_with(&Self::UTF16_LE_BYTES) {
return Some(BOM::Utf16Le);
}
None
}
pub fn header(self) -> &'static [u8] {
match self {
BOM::Utf8 => &Self::UTF8_BYTES,
BOM::Utf16Le => &Self::UTF16_LE_BYTES,
BOM::Utf16Be => &Self::UTF16_BE_BYTES,
BOM::Utf32Le => &Self::UTF32_LE_BYTES,
BOM::Utf32Be => &Self::UTF32_BE_BYTES,
}
}
pub fn tag_name(self) -> &'static str {
match self {
BOM::Utf8 => "utf8",
BOM::Utf16Le => "utf16_le",
BOM::Utf16Be => "utf16_be",
BOM::Utf32Le => "utf32_le",
BOM::Utf32Be => "utf32_be",
}
}
pub fn remove_and_convert_to_utf8_and_free(self, mut bytes: Vec<u8>) -> Vec<u8> {
match self {
BOM::Utf8 => {
let n = Self::UTF8_BYTES.len();
bytes.copy_within(n.., 0);
bytes.truncate(bytes.len() - n);
bytes
}
BOM::Utf16Le => {
let trimmed = &bytes[Self::UTF16_LE_BYTES.len()..];
let out = strings::to_utf8_alloc_from_le_bytes(trimmed);
drop(bytes);
out
}
_ => {
let n = self.header().len();
bytes.copy_within(n.., 0);
bytes.truncate(bytes.len() - n);
bytes
}
}
}
pub fn remove_and_convert_to_utf8_without_dealloc<'a>(self, list: &'a mut Vec<u8>) -> &'a [u8] {
match self {
BOM::Utf8 => {
let n = Self::UTF8_BYTES.len();
let len = list.len();
list.copy_within(n.., 0);
&list[..len - n]
}
BOM::Utf16Le => {
let out = strings::to_utf8_alloc_from_le_bytes(&list[Self::UTF16_LE_BYTES.len()..]);
list.clear();
list.extend_from_slice(&out);
&list[..]
}
_ => {
let n = self.header().len();
let len = list.len();
list.copy_within(n.., 0);
&list[..len - n]
}
}
}
}
pub(crate) mod preallocate {
pub(crate) mod counts {
pub(crate) const DIR_ENTRY: usize = 2048;
pub(crate) const FILES: usize = 4096;
}
}
pub(crate) type DirnameStoreBacking =
allocators::BSSStringList<{ preallocate::counts::DIR_ENTRY * 2 }, { 128 + 1 }>;
pub(crate) type FilenameStoreBacking =
allocators::BSSStringList<{ preallocate::counts::FILES * 2 }, { 64 + 1 }>;
pub(crate) type EntryStoreBacking = allocators::BSSList<Entry, { preallocate::counts::FILES * 2 }>;
bun_alloc::bss_string_list! { pub dirname_store_backing : preallocate::counts::DIR_ENTRY * 2, 128 + 1 }
bun_alloc::bss_string_list! { pub filename_store_backing : preallocate::counts::FILES * 2, 64 + 1 }
bun_alloc::bss_list! { pub entry_store_backing : Entry, preallocate::counts::FILES * 2 }
pub struct DirnameStore(());
pub struct FilenameStore(());
static DIRNAME_STORE_ZST: DirnameStore = DirnameStore(());
static FILENAME_STORE_ZST: FilenameStore = FilenameStore(());
macro_rules! string_store_impl {
($t:ty, $zst:ident, $backing:ident, $bty:ty) => {
impl $t {
#[inline]
pub fn instance() -> &'static Self {
&$zst
}
#[inline]
fn backing() -> *mut $bty {
$backing()
}
pub fn append(&self, value: &[u8]) -> core::result::Result<&'static [u8], AllocError> {
unsafe { <$bty>::append(Self::backing(), &value) }
}
pub fn print(
&self,
args: core::fmt::Arguments<'_>,
) -> core::result::Result<&'static [u8], AllocError> {
let s = unsafe { <$bty>::print(Self::backing(), args)? };
Ok(unsafe { bun_ptr::Interned::assume(s) }.as_bytes())
}
#[inline]
pub fn exists(&self, value: &[u8]) -> bool {
unsafe { (*Self::backing()).exists(value) }
}
}
impl strings::Appender for &'static $t {
fn append(&mut self, s: &[u8]) -> core::result::Result<&[u8], AllocError> {
unsafe { <$bty>::append(<$t>::backing(), &s) }
}
fn append_lower_case(&mut self, s: &[u8]) -> core::result::Result<&[u8], AllocError> {
unsafe { <$bty>::append_lower_case(<$t>::backing(), s) }
}
}
};
}
string_store_impl!(
DirnameStore,
DIRNAME_STORE_ZST,
dirname_store_backing,
DirnameStoreBacking
);
string_store_impl!(
FilenameStore,
FILENAME_STORE_ZST,
filename_store_backing,
FilenameStoreBacking
);
pub struct FilenameStoreAppender {
backing: *mut FilenameStoreBacking,
}
impl FilenameStoreAppender {
#[inline]
pub fn new() -> Self {
Self {
backing: filename_store_backing(),
}
}
}
impl strings::Appender for FilenameStoreAppender {
#[inline]
fn append(&mut self, s: &[u8]) -> core::result::Result<&[u8], AllocError> {
let r = unsafe { FilenameStoreBacking::append(self.backing, &s)? };
Ok(unsafe { bun_ptr::Interned::assume(r) }.as_bytes())
}
#[inline]
fn append_lower_case(&mut self, s: &[u8]) -> core::result::Result<&[u8], AllocError> {
let r = unsafe { FilenameStoreBacking::append_lower_case(self.backing, s)? };
Ok(unsafe { bun_ptr::Interned::assume(r) }.as_bytes())
}
}
#[cfg(not(windows))]
pub(crate) static MAX_FD: bun_core::AtomicCell<bun_sys::RawFd> = bun_core::AtomicCell::new(0);
pub(crate) struct FileSystem;
impl FileSystem {
#[inline]
pub(crate) fn set_max_fd(fd: bun_sys::RawFd) {
#[cfg(windows)]
{
let _ = fd;
return;
}
#[cfg(not(windows))]
{
if !FeatureFlags::STORE_FILE_DESCRIPTORS {
return;
}
let _ = MAX_FD.fetch_update(|cur| (fd > cur).then_some(fd));
}
}
}
pub trait EntryKindResolver {
fn resolve_kind(
&mut self,
dir: &[u8],
base: &[u8],
existing_fd: Fd,
store_fd: bool,
) -> core::result::Result<EntryCache, bun_core::Error>;
}
#[repr(u8)]
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum EntryKind {
Dir,
File,
}
#[derive(Clone, Copy)]
pub struct EntryCache {
pub symlink: PathString,
pub fd: Fd,
pub kind: EntryKind,
}
impl Default for EntryCache {
fn default() -> Self {
Self {
symlink: PathString::EMPTY,
fd: Fd::INVALID,
kind: EntryKind::File,
}
}
}
pub struct Entry {
pub cache: core::cell::Cell<EntryCache>,
pub dir: &'static [u8],
pub base_: strings::StringOrTinyString,
pub base_lowercase_: strings::StringOrTinyString,
pub mutex: Mutex,
pub need_stat: core::cell::Cell<bool>,
pub abs_path: PathString,
}
impl Entry {
#[inline(always)]
pub fn cache(&self) -> EntryCache {
self.cache.get()
}
#[inline(always)]
pub fn set_cache(&self, c: EntryCache) {
self.cache.set(c);
}
#[inline(always)]
pub fn set_cache_fd(&self, fd: Fd) {
let mut c = self.cache.get();
c.fd = fd;
self.cache.set(c);
}
#[inline(always)]
pub fn set_cache_kind(&self, kind: EntryKind) {
let mut c = self.cache.get();
c.kind = kind;
self.cache.set(c);
}
#[inline(always)]
pub fn set_cache_symlink(&self, symlink: PathString) {
let mut c = self.cache.get();
c.symlink = symlink;
self.cache.set(c);
}
#[inline]
pub fn base(&self) -> &[u8] {
self.base_.slice()
}
#[inline]
pub fn base_lowercase(&self) -> &[u8] {
self.base_lowercase_.slice()
}
#[inline]
pub fn dir(&self) -> &'static [u8] {
self.dir
}
#[inline]
pub fn abs_path(&self) -> PathString {
self.abs_path
}
#[inline]
pub fn set_abs_path(&mut self, p: PathString) {
self.abs_path = p;
}
pub unsafe fn kind<R: EntryKindResolver>(&self, fs: *mut R, store_fd: bool) -> EntryKind {
if self.need_stat.get() {
self.need_stat.set(false);
match unsafe { &mut *fs }.resolve_kind(self.dir, self.base(), self.cache().fd, store_fd)
{
Ok(c) => self.cache.set(c),
Err(_) => return self.cache().kind,
}
}
self.cache().kind
}
pub unsafe fn symlink<R: EntryKindResolver>(
&self,
fs: *mut R,
store_fd: bool,
) -> &'static [u8] {
if self.need_stat.get() {
self.need_stat.set(false);
match unsafe { &mut *fs }.resolve_kind(self.dir, self.base(), self.cache().fd, store_fd)
{
Ok(c) => self.cache.set(c),
Err(_) => return b"",
}
}
crate::path_string_static(&self.cache().symlink)
}
}
impl Clone for Entry {
fn clone(&self) -> Self {
Self {
cache: core::cell::Cell::new(self.cache.get()),
dir: self.dir,
base_: strings::StringOrTinyString::init(self.base_.slice()),
base_lowercase_: strings::StringOrTinyString::init(self.base_lowercase_.slice()),
mutex: Mutex::default(),
need_stat: core::cell::Cell::new(self.need_stat.get()),
abs_path: self.abs_path,
}
}
}
impl Default for Entry {
fn default() -> Self {
Self {
cache: core::cell::Cell::new(EntryCache::default()),
dir: b"",
base_: strings::StringOrTinyString::init(b""),
base_lowercase_: strings::StringOrTinyString::init(b""),
mutex: Mutex::default(),
need_stat: core::cell::Cell::new(true),
abs_path: PathString::EMPTY,
}
}
}
#[derive(Clone, Copy)]
pub struct DifferentCase<'a> {
pub dir: &'a [u8],
pub query: &'a [u8],
pub actual: &'a [u8],
}
pub struct EntryLookup<'a> {
pub entry: *mut Entry,
pub diff_case: Option<DifferentCase<'static>>,
_marker: core::marker::PhantomData<&'a Entry>,
}
impl<'a> EntryLookup<'a> {
#[inline(always)]
pub fn entry(&self) -> &'a Entry {
unsafe { &*self.entry }
}
}
pub mod dir_entry {
use super::{Entry, EntryStoreBacking};
pub(crate) type EntryMap = bun_collections::StringHashMap<*mut Entry>;
pub(crate) struct EntryStore(());
impl EntryStore {
#[inline]
pub(crate) fn instance() -> *mut EntryStoreBacking {
super::entry_store_backing()
}
#[inline(always)]
pub(crate) fn append_uninit()
-> core::result::Result<*mut core::mem::MaybeUninit<Entry>, bun_alloc::AllocError> {
unsafe { EntryStoreBacking::append_uninit(Self::instance()) }
}
}
#[derive(Clone, Copy)]
pub struct Err {
pub original_err: bun_core::Error,
pub canonical_error: bun_core::Error,
}
}
pub trait DirEntryIterator {
const IS_VOID: bool = false;
fn next(&self, entry: &mut Entry, fd: Fd);
}
impl DirEntryIterator for () {
const IS_VOID: bool = true;
fn next(&self, _entry: &mut Entry, _fd: Fd) {}
}
impl<T: DirEntryIterator + ?Sized> DirEntryIterator for &T {
const IS_VOID: bool = T::IS_VOID;
#[inline]
fn next(&self, entry: &mut Entry, fd: Fd) {
(**self).next(entry, fd)
}
}
pub struct DirEntry {
pub dir: &'static [u8],
pub fd: Fd,
pub generation: Generation,
pub data: dir_entry::EntryMap,
}
impl DirEntry {
pub fn init(dir: &'static [u8], generation: Generation) -> DirEntry {
if FeatureFlags::VERBOSE_FS {
bun_core::prettyln!("\n {}", BStr::new(dir));
}
DirEntry {
dir,
data: dir_entry::EntryMap::default(),
generation,
fd: Fd::INVALID,
}
}
#[inline]
pub fn add_entry<I: DirEntryIterator>(
&mut self,
prev_map: Option<&mut dir_entry::EntryMap>,
entry: &bun_sys::dir_iterator::IteratorResult,
iterator: I,
) -> core::result::Result<(), bun_core::Error> {
self.add_entry_with_store(prev_map, entry, &mut FilenameStoreAppender::new(), iterator)
}
pub fn add_entry_with_store<I: DirEntryIterator>(
&mut self,
prev_map: Option<&mut dir_entry::EntryMap>,
entry: &bun_sys::dir_iterator::IteratorResult,
filename_store: &mut FilenameStoreAppender,
iterator: I,
) -> core::result::Result<(), bun_core::Error> {
use bun_sys::FileKind as DK;
let name_slice = entry.name.slice_u8();
let found_kind: Option<EntryKind> = match entry.kind {
DK::Directory => Some(EntryKind::Dir),
DK::File => Some(EntryKind::File),
DK::SymLink
| DK::Unknown => None,
DK::BlockDevice
| DK::CharacterDevice
| DK::NamedPipe
| DK::UnixDomainSocket
| DK::Whiteout
| DK::Door
| DK::EventPort => return Ok(()),
};
let mut name_lc_buf = PathBuffer::uninit();
let name_lc_heap: Option<bun_collections::StringHashMapContext::PrehashedCaseInsensitive> =
if name_slice.len() <= MAX_PATH_BYTES {
None
} else {
Some(
bun_collections::StringHashMapContext::PrehashedCaseInsensitive::init(
name_slice,
),
)
};
let name_lc: &[u8] = match &name_lc_heap {
Some(p) => &p.input[..],
None => strings::copy_lowercase_if_needed(name_slice, &mut name_lc_buf[..]),
};
let name_hash = self.data.hash_key(name_lc);
let stored: *mut Entry = 'brk: {
if let Some(map) = prev_map {
if let Some(&existing_ptr) = map.get_hashed(name_hash, name_lc) {
let existing = unsafe { &mut *existing_ptr };
let _guard = existing.mutex.lock_guard();
existing.dir = self.dir;
existing.need_stat.set(
existing.need_stat.get()
|| found_kind.is_none()
|| Some(existing.cache().kind) != found_kind,
);
if Some(existing.cache().kind) != found_kind {
existing.set_cache_kind(found_kind.unwrap_or(EntryKind::File));
existing.set_cache_symlink(PathString::EMPTY);
}
break 'brk existing_ptr;
}
}
let slot = dir_entry::EntryStore::append_uninit()?;
unsafe {
use core::ptr::addr_of_mut;
let p = (*slot).as_mut_ptr();
addr_of_mut!((*p).base_).write(strings::StringOrTinyString::init_append_if_needed(
name_slice,
filename_store,
)?);
let base_lowercase = if core::ptr::eq(name_lc.as_ptr(), name_slice.as_ptr()) {
(*p).base_
} else {
strings::StringOrTinyString::init_append_if_needed(name_lc, filename_store)?
};
addr_of_mut!((*p).base_lowercase_).write(base_lowercase);
addr_of_mut!((*p).dir).write(self.dir);
addr_of_mut!((*p).mutex).write(Mutex::new());
addr_of_mut!((*p).need_stat).write(core::cell::Cell::new(found_kind.is_none()));
addr_of_mut!((*p).cache).write(core::cell::Cell::new(EntryCache {
symlink: PathString::EMPTY,
kind: found_kind.unwrap_or(EntryKind::File),
fd: Fd::INVALID,
}));
addr_of_mut!((*p).abs_path).write(PathString::EMPTY);
p
}
};
let stored_ref = unsafe { &mut *stored };
let key: &'static [u8] =
unsafe { &*core::ptr::from_ref::<[u8]>((*stored).base_lowercase()) };
self.data.put_static_key_hashed(name_hash, key, stored)?;
if !I::IS_VOID {
iterator.next(stored_ref, self.fd);
}
if FeatureFlags::VERBOSE_FS {
let stored_name = stored_ref.base();
if found_kind == Some(EntryKind::Dir) {
bun_core::prettyln!(" + {}/", BStr::new(stored_name));
} else {
bun_core::prettyln!(" + {}", BStr::new(stored_name));
}
}
Ok(())
}
pub fn get<'a>(&'a self, query_: &[u8]) -> Option<EntryLookup<'a>> {
if query_.is_empty() || query_.len() > MAX_PATH_BYTES {
return None;
}
let mut scratch_lookup_buffer = PathBuffer::uninit();
let query = strings::copy_lowercase_if_needed(query_, &mut scratch_lookup_buffer[..]);
let &result_ptr = self.data.get(query)?;
let basename = unsafe { &*result_ptr }.base();
if !strings::eql_long(basename, query_, true) {
return Some(EntryLookup {
entry: result_ptr,
diff_case: Some(DifferentCase {
dir: self.dir,
query: unsafe { &*core::ptr::from_ref::<[u8]>(query_) },
actual: unsafe { &*core::ptr::from_ref::<[u8]>(basename) },
}),
_marker: core::marker::PhantomData,
});
}
Some(EntryLookup {
entry: result_ptr,
diff_case: None,
_marker: core::marker::PhantomData,
})
}
pub fn get_comptime_query<'a>(&'a self, query_lower: &'static [u8]) -> Option<EntryLookup<'a>> {
let &result_ptr = self.data.get(query_lower)?;
let basename = unsafe { &*result_ptr }.base();
if basename != query_lower {
return Some(EntryLookup {
entry: result_ptr,
diff_case: Some(DifferentCase {
dir: self.dir,
query: query_lower,
actual: unsafe { &*core::ptr::from_ref::<[u8]>(basename) },
}),
_marker: core::marker::PhantomData,
});
}
Some(EntryLookup {
entry: result_ptr,
diff_case: None,
_marker: core::marker::PhantomData,
})
}
pub fn has_comptime_query(&self, query_lower: &'static [u8]) -> bool {
self.data.contains_key(query_lower)
}
#[inline]
pub fn fd(&self) -> Fd {
self.fd
}
#[inline]
pub fn iter(&self) -> impl Iterator<Item = *mut Entry> + '_ {
self.data.values().copied()
}
}
impl bun_dotenv::DirEntryProbe for DirEntry {
#[inline]
fn has_comptime_query(&self, query_lower: &'static [u8]) -> bool {
DirEntry::has_comptime_query(self, query_lower)
}
}
pub use EntryKind as FsEntryKind;
pub use dir_entry::Err as DirEntryErr;
pub(crate) type EntriesOptionMap =
allocators::BSSMapInner<EntriesOption, { preallocate::counts::DIR_ENTRY }, true>;
bun_alloc::bss_map_inner! { pub entries_option_map : EntriesOption, preallocate::counts::DIR_ENTRY, true }
pub struct EntriesMap(());
impl EntriesMap {
#[inline]
pub const fn new() -> Self {
Self(())
}
}
pub(crate) struct EntriesGuard {
_lock: bun_threading::MutexGuard,
}
impl EntriesGuard {
#[inline]
#[allow(clippy::mut_from_ref)]
fn map_mut(&self) -> &mut EntriesOptionMap {
unsafe { &mut *entries_option_map() }
}
pub(crate) fn get_or_put(
&self,
key: &[u8],
) -> core::result::Result<allocators::Result, AllocError> {
self.map_mut().get_or_put(key)
}
pub(crate) fn at_index(&self, index: allocators::IndexType) -> Option<*mut EntriesOption> {
let r = self.map_mut().at_index(index)?;
Some(std::ptr::from_mut::<EntriesOption>(r))
}
pub(crate) fn put(
&self,
result: &mut allocators::Result,
value: EntriesOption,
) -> core::result::Result<*mut EntriesOption, AllocError> {
let r = self.map_mut().put(result, value)?;
Ok(std::ptr::from_mut::<EntriesOption>(r))
}
pub(crate) fn mark_not_found(&self, result: allocators::Result) {
self.map_mut().mark_not_found(result)
}
pub(crate) fn remove(&self, key: &[u8]) -> bool {
self.map_mut().remove(key)
}
}
pub struct RealFS {
pub entries_mutex: Mutex,
pub entries: EntriesMap,
pub cwd: &'static [u8], pub file_limit: usize,
pub file_quota: usize,
}
pub(crate) mod limit {
#[cfg(unix)]
pub(crate) static HANDLES: core::sync::atomic::AtomicUsize =
core::sync::atomic::AtomicUsize::new(0);
#[cfg(unix)]
pub(crate) static HANDLES_BEFORE: bun_core::RacyCell<bun_sys::posix::Rlimit> =
bun_core::RacyCell::new(bun_core::ffi::zeroed());
}
thread_local! {
static TEMP_ENTRIES_OPTION: RefCell<core::mem::MaybeUninit<EntriesOption>> =
const { RefCell::new(core::mem::MaybeUninit::uninit()) };
}
impl RealFS {
fn platform_temp_dir_compute() -> &'static [u8] {
if let Some(dir) = env_var::TMPDIR::get_not_empty()
.or_else(env_var::TMP::get_not_empty)
.or_else(env_var::TEMP::get_not_empty)
{
if dir.len() > 1 && dir[dir.len() - 1] == SEP {
return &dir[0..dir.len() - 1];
}
return dir;
}
#[cfg(target_os = "windows")]
{
if let Some(windir) = env_var::SYSTEMROOT::get().or_else(env_var::WINDIR::get) {
let mut v = Vec::new();
write!(
&mut v,
"{}\\Temp",
BStr::new(strings::without_trailing_slash(windir))
)
.expect("oom");
return DirnameStore::instance().append(&v).expect("oom");
}
if let Some(profile) = env_var::HOME::get() {
let mut buf = PathBuffer::uninit();
let parts: [&[u8]; 1] = [b"AppData\\Local\\Temp"];
let out = path_handler::join_abs_string_buf::<platform::Loose>(
profile,
&mut buf[..],
&parts,
);
return DirnameStore::instance().append(out).expect("oom");
}
let mut tmp_buf = PathBuffer::uninit();
let n =
bun_sys::getcwd(&mut tmp_buf[..]).expect("Failed to get cwd for platformTempDir");
let cwd = &tmp_buf[..n];
let root = path_handler::windows_filesystem_root(cwd);
let mut v = Vec::new();
write!(
&mut v,
"{}\\Windows\\Temp",
BStr::new(strings::without_trailing_slash(root))
)
.expect("oom");
return DirnameStore::instance().append(&v).expect("oom");
}
#[cfg(target_os = "macos")]
{
return b"/private/tmp";
}
#[cfg(all(not(target_os = "windows"), not(target_os = "macos")))]
{
#[cfg(target_os = "android")]
{
return b"/data/local/tmp";
}
#[cfg(not(target_os = "android"))]
{
return b"/tmp";
}
}
}
pub fn platform_temp_dir() -> &'static [u8] {
static ONCE: bun_core::Once<&'static [u8]> = bun_core::Once::new();
ONCE.call(Self::platform_temp_dir_compute)
}
pub fn tmpdir_path() -> &'static [u8] {
env_var::BUN_TMPDIR::get_not_empty().unwrap_or_else(Self::platform_temp_dir)
}
pub fn open_tmp_dir(&self) -> Result<bun_sys::Dir, bun_core::Error> {
#[cfg(windows)]
{
return bun_sys::open_dir_at_windows_a(
Fd::INVALID,
Self::tmpdir_path(),
bun_sys::WindowsOpenDirOptions {
iterable: true,
can_rename_or_delete: false,
read_only: true,
..Default::default()
},
)
.map(bun_sys::Dir::from_fd)
.map_err(Into::into);
}
#[cfg(not(windows))]
{
bun_sys::Dir::open(Self::tmpdir_path()).map_err(Into::into)
}
}
#[inline]
pub fn entries_locked(&self) -> EntriesGuard {
EntriesGuard {
_lock: self.entries_mutex.lock_guard(),
}
}
pub fn entries_at(
&mut self,
index: allocators::IndexType,
generation: Generation,
) -> Option<*mut EntriesOption> {
let map = self.entries_locked();
let existing_ptr = map.at_index(index)?;
if let EntriesOption::Entries(entries) = unsafe { &mut *existing_ptr } {
if entries.generation < generation {
let dir_path = entries.dir;
let entries_ptr: *mut DirEntry = &raw mut **entries;
let prev_map_ptr: *mut dir_entry::EntryMap =
unsafe { core::ptr::addr_of_mut!((*entries_ptr).data) };
let handle_dir = match bun_sys::Dir::open(dir_path) {
Ok(h) => h,
Err(err) => {
unsafe { (*prev_map_ptr).clear() };
return Some(
self.read_directory_error(Some(&map), dir_path, err.into())
.expect("unreachable"),
);
}
};
let new_entry = match self.readdir(
false,
false,
Some(unsafe { &mut *prev_map_ptr }),
dir_path,
generation,
&handle_dir,
(),
) {
Ok(e) => e,
Err(err) => {
unsafe { (*prev_map_ptr).clear() };
return Some(
self.read_directory_error(Some(&map), dir_path, err)
.expect("unreachable"),
);
}
};
unsafe {
(*prev_map_ptr).clear();
*entries_ptr = new_entry;
}
}
}
map.at_index(index)
}
pub fn get_default_temp_dir() -> &'static [u8] {
env_var::BUN_TMPDIR::get().unwrap_or_else(Self::platform_temp_dir)
}
pub fn need_to_close_files(&self) -> bool {
if !FeatureFlags::STORE_FILE_DESCRIPTORS {
return true;
}
#[cfg(windows)]
{
return false;
}
#[cfg(not(windows))]
{
!(self.file_limit > 254 && self.file_limit > (MAX_FD.load() as usize + 1) * 2)
}
}
pub fn bust_entries_cache(&mut self, file_path: &[u8]) -> bool {
self.entries_locked().remove(file_path)
}
pub fn adjust_ulimit() -> Result<usize, bun_core::Error> {
#[cfg(not(unix))]
{
return Ok(usize::MAX);
}
#[cfg(unix)]
{
let resource = bun_sys::posix::RlimitResource::NOFILE;
let mut lim = bun_sys::posix::getrlimit(resource)?;
unsafe { limit::HANDLES_BEFORE.write(lim) };
let target = {
#[cfg(target_env = "musl")]
let max = lim.max.max(163840);
#[cfg(not(target_env = "musl"))]
let max = lim.max;
max.min(1 << 20)
};
if lim.cur < target {
let mut raised = lim;
raised.cur = target;
raised.max = lim.max.max(target);
if bun_sys::posix::setrlimit(resource, raised).is_ok() {
lim.cur = raised.cur;
}
}
limit::HANDLES.store(
usize::try_from(lim.cur).expect("int cast"),
core::sync::atomic::Ordering::Relaxed,
);
Ok(usize::try_from(lim.cur).expect("int cast"))
}
}
pub fn init(cwd: &'static [u8]) -> RealFS {
let file_limit = Self::adjust_ulimit().expect("unreachable");
let _ = entries_option_map();
RealFS {
entries_mutex: Mutex::default(),
entries: EntriesMap::new(),
cwd,
file_limit,
file_quota: file_limit,
}
}
}
#[derive(Default, Clone, Copy)]
pub struct ModKey {
pub inode: u64, pub size: u64,
pub mtime: i128,
pub mode: u32, }
thread_local! {
static HASH_NAME_BUF: RefCell<[u8; 1024]> = const { RefCell::new([0u8; 1024]) };
}
impl ModKey {
pub fn hash_name(&self, basename: &[u8]) -> Result<&'static [u8], bun_core::Error> {
let hex_int = self.hash();
HASH_NAME_BUF.with_borrow_mut(|buf| {
let len = buf.len();
let mut cursor = &mut buf[..];
cursor
.write_all(basename)
.map_err(|_| bun_core::err!("NoSpaceLeft"))?;
cursor
.write_all(b"-")
.map_err(|_| bun_core::err!("NoSpaceLeft"))?;
write!(&mut cursor, "{:x}", hex_int).map_err(|_| bun_core::err!("NoSpaceLeft"))?;
let written = len - cursor.len();
Ok(unsafe { bun_ptr::detach_lifetime(&buf[..written]) })
})
}
pub fn hash(&self) -> u64 {
let mut hash_bytes = [0u8; 32];
hash_bytes[0..8].copy_from_slice(&self.size.to_le_bytes());
hash_bytes[8..24].copy_from_slice(&self.mtime.to_le_bytes());
debug_assert!(hash_bytes[24..].len() == 8);
hash_bytes[24..32].copy_from_slice(&0u64.to_ne_bytes());
bun_wyhash::hash(&hash_bytes)
}
pub fn generate(
_: &mut RealFS,
_: &[u8],
file: &bun_sys::File,
) -> Result<ModKey, bun_core::Error> {
let stat = file.stat()?;
const NS_PER_S: i128 = 1_000_000_000;
#[cfg(unix)]
let mtime: i128 = (stat.st_mtime as i128) * NS_PER_S + stat.st_mtime_nsec as i128;
#[cfg(windows)]
let mtime: i128 = (stat.mtim.sec as i128) * NS_PER_S + stat.mtim.nsec as i128;
let seconds = mtime / NS_PER_S;
if seconds == 0 && NS_PER_S == 0 {
return Err(bun_core::err!("Unusable"));
}
let now = bun_core::time::nano_timestamp();
let now_seconds = now / NS_PER_S;
#[allow(clippy::eq_op)]
if seconds > seconds || (seconds == now_seconds && mtime > now) {
return Err(bun_core::err!("Unusable"));
}
Ok(ModKey {
inode: stat.st_ino,
size: stat.st_size as u64,
mtime,
mode: stat.st_mode as u32,
})
}
}
impl RealFS {
pub fn mod_key_with_file(
&mut self,
path: &[u8],
file: &bun_sys::File,
) -> Result<ModKey, bun_core::Error> {
ModKey::generate(self, path, file)
}
pub fn mod_key(&mut self, path: &[u8]) -> Result<ModKey, bun_core::Error> {
let file = bun_sys::open_file(path, bun_sys::OpenFlags::READ_ONLY)?;
self.mod_key_with_file(path, &file)
}
}
pub enum EntriesOption {
Entries(Box<DirEntry>),
Err(dir_entry::Err),
}
unsafe impl Sync for EntriesOption {}
unsafe impl Send for EntriesOption {}
unsafe impl Sync for Entry {}
unsafe impl Send for Entry {}
impl RealFS {
pub fn open_dir(&self, unsafe_dir_string: &[u8]) -> Result<bun_sys::Dir, bun_core::Error> {
#[cfg(windows)]
let dirfd = bun_sys::open_dir_at_windows_a(
Fd::INVALID,
unsafe_dir_string,
bun_sys::WindowsOpenDirOptions {
iterable: true,
no_follow: false,
read_only: true,
..Default::default()
},
);
#[cfg(not(windows))]
let dirfd = bun_sys::open_a(unsafe_dir_string, bun_sys::O::DIRECTORY, 0);
let fd = dirfd?;
Ok(bun_sys::Dir::from_fd(fd))
}
fn readdir<I: DirEntryIterator>(
&mut self,
store_fd: bool,
publish_fd: bool,
prev_map: Option<&mut dir_entry::EntryMap>,
dir_: &'static [u8],
generation: Generation,
handle: &bun_sys::Dir,
iterator: I,
) -> Result<DirEntry, bun_core::Error> {
let handle_fd = handle.fd();
let mut iter = bun_sys::iterate_dir(handle_fd);
let mut dir = DirEntry::init(dir_, generation);
let mut prev_map = prev_map;
if let Some(prev) = prev_map.as_deref() {
dir.data.reserve(prev.len());
}
if store_fd {
FileSystem::set_max_fd(handle_fd.native());
}
if publish_fd {
dir.fd = handle_fd;
}
dir.data
.ensure_unused_capacity(prev_map.as_deref().map(|m| m.count()).unwrap_or(0).max(64))?;
let mut filename_store = FilenameStoreAppender::new();
while let Some(entry_) = iter.next()? {
debug!("readdir entry {}", BStr::new(entry_.name.slice_u8()));
dir.add_entry_with_store(
prev_map.as_deref_mut(),
&entry_,
&mut filename_store,
&iterator,
)?;
}
debug!(
"readdir({}, {}) = {}",
print_handle(handle_fd),
BStr::new(dir_),
dir.data.count()
);
Ok(dir)
}
fn read_directory_error(
&mut self,
entries: Option<&EntriesGuard>,
dir: &[u8],
err: bun_core::Error,
) -> Result<*mut EntriesOption, AllocError> {
if FeatureFlags::ENABLE_ENTRY_CACHE {
let entries = entries.expect("caller holds entries_mutex when ENABLE_ENTRY_CACHE");
let mut get_or_put_result = entries.get_or_put(dir)?;
if err == bun_core::err!("ENOENT") || err == bun_core::err!("FileNotFound") {
entries.mark_not_found(get_or_put_result);
return Ok(TEMP_ENTRIES_OPTION.with_borrow_mut(|slot| {
slot.write(EntriesOption::Err(dir_entry::Err {
original_err: err,
canonical_error: err,
}));
slot.as_mut_ptr()
}));
} else {
let opt = entries.put(
&mut get_or_put_result,
EntriesOption::Err(dir_entry::Err {
original_err: err,
canonical_error: err,
}),
)?;
return Ok(opt);
}
}
Ok(TEMP_ENTRIES_OPTION.with_borrow_mut(|slot| {
slot.write(EntriesOption::Err(dir_entry::Err {
original_err: err,
canonical_error: err,
}));
slot.as_mut_ptr()
}))
}
pub fn read_directory(
&mut self,
dir_: &[u8],
handle_: Option<&bun_sys::Dir>,
generation: Generation,
store_fd: bool,
) -> Result<*mut EntriesOption, bun_core::Error> {
self.read_directory_with_iterator(dir_, handle_, generation, store_fd, ())
}
pub fn read_directory_with_iterator<I: DirEntryIterator>(
&mut self,
dir_maybe_trail_slash: &[u8],
maybe_handle: Option<&bun_sys::Dir>,
generation: Generation,
store_fd: bool,
iterator: I,
) -> Result<*mut EntriesOption, bun_core::Error> {
let dir = strings::paths::without_trailing_slash_windows_path(dir_maybe_trail_slash);
crate::Resolver::assert_valid_cache_key(dir);
let mut cache_result: Option<allocators::Result> = None;
let entries_guard = if FeatureFlags::ENABLE_ENTRY_CACHE {
Some(self.entries_locked())
} else {
None
};
let mut in_place: Option<*mut DirEntry> = None;
if let Some(entries) = entries_guard.as_ref() {
cache_result = Some(entries.get_or_put(dir)?);
let cr = cache_result.as_ref().unwrap();
if cr.has_checked_if_exists() {
if let Some(cached_result) = entries.at_index(cr.index) {
match unsafe { &mut *cached_result } {
EntriesOption::Err(_) => return Ok(cached_result),
EntriesOption::Entries(e) if e.generation >= generation => {
return Ok(cached_result);
}
EntriesOption::Entries(e) => {
in_place = Some(&raw mut **e);
}
}
} else if cr.status == allocators::ItemStatus::NotFound && generation == 0 {
return Ok(TEMP_ENTRIES_OPTION.with_borrow_mut(|slot| {
slot.write(EntriesOption::Err(dir_entry::Err {
original_err: bun_core::err!("ENOENT"),
canonical_error: bun_core::err!("ENOENT"),
}));
slot.as_mut_ptr()
}));
}
}
}
let had_handle = maybe_handle.is_some();
let mut _opened: Option<bun_sys::Dir> = None;
let handle: &bun_sys::Dir = match maybe_handle {
Some(h) => h,
None => match self.open_dir(dir) {
Ok(h) => {
_opened = Some(h);
_opened.as_ref().unwrap()
}
Err(err) => {
return Ok(self.read_directory_error(entries_guard.as_ref(), dir, err)?);
}
},
};
let dir: &'static [u8] = if !had_handle {
if let Some(existing) = in_place {
unsafe { (*existing).dir }
} else {
DirnameStore::instance().append(dir_maybe_trail_slash)?
}
} else {
DirnameStore::instance().append(dir)?
};
let prev = in_place.map(|p| {
unsafe { &mut (*p).data }
});
let publish_fd = store_fd && (had_handle || !self.need_to_close_files());
let mut entries = match self.readdir(
store_fd, publish_fd, prev, dir, generation, handle, iterator,
) {
Ok(e) => e,
Err(err) => {
if let Some(existing) = in_place {
unsafe { (*existing).data.clear() };
}
return Ok(self.read_directory_error(entries_guard.as_ref(), dir, err)?);
}
};
let handle_fd = handle.fd();
if !had_handle && store_fd && !self.need_to_close_files() {
if let Some(d) = _opened.take() {
let _ = d.into_raw();
}
}
if let Some(map) = entries_guard.as_ref() {
if publish_fd && !entries.fd.is_valid() {
entries.fd = handle_fd;
}
let out = match in_place {
Some(p) => {
unsafe {
(*p).data.clear();
*p = entries;
}
let idx = cache_result.as_ref().unwrap().index;
map.at_index(idx)
.expect("in_place entry must exist in BSSMap")
}
None => {
let result = EntriesOption::Entries(Box::new(entries));
map.put(cache_result.as_mut().unwrap(), result)?
}
};
return Ok(out);
}
Ok(TEMP_ENTRIES_OPTION.with_borrow_mut(|slot| {
slot.write(EntriesOption::Entries(Box::new(entries)));
slot.as_mut_ptr()
}))
}
pub fn read_file_with_handle<'p, 'buf, const USE_SHARED_BUFFER: bool, const STREAM: bool>(
&mut self,
path: &'p [u8],
size_: Option<usize>,
file: &bun_sys::File,
shared_buffer: &'buf mut MutableString,
) -> Result<PathContentsPair<'p, 'buf>, bun_core::Error> {
read_file_with_handle_impl::<USE_SHARED_BUFFER, STREAM>(path, size_, file, shared_buffer)
}
pub fn read_file_with_handle_and_allocator<
'p,
'buf,
const USE_SHARED_BUFFER: bool,
const STREAM: bool,
>(
&mut self,
path: &'p [u8],
size_hint: Option<usize>,
file: &bun_sys::File,
shared_buffer: &'buf mut MutableString,
) -> Result<PathContentsPair<'p, 'buf>, bun_core::Error> {
read_file_with_handle_impl::<USE_SHARED_BUFFER, STREAM>(
path,
size_hint,
file,
shared_buffer,
)
}
}
pub fn read_file_contents<'buf>(
file: &bun_sys::File,
path: &[u8],
use_shared_buffer: bool,
shared: &'buf mut MutableString,
stream: bool,
) -> Result<Cow<'buf, [u8]>, bun_core::Error> {
match (use_shared_buffer, stream) {
(true, true) => read_file_with_handle_impl::<true, true>(path, None, file, shared),
(true, false) => read_file_with_handle_impl::<true, false>(path, None, file, shared),
(false, true) => read_file_with_handle_impl::<false, true>(path, None, file, shared),
(false, false) => read_file_with_handle_impl::<false, false>(path, None, file, shared),
}
.map(|p| p.contents)
}
pub fn read_file_contents_in_arena(
file: &bun_sys::File,
path: &[u8],
arena: &bun_alloc::Arena,
) -> Result<(core::ptr::NonNull<u8>, usize), bun_core::Error> {
let _ = path;
FileSystem::set_max_fd(file.handle().native());
let mut initial_buf = [0u8; 16384];
let read_count = file.read_all(&mut initial_buf)?;
if read_count + 1 < initial_buf.len() {
let buf = arena_alloc_uninit_bytes(arena, read_count + 1);
buf[..read_count].copy_from_slice(&initial_buf[..read_count]);
return Ok(finish_arena_contents(arena, buf, read_count));
}
let initial_len = read_count;
let size = file.get_end_pos()?;
debug!("stat({}) = {}", file.handle(), size);
if size == 0 {
return Ok((core::ptr::NonNull::dangling(), 0));
}
let cap = size.max(initial_len);
let buf = arena_alloc_uninit_bytes(arena, cap + 1);
buf[..initial_len].copy_from_slice(&initial_buf[..initial_len]);
let read_count = file.read_all(&mut buf[initial_len..cap])?;
let total = read_count + initial_len;
debug!("read({}, {}) = {}", file.handle(), size, read_count);
Ok(finish_arena_contents(arena, buf, total))
}
#[inline]
#[allow(clippy::mut_from_ref)]
fn arena_alloc_uninit_bytes(arena: &bun_alloc::Arena, len: usize) -> &mut [u8] {
let slot = arena.alloc_uninit_slice::<u8>(len);
unsafe { core::slice::from_raw_parts_mut(slot.as_mut_ptr().cast::<u8>(), len) }
}
#[inline]
fn finish_arena_contents(
arena: &bun_alloc::Arena,
buf: &mut [u8],
mut total: usize,
) -> (core::ptr::NonNull<u8>, usize) {
if let Some(bom) = BOM::detect(&buf[..total]) {
debug!("Convert {} BOM", bom.tag_name());
match bom {
BOM::Utf8 => {
let n = BOM::UTF8_BYTES.len();
buf.copy_within(n..total, 0);
total -= n;
}
other => {
let converted = other.remove_and_convert_to_utf8_and_free(buf[..total].to_vec());
let dst = arena.alloc_slice_fill_copy::<u8>(converted.len() + 1, 0);
dst[..converted.len()].copy_from_slice(&converted);
let ptr = unsafe { core::ptr::NonNull::new_unchecked(dst.as_mut_ptr()) };
return (ptr, converted.len());
}
}
}
debug_assert!(buf.len() > total);
buf[total] = 0;
let ptr = unsafe { core::ptr::NonNull::new_unchecked(buf.as_mut_ptr()) };
(ptr, total)
}
pub fn read_file_with_handle_impl<'p, 'buf, const USE_SHARED_BUFFER: bool, const STREAM: bool>(
path: &'p [u8],
size_hint: Option<usize>,
file: &bun_sys::File,
shared_buffer: &'buf mut MutableString,
) -> Result<PathContentsPair<'p, 'buf>, bun_core::Error> {
FileSystem::set_max_fd(file.handle().native());
let mut file_contents_ptr: *const u8;
let mut file_contents_len: usize;
if USE_SHARED_BUFFER {
shared_buffer.reset();
let mut size = match size_hint {
Some(s) => s,
None => file.get_end_pos()?,
};
debug!("stat({}) = {}", file.handle(), size);
if size == 0 {
if USE_SHARED_BUFFER {
shared_buffer.reset();
return Ok(PathContentsPair {
path: Path::init(path),
contents: Cow::Borrowed(b""),
});
} else {
return Ok(PathContentsPair {
path: Path::init(path),
contents: Cow::Borrowed(b""),
});
}
}
let mut bytes_read: u64 = 0;
shared_buffer.grow_by(size + 1)?;
unsafe { shared_buffer.list.expand_to_capacity() };
loop {
let read_count = file.read_all(&mut shared_buffer.list[bytes_read as usize..])?;
shared_buffer
.list
.truncate(read_count + bytes_read as usize);
file_contents_ptr = shared_buffer.list.as_ptr();
file_contents_len = shared_buffer.list.len();
debug!("read({}, {}) = {}", file.handle(), size, read_count);
if STREAM {
let new_size = file.get_end_pos()?;
bytes_read += read_count as u64;
if read_count == 0 {
break;
}
if (bytes_read as usize) < new_size {
shared_buffer.grow_by(new_size - size)?;
unsafe { shared_buffer.list.expand_to_capacity() };
size = new_size;
continue;
}
}
break;
}
if shared_buffer.list.capacity() > file_contents_len {
unsafe {
*shared_buffer.list.as_mut_ptr().add(file_contents_len) = 0;
}
}
if let Some(bom) = BOM::detect(&shared_buffer.list[..file_contents_len]) {
debug!("Convert {} BOM", bom.tag_name());
shared_buffer.list.truncate(file_contents_len);
let converted = bom.remove_and_convert_to_utf8_without_dealloc(&mut shared_buffer.list);
file_contents_ptr = converted.as_ptr();
file_contents_len = converted.len();
}
} else {
let mut initial_buf = [0u8; 16384];
let initial_read: &[u8] = if size_hint.is_none() {
let buf: &mut [u8] = &mut initial_buf;
let read_count = file.read_all(buf)?;
if read_count + 1 < buf.len() {
let mut allocation: Vec<u8> = Vec::with_capacity(read_count + 1);
allocation.extend_from_slice(&buf[..read_count]);
allocation.push(0);
allocation.truncate(read_count);
if let Some(bom) = BOM::detect(&allocation) {
debug!("Convert {} BOM", bom.tag_name());
allocation = bom.remove_and_convert_to_utf8_and_free(allocation);
}
return Ok(PathContentsPair {
path: Path::init(path),
contents: Cow::Owned(allocation),
});
}
&initial_buf[..read_count]
} else {
&initial_buf[..0]
};
let size = match size_hint {
Some(s) => s,
None => file.get_end_pos()?,
};
debug!("stat({}) = {}", file.handle(), size);
let mut buf: Vec<u8> = Vec::with_capacity(size + 1);
buf.extend_from_slice(initial_read);
if size == 0 {
return Ok(PathContentsPair {
path: Path::init(path),
contents: Cow::Borrowed(b""),
});
}
let tail_len = size + 1 - initial_read.len();
let tail = &mut buf.spare_capacity_mut()[..tail_len];
tail[tail_len - 1].write(0);
let read_count = file.read_all(unsafe {
core::slice::from_raw_parts_mut(tail.as_mut_ptr().cast::<u8>(), tail_len)
})?;
let total = read_count + initial_read.len();
debug!("read({}, {}) = {}", file.handle(), size, read_count);
unsafe { buf.set_len(total) };
if let Some(bom) = BOM::detect(&buf) {
debug!("Convert {} BOM", bom.tag_name());
buf = bom.remove_and_convert_to_utf8_and_free(buf);
}
return Ok(PathContentsPair {
path: Path::init(path),
contents: Cow::Owned(buf),
});
}
debug_assert!(core::ptr::eq(
file_contents_ptr,
shared_buffer.list.as_ptr()
));
let _ = file_contents_ptr;
let file_contents: &'buf [u8] = &shared_buffer.list[..file_contents_len];
Ok(PathContentsPair {
path: Path::init(path),
contents: Cow::Borrowed(file_contents),
})
}
impl RealFS {
pub fn kind(
&mut self,
dir_: &[u8],
base: &[u8],
existing_fd: Fd,
store_fd: bool,
) -> Result<EntryCache, bun_core::Error> {
#[cfg(windows)]
let _ = (existing_fd, store_fd);
let mut cache = EntryCache {
kind: EntryKind::File,
symlink: PathString::EMPTY,
fd: Fd::INVALID,
};
let dir = dir_;
let combo: [&[u8]; 2] = [dir, base];
let mut outpath = PathBuffer::uninit();
let entry_path =
path_handler::join_abs_string_buf::<platform::Auto>(self.cwd, &mut outpath[..], &combo);
let entry_path_len = entry_path.len();
outpath[entry_path_len + 1] = 0;
outpath[entry_path_len] = 0;
let absolute_path_c = ZStr::from_buf(&outpath[..], entry_path_len);
#[cfg(windows)]
{
let file = bun_sys::get_file_attributes(absolute_path_c)
.ok_or(bun_core::err!("FileNotFound"))?;
cache.kind = if file.is_directory {
EntryKind::Dir
} else {
EntryKind::File
};
if !file.is_reparse_point {
return Ok(cache);
}
use bun_sys::windows as w;
let mut wbuf = bun_paths::w_path_buffer_pool::get();
let wpath = strings::paths::to_kernel32_path(&mut *wbuf, absolute_path_c.as_bytes());
let handle = unsafe {
w::kernel32::CreateFileW(
wpath.as_ptr(),
0,
w::FILE_SHARE_READ | w::FILE_SHARE_WRITE | w::FILE_SHARE_DELETE,
core::ptr::null_mut(),
w::OPEN_EXISTING,
w::FILE_FLAG_BACKUP_SEMANTICS,
core::ptr::null_mut(),
)
};
if handle == w::INVALID_HANDLE_VALUE {
return Ok(cache);
}
scopeguard::defer! {
let _ = unsafe { w::CloseHandle(handle) };
}
let mut info: w::BY_HANDLE_FILE_INFORMATION =
unsafe { bun_core::ffi::zeroed_unchecked() };
if unsafe { w::GetFileInformationByHandle(handle, &mut info) } != 0 {
cache.kind = if info.dwFileAttributes & w::FILE_ATTRIBUTE_DIRECTORY != 0 {
EntryKind::Dir
} else {
EntryKind::File
};
}
let mut buf2 = bun_paths::path_buffer_pool::get();
match bun_sys::get_fd_path(Fd::from_native(handle as usize as u64), &mut *buf2) {
bun_sys::Result::Ok(real) => {
cache.symlink = PathString::init(FilenameStore::instance().append(real)?);
}
bun_sys::Result::Err(_) => {}
}
return Ok(cache);
}
#[cfg(not(windows))]
{
let stat = bun_sys::lstat(absolute_path_c)?;
let mut file_kind = bun_sys::kind_from_mode(stat.st_mode as bun_sys::Mode);
let is_symlink = file_kind == bun_sys::FileKind::SymLink;
let mut symlink: &[u8] = b"";
if is_symlink {
let file: Fd = if let Some(valid) = existing_fd.unwrap_valid() {
valid
} else if store_fd {
bun_sys::open_file_absolute_z(absolute_path_c, bun_sys::OpenFlags::READ_ONLY)?
.into_raw()
} else {
#[cfg(any(target_os = "linux", target_os = "android"))]
let flags = bun_sys::O::PATH | bun_sys::O::CLOEXEC | bun_sys::O::NOCTTY;
#[cfg(not(any(target_os = "linux", target_os = "android")))]
let flags = bun_sys::O::RDONLY | bun_sys::O::CLOEXEC | bun_sys::O::NOCTTY;
bun_sys::open(absolute_path_c, flags, 0)?
};
FileSystem::set_max_fd(file.native());
let need_to_close_files = self.need_to_close_files();
let cache_ptr: *mut EntryCache = &raw mut cache;
let _guard = scopeguard::guard(file, move |file| {
if (!store_fd || need_to_close_files) && !existing_fd.is_valid() {
let _ = bun_sys::close(file);
} else if FeatureFlags::STORE_FILE_DESCRIPTORS {
unsafe { (*cache_ptr).fd = file };
}
});
let file_stat = bun_sys::fstat(*_guard)?;
symlink = bun_sys::get_fd_path(*_guard, &mut outpath)?;
file_kind = bun_sys::kind_from_mode(file_stat.st_mode as bun_sys::Mode);
}
debug_assert!(file_kind != bun_sys::FileKind::SymLink);
if file_kind == bun_sys::FileKind::Directory {
cache.kind = EntryKind::Dir;
} else {
cache.kind = EntryKind::File;
}
if !symlink.is_empty() {
cache.symlink = PathString::init(FilenameStore::instance().append(symlink)?);
}
Ok(cache)
}
}
}
impl EntryKindResolver for RealFS {
#[inline(always)]
fn resolve_kind(
&mut self,
dir: &[u8],
base: &[u8],
existing_fd: Fd,
store_fd: bool,
) -> core::result::Result<EntryCache, bun_core::Error> {
self.kind(dir, base, existing_fd, store_fd)
}
}
pub struct PathContentsPair<'a, 'buf> {
pub path: Path<'a>,
pub contents: Cow<'buf, [u8]>,
}
pub(crate) use crate::fs::Path;
thread_local! {
static NORMALIZE_BUF: RefCell<[u8; 1024]> = const { RefCell::new([0u8; 1024]) };
static JOIN_BUF: RefCell<[u8; 1024]> = const { RefCell::new([0u8; 1024]) };
}
pub(crate) struct PrintHandle<T>(pub T);
pub(crate) fn print_handle<T>(handle: T) -> PrintHandle<T> {
PrintHandle(handle)
}
impl core::fmt::Display for PrintHandle<i32> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}", self.0)
}
}
impl core::fmt::Display for PrintHandle<*mut c_void> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{:p}", self.0)
}
}
impl core::fmt::Display for PrintHandle<Fd> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}", self.0)
}
}
#[path = "fs/stat_hash.rs"]
pub mod stat_hash;