Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
3 changes: 3 additions & 0 deletions src/bun_core/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ pub enum Error {
AccessDenied,
#[error("WriteFailed")]
WriteFailed,
#[error("CurrentWorkingDirectoryUnlinked")]
CurrentWorkingDirectoryUnlinked,
#[error(transparent)]
Alloc(#[from] bun_alloc::AllocError),
}
Expand All @@ -45,6 +47,7 @@ impl Error {
Self::FileNotFound => "FileNotFound",
Self::AccessDenied => "AccessDenied",
Self::WriteFailed => "WriteFailed",
Self::CurrentWorkingDirectoryUnlinked => "CurrentWorkingDirectoryUnlinked",
Self::Alloc(_) => "OutOfMemory",
}
}
Expand Down
3 changes: 3 additions & 0 deletions src/bun_core/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4165,6 +4165,9 @@ fn getcwd_len(buf: &mut PathBuffer) -> crate::CrateResult<usize> {
unsafe {
let p = libc::getcwd(buf.0.as_mut_ptr().cast(), buf.0.len());
if p.is_null() {
if crate::ffi::errno() == libc::ENOENT {
return Err(crate::CrateError::CurrentWorkingDirectoryUnlinked);
}
return Err(crate::CrateError::Unexpected);
}
Ok(libc::strlen(p))
Expand Down
3 changes: 3 additions & 0 deletions src/resolver/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ impl bun_core::output::ErrName for Error {

impl From<bun_sys::Error> for Error {
fn from(e: bun_sys::Error) -> Self {
if let Some(named) = e.to_named_core_err() {
return Self::Core(named);
}
Self::Sys(e.into())
}
}
Expand Down
3 changes: 3 additions & 0 deletions src/runtime/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,9 @@ pub enum Error {

impl From<bun_sys::Error> for Error {
fn from(e: bun_sys::Error) -> Self {
if let Some(named) = e.to_named_core_err() {
return Self::Core(named);
}
Self::Sys(e.into())
}
}
Expand Down
7 changes: 7 additions & 0 deletions src/sys/Error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,13 @@ impl Error {
self.resolve_system_errno().unwrap_or(SystemErrno::EIO)
}

pub fn to_named_core_err(&self) -> Option<bun_core::Error> {
if self.syscall == Tag::getcwd && self.get_errno() == E::ENOENT {
return Some(bun_core::Error::CurrentWorkingDirectoryUnlinked);
}
None
}

/// 1. Convert libuv errno values into libc ones.
/// 2. Get the tag name as a string for printing.
pub fn get_error_code_tag_name(&self) -> Option<(&'static str, SystemErrno)> {
Expand Down
4 changes: 1 addition & 3 deletions test/bundler/bun-build-compile.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -758,10 +758,8 @@ describe("compiled binary in a deleted cwd", () => {
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);

// The entry never runs (VM init aborts first), the ENOENT surfaces, and the
// process exits 1 — a crash would terminate via a signal, never exit 1.
expect(stdout).toBe("");
expect(stderr).toContain("ENOENT");
expect(stderr).toContain("The current working directory was deleted");
expect(exitCode).toBe(1);
},
60_000,
Expand Down
40 changes: 40 additions & 0 deletions test/cli/run/run-crash-handler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,46 @@ describe.if(isPosix)("terminal signal reflects the crash cause", () => {
});
});

// POSIX-only: Windows refuses to remove a directory that is any process's cwd.
describe.if(isPosix)("cwd deleted before startup", () => {
test.concurrent.each(["install", "test"])("bun %s prints the cwd-deleted hint", async cmd => {
using dir = tempDir("cwd-unlinked", {});
const gone = String(dir);

await using proc = Bun.spawn({
cmd: ["/bin/sh", "-c", `cd "${gone}" && rmdir "${gone}" && exec "${bunExe()}" '${cmd}'`],
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);

expect({ stdout, stderr, exitCode }).toEqual({
stdout: "",
stderr: expect.stringContaining("The current working directory was deleted"),
exitCode: 1,
});
expect(stderr).not.toContain("Bun could not find a file");
});

test.concurrent("bun -e boots via the exe-dir fallback instead", async () => {
using dir = tempDir("cwd-unlinked-run", {});
const gone = String(dir);

await using proc = Bun.spawn({
cmd: ["/bin/sh", "-c", `cd "${gone}" && rmdir "${gone}" && exec "${bunExe()}" -e 'console.log(1)'`],
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);

expect(stdout).toBe("1\n");
expect(stderr).toBe("");
expect(exitCode).toBe(0);
});
});

// Windows: the VEH handler must walk the stack from the fault CONTEXT record
// (RtlVirtualUnwind), not from inside the handler. When the fault is in an
// external DLL the old RtlCaptureStackBackTrace path could stop at
Expand Down