Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
46 changes: 30 additions & 16 deletions bt-daemon/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,8 +173,6 @@ pub async fn ensure_daemon(
}

fn spawn_daemon(host: &HostInfo, socket: &Path) -> anyhow::Result<()> {
use std::process::Stdio;

let (exe, rest) = host
.serve_argv
.split_first()
Expand All @@ -188,22 +186,38 @@ fn spawn_daemon(host: &HostInfo, socket: &Path) -> anyhow::Result<()> {
.open(data_dir.join("serve.log"))
.ok();

let mut cmd = crate::subprocess::detached_daemon_command(exe);
cmd.args(rest);
cmd.arg("--socket").arg(socket);
cmd.stdin(Stdio::null());
match log {
Some(f) => {
let f2 = f.try_clone()?;
cmd.stdout(Stdio::from(f));
cmd.stderr(Stdio::from(f2));
}
None => {
cmd.stdout(Stdio::null());
cmd.stderr(Stdio::null());
#[cfg(windows)]
{
let mut args = rest.to_vec();
args.push("--socket".into());
args.push(socket.as_os_str().to_owned());
// A hook's stdout/stderr are pipes owned by the agent. Windows normally
// inherits every inheritable handle, even when the daemon's standard
// streams are redirected to serve.log. The daemon must not keep those
// hook pipes open after the short-lived hook process exits.
crate::subprocess::spawn_detached_daemon(exe, &args, log)?;
}
#[cfg(not(windows))]
{
use std::process::Stdio;

let mut cmd = crate::subprocess::detached_daemon_command(exe);
cmd.args(rest);
cmd.arg("--socket").arg(socket);
cmd.stdin(Stdio::null());
match log {
Some(f) => {
let f2 = f.try_clone()?;
cmd.stdout(Stdio::from(f));
cmd.stderr(Stdio::from(f2));
}
None => {
cmd.stdout(Stdio::null());
cmd.stderr(Stdio::null());
}
}
cmd.spawn()?;
}
cmd.spawn()?;
Ok(())
}

Expand Down
35 changes: 18 additions & 17 deletions bt-daemon/src/subprocess.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@
use std::ffi::OsStr;
use std::process::Command;

#[cfg(windows)]
mod windows_daemon;
#[cfg(windows)]
pub(crate) use windows_daemon::spawn_detached_daemon;

pub(crate) fn background_command(program: impl AsRef<OsStr>) -> Command {
let command = Command::new(program);
#[cfg(windows)]
Expand All @@ -30,30 +35,22 @@ pub(crate) fn interactive_command(program: impl AsRef<OsStr>) -> Command {
Command::new(program)
}

/// Only daemon startup may detach; ordinary background children must use
/// `background_command` so redirected pipes keep their normal semantics.
/// Unix daemon startup detaches from the hook's process group. Windows daemon
/// startup uses `spawn_detached_daemon` to constrain inherited handles.
#[cfg(unix)]
pub(crate) fn detached_daemon_command(program: impl AsRef<OsStr>) -> Command {
let mut command = Command::new(program);
#[cfg(unix)]
{
use std::os::unix::process::CommandExt;
command.process_group(0);
}
#[cfg(windows)]
{
use std::os::windows::process::CommandExt;

const DETACHED_PROCESS: u32 = 0x0000_0008;
const CREATE_NEW_PROCESS_GROUP: u32 = 0x0000_0200;
command.creation_flags(DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP);
}
use std::os::unix::process::CommandExt;
command.process_group(0);
command
}

#[cfg(all(test, windows))]
mod tests {
use super::{background_command, detached_daemon_command, interactive_command};
use super::{background_command, interactive_command};
use std::io::{Read, Write};
use std::os::windows::process::CommandExt;
use std::process::Command;
use std::process::Stdio;

fn has_console() -> bool {
Expand Down Expand Up @@ -117,7 +114,11 @@ mod tests {
assert!(String::from_utf8_lossy(&output.stderr).contains("metadata diagnostic"));
}
_ => {
let output = detached_daemon_command(std::env::current_exe().unwrap())
const DETACHED_PROCESS: u32 = 0x0000_0008;
const CREATE_NEW_PROCESS_GROUP: u32 = 0x0000_0200;
let mut detached = Command::new(std::env::current_exe().unwrap());
detached.creation_flags(DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP);
let output = detached
.args(["--exact", TEST, "--nocapture"])
.env(ROLE, "detached")
.output()
Expand Down
Loading
Loading