1use std::path::Path;
9use std::process::Stdio;
10
11use anyhow::{Context, Result};
12use tokio::fs::OpenOptions;
13use tokio::process::{Child, Command};
14
15fn respawn_args(verb: &[&str], addr: &str, config_path: Option<&Path>) -> Result<Vec<String>> {
17 let mut args: Vec<String> = verb.iter().map(|s| (*s).to_owned()).collect();
18 args.push("--device".to_owned());
19 args.push(addr.to_owned());
20 if let Some(p) = config_path {
21 args.push("--config".to_owned());
22 args.push(p.to_str().context("config path is not valid UTF-8")?.to_owned());
23 }
24 Ok(args)
25}
26
27async fn open_log(log_path: &Path) -> Result<std::fs::File> {
29 if let Some(parent) = log_path.parent() {
30 tokio::fs::create_dir_all(parent).await.context("creating log directory")?;
31 }
32 let mut open_opts = OpenOptions::new();
33 open_opts.create(true).write(true).truncate(true);
34 #[cfg(unix)]
35 open_opts.mode(0o600);
36 Ok(open_opts
37 .open(log_path)
38 .await
39 .with_context(|| format!("opening log file: {}", log_path.display()))?
40 .into_std()
41 .await)
42}
43
44pub async fn respawn_self(
55 verb: &[&str],
56 addr: &str,
57 config_path: Option<&Path>,
58 log_path: &Path,
59 capture_stdout: bool,
60 detach: bool,
61) -> Result<Child> {
62 let args = respawn_args(verb, addr, config_path)?;
63 let log_file = open_log(log_path).await?;
64 let exe = std::env::current_exe().context("resolving current executable path")?;
65
66 let mut cmd = Command::new(exe);
67 cmd.args(args);
68 cmd.stdin(Stdio::null());
69 cmd.stdout(if capture_stdout {
70 Stdio::from(log_file.try_clone().context("duplicating log file handle")?)
71 } else {
72 Stdio::null()
73 });
74 cmd.stderr(Stdio::from(log_file));
75 #[cfg(unix)]
76 if detach {
77 cmd.process_group(0);
78 }
79 cmd.spawn().context("spawning detached subprocess")
80}
81
82#[cfg(test)]
83mod tests;