diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index f0b7b36..d35d514 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -11,6 +11,7 @@ RUN apt-get update && apt-get install -y \ nginx \ && rm -rf /var/lib/apt/lists/* +RUN groupadd taskmaster RUN rustup component add rustfmt clippy RUN echo 'export PS1="\W> "' >> ~/.bashrc \ No newline at end of file diff --git a/config/autorestart.toml b/config/autorestart.toml new file mode 100644 index 0000000..fa929c0 --- /dev/null +++ b/config/autorestart.toml @@ -0,0 +1,18 @@ +[processes.no] +workingdir = "." +cmd = "/usr/bin/ls" +autorestart = "no" +autostart = true + +[processes.always] +workingdir = "." +cmd = "/usr/bin/ls" +autorestart = "always" +autostart = true + +[processes.on-failure] +workingdir = "." +cmd = "/usr/bin/ls" +autorestart = "on-failure[:5]" +exitcodes = [1] +autostart = true diff --git a/config/autostart.toml b/config/autostart.toml new file mode 100644 index 0000000..438a8b5 --- /dev/null +++ b/config/autostart.toml @@ -0,0 +1,9 @@ +[processes.autostart] +cmd = "/usr/bin/ls" +workingdir = "." +autostart = true + +[processes.notautostart] +cmd = "/usr/bin/ls" +workingdir = "." +autostart = false diff --git a/config/command.toml b/config/command.toml new file mode 100644 index 0000000..37b2079 --- /dev/null +++ b/config/command.toml @@ -0,0 +1,5 @@ +[processes.command] + +cmd = "/usr/bin/echo" +args = ["Hello, World!"] +workingdir = "." diff --git a/config/duplicatestdout.toml b/config/duplicatestdout.toml new file mode 100644 index 0000000..b2a4997 --- /dev/null +++ b/config/duplicatestdout.toml @@ -0,0 +1,11 @@ +[processes.no] +workingdir = "." +cmd = "/usr/bin/ls" +stdout = "asd" +autostart = true + +[processes.always] +workingdir = "." +cmd = "/usr/bin/ls" +stdout = "asd" +autostart = true diff --git a/config/env.toml b/config/env.toml new file mode 100644 index 0000000..491bd5e --- /dev/null +++ b/config/env.toml @@ -0,0 +1,7 @@ +[processes.env] + +cmd = "/bin/bash" +args = ["executables/env.sh"] +env = [["HELLO", "WORLD"]] +workingdir = "." +stdout = "/tmp/env.stdout" diff --git a/config/example.toml b/config/example.toml index ef2ea1c..5979bb8 100644 --- a/config/example.toml +++ b/config/example.toml @@ -1,6 +1,3 @@ -socketpath = "/tmp/.taskmaster.sock" -authgroup = "winstonallo" - [processes.sleep] cmd = "/usr/bin/sleep" args = ["1"] diff --git a/config/exitcode.toml b/config/exitcode.toml new file mode 100644 index 0000000..ed0e48a --- /dev/null +++ b/config/exitcode.toml @@ -0,0 +1,13 @@ +[processes.success] +cmd = "/bin/bash" +args = ["-c", "exit 0"] +workingdir = "." +exitcodes = [0] +autostart = true + +[processes.failure] +cmd = "/bin/bash" +args = ["-c", "exit 1"] +workingdir = "." +exitcodes = [0] +autostart = true diff --git a/config/felix.toml b/config/felix.toml index 4c87369..c5a17c6 100644 --- a/config/felix.toml +++ b/config/felix.toml @@ -1,6 +1,3 @@ -socketpath = "/tmp/taskmaster.sock" -authgroup = "root" - [processes.sleep] cmd = "/bin/sleep" args = ["3"] diff --git a/config/healthcheck.toml b/config/healthcheck.toml index f0ecea2..b7b72a3 100644 --- a/config/healthcheck.toml +++ b/config/healthcheck.toml @@ -1,6 +1,3 @@ -socketpath = "/tmp/.taskmaster.sock" - - [processes.sleep] cmd = "/usr/bin/sleep" @@ -20,5 +17,5 @@ env = [["STARTED_BY", "abied-ch"], ["ANSWER", "42"]] cmd = "/usr/bin/sleep" args = ["2"] backoff = 1 -timeout = 1 +timeout = 5 retries = 1 diff --git a/config/privilege_deescalation.toml b/config/privilege_deescalation.toml index da237f4..98fab60 100644 --- a/config/privilege_deescalation.toml +++ b/config/privilege_deescalation.toml @@ -1,5 +1,3 @@ -authgroup = "root" - [processes.root] cmd = "/usr/bin/id" diff --git a/config/processes.toml b/config/processes.toml new file mode 100644 index 0000000..11776ba --- /dev/null +++ b/config/processes.toml @@ -0,0 +1,8 @@ +[processes.processes] + +cmd = "/usr/bin/sleep" +args = ["1"] +workingdir = "." +autorestart = "no" +autostart = true +processes = 10 diff --git a/config/attach.toml b/config/redirect.toml similarity index 66% rename from config/attach.toml rename to config/redirect.toml index 06e7d07..6bfbb84 100644 --- a/config/attach.toml +++ b/config/redirect.toml @@ -1,15 +1,12 @@ -socketpath = "/tmp/taskmaster.sock" -authgroup = "winstonallo" - [processes.sleepwrite] cmd = "/usr/bin/python3" -args = ["/home/winstonallo/mastery/taskmaster/executables/sleepwrite.py"] +args = ["executables/sleepwrite.py"] processes = 1 umask = "022" -workingdir = "/tmp" +workingdir = "." autostart = true autorestart = "always" -exitcodes = [0, 2] +exitcodes = [0] stopsignals = ["USR1"] stoptime = 5 stdout = "/tmp/sleepwrite.stdout" diff --git a/config/retries.toml b/config/retries.toml new file mode 100644 index 0000000..5e4fd12 --- /dev/null +++ b/config/retries.toml @@ -0,0 +1,21 @@ +[processes.retries_1] +cmd = "/usr/bin/sleep" +args = ["1"] +workingdir = "." +autostart = true +exitcodes = [1] +[processes.retries_1.healthcheck] +starttime = 2 +retries = 1 +backoff = 2 + +[processes.retries_5] +cmd = "/usr/bin/sleep" +args = ["1"] +workingdir = "." +autostart = true +exitcodes = [1] +[processes.retries_5.healthcheck] +starttime = 2 +retries = 5 +backoff = 2 diff --git a/config/signal.toml b/config/signal.toml index 573a4ce..eee2b31 100644 --- a/config/signal.toml +++ b/config/signal.toml @@ -1,8 +1,8 @@ -authgroup = "winstonallo" - [processes.signal] cmd = "/usr/bin/python3" args = ["executables/signals.py"] workingdir = "." autostart = true +stopsignals = ["TERM", "USR1", "ILL"] +stdout = "/tmp/signal.stdout" \ No newline at end of file diff --git a/config/stoptime.toml b/config/stoptime.toml new file mode 100644 index 0000000..bdc3eef --- /dev/null +++ b/config/stoptime.toml @@ -0,0 +1,9 @@ +[processes.signal] + +cmd = "/usr/bin/python3" +args = ["executables/signals.py"] +workingdir = "." +autostart = true +stopsignals = ["TERM", "USR1", "ILL"] +stoptime = 5 +stdout = "/tmp/signal.stdout" diff --git a/config/umask.toml b/config/umask.toml new file mode 100644 index 0000000..b6fb669 --- /dev/null +++ b/config/umask.toml @@ -0,0 +1,15 @@ +[processes.000] + +cmd = "/usr/bin/touch" +args = ["000"] +workingdir = "." +autostart = true +umask = "000" + +[processes.022] + +cmd = "/usr/bin/touch" +args = ["022"] +workingdir = "." +autostart = true +umask = "022" diff --git a/config/unexisting_group.toml b/config/unexisting_group.toml index 7237d65..412afca 100644 --- a/config/unexisting_group.toml +++ b/config/unexisting_group.toml @@ -1,4 +1,3 @@ -authgroup = "asdpihgasbldkjalshdbliasjdlkasbdasd" [processes.sleep] cmd = "/usr/bin/sleep" diff --git a/config/uptime.toml b/config/uptime.toml new file mode 100644 index 0000000..4af69e7 --- /dev/null +++ b/config/uptime.toml @@ -0,0 +1,17 @@ +[processes.uptime_success] +cmd = "/usr/bin/sleep" +args = ["10"] +workingdir = "." +autostart = true +[processes.uptime_success.healthcheck] +starttime = 2 + +[processes.uptime_failure] +cmd = "/usr/bin/sleep" +args = ["1"] +workingdir = "." +autostart = true +exitcodes = [1] +[processes.uptime_failure.healthcheck] +starttime = 2 +retries = 1 diff --git a/config/workingdir.toml b/config/workingdir.toml new file mode 100644 index 0000000..f84a45c --- /dev/null +++ b/config/workingdir.toml @@ -0,0 +1,13 @@ +[processes.workingdir_dot] + +cmd = "/usr/bin/pwd" +workingdir = "." +stdout = "/tmp/workingdir_dot.stdout" +autostart = true + +[processes.workingdir_tmp] + +cmd = "/usr/bin/pwd" +workingdir = "/tmp" +stdout = "/tmp/workingdir_tmp.stdout" +autostart = true diff --git a/executables/env.sh b/executables/env.sh new file mode 100755 index 0000000..922b12c --- /dev/null +++ b/executables/env.sh @@ -0,0 +1 @@ +echo $HELLO \ No newline at end of file diff --git a/src/bin/taskmaster.rs b/src/bin/taskmaster.rs index 9352f61..f25f8e2 100644 --- a/src/bin/taskmaster.rs +++ b/src/bin/taskmaster.rs @@ -2,19 +2,11 @@ use std::{ env::{self}, error::Error, fs::remove_file, - io::Write, }; use tasklib::{conf::Config, log, log_info, run::daemon::Daemon}; -const PID_FILE_PATH: &str = "/tmp/taskmaster.pid"; - -fn write_pid_file() -> Result<(), Box> { - let pid = unsafe { libc::getpid() }; - let mut pid_file = std::fs::File::create(PID_FILE_PATH)?; - pid_file.write_all(pid.to_string().as_bytes())?; - Ok(()) -} +pub const PID_FILE_PATH: &str = "/tmp/taskmaster.pid"; #[tokio::main] async fn main() -> Result<(), Box> { @@ -40,8 +32,6 @@ async fn main() -> Result<(), Box> { } }; - write_pid_file()?; - log::init(conf.logfile())?; let mut daemon = Daemon::from_config(conf, arg); diff --git a/src/bin/taskshell.rs b/src/bin/taskshell.rs index 41440d5..10359f3 100644 --- a/src/bin/taskshell.rs +++ b/src/bin/taskshell.rs @@ -200,7 +200,7 @@ async fn attach(name: &str, socket_path: &str, to: &str, mut orig: Option<&mut l let stream = match UnixStream::connect(socket_path).await { Ok(stream) => stream, Err(e) => { - eprintln!("could not establish connection on attach socket at path {socket_path}: {e}"); + eprintln!("Could not establish connection on attach socket at path {socket_path}: {e}.\nIs the process running?"); return "".to_string(); } }; diff --git a/src/conf.rs b/src/conf.rs index ffde888..b3ba3e7 100644 --- a/src/conf.rs +++ b/src/conf.rs @@ -1,12 +1,12 @@ -use std::{collections::HashMap, error::Error, fs}; +use std::{ + collections::{HashMap, HashSet}, + error::Error, + fs, +}; -#[cfg(test)] -use defaults::{dflt_logfile, dflt_socketpath}; +use defaults::{dflt_authgroup, dflt_logfile, dflt_socketpath}; -use proc::{ - ProcessConfig, - types::{AuthGroup, WritableFile}, -}; +use proc::ProcessConfig; use serde::Deserialize; pub const PID_FILE_PATH: &str = "/tmp/taskmaster.pid"; @@ -22,9 +22,9 @@ pub struct Config { /// /// Default: /// ```toml - /// socketpath = "/tmp/.taskmaster.sock" + /// socketpath = "/tmp/taskmaster.sock" /// ``` - #[serde(default = "defaults::dflt_socketpath")] + #[serde(skip)] socketpath: String, /// Name of the group to be used for authenticating the client (similarly to @@ -34,7 +34,8 @@ pub struct Config { /// ```toml /// authgroup = "taskmaster" /// ``` - authgroup: Option, + #[serde(skip)] + authgroup: String, /// Path to the file the logs will be written to. /// @@ -42,8 +43,8 @@ pub struct Config { /// ```toml /// logfile = "/tmp/tasmaster.log" /// ``` - #[serde(default = "defaults::dflt_logfile")] - logfile: WritableFile, + #[serde(skip)] + logfile: String, /// Map of processes to configure individually. For process-level configuration, /// see [`crate::conf::proc::ProcessConfig`]. @@ -77,17 +78,47 @@ impl Config { } fn parse(config_str: &str) -> Result> { - let conf: Config = match toml::from_str(config_str) { + let mut conf: Config = match toml::from_str(config_str) { Ok(cnf) => cnf, Err(err) => { return Err(err.into()); } }; + conf.authgroup = dflt_authgroup(); + conf.socketpath = dflt_socketpath(); + conf.logfile = dflt_logfile(); + if conf.processes.is_empty() { return Err("taskmaster expects at least one process to be defined to operate".into()); } + let mut seen = HashSet::new(); + let duplicates = conf + .processes + .iter() + .filter(|p| p.1.stdout().is_some()) + .map(|p| p.1.stdout().as_ref().unwrap().path().to_owned()) + .filter(|path| !seen.insert(path.clone())) + .collect::>(); + + if !duplicates.is_empty() { + return Err(Box::::from(format!("Found duplicated stdout paths: {duplicates:?}"))); + } + + let mut seen = HashSet::new(); + let duplicates = conf + .processes + .iter() + .filter(|p| p.1.stderr().is_some()) + .map(|p| p.1.stderr().as_ref().unwrap().path().to_owned()) + .filter(|path| !seen.insert(path.clone())) + .collect::>(); + + if !duplicates.is_empty() { + return Err(Box::::from(format!("Found duplicated stderr paths: {duplicates:?}"))); + } + Ok(conf) } @@ -99,12 +130,12 @@ impl Config { &self.socketpath } - pub fn authgroup(&self) -> &Option { + pub fn authgroup(&self) -> &str { &self.authgroup } pub fn logfile(&self) -> &str { - self.logfile.path() + &self.logfile } } @@ -113,7 +144,7 @@ impl Default for Config { fn default() -> Self { Self { socketpath: dflt_socketpath(), - authgroup: None, + authgroup: dflt_authgroup(), logfile: dflt_logfile(), processes: HashMap::new(), } @@ -127,8 +158,8 @@ impl Config { self } - pub fn set_authgroup(&mut self, authgroup: AuthGroup) -> &mut Self { - self.authgroup = Some(authgroup); + pub fn set_authgroup(&mut self, authgroup: &str) -> &mut Self { + self.authgroup = authgroup.into(); self } diff --git a/src/conf/defaults.rs b/src/conf/defaults.rs index 47b33ba..7f1c0f9 100644 --- a/src/conf/defaults.rs +++ b/src/conf/defaults.rs @@ -1,5 +1,3 @@ -use super::proc::types::WritableFile; - pub fn dflt_socketpath() -> String { "/tmp/taskmaster.sock".to_string() } @@ -8,6 +6,6 @@ pub fn dflt_authgroup() -> String { "taskmaster".to_string() } -pub fn dflt_logfile() -> WritableFile { - WritableFile::from_path("/tmp/taskmaster.log") +pub fn dflt_logfile() -> String { + "/tmp/taskmaster.log".to_string() } diff --git a/src/conf/proc/types.rs b/src/conf/proc/types.rs index 5c816ac..f7509e3 100644 --- a/src/conf/proc/types.rs +++ b/src/conf/proc/types.rs @@ -1,4 +1,3 @@ -mod authgroup; mod autorestart; mod healthcheck; mod path; @@ -6,7 +5,6 @@ mod stopsignal; mod umask; pub use self::{ - authgroup::AuthGroup, autorestart::AutoRestart, healthcheck::{CommandHealthCheck, HealthCheck, HealthCheckType, UptimeHealthCheck}, path::{AccessibleDirectory, ExecutableFile, WritableFile}, diff --git a/src/conf/proc/types/authgroup.rs b/src/conf/proc/types/authgroup.rs deleted file mode 100644 index 82ff764..0000000 --- a/src/conf/proc/types/authgroup.rs +++ /dev/null @@ -1,65 +0,0 @@ -use serde::Deserialize; - -#[derive(Clone)] -pub struct AuthGroup { - id: u32, - name: String, -} - -impl AuthGroup { - pub fn from_group_name(group_name: &str) -> Result { - match get_group_id(group_name) { - Ok(id) => Ok(AuthGroup { id, name: group_name.into() }), - Err(e) => Err(e), - } - } - - pub fn id(&self) -> u32 { - self.id - } - - pub fn name(&self) -> &str { - &self.name - } -} - -fn get_group_id(group_name: &str) -> Result { - let c_group = std::ffi::CString::new(group_name).map_err(|e| format!("{e}"))?; - - unsafe { - let grp_ptr = libc::getgrnam(c_group.as_ptr()); - if grp_ptr.is_null() { - Err(format!("Group '{group_name}' not found")) - } else { - Ok((*grp_ptr).gr_gid) - } - } -} - -impl<'de> Deserialize<'de> for AuthGroup { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - let group_name = String::deserialize(deserializer)?; - match AuthGroup::from_group_name(&group_name) { - Ok(group) => Ok(group), - Err(e) => Err(serde::de::Error::custom(e.to_string())), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn get_group_id_success() { - assert_eq!(get_group_id("root").unwrap(), 0); - } - - #[test] - fn get_group_id_nonexisting() { - assert!(get_group_id("randomaaaahgroup").is_err()); - } -} diff --git a/src/jsonrpc/handlers.rs b/src/jsonrpc/handlers.rs index e4d6af5..4cd2ce5 100644 --- a/src/jsonrpc/handlers.rs +++ b/src/jsonrpc/handlers.rs @@ -9,7 +9,6 @@ use super::{ response::ErrorCode, }; use crate::{ - conf::proc::types::AuthGroup, jsonrpc::{ response::{ResponseResult, ResponseType}, short_process::ShortProcess, @@ -186,7 +185,7 @@ async fn update_attach_stream(file: &mut tokio::fs::File, pos: u64, len: u64, li Ok(pos) } -async fn attach(socketpath: &str, to: &str, authgroup: &Option) -> Result<(), Box> { +async fn attach(socketpath: &str, to: &str, authgroup: &str) -> Result<(), Box> { let mut listener = AsyncUnixSocket::new(socketpath, authgroup).map_err(|e| Box::::from(format!("could not create new socket stream: {e}")))?; @@ -222,11 +221,7 @@ pub struct AttachmentManager { } enum AttachmentRequest { - New { - socketpath: String, - to: String, - authgroup: Option, - }, + New { socketpath: String, to: String, authgroup: String }, } impl Default for AttachmentManager { @@ -264,7 +259,7 @@ impl AttachmentManager { Self { tx } } - pub async fn attach(&self, socketpath: &str, to: &str, authgroup: &Option) -> Result<(), Box> { + pub async fn attach(&self, socketpath: &str, to: &str, authgroup: &str) -> Result<(), Box> { self.tx .send(AttachmentRequest::New { socketpath: socketpath.to_owned(), @@ -573,8 +568,6 @@ mod tests { #[tokio::test] async fn reload_config_file_gone() { let conf = r#" - socketpath = "/tmp/.taskmaster.sock" - [processes.sleep] cmd = "/usr/bin/sleep" args = ["2"] diff --git a/src/jsonrpc/short_process.rs b/src/jsonrpc/short_process.rs index 6296fb8..f5c61ff 100644 --- a/src/jsonrpc/short_process.rs +++ b/src/jsonrpc/short_process.rs @@ -1,4 +1,4 @@ -use std::fmt; +use std::{fmt, time::Instant}; use serde::{Deserialize, Serialize}; @@ -48,7 +48,7 @@ impl State { ProcessState::HealthCheck(instant) => Self::HealthCheck(instant.elapsed().as_secs()), ProcessState::Healthy => Self::Healthy, ProcessState::Failed(_) => Self::Failed, - ProcessState::WaitingForRetry(instant) => Self::WaitingForRetry(instant.elapsed().as_secs()), + ProcessState::WaitingForRetry(instant) => Self::WaitingForRetry(instant.duration_since(Instant::now()).as_secs()), ProcessState::Completed => Self::Completed, ProcessState::Stopping(instant) => Self::Stopping(instant.elapsed().as_secs()), ProcessState::Stopped => Self::Stopped, @@ -66,7 +66,7 @@ impl fmt::Display for State { HealthCheck(s) => format!("healthcheck since {s} seconds"), Healthy => "healthy".to_owned(), Failed => "failed".to_owned(), - WaitingForRetry(s) => format!("waiting for retry since {s} seconds"), + WaitingForRetry(s) => format!("waiting for retry - {s} seconds left"), Completed => "completed".to_owned(), Stopping(s) => format!("stopping since {s} seconds"), Stopped => "stopped".to_owned(), diff --git a/src/log.rs b/src/log.rs index b9a6457..c951021 100644 --- a/src/log.rs +++ b/src/log.rs @@ -40,6 +40,12 @@ impl Logger { String::from_utf8_lossy(&buf[..ret as usize]).into_owned() } + pub fn plain(&self, message: &str) { + let mut guard = self.logfile.lock().expect("Mutex lock panicked in another thread"); + let _ = guard.write_all(message.as_bytes()); + let _ = guard.write_all(b"\n"); + } + pub fn error(&self, message: fmt::Arguments, fields: BTreeMap) { let mut log_entry = fields; log_entry.insert("timestamp".to_string(), json!(Logger::get_time_fmt())); @@ -98,6 +104,10 @@ impl Logger { static mut INSTANCE: Option = None; static INIT: Once = Once::new(); +pub fn plain(message: &str) { + get_logger().plain(message); +} + pub fn error(message: fmt::Arguments, fields: BTreeMap) { get_logger().error(message, fields); } diff --git a/src/run/daemon.rs b/src/run/daemon.rs index 27f5b8d..ce3f9d8 100644 --- a/src/run/daemon.rs +++ b/src/run/daemon.rs @@ -1,3 +1,4 @@ +use std::io::Write; use std::sync::atomic::{AtomicBool, Ordering}; use std::{collections::HashMap, error::Error, sync::Arc, time::Duration}; @@ -11,7 +12,6 @@ use tokio::time::sleep; use super::proc::{self, Process}; use super::statemachine::states::ProcessState; -use crate::conf::proc::types::AuthGroup; use crate::conf::{Config, PID_FILE_PATH}; use crate::jsonrpc::handlers::AttachmentManager; use crate::jsonrpc::response::{Response, ResponseError, ResponseType}; @@ -27,7 +27,7 @@ pub mod socket; pub struct Daemon { processes: HashMap, socket_path: String, - auth_group: Option, + auth_group: String, config_path: String, shutting_down: bool, attachment_manager: AttachmentManager, @@ -72,7 +72,7 @@ impl Daemon { &self.socket_path } - pub fn auth_group(&self) -> &Option { + pub fn auth_group(&self) -> &str { &self.auth_group } @@ -152,6 +152,7 @@ impl Daemon { } pub fn reload(&mut self) -> Result<(), String> { + log_info!("reloading configuration"); let conf = match Config::from_file(self.config_path()) { Ok(c) => c, Err(e) => return Err(format!("{}", e).to_owned()), @@ -168,14 +169,18 @@ impl Daemon { Some(process_old) => { if process_old.config() != process_new.config() { process_old.push_desired_state(ProcessState::Stopped); + *process_old.config_mut() = process_new.config().clone(); + (*process_old) + .healthcheck_mut() + .set_healthcheck(process_new.healthcheck().check()) + .set_backoff(process_new.healthcheck().backoff()) + .set_retries(process_new.healthcheck().retries()); + + match process_old.config().autostart() { + false => process_old.push_desired_state(ProcessState::Idle), + true => process_old.push_desired_state(ProcessState::Healthy), + } } - *process_old.config_mut() = process_new.config().clone(); - - match process_old.config().autostart() { - false => process_old.push_desired_state(ProcessState::Idle), - true => process_old.push_desired_state(ProcessState::Healthy), - } - leftover.retain(|n| n != process_old.name()); } None => { @@ -193,6 +198,13 @@ impl Daemon { Ok(()) } + fn write_pid_file() -> Result<(), Box> { + let pid = unsafe { libc::getpid() }; + let mut pid_file = std::fs::File::create(PID_FILE_PATH)?; + pid_file.write_all(pid.to_string().as_bytes())?; + Ok(()) + } + pub async fn run(&mut self) -> Result<(), Box> { unsafe { libc::signal(SIGHUP, handler_reload as usize); @@ -207,6 +219,8 @@ impl Daemon { let sender = Arc::new(sender); let mut sigint = signal(SignalKind::interrupt())?; + Self::write_pid_file()?; + loop { tokio::select! { accept_result = listener.accept() => { @@ -465,7 +479,7 @@ mod tests { let hc = hc .set_check(HealthCheckType::Uptime(UptimeHealthCheck { starttime: 2 })) .set_backoff(1) - .set_retries(1); + .set_retries(0); let mut proc = ProcessConfig::default(); let proc = proc .set_cmd("sh") diff --git a/src/run/daemon/socket.rs b/src/run/daemon/socket.rs index e57f6e6..d4f6425 100644 --- a/src/run/daemon/socket.rs +++ b/src/run/daemon/socket.rs @@ -8,28 +8,40 @@ use tokio::{ net::{UnixListener, UnixStream, unix::SocketAddr}, }; -use crate::conf::proc::types::AuthGroup; - #[allow(unused)] pub struct AsyncUnixSocket { socketpath: String, - authgroup: Option, + authgroup: String, listener: Option, stream: Option, } #[cfg(not(test))] -fn set_permissions(socketpath: &str, authgroup: &AuthGroup) -> Result<(), String> { +fn get_group_id(group_name: &str) -> Result { + let c_group = std::ffi::CString::new(group_name).map_err(|e| format!("{e}"))?; + + unsafe { + let grp_ptr = libc::getgrnam(c_group.as_ptr()); + if grp_ptr.is_null() { + Err(format!("Group '{group_name}' not found")) + } else { + Ok((*grp_ptr).gr_gid) + } + } +} + +#[cfg(not(test))] +fn set_permissions(socketpath: &str, authgroup: &str, gid: u32) -> Result<(), String> { use std::os::unix::fs::PermissionsExt; let c_path = std::ffi::CString::new(socketpath).map_err(|e| format!("invalid path: {e}"))?; unsafe { - if libc::chown(c_path.as_ptr(), u32::MAX, authgroup.id() as libc::gid_t) != 0 { + if libc::chown(c_path.as_ptr(), u32::MAX, gid as libc::gid_t) != 0 { return Err(format!( "could not change group ownership: {} - do you have permissions for group '{}'?", std::io::Error::last_os_error(), - authgroup.name() + authgroup )); } } @@ -38,7 +50,7 @@ fn set_permissions(socketpath: &str, authgroup: &AuthGroup) -> Result<(), String } impl AsyncUnixSocket { - pub fn new(socketpath: &str, authgroup: &Option) -> Result { + pub fn new(socketpath: &str, authgroup: &str) -> Result { if fs::metadata(socketpath).is_ok() { let _ = fs::remove_file(socketpath); } @@ -51,10 +63,10 @@ impl AsyncUnixSocket { }; #[cfg(not(test))] - if authgroup.is_some() { - if let Err(e) = set_permissions(socketpath, authgroup.as_ref().unwrap()) { - return Err(format!("could not create UNIX socket at path {socketpath}: {e}")); - } + let gid = get_group_id(authgroup)?; + #[cfg(not(test))] + if let Err(e) = set_permissions(socketpath, authgroup, gid) { + return Err(format!("could not create UNIX socket at path {socketpath}: {e}")); } Ok(Self { diff --git a/src/run/statemachine/healthcheck.rs b/src/run/statemachine/healthcheck.rs index ffacdf0..a8ab603 100644 --- a/src/run/statemachine/healthcheck.rs +++ b/src/run/statemachine/healthcheck.rs @@ -35,6 +35,24 @@ impl HealthCheckRunner { backoff: hc.backoff(), } } + pub fn set_healthcheck(&mut self, check: HealthCheckType) -> &mut Self { + self.check = check; + self + } + + pub fn set_retries(&mut self, retries: usize) -> &mut Self { + self.retries = retries; + self + } + + pub fn set_backoff(&mut self, backoff: usize) -> &mut Self { + self.backoff = backoff; + self + } + + pub fn check(&self) -> HealthCheckType { + self.check.clone() + } pub fn has_command_healthcheck(&self) -> bool { matches!(self.check, HealthCheckType::Command { .. }) diff --git a/src/run/statemachine/monitor.rs b/src/run/statemachine/monitor.rs index b5116aa..c7add1f 100644 --- a/src/run/statemachine/monitor.rs +++ b/src/run/statemachine/monitor.rs @@ -147,7 +147,7 @@ pub fn failed_healthy(p: &mut Process) -> Option { pub fn failed_healthcheck(p: &mut Process) -> Option { p.increment_healthcheck_failures(); - if p.healthcheck_failures() == p.healthcheck().retries() { + if p.healthcheck_failures() > p.healthcheck().retries() { p.push_desired_state(ProcessState::Stopped); proc_warning!(p, "not healthy after {} attempts, giving up", p.healthcheck().retries()); diff --git a/tests/configs/example.toml b/tests/configs/example.toml index fc4e8b9..7c67dbd 100644 --- a/tests/configs/example.toml +++ b/tests/configs/example.toml @@ -1,9 +1,3 @@ -# Example config for taskmaster. - -# Path to the socket used for connections between taskmaster and its client. -# Defaults to "/tmp/.taskmaster.sock". -socketpath = "/tmp/.taskmaster.sock" - # Process name. [processes.sleep] diff --git a/tests/configs/sleep.toml b/tests/configs/sleep.toml index ba3536d..087c723 100644 --- a/tests/configs/sleep.toml +++ b/tests/configs/sleep.toml @@ -1,5 +1,3 @@ -socketpath = "/tmp/.taskmaster.sock" - [processes.sleep] cmd = "/usr/bin/sleep" args = ["2"]