Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/bun_core/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2738,6 +2738,12 @@ pub mod ffi {
#[cfg(windows)]
unsafe impl Zeroable for bun_windows_sys::externs::FILE_BASIC_INFORMATION {}
#[cfg(windows)]
unsafe impl Zeroable for bun_windows_sys::externs::FILE_ALL_INFORMATION {}
#[cfg(windows)]
unsafe impl Zeroable for bun_windows_sys::externs::FILE_FS_DEVICE_INFORMATION {}
#[cfg(windows)]
unsafe impl Zeroable for bun_windows_sys::externs::FILE_FS_VOLUME_INFORMATION {}
#[cfg(windows)]
unsafe impl Zeroable for bun_windows_sys::externs::BY_HANDLE_FILE_INFORMATION {}
#[cfg(windows)]
unsafe impl Zeroable for bun_windows_sys::externs::WIN32_FILE_ATTRIBUTE_DATA {}
Expand Down
1 change: 1 addition & 0 deletions src/libuv_sys/libuv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2253,6 +2253,7 @@ unsafe impl bun_core::ffi::Zeroable for uv_fs_event_t {}
unsafe impl bun_core::ffi::Zeroable for uv_getaddrinfo_t {}
unsafe impl bun_core::ffi::Zeroable for uv_tty_t {}
unsafe impl bun_core::ffi::Zeroable for fs_t {}
unsafe impl bun_core::ffi::Zeroable for uv_stat_t {}
impl ReturnCode {
pub const ZERO: ReturnCode = ReturnCode(0);
#[inline]
Expand Down
167 changes: 141 additions & 26 deletions src/sys/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3763,14 +3763,138 @@ mod windows_impl {
sys_uv::stat(path)
}
pub fn fstat(fd: Fd) -> Maybe<Stat> {
// sys_uv::fstat does `fd.uv()` which PANICS for HANDLE-backed
// (`FdKind::System`) Fds — i.e. the result of `openat()`. Convert
// via `_open_osfhandle` first (acknowledged CRT-fd leak —
// a leak is strictly better than a guaranteed panic).
let uvfd = fd
.make_libuv_owned()
.map_err(|_| Error::new(E::EMFILE, Tag::uv_open_osfhandle).with_fd(fd))?;
sys_uv::fstat(uvfd)
if fd.kind() == FdKind::Uv {
return sys_uv::fstat(fd);
}
// HANDLE-backed (`FdKind::System`, e.g. `openat()` results): stat the
// HANDLE directly instead of allocating a throwaway CRT fd via
// `_open_osfhandle` (which cannot be `_close`d without also closing
// the caller's HANDLE, so it leaked a CRT slot per call).
fstat_handle(fd)
}
/// Port of libuv's `fs__fstat_handle` + `fs__stat_handle` +
/// `fs__stat_assign_statbuf` (`src/win/fs.c`). Fills a `uv_stat_t` from a
/// raw HANDLE without touching the CRT fd table.
fn fstat_handle(fd: Fd) -> Maybe<Stat> {
use bun_core::S;
let handle = fd.native();
let nt_err = |rc: w::NTSTATUS| {
Error::new(w::translate_nt_status_to_errno(rc), Tag::fstat).with_fd(fd)
};
let mut st: Stat = bun_core::ffi::zeroed();

// Dispatch on handle type; pipes and consoles get a synthetic stat.
let file_type = w::GetFileType(handle);
if file_type == w::FILE_TYPE_PIPE {
st.st_mode = S::IFIFO as u64;
st.st_nlink = 1;
st.st_rdev = (w::FILE_DEVICE_NAMED_PIPE as u64) << 16;
st.st_ino = handle as usize as u64;
return Ok(st);
}
if file_type == w::FILE_TYPE_CHAR {
let mut mode: w::DWORD = 0;
// SAFETY: FFI; `handle` is a valid HANDLE, `mode` valid for write.
if unsafe { w::kernel32::GetConsoleMode(handle, &mut mode) } != 0 {
st.st_mode = S::IFCHR as u64;
st.st_nlink = 1;
st.st_rdev = (w::FILE_DEVICE_CONSOLE as u64) << 16;
st.st_ino = handle as usize as u64;
return Ok(st);
}
// Non-console char device (NUL, COM1, ...): fall through to the
// disk path, which special-cases `FILE_DEVICE_NULL`.
} else if file_type != w::FILE_TYPE_DISK {
return Err(Error::new(E::EBADF, Tag::fstat).with_fd(fd));
}

let mut io: w::IO_STATUS_BLOCK = bun_core::ffi::zeroed();

let mut device_info: w::FILE_FS_DEVICE_INFORMATION = bun_core::ffi::zeroed();
// SAFETY: FFI; `handle` valid, output buffers valid for write.
let rc = unsafe {
w::ntdll::NtQueryVolumeInformationFile(
handle,
&mut io,
core::ptr::from_mut(&mut device_info).cast(),
core::mem::size_of::<w::FILE_FS_DEVICE_INFORMATION>() as u32,
w::FS_INFORMATION_CLASS::FileFsDeviceInformation,
)
};
if w::NT_ERROR(rc) {
return Err(nt_err(rc));
}
if device_info.DeviceType == w::FILE_DEVICE_NULL {
st.st_mode = (S::IFCHR | S::IRUSR | S::IWUSR) as u64;
st.st_mode |= (st.st_mode & 0o700) >> 3 | (st.st_mode & 0o700) >> 6;
st.st_nlink = 1;
st.st_blksize = 4096;
st.st_rdev = (w::FILE_DEVICE_NULL as u64) << 16;
return Ok(st);
}

let mut file_info: w::FILE_ALL_INFORMATION = bun_core::ffi::zeroed();
// SAFETY: FFI; `handle` valid, output buffers valid for write.
// STATUS_BUFFER_OVERFLOW (variable-length name truncated) is expected
// and is a warning, not an error; `NT_ERROR` excludes it.
let rc = unsafe {
w::ntdll::NtQueryInformationFile(
handle,
&mut io,
core::ptr::from_mut(&mut file_info).cast(),
core::mem::size_of::<w::FILE_ALL_INFORMATION>() as u32,
w::FILE_INFORMATION_CLASS::FileAllInformation,
)
};
if w::NT_ERROR(rc) {
return Err(nt_err(rc));
}

let mut volume_info: w::FILE_FS_VOLUME_INFORMATION = bun_core::ffi::zeroed();
// SAFETY: FFI; `handle` valid, output buffers valid for write.
let rc = unsafe {
w::ntdll::NtQueryVolumeInformationFile(
handle,
&mut io,
core::ptr::from_mut(&mut volume_info).cast(),
core::mem::size_of::<w::FILE_FS_VOLUME_INFORMATION>() as u32,
w::FS_INFORMATION_CLASS::FileFsVolumeInformation,
)
};
if rc == w::NTSTATUS::NOT_IMPLEMENTED {
st.st_dev = 0;
} else if w::NT_ERROR(rc) {
return Err(nt_err(rc));
} else {
st.st_dev = volume_info.VolumeSerialNumber as u64;
}

// libuv's `S_IFLNK` arm is gated on `do_lstat`, which is always 0 on
// the fstat path, so reparse points fall through to DIR-or-REG.
let attrs = file_info.BasicInformation.FileAttributes;
if attrs & w::FILE_ATTRIBUTE_DIRECTORY != 0 {
st.st_mode = S::IFDIR as u64;
st.st_size = 0;
} else {
st.st_mode = S::IFREG as u64;
st.st_size = file_info.StandardInformation.EndOfFile as u64;
}
if attrs & w::FILE_ATTRIBUTE_READONLY != 0 {
st.st_mode |= S::IRUSR as u64;
} else {
st.st_mode |= (S::IRUSR | S::IWUSR) as u64;
}
st.st_mode |= (st.st_mode & 0o700) >> 3 | (st.st_mode & 0o700) >> 6;

st.atim = w::filetime_to_timespec(file_info.BasicInformation.LastAccessTime);
st.mtim = w::filetime_to_timespec(file_info.BasicInformation.LastWriteTime);
st.ctim = w::filetime_to_timespec(file_info.BasicInformation.ChangeTime);
st.birthtim = w::filetime_to_timespec(file_info.BasicInformation.CreationTime);
st.st_ino = file_info.InternalInformation.IndexNumber as u64;
st.st_nlink = file_info.StandardInformation.NumberOfLinks as u64;
st.st_blocks = (file_info.StandardInformation.AllocationSize as u64) >> 9;
st.st_blksize = 4096;
Ok(st)
}
pub fn lstat(path: &ZStr) -> Maybe<Stat> {
sys_uv::lstat(path)
Expand Down Expand Up @@ -4206,24 +4330,15 @@ mod windows_impl {
}
}
pub fn futimens(fd: Fd, atime: TimeLike, mtime: TimeLike) -> Maybe<()> {
// `uv_fs_futime` takes a CRT
// fd, and `fd.uv()` PANICS for HANDLE-backed (`FdKind::System`) Fds.
// Convert via `make_libuv_owned()` first (passes uv-backed Fds through
// unchanged) so an `openat()` result no longer crashes.
let uvfd = fd
.make_libuv_owned()
.map_err(|_| Error::new(E::EMFILE, Tag::uv_open_osfhandle).with_fd(fd))?;
let a = atime.sec as f64 + atime.nsec as f64 / 1e9;
let m = mtime.sec as f64 + mtime.nsec as f64 / 1e9;
let mut req = uv::fs_t::uninitialized();
let rc =
unsafe { uv::uv_fs_futime(core::ptr::null_mut(), &mut req, uvfd.uv(), a, m, None) };
// fs_t has no Drop impl; uv_fs_req_cleanup
// must run before any return (fd-based, so no path buffer is captured,
// but keep the pattern uniform with utimens/lutimens below).
req.deinit();
if let Some(err) = Error::from_uv_rc(rc, Tag::futimens) {
return Err(err.with_fd(fd));
// `uv_fs_futime` takes a CRT fd (`fd.uv()` PANICS for HANDLE-backed
// `FdKind::System` fds); `SetFileTime` operates on the HANDLE
// directly. `fd.native()` yields the HANDLE for both kinds.
let a = w::timespec_to_filetime(atime);
let m = w::timespec_to_filetime(mtime);
// SAFETY: FFI; `fd.native()` is a valid HANDLE, `a`/`m` valid for read.
let rc = unsafe { w::kernel32::SetFileTime(fd.native(), core::ptr::null(), &a, &m) };
if rc == 0 {
return Err(Error::new(w::get_last_errno(), Tag::futimens).with_fd(fd));
}
Ok(())
}
Expand Down
40 changes: 40 additions & 0 deletions src/sys/windows/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@
pub use bun_windows_sys::LPCWSTR;
pub use bun_windows_sys::LPSTR;
pub use bun_windows_sys::LPWSTR;
pub use bun_windows_sys::NT_ERROR;
pub use bun_windows_sys::NT_SUCCESS;
pub use bun_windows_sys::NTSTATUS;
pub use bun_windows_sys::PWSTR;
Expand All @@ -139,6 +140,7 @@
pub use bun_windows_sys::FILETIME;

pub use bun_windows_sys::DUPLICATE_SAME_ACCESS;
pub use bun_windows_sys::FILE_ALL_INFORMATION;
pub use bun_windows_sys::FILE_ATTRIBUTE_ARCHIVE;
pub use bun_windows_sys::FILE_ATTRIBUTE_COMPRESSED;
pub use bun_windows_sys::FILE_ATTRIBUTE_DEVICE;
Expand All @@ -153,8 +155,13 @@
pub use bun_windows_sys::FILE_ATTRIBUTE_SYSTEM;
pub use bun_windows_sys::FILE_ATTRIBUTE_TEMPORARY;
pub use bun_windows_sys::FILE_BASIC_INFORMATION;
pub use bun_windows_sys::FILE_DEVICE_CONSOLE;
pub use bun_windows_sys::FILE_DEVICE_NAMED_PIPE;
pub use bun_windows_sys::FILE_DEVICE_NULL;
pub use bun_windows_sys::FILE_DIRECTORY_FILE;
pub use bun_windows_sys::FILE_DIRECTORY_INFORMATION;
pub use bun_windows_sys::FILE_FS_DEVICE_INFORMATION;
pub use bun_windows_sys::FILE_FS_VOLUME_INFORMATION;
pub use bun_windows_sys::FILE_INFO_BY_HANDLE_CLASS;
pub use bun_windows_sys::FILE_INFORMATION_CLASS;
pub use bun_windows_sys::FILE_NON_DIRECTORY_FILE;
Expand All @@ -165,6 +172,7 @@
pub use bun_windows_sys::FILE_SHARE_WRITE;
pub use bun_windows_sys::FILE_SYNCHRONOUS_IO_NONALERT;
pub use bun_windows_sys::FILE_WRITE_THROUGH;
pub use bun_windows_sys::FS_INFORMATION_CLASS;
pub use bun_windows_sys::IO_STATUS_BLOCK;
pub use bun_windows_sys::OBJECT_ATTRIBUTES;
pub use bun_windows_sys::STANDARD_RIGHTS_READ;
Expand Down Expand Up @@ -204,6 +212,38 @@
(nt_time as i128 + WINDOWS_EPOCH_TO_UNIX_EPOCH_100NS) * 100
}

/// 1601-01-01 → 1970-01-01 offset in 100-ns ticks.
pub const EPOCH_DIFFERENCE_100NS: i64 = 11_644_473_600 * 10_000_000;

Check warning on line 216 in src/sys/windows/mod.rs

View check run for this annotation

Claude / Claude Code Review

Duplicated 1601→1970 epoch-offset constant

The new `pub const EPOCH_DIFFERENCE_100NS` duplicates the function-local `WINDOWS_EPOCH_TO_UNIX_EPOCH_100NS` five lines above inside `from_sys_time` — same fact (1601→1970 offset in 100-ns ticks), opposite sign, different width. Now that a module-level constant exists, `from_sys_time` could derive from it (`(nt_time as i128 - EPOCH_DIFFERENCE_100NS as i128) * 100`) and drop the private duplicate.
Comment thread
robobun marked this conversation as resolved.
Resolved

/// Convert a 64-bit Windows `FILETIME` (100-ns ticks since 1601-01-01 UTC)
/// into a libuv `uv_timespec_t` (seconds + nanoseconds since the Unix epoch).
/// Matches libuv's `uv__filetime_to_timespec`.
#[inline]
pub fn filetime_to_timespec(filetime: i64) -> bun_libuv_sys::uv_timespec_t {
let t = filetime - EPOCH_DIFFERENCE_100NS;
let mut sec = t / 10_000_000;
let mut nsec = (t - sec * 10_000_000) * 100;
if nsec < 0 {
sec -= 1;
nsec += 1_000_000_000;
}
bun_libuv_sys::uv_timespec_t {
sec: sec as _,
nsec: nsec as _,
}
}

/// Convert a [`TimeLike`](crate::TimeLike) (seconds + nanoseconds since the
/// Unix epoch) into a Windows `FILETIME`.
#[inline]
pub fn timespec_to_filetime(t: crate::TimeLike) -> FILETIME {
let ticks = (t.sec as i64 * 10_000_000 + t.nsec as i64 / 100 + EPOCH_DIFFERENCE_100NS) as u64;
FILETIME {
dwLowDateTime: ticks as u32,
dwHighDateTime: (ticks >> 32) as u32,
}
}

pub const INVALID_FILE_ATTRIBUTES: u32 = u32::MAX;

pub const NT_OBJECT_PREFIX: [u16; 4] = [b'\\' as u16, b'?' as u16, b'?' as u16, b'\\' as u16];
Expand Down
Loading
Loading