diff --git a/src/main.rs b/src/main.rs index 7cbe50d..47bb9ef 100644 --- a/src/main.rs +++ b/src/main.rs @@ -74,6 +74,12 @@ fn cmd_start(name: &str, config: &Config, config_path: &str) { eprintln!("error: service '{}' not found in config", name); std::process::exit(1); } + let state = State::new(); + let svc_pid = state.service_pid_path(name); + if State::read_pid(&svc_pid).map_or(false, |p| process_running(p)) { + eprintln!("error: '{}' is already running", name); + std::process::exit(1); + } let self_exe = std::env::current_exe().unwrap_or_else(|_| { eprintln!("error: cannot determine binary path"); std::process::exit(1); @@ -251,6 +257,7 @@ fn cmd_disable(name: &str, state: &State) { } fn cmd_list(config: &Config, state: &State) { + cleanup_orphans(config, state); let mut names: Vec<&String> = config.service.keys().collect(); names.sort(); @@ -292,7 +299,55 @@ fn cmd_list(config: &Config, state: &State) { } } +fn cleanup_orphans(config: &Config, state: &State) { + for name in State::list_pid_names(&state.supervise_dir()) { + if !config.service.contains_key(&name) { + stop_orphan(&name, state); + } + } + for name in State::list_pid_names(&state.service_dir()) { + if !config.service.contains_key(&name) { + State::remove_pid(&state.service_pid_path(&name)); + } + } +} + +fn stop_orphan(name: &str, state: &State) { + let svc = state.service_pid_path(name); + let sup = state.supervise_pid_path(name); + + if let Some(pid) = State::read_pid(&svc) { + if process_running(pid) { + let _ = nix::sys::signal::kill( + nix::unistd::Pid::from_raw(pid as i32), + nix::sys::signal::Signal::SIGTERM, + ); + wait_for_exit(pid, 5); + if process_running(pid) { + let _ = nix::sys::signal::kill( + nix::unistd::Pid::from_raw(pid as i32), + nix::sys::signal::Signal::SIGKILL, + ); + wait_for_exit(pid, 3); + } + } + } + if let Some(pid) = State::read_pid(&sup) { + if process_running(pid) { + let _ = nix::sys::signal::kill( + nix::unistd::Pid::from_raw(pid as i32), + nix::sys::signal::Signal::SIGKILL, + ); + wait_for_exit(pid, 3); + } + } + State::remove_pid(&svc); + State::remove_pid(&sup); +} + fn cmd_daemon(config: &Config, state: &State, config_path: &str) { + cleanup_orphans(config, state); + let enabled = state.list_enabled().unwrap_or_default(); if enabled.is_empty() { println!("no enabled services"); @@ -300,15 +355,12 @@ fn cmd_daemon(config: &Config, state: &State, config_path: &str) { } for name in &enabled { - if !config.service.contains_key(name) { - continue; - } let svc_pid_path = state.service_pid_path(name); let already = State::read_pid(&svc_pid_path).map_or(false, |p| process_running(p)); if already { continue; } - println!("starting {}...", name); + println!("{}", name); cmd_start(name, config, config_path); } } diff --git a/src/state.rs b/src/state.rs index de1f57a..db64768 100644 --- a/src/state.rs +++ b/src/state.rs @@ -26,10 +26,32 @@ impl State { self.state_dir.join("logs").join(format!("{}.log", name)) } + pub fn supervise_dir(&self) -> PathBuf { + self.state_dir.join("supervise") + } + + pub fn service_dir(&self) -> PathBuf { + self.state_dir.join("service") + } + pub fn enabled_path(&self, name: &str) -> PathBuf { self.config_dir.join("enabled").join(name) } + pub fn list_pid_names(dir: &PathBuf) -> Vec { + let mut names = Vec::new(); + if let Ok(entries) = std::fs::read_dir(dir) { + for entry in entries.flatten() { + let fname = entry.file_name(); + let fname = fname.to_string_lossy().to_string(); + if let Some(stripped) = fname.strip_suffix(".pid") { + names.push(stripped.to_string()); + } + } + } + names + } + pub fn write_pid(path: &PathBuf) -> std::io::Result<()> { Self::write_pid_with(path, std::process::id()) }