tab_api/
launch.rs

1//! Launches `tab-daemon` and `tab-pty` processes.
2//! The initial launch occurs in the `tab-cli`, using the currently running executible id.
3//! `tab` exposes a hidden `tab --_launch [daemon|pty]` argument, which is used here to launch associated services.
4
5use crate::{
6    config::{is_running, load_daemon_file, DaemonConfig},
7    env::is_raw_mode,
8    log::get_level_str,
9};
10use anyhow::{bail, Context};
11use lifeline::prelude::*;
12use log::*;
13use nix::{
14    sys::wait::{waitpid, WaitStatus},
15    unistd::{fork, setsid, ForkResult},
16};
17use std::{
18    path::Path,
19    process::Stdio,
20    time::{Duration, Instant},
21};
22use tokio::{process::Command, select, signal::ctrl_c, time};
23
24/// Launches a new daemon process (if it is not already running), and waits until it is ready for websocket connections.
25pub async fn launch_daemon() -> anyhow::Result<DaemonConfig> {
26    let exec = std::env::current_exe()?;
27    let daemon_file = load_daemon_file()?;
28
29    let running = daemon_file
30        .as_ref()
31        .map(|config| is_running(config))
32        .unwrap_or(false);
33
34    let start_wait = Instant::now();
35    if !running {
36        debug!("launching `tab-daemon` at {}", &exec.to_string_lossy());
37
38        if let Err(e) = fork_launch_daemon(exec.as_path()) {
39            warn!("Failed to launch daemon as a detached process.  Falling back to child process.  Cause: {}", e);
40            spawn_daemon(exec.as_path())
41                .context("Failed to launch daemon during child process fallback")?;
42        }
43    }
44
45    let timeout_duration = Duration::from_secs(2);
46
47    let mut index = 0usize;
48    let daemon_file = loop {
49        if let Some(daemon_file) = load_daemon_file()? {
50            if is_running(&daemon_file) {
51                break daemon_file;
52            }
53        }
54
55        time::delay_for(Duration::from_millis(50)).await;
56        if Instant::now().duration_since(start_wait) > timeout_duration {
57            return Err(anyhow::Error::msg("timeout while waiting for tab daemon"));
58        }
59
60        if index == 1 {
61            info!("waiting for daemon...");
62        }
63
64        index += 1;
65    };
66
67    Ok(daemon_file)
68}
69
70/// Launches a new PTY process, which will connect to the running daemon.
71pub fn launch_pty() -> anyhow::Result<()> {
72    let exec = std::env::current_exe().context("failed to get current executable")?;
73    debug!("launching `tab-pty` at {}", &exec.to_string_lossy());
74
75    let mut child = Command::new(exec);
76    child
77        .args(&[
78            "--_launch",
79            "pty",
80            "--log",
81            get_level_str().unwrap_or("info"),
82        ])
83        .stdin(Stdio::null())
84        .stdout(Stdio::null())
85        .stderr(Stdio::inherit())
86        .kill_on_drop(false);
87
88    crate::env::forward_env(&mut child);
89
90    let _child = child.spawn().context("failed to spawn child process")?;
91
92    Ok(())
93}
94
95/// Waits for either a ctrl-c signal, or a message on the given channel.
96///
97/// Useful in main() functions.
98pub async fn wait_for_shutdown<T: Default>(mut receiver: impl Receiver<T>) -> T {
99    loop {
100        select! {
101            _ = ctrl_c() => {
102                return T::default();
103            },
104            msg = receiver.recv() => {
105                return msg.unwrap_or(T::default());
106            }
107        }
108    }
109}
110
111fn fork_launch_daemon(exec: &Path) -> anyhow::Result<()> {
112    debug!("Forking the process to launch the daemon.");
113    match unsafe { fork()? } {
114        ForkResult::Parent { child } => {
115            let result = waitpid(Some(child), None)?;
116            if let WaitStatus::Exited(_pid, code) = result {
117                if code != 0 {
118                    bail!("Forked process exited with code {}", code);
119                }
120            }
121        }
122        ForkResult::Child => {
123            let result: anyhow::Result<()> = {
124                setsid()?;
125                spawn_daemon(exec)?;
126                Ok(())
127            };
128
129            let exit_code = result.map(|_| 0i32).unwrap_or(1i32);
130            std::process::exit(exit_code);
131        }
132    }
133
134    Ok(())
135}
136
137fn spawn_daemon(exec: &Path) -> anyhow::Result<()> {
138    // because this is invoked in the forked process, we cannot use tokio
139    let mut child = std::process::Command::new(exec);
140
141    child
142        .args(&[
143            "--_launch",
144            "daemon",
145            "--log",
146            get_level_str().unwrap_or("info"),
147        ])
148        .stdin(Stdio::null())
149        .stdout(Stdio::null());
150
151    if is_raw_mode() {
152        child.stderr(Stdio::null());
153    } else {
154        child.stderr(Stdio::inherit());
155    }
156
157    crate::env::forward_env_std(&mut child);
158
159    let _child = child.spawn()?;
160    Ok(())
161}