1use 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
24pub 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
70pub 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
95pub 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 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}