diff --git a/Cargo.lock b/Cargo.lock index 6975589..d3b1437 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -86,6 +86,15 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "deranged" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3" +dependencies = [ + "powerfmt", +] + [[package]] name = "futures-core" version = "0.3.29" @@ -179,6 +188,15 @@ dependencies = [ "libc", ] +[[package]] +name = "num_threads" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +dependencies = [ + "libc", +] + [[package]] name = "object" version = "0.32.1" @@ -217,6 +235,12 @@ version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "proc-macro2" version = "1.0.69" @@ -285,6 +309,26 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "serde" +version = "1.0.193" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.193" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "signal-hook-registry" version = "1.4.1" @@ -341,6 +385,26 @@ dependencies = [ "syn", ] +[[package]] +name = "time" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5" +dependencies = [ + "deranged", + "libc", + "num_threads", + "powerfmt", + "serde", + "time-core", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + [[package]] name = "tokio" version = "1.34.0" @@ -471,6 +535,7 @@ dependencies = [ "nix", "regex", "thiserror", + "time", "tokio", "tokio-util", ] diff --git a/Cargo.toml b/Cargo.toml index bb8888c..2762a51 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,5 +11,6 @@ lazy_static = "1.4.0" nix = {version = "0.27.1", features = ["process", "signal", "fs"]} regex = "1.10.2" thiserror = "1.0.50" +time = { version = "0.3.30", features = ["local-offset"]} tokio = { version = "1.34.0", features = ["full"] } tokio-util = "0.7.10" diff --git a/src/init/config.rs b/src/init/config.rs index 1a67504..6fee1f2 100644 --- a/src/init/config.rs +++ b/src/init/config.rs @@ -266,6 +266,10 @@ impl Config { self.services.iter() } + pub fn get_cron_iter(&self) -> std::slice::Iter { + self.cron.iter() + } + pub fn get_shell(&self) -> Option { if let Some(shell) = &self.shell_path { return Some(shell.clone()); @@ -282,3 +286,84 @@ impl Clone for Command { } } } + +impl Clone for Crontab { + fn clone(&self) -> Self { + Self { + minute: self.minute.clone(), + hour: self.hour.clone(), + day_of_month: self.day_of_month.clone(), + month: self.month.clone(), + day_of_week: self.day_of_week.clone(), + command: self.command.clone() + } + } +} + +impl Clone for CronTimeFieldSpec { + fn clone(&self) -> Self { + match self { + CronTimeFieldSpec::Any => CronTimeFieldSpec::Any, + CronTimeFieldSpec::Every(x) => CronTimeFieldSpec::Every(*x), + CronTimeFieldSpec::Exact(x) => CronTimeFieldSpec::Exact(*x), + CronTimeFieldSpec::MultiOccurrence(x) => { + CronTimeFieldSpec::MultiOccurrence(x.clone()) + }, + } + } +} + + +struct CronFieldCmpHelper<'a>(u8, u8, Option<&'a Vec>); +impl PartialEq for CronTimeFieldSpec { + fn eq(&self, other: &Self) -> bool { + let lhs: CronFieldCmpHelper; + let rhs: CronFieldCmpHelper; + match self { + CronTimeFieldSpec::Any => { lhs = CronFieldCmpHelper(0, 0, None); } + CronTimeFieldSpec::Every(x) => { lhs = CronFieldCmpHelper(1, *x, None); } + CronTimeFieldSpec::Exact(x) => { lhs = CronFieldCmpHelper(2, *x, None); } + CronTimeFieldSpec::MultiOccurrence(v) => { lhs = CronFieldCmpHelper(3, 0, Some(v)); } + } + + match other { + CronTimeFieldSpec::Any => { rhs = CronFieldCmpHelper(0, 0, None); } + CronTimeFieldSpec::Every(x) => { rhs = CronFieldCmpHelper(1, *x, None); } + CronTimeFieldSpec::Exact(x) => { rhs = CronFieldCmpHelper(2, *x, None); } + CronTimeFieldSpec::MultiOccurrence(v) => { rhs = CronFieldCmpHelper(3, 0, Some(v)); } + } + + if lhs.0 == rhs.0 { + if lhs.0 == 3u8 { + if let Some(lv) = lhs.2 { + if let Some(rv) = rhs.2 { + if lv.len() != rv.len() { + return false; + } + + let mut l_iter = lv.iter(); + let mut r_iter = rv.iter(); + 'item: loop { + if let Some(liv) = l_iter.next() { + if let Some(riv) = r_iter.next() { + if *liv != *riv { + return false; + } + } else { + break 'item; + } + } else { + break 'item; + } + } + return true; + } + } + } else { + return lhs.1 == rhs.1; + } + } + + false + } +} \ No newline at end of file diff --git a/src/init/daemon.rs b/src/init/daemon.rs index 26c2b51..a8324a7 100644 --- a/src/init/daemon.rs +++ b/src/init/daemon.rs @@ -18,15 +18,16 @@ pub async fn start(cfg: config::Config) -> Result<(), WingmateInitError> { let waiter_cancel_sighandler = sighandler_cancel.clone(); let cancel = CancellationToken::new(); - let starter_cancel = cancel.clone(); + let starter_service_cancel = cancel.clone(); + let starter_cron_cancel = cancel.clone(); let mut set: JoinSet> = JoinSet::new(); set.spawn(async move { sighandler::sighandler(sig_sync_flag, cancel, sighandler_cancel).await }); - //TODO: start the process starter - starter::start_services(&mut set, &cfg, starter_cancel)?; + starter::start_services(&mut set, &cfg, starter_service_cancel)?; + starter::start_cron(&mut set, &cfg, starter_cron_cancel)?; //TODO: spawn_blocking for waiter set.spawn_blocking(move || { diff --git a/src/init/daemon/starter.rs b/src/init/daemon/starter.rs index b11ca1b..9aed693 100644 --- a/src/init/daemon/starter.rs +++ b/src/init/daemon/starter.rs @@ -1,3 +1,5 @@ +mod time_calc; + use tokio::task::JoinSet; use tokio::process::{Command, Child}; use tokio_util::sync::CancellationToken; @@ -10,8 +12,9 @@ use nix::sys::signal::{kill, Signal}; use nix::errno::Errno; use nix::unistd::Pid; use anyhow::Context; -use crate::init::config; -use crate::init::error::WingmateInitError; +use time::OffsetDateTime; +use crate::init::config::{self, CronTimeFieldSpec}; +use crate::init::error::{WingmateInitError, CronConfigError}; pub fn start_services(ts: &mut JoinSet>, cfg: &config::Config, cancel: CancellationToken) @@ -102,7 +105,37 @@ fn result_match(result: tokio_result) -> Result<(), anyhow::Error> { } } - dbg!("starter: sleep exited"); + Ok(()) +} +pub fn start_cron(ts: &mut JoinSet>, cfg: &config::Config, cancel: CancellationToken) + -> Result<(), WingmateInitError> { + + for c_ in cfg.get_cron_iter() { + let shell = cfg.get_shell().ok_or::(WingmateInitError::NoShellAvailable)?; + let cron = c_.clone(); + let cancel = cancel.clone(); + + ts.spawn(async move { + if cron.day_of_month != config::CronTimeFieldSpec::Any + && cron.day_of_week != config::CronTimeFieldSpec::Any { + return Err(WingmateInitError::CronConfig { source: CronConfigError::ClashingConfig }); + } + + // let cron = cron.clone(); + let mut last_running: Option = None; + 'continuous: loop { + let cron = cron.clone(); + + time_calc::wait_calc(cron.clone(), &last_running); + select! { + _ = cancel.cancelled() => { + break 'continuous; + } + } + } + Ok(()) + }); + } Ok(()) } \ No newline at end of file diff --git a/src/init/daemon/starter/time_calc.rs b/src/init/daemon/starter/time_calc.rs new file mode 100644 index 0000000..714541d --- /dev/null +++ b/src/init/daemon/starter/time_calc.rs @@ -0,0 +1,77 @@ +use std::time::Duration; +use anyhow::Context; +use time::OffsetDateTime; +use crate::init::config::{Crontab,CronTimeFieldSpec}; +use crate::init::error; + +const MINUTE: i8 = 0; +const HOUR: i8 = 1; +const DAY_OF_MONTH: i8 = 2; +const MONTH: i8 = 3; +const DAY_OF_WEEK: i8 = 4; + +struct CronField { + spec: CronTimeFieldSpec, + tag: i8, +} + +fn convert_cron_spec_to_vec(cron: Crontab) -> Vec { + let mut res: Vec = Vec::with_capacity(5); + + res.push(CronField { spec: cron.minute, tag: MINUTE }); + res.push(CronField { spec: cron.hour, tag: HOUR }); + res.push(CronField { spec: cron.day_of_month, tag: DAY_OF_MONTH }); + res.push(CronField { spec: cron.month, tag: MONTH }); + res.push(CronField { spec: cron.day_of_week, tag: DAY_OF_WEEK }); + res +} + +pub fn wait_calc(cron: Crontab, last_running: &Option) -> Result { + let local_clock = OffsetDateTime::now_local() + .context("getting current time in local timezone") + .map_err(|e| { error::CronConfigError::Other { source: e } })?; + + match last_running { + Some(t) => { + let vec_cron = convert_cron_spec_to_vec(cron); + for vc in vec_cron { + + } + + // match cron.minute { + // CronTimeFieldSpec::Any => {}, + // CronTimeFieldSpec::Every(x) => {}, + // CronTimeFieldSpec::Exact(x) => {}, + // CronTimeFieldSpec::MultiOccurrence(v) => {} + // } + // match cron.hour { + // CronTimeFieldSpec::Any => {}, + // CronTimeFieldSpec::Every(x) => {}, + // CronTimeFieldSpec::Exact(x) => {}, + // CronTimeFieldSpec::MultiOccurrence(v) => {} + // } + // match cron.day_of_month { + // CronTimeFieldSpec::Any => {}, + // CronTimeFieldSpec::Every(x) => {}, + // CronTimeFieldSpec::Exact(x) => {}, + // CronTimeFieldSpec::MultiOccurrence(v) => {} + // } + // match cron.month { + // CronTimeFieldSpec::Any => {}, + // CronTimeFieldSpec::Every(x) => {}, + // CronTimeFieldSpec::Exact(x) => {}, + // CronTimeFieldSpec::MultiOccurrence(v) => {} + // } + // match cron.day_of_week { + // CronTimeFieldSpec::Any => {}, + // CronTimeFieldSpec::Every(x) => {}, + // CronTimeFieldSpec::Exact(x) => {}, + // CronTimeFieldSpec::MultiOccurrence(v) => {} + // } + }, + None => { + } + } + + Ok(Duration::from_secs(1)) //PLACEHOLDER +} \ No newline at end of file diff --git a/src/init/error.rs b/src/init/error.rs index 8ecd9ac..8cdf1a4 100644 --- a/src/init/error.rs +++ b/src/init/error.rs @@ -51,6 +51,12 @@ pub enum WingmateInitError { source: tokio::task::JoinError, }, + #[error("cron config")] + CronConfig { + #[source] + source: CronConfigError, + }, + #[error("tripped over")] Other { #[source] @@ -58,6 +64,21 @@ pub enum WingmateInitError { } } +#[derive(Error,Debug)] +pub enum CronConfigError { + #[error("setting day of week and day of month at the same time will lead to unexpected behavior")] + ClashingConfig, + + #[error("when setting time for higher order, the smallest (minute) muste be set")] + MissingMinute, + + #[error("something went wrong")] + Other { + #[source] + source: anyhow::Error, + } +} + #[derive(Error,Debug)] pub enum CronParseError { #[error("invalid cron syntax: {}", .0)]