Last active
October 29, 2025 09:47
-
-
Save fengmk2/323b5e15c13d7c9d0c95c6c3cf34730d to your computer and use it in GitHub Desktop.
libuv may mark stdin/stdout/stderr as close-on-exec, which interferes with Rust's subprocess spawning.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #[cfg(unix)] | |
| fn fix_stdio_streams() { | |
| // libuv may mark stdin/stdout/stderr as close-on-exec, which interferes with Rust's subprocess spawning. | |
| // As a workaround, we clear the FD_CLOEXEC flag on these file descriptors to prevent them | |
| // from being closed when spawning child processes. | |
| // | |
| // For details see https://github.com/libuv/libuv/issues/2062 | |
| // Fixed by reference from https://github.com/electron/electron/pull/15555 | |
| use std::os::fd::BorrowedFd; | |
| use nix::{ | |
| fcntl::{FcntlArg, FdFlag, fcntl}, | |
| libc::{STDERR_FILENO, STDIN_FILENO, STDOUT_FILENO}, | |
| }; | |
| // Safe function to clear FD_CLOEXEC flag | |
| fn clear_cloexec(fd: BorrowedFd<'_>) { | |
| // Borrow RawFd as BorrowedFd to satisfy AsFd constraint | |
| if let Ok(flags) = fcntl(fd, FcntlArg::F_GETFD) { | |
| let mut fd_flags = FdFlag::from_bits_retain(flags); | |
| if fd_flags.contains(FdFlag::FD_CLOEXEC) { | |
| fd_flags.remove(FdFlag::FD_CLOEXEC); | |
| // Ignore errors: some fd may be closed | |
| let _ = fcntl(fd, FcntlArg::F_SETFD(fd_flags)); | |
| } | |
| } | |
| } | |
| // Clear FD_CLOEXEC on stdin, stdout, stderr | |
| clear_cloexec(unsafe { BorrowedFd::borrow_raw(STDIN_FILENO) }); | |
| clear_cloexec(unsafe { BorrowedFd::borrow_raw(STDOUT_FILENO) }); | |
| clear_cloexec(unsafe { BorrowedFd::borrow_raw(STDERR_FILENO) }); | |
| } | |
| // TODO: should move to vite-command crate later | |
| /// Run a command with the given bin name, arguments, environment variables, and current working directory. | |
| /// | |
| /// # Arguments | |
| /// | |
| /// * `bin_name`: The name of the binary to run. | |
| /// * `args`: The arguments to pass to the binary. | |
| /// * `envs`: The custom environment variables to set for the command, will be merged with the system environment variables. | |
| /// * `cwd`: The current working directory for the command. | |
| /// | |
| /// # Returns | |
| /// | |
| /// Returns the exit status of the command. | |
| pub(crate) async fn run_command( | |
| bin_name: &str, | |
| args: &[String], | |
| envs: &HashMap<String, String>, | |
| cwd: impl AsRef<AbsolutePath>, | |
| ) -> Result<ExitStatus, Error> { | |
| println!("Running: {} {}", bin_name, args.join(" ")); | |
| let mut cmd = Command::new(bin_name); | |
| cmd.args(args) | |
| .envs(envs) | |
| .current_dir(cwd.as_ref()) | |
| .stdin(Stdio::inherit()) | |
| .stdout(Stdio::inherit()) | |
| .stderr(Stdio::inherit()); | |
| // fix stdio streams on unix | |
| #[cfg(unix)] | |
| unsafe { | |
| cmd.pre_exec(|| { | |
| fix_stdio_streams(); | |
| Ok(()) | |
| }); | |
| } | |
| let status = cmd.status().await?; | |
| Ok(status) | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment