@@ -9159,6 +9159,7 @@ dependencies = [
"client",
"clock",
"env_logger",
+ "fork",
"fs",
"futures 0.3.30",
"git",
@@ -9167,6 +9168,7 @@ dependencies = [
"http_client",
"language",
"languages",
+ "libc",
"log",
"lsp",
"node_runtime",
@@ -53,6 +53,10 @@ smol.workspace = true
util.workspace = true
worktree.workspace = true
+[target.'cfg(not(windows))'.dependencies]
+fork.workspace = true
+libc.workspace = true
+
[dev-dependencies]
client = { workspace = true, features = ["test-support"] }
clock = { workspace = true, features = ["test-support"] }
@@ -27,6 +27,7 @@ use smol::io::AsyncReadExt;
use smol::Async;
use smol::{net::unix::UnixListener, stream::StreamExt as _};
+use std::ops::ControlFlow;
use std::{
io::Write,
mem,
@@ -305,10 +306,15 @@ pub fn execute_run(
stdout_socket: PathBuf,
stderr_socket: PathBuf,
) -> Result<()> {
- let log_rx = init_logging_server(log_file)?;
- init_panic_hook();
init_paths()?;
+ match daemonize()? {
+ ControlFlow::Break(_) => return Ok(()),
+ ControlFlow::Continue(_) => {}
+ }
+
+ init_panic_hook();
+ let log_rx = init_logging_server(log_file)?;
log::info!(
"starting up. pid_file: {:?}, stdin_socket: {:?}, stdout_socket: {:?}, stderr_socket: {:?}",
pid_file,
@@ -322,8 +328,6 @@ pub fn execute_run(
let listeners = ServerListeners::new(stdin_socket, stdout_socket, stderr_socket)?;
- log::info!("starting headless gpui app");
-
let git_hosting_provider_registry = Arc::new(GitHostingProviderRegistry::new());
gpui::App::headless().run(move |cx| {
settings::init(cx);
@@ -523,7 +527,8 @@ fn spawn_server(paths: &ServerPaths) -> Result<()> {
}
let binary_name = std::env::current_exe()?;
- let server_process = smol::process::Command::new(binary_name)
+ let mut server_process = std::process::Command::new(binary_name);
+ server_process
.arg("run")
.arg("--log-file")
.arg(&paths.log_file)
@@ -534,12 +539,14 @@ fn spawn_server(paths: &ServerPaths) -> Result<()> {
.arg("--stdout-socket")
.arg(&paths.stdout_socket)
.arg("--stderr-socket")
- .arg(&paths.stderr_socket)
- .spawn()?;
-
- log::info!(
- "proxy spawned server process. PID: {:?}",
- server_process.id()
+ .arg(&paths.stderr_socket);
+
+ let status = server_process
+ .status()
+ .context("failed to launch server process")?;
+ anyhow::ensure!(
+ status.success(),
+ "failed to launch and detach server process"
);
let mut total_time_waited = std::time::Duration::from_secs(0);
@@ -745,3 +752,43 @@ fn read_proxy_settings(cx: &mut ModelContext<'_, HeadlessProject>) -> Option<Uri
.or_else(read_proxy_from_env);
proxy_url
}
+
+fn daemonize() -> Result<ControlFlow<()>> {
+ match fork::fork().map_err(|e| anyhow::anyhow!("failed to call fork with error code {}", e))? {
+ fork::Fork::Parent(_) => {
+ return Ok(ControlFlow::Break(()));
+ }
+ fork::Fork::Child => {}
+ }
+
+ // Once we've detached from the parent, we want to close stdout/stderr/stdin
+ // so that the outer SSH process is not attached to us in any way anymore.
+ unsafe { redirect_standard_streams() }?;
+
+ Ok(ControlFlow::Continue(()))
+}
+
+unsafe fn redirect_standard_streams() -> Result<()> {
+ let devnull_fd = libc::open(b"/dev/null\0" as *const [u8; 10] as _, libc::O_RDWR);
+ anyhow::ensure!(devnull_fd != -1, "failed to open /dev/null");
+
+ let process_stdio = |name, fd| {
+ let reopened_fd = libc::dup2(devnull_fd, fd);
+ anyhow::ensure!(
+ reopened_fd != -1,
+ format!("failed to redirect {} to /dev/null", name)
+ );
+ Ok(())
+ };
+
+ process_stdio("stdin", libc::STDIN_FILENO)?;
+ process_stdio("stdout", libc::STDOUT_FILENO)?;
+ process_stdio("stderr", libc::STDERR_FILENO)?;
+
+ anyhow::ensure!(
+ libc::close(devnull_fd) != -1,
+ "failed to close /dev/null fd after redirecting"
+ );
+
+ Ok(())
+}