Compare commits
4 Commits
aabf39f041
...
using-alpi
| Author | SHA1 | Date | |
|---|---|---|---|
| 1371e0e637 | |||
| 05dde284a7 | |||
| 5a0c901281 | |||
| 6e60f58145 |
@@ -1,8 +1,8 @@
|
|||||||
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
|
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
|
||||||
// README at: https://github.com/devcontainers/templates/tree/main/src/debian
|
// README at: https://github.com/devcontainers/templates/tree/main/src/debian
|
||||||
{
|
{
|
||||||
"name": "Ubuntu Dev",
|
"name": "Alpine Dev",
|
||||||
"image": "ubuntu-dev:user",
|
"image": "alpine-dev:user",
|
||||||
"customizations": {
|
"customizations": {
|
||||||
"vscode": {
|
"vscode": {
|
||||||
"extensions": [
|
"extensions": [
|
||||||
|
|||||||
65
Cargo.lock
generated
65
Cargo.lock
generated
@@ -86,6 +86,15 @@ version = "1.0.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "deranged"
|
||||||
|
version = "0.3.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3"
|
||||||
|
dependencies = [
|
||||||
|
"powerfmt",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-core"
|
name = "futures-core"
|
||||||
version = "0.3.29"
|
version = "0.3.29"
|
||||||
@@ -179,6 +188,15 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num_threads"
|
||||||
|
version = "0.1.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "object"
|
name = "object"
|
||||||
version = "0.32.1"
|
version = "0.32.1"
|
||||||
@@ -217,6 +235,12 @@ version = "0.2.13"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
|
checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "powerfmt"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.69"
|
version = "1.0.69"
|
||||||
@@ -285,6 +309,26 @@ version = "1.2.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
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]]
|
[[package]]
|
||||||
name = "signal-hook-registry"
|
name = "signal-hook-registry"
|
||||||
version = "1.4.1"
|
version = "1.4.1"
|
||||||
@@ -341,6 +385,26 @@ dependencies = [
|
|||||||
"syn",
|
"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]]
|
[[package]]
|
||||||
name = "tokio"
|
name = "tokio"
|
||||||
version = "1.34.0"
|
version = "1.34.0"
|
||||||
@@ -471,6 +535,7 @@ dependencies = [
|
|||||||
"nix",
|
"nix",
|
||||||
"regex",
|
"regex",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
|
"time",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -11,5 +11,6 @@ lazy_static = "1.4.0"
|
|||||||
nix = {version = "0.27.1", features = ["process", "signal", "fs"]}
|
nix = {version = "0.27.1", features = ["process", "signal", "fs"]}
|
||||||
regex = "1.10.2"
|
regex = "1.10.2"
|
||||||
thiserror = "1.0.50"
|
thiserror = "1.0.50"
|
||||||
|
time = { version = "0.3.30", features = ["local-offset"]}
|
||||||
tokio = { version = "1.34.0", features = ["full"] }
|
tokio = { version = "1.34.0", features = ["full"] }
|
||||||
tokio-util = "0.7.10"
|
tokio-util = "0.7.10"
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use wingmate_rs::init;
|
|||||||
async fn main() -> Result<(), Box<dyn error::Error>> {
|
async fn main() -> Result<(), Box<dyn error::Error>> {
|
||||||
if let Err(e) = init::start().await {
|
if let Err(e) = init::start().await {
|
||||||
eprintln!("{}", e);
|
eprintln!("{}", e);
|
||||||
return Err(e);
|
return Err(e.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
11
src/init.rs
11
src/init.rs
@@ -3,12 +3,14 @@ mod config;
|
|||||||
pub(crate) mod error;
|
pub(crate) mod error;
|
||||||
|
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::error as std_err;
|
use anyhow::Context;
|
||||||
|
|
||||||
pub async fn start() -> Result<(), Box<dyn std_err::Error>> {
|
const WINGMATE_CONFIG_PATH: &'static str = "WINGMATE_CONFIG_PATH";
|
||||||
|
|
||||||
|
pub async fn start() -> Result<(), error::WingmateInitError> {
|
||||||
let mut vec_search: Vec<String> = Vec::new();
|
let mut vec_search: Vec<String> = Vec::new();
|
||||||
|
|
||||||
match env::var("WINGMATE_CONFIG_PATH") {
|
match env::var(WINGMATE_CONFIG_PATH) {
|
||||||
Ok(paths) => {
|
Ok(paths) => {
|
||||||
for p in paths.split(':') {
|
for p in paths.split(':') {
|
||||||
vec_search.push(String::from(p));
|
vec_search.push(String::from(p));
|
||||||
@@ -16,7 +18,8 @@ pub async fn start() -> Result<(), Box<dyn std_err::Error>> {
|
|||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
if let env::VarError::NotUnicode(_) = e {
|
if let env::VarError::NotUnicode(_) = e {
|
||||||
return Err(e.into());
|
return Err(e).context(format!("reading {} env var", WINGMATE_CONFIG_PATH))
|
||||||
|
.map_err(|e| {error::WingmateInitError::Other { source: e }} );
|
||||||
} else {
|
} else {
|
||||||
vec_search.push(String::from("/etc/wingmate"));
|
vec_search.push(String::from("/etc/wingmate"));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,26 @@ use std::fs;
|
|||||||
use std::env;
|
use std::env;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::io::{BufReader, BufRead};
|
use std::io::{BufReader, BufRead};
|
||||||
use std::error as std_error;
|
|
||||||
use crate::init::error as wingmate_error;
|
use crate::init::error as wingmate_error;
|
||||||
|
use anyhow::anyhow;
|
||||||
use nix::unistd::{access, AccessFlags};
|
use nix::unistd::{access, AccessFlags};
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
use anyhow::Context;
|
||||||
|
|
||||||
|
pub const MAX_TERM_WAIT_TIME_SECS: u64 = 5;
|
||||||
|
|
||||||
|
const CRON_REGEX_STR: &'static str = r"^\s*(?P<minute>\S+)\s+(?P<hour>\S+)\s+(?P<dom>\S+)\s+(?P<month>\S+)\s+(?P<dow>\S+)\s+(?P<command>\S.*\S)\s*$";
|
||||||
|
const MINUTE: &'static str = "minute";
|
||||||
|
const HOUR: &'static str = "hour";
|
||||||
|
const DAY_OF_MONTH_ABBRV: &'static str = "dom";
|
||||||
|
const DAY_OF_MONTH: &'static str = "day of month";
|
||||||
|
const MONTH: &'static str = "month";
|
||||||
|
const DAY_OF_WEEK_ABBRV: &'static str = "dow";
|
||||||
|
const DAY_OF_WEEK: &'static str = "day of week";
|
||||||
|
const COMMAND: &'static str = "command";
|
||||||
|
const WINGMATE_SHELL_ENV: &'static str = "WINGMATE_SHELL";
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Command {
|
pub enum Command {
|
||||||
@@ -18,8 +33,7 @@ pub enum Command {
|
|||||||
pub enum CronTimeFieldSpec {
|
pub enum CronTimeFieldSpec {
|
||||||
Any,
|
Any,
|
||||||
Exact(u8),
|
Exact(u8),
|
||||||
MultiOccurrence(Vec<u8>),
|
MultiOccurrence(Vec<u8>)
|
||||||
Every(u8)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -40,7 +54,7 @@ pub struct Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
pub fn find(search_path: Vec<String>) -> Result<Config, Box<dyn std_error::Error>> {
|
pub fn find(search_path: Vec<String>) -> Result<Config, wingmate_error::WingmateInitError> {
|
||||||
if search_path.is_empty() {
|
if search_path.is_empty() {
|
||||||
return Err(wingmate_error::WingmateInitError::InvalidConfigSearchPath.into());
|
return Err(wingmate_error::WingmateInitError::InvalidConfigSearchPath.into());
|
||||||
}
|
}
|
||||||
@@ -68,7 +82,7 @@ impl Config {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cron = Self::read_crontab(&mut buf)?;
|
cron = Self::read_crontab(&mut buf).map_err(|e| { wingmate_error::WingmateInitError::Cron { source: e }})?;
|
||||||
|
|
||||||
//TODO: need to include cron in the condition
|
//TODO: need to include cron in the condition
|
||||||
if !svc_commands.is_empty() || !cron.is_empty() {
|
if !svc_commands.is_empty() || !cron.is_empty() {
|
||||||
@@ -89,16 +103,14 @@ impl Config {
|
|||||||
cron,
|
cron,
|
||||||
shell_path: None,
|
shell_path: None,
|
||||||
};
|
};
|
||||||
config.find_shell()?;
|
config.find_shell().map_err(|e| { wingmate_error::WingmateInitError::FindShell { source: e } })?;
|
||||||
|
|
||||||
Ok(config)
|
Ok(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_crontab(path: &mut PathBuf) -> Result<Vec<Crontab>, Box<dyn std_error::Error>> {
|
fn read_crontab(path: &mut PathBuf) -> Result<Vec<Crontab>, wingmate_error::CronParseError> {
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref CRON_REGEX: Regex = Regex::new(
|
static ref CRON_REGEX: Regex = Regex::new(CRON_REGEX_STR).unwrap();
|
||||||
r"^\s*(?P<minute>\S+)\s+(?P<hour>\S+)\s+(?P<dom>\S+)\s+(?P<month>\S+)\s+(?P<dow>\S+)\s+(?P<command>\S.*\S)\s*$"
|
|
||||||
).unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let cron_path = path.join("crontab");
|
let cron_path = path.join("crontab");
|
||||||
@@ -107,45 +119,72 @@ impl Config {
|
|||||||
if let Ok(f) = fs::File::open(cron_path.as_path()) {
|
if let Ok(f) = fs::File::open(cron_path.as_path()) {
|
||||||
for line in BufReader::new(f).lines() {
|
for line in BufReader::new(f).lines() {
|
||||||
if let Ok(l) = line {
|
if let Ok(l) = line {
|
||||||
let cap = CRON_REGEX.captures(&l).ok_or::<Box<dyn std_error::Error>>(wingmate_error::CronSyntaxError(String::from(&l)).into())?;
|
let cap = CRON_REGEX.captures(&l).ok_or::<wingmate_error::CronParseError>(
|
||||||
|
wingmate_error::CronParseError::InvalidSyntax(String::from(&l))
|
||||||
let mut match_str = cap.name("minute").ok_or::<Box<dyn std_error::Error>>(
|
|
||||||
wingmate_error::CronSyntaxError(format!("cannot capture minute in \"{}\"", &l)).into()
|
|
||||||
)?;
|
)?;
|
||||||
let minute = Self::to_cron_time_field_spec(&match_str).map_err(|e| {
|
|
||||||
Box::new(wingmate_error::CronSyntaxError(format!("failed to parse minute \"{}\" in \"{}\": {}", &match_str.as_str(), &l, e)))
|
let mut match_str = cap.name(MINUTE).ok_or::<wingmate_error::CronParseError>(
|
||||||
|
wingmate_error::CronParseError::FieldMatch { cron_line: String::from(&l), field_name: String::from(MINUTE) }
|
||||||
|
)?;
|
||||||
|
let minute = Self::to_cron_time_field_spec(&match_str, 60u8).map_err(|e| {
|
||||||
|
wingmate_error::CronParseError::Parse {
|
||||||
|
source: e,
|
||||||
|
cron_line: String::from(&l),
|
||||||
|
matched: String::from(match_str.as_str()),
|
||||||
|
field_name: String::from(MINUTE)
|
||||||
|
}
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
match_str = cap.name("hour").ok_or::<Box<dyn std_error::Error>>(
|
match_str = cap.name(HOUR).ok_or::<wingmate_error::CronParseError>(
|
||||||
wingmate_error::CronSyntaxError(format!("cannot capture hour in \"{}\"", &l)).into()
|
wingmate_error::CronParseError::FieldMatch { cron_line: String::from(&l), field_name: String::from(HOUR) }
|
||||||
)?;
|
)?;
|
||||||
let hour = Self::to_cron_time_field_spec(&match_str).map_err(|e| {
|
let hour = Self::to_cron_time_field_spec(&match_str, 24u8).map_err(|e| {
|
||||||
Box::new(wingmate_error::CronSyntaxError(format!("failed to parse hour \"{}\" in \"{}\": {}", &match_str.as_str(), &l, e)))
|
wingmate_error::CronParseError::Parse {
|
||||||
|
source: e,
|
||||||
|
cron_line: String::from(&l),
|
||||||
|
matched: String::from(match_str.as_str()),
|
||||||
|
field_name: String::from(HOUR)
|
||||||
|
}
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
match_str = cap.name("dom").ok_or::<Box<dyn std_error::Error>>(
|
match_str = cap.name(DAY_OF_MONTH_ABBRV).ok_or::<wingmate_error::CronParseError>(
|
||||||
wingmate_error::CronSyntaxError(format!("cannot capture day of month in \"{}\"", &l)).into()
|
wingmate_error::CronParseError::FieldMatch { cron_line: String::from(&l), field_name: String::from(DAY_OF_MONTH) }
|
||||||
)?;
|
)?;
|
||||||
let dom = Self::to_cron_time_field_spec(&match_str).map_err(|e| {
|
let dom = Self::to_cron_time_field_spec(&match_str, 31u8).map_err(|e| {
|
||||||
Box::new(wingmate_error::CronSyntaxError(format!("failed to parse day of month \"{}\" in \"{}\": {}", &match_str.as_str(), &l, e)))
|
wingmate_error::CronParseError::Parse {
|
||||||
|
source: e,
|
||||||
|
cron_line: String::from(&l),
|
||||||
|
matched: String::from(match_str.as_str()),
|
||||||
|
field_name: String::from(DAY_OF_MONTH)
|
||||||
|
}
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
match_str = cap.name("month").ok_or::<Box<dyn std_error::Error>>(
|
match_str = cap.name(MONTH).ok_or::<wingmate_error::CronParseError>(
|
||||||
wingmate_error::CronSyntaxError(format!("cannot capture month in \"{}\"", &l)).into()
|
wingmate_error::CronParseError::FieldMatch { cron_line: String::from(&l), field_name: String::from(MONTH) }
|
||||||
)?;
|
)?;
|
||||||
let month = Self::to_cron_time_field_spec(&match_str).map_err(|e| {
|
let month = Self::to_cron_time_field_spec(&match_str, 12u8).map_err(|e| {
|
||||||
Box::new(wingmate_error::CronSyntaxError(format!("failed to parse month \"{}\" in \"{}\": {}", &match_str.as_str(), &l, e)))
|
wingmate_error::CronParseError::Parse {
|
||||||
|
source: e,
|
||||||
|
cron_line: String::from(&l),
|
||||||
|
matched: String::from(match_str.as_str()),
|
||||||
|
field_name: String::from(MONTH)
|
||||||
|
}
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
match_str = cap.name("dow").ok_or::<Box<dyn std_error::Error>>(
|
match_str = cap.name(DAY_OF_WEEK_ABBRV).ok_or::<wingmate_error::CronParseError>(
|
||||||
wingmate_error::CronSyntaxError(format!("cannot capture day of week in \"{}\"", &l)).into()
|
wingmate_error::CronParseError::FieldMatch { cron_line: String::from(&l), field_name: String::from(DAY_OF_WEEK) }
|
||||||
)?;
|
)?;
|
||||||
let dow = Self::to_cron_time_field_spec(&match_str).map_err(|e| {
|
let dow = Self::to_cron_time_field_spec(&match_str, 7u8).map_err(|e| {
|
||||||
Box::new(wingmate_error::CronSyntaxError(format!("failed to parse day of week \"{}\" in \"{}\": {}", &match_str.as_str(), &l, e)))
|
wingmate_error::CronParseError::Parse {
|
||||||
|
source: e,
|
||||||
|
cron_line: String::from(&l),
|
||||||
|
matched: String::from(match_str.as_str()),
|
||||||
|
field_name: String::from(DAY_OF_WEEK)
|
||||||
|
}
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
match_str = cap.name("command").ok_or::<Box<dyn std_error::Error>>(
|
match_str = cap.name(COMMAND).ok_or::<wingmate_error::CronParseError>(
|
||||||
wingmate_error::CronSyntaxError(format!("cannot capture command in \"{}\"", &l)).into()
|
wingmate_error::CronParseError::FieldMatch { cron_line: String::from(&l), field_name: String::from(COMMAND) }
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
ret_vec.push(Crontab {
|
ret_vec.push(Crontab {
|
||||||
@@ -163,34 +202,49 @@ impl Config {
|
|||||||
Ok(ret_vec)
|
Ok(ret_vec)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_cron_time_field_spec(match_str: ®ex::Match) -> Result<CronTimeFieldSpec, Box<dyn std_error::Error>> {
|
fn to_cron_time_field_spec(match_str: ®ex::Match, max: u8) -> Result<CronTimeFieldSpec, anyhow::Error> {
|
||||||
let field = match_str.as_str();
|
let field = match_str.as_str();
|
||||||
|
|
||||||
if field == "*" {
|
if field == "*" {
|
||||||
return Ok(CronTimeFieldSpec::Any);
|
return Ok(CronTimeFieldSpec::Any);
|
||||||
} else if field.starts_with("*/") {
|
} else if field.starts_with("*/") {
|
||||||
let every = field[2..].parse::<u8>()?;
|
let every = field[2..].parse::<u8>().context("parsing on field matching \"every\" pattern")?;
|
||||||
return Ok(CronTimeFieldSpec::Every(every));
|
if every >= max {
|
||||||
|
return Err(anyhow!("invalid value {}", every));
|
||||||
|
}
|
||||||
|
let mut next_value = every;
|
||||||
|
let mut multi: Vec<u8> = Vec::new();
|
||||||
|
while next_value < max {
|
||||||
|
multi.push(next_value);
|
||||||
|
next_value += every;
|
||||||
|
}
|
||||||
|
return Ok(CronTimeFieldSpec::MultiOccurrence(multi));
|
||||||
} else if field.contains(",") {
|
} else if field.contains(",") {
|
||||||
let multi: Vec<&str> = field.split(",").collect();
|
let multi: Vec<&str> = field.split(",").collect();
|
||||||
let mut multi_occurrence: Vec<u8> = Vec::new();
|
let mut multi_occurrence: Vec<u8> = Vec::new();
|
||||||
|
|
||||||
for m in multi {
|
for m in multi {
|
||||||
let ur = m.parse::<u8>()?;
|
let ur = m.parse::<u8>().context("parsing on field matching \"multi occurrence\" pattern")?;
|
||||||
|
if ur >= max {
|
||||||
|
return Err(anyhow!("invalid value {}", field));
|
||||||
|
}
|
||||||
multi_occurrence.push(ur);
|
multi_occurrence.push(ur);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok(CronTimeFieldSpec::MultiOccurrence(multi_occurrence));
|
return Ok(CronTimeFieldSpec::MultiOccurrence(multi_occurrence));
|
||||||
} else {
|
} else {
|
||||||
let n = field.parse::<u8>()?;
|
let n = field.parse::<u8>().context("parsing on field matching \"exact\" pattern")?;
|
||||||
|
if n >= max {
|
||||||
|
return Err(anyhow!("invalid value {}", n));
|
||||||
|
}
|
||||||
return Ok(CronTimeFieldSpec::Exact(n));
|
return Ok(CronTimeFieldSpec::Exact(n));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_shell(&mut self) -> Result<(), Box<dyn std_error::Error>> {
|
fn find_shell(&mut self) -> Result<(), wingmate_error::FindShellError> {
|
||||||
|
|
||||||
let shell: String;
|
let shell: String;
|
||||||
match env::var("WINGMATE_SHELL") {
|
match env::var(WINGMATE_SHELL_ENV) {
|
||||||
Ok(sh) => {
|
Ok(sh) => {
|
||||||
shell = sh;
|
shell = sh;
|
||||||
},
|
},
|
||||||
@@ -200,13 +254,15 @@ impl Config {
|
|||||||
shell = String::from("sh");
|
shell = String::from("sh");
|
||||||
},
|
},
|
||||||
env::VarError::NotUnicode(_) => {
|
env::VarError::NotUnicode(_) => {
|
||||||
return Err(e.into());
|
return Err(e).context(format!("reading {} env var", WINGMATE_SHELL_ENV))
|
||||||
|
.map_err(|e| { wingmate_error::FindShellError::Other { source: e } })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let env_path = env::var("PATH")?;
|
let env_path = env::var("PATH").context("getting PATH env variable")
|
||||||
|
.map_err(|e| { wingmate_error::FindShellError::Other { source: e } })?;
|
||||||
let vec_path: Vec<&str> = env_path.split(':').collect();
|
let vec_path: Vec<&str> = env_path.split(':').collect();
|
||||||
|
|
||||||
for p in vec_path {
|
for p in vec_path {
|
||||||
@@ -220,13 +276,17 @@ impl Config {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(wingmate_error::ShellNotFoundError(shell).into())
|
Err(wingmate_error::FindShellError::ShellNotFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_service_iter(&self) -> std::slice::Iter<Command> {
|
pub fn get_service_iter(&self) -> std::slice::Iter<Command> {
|
||||||
self.services.iter()
|
self.services.iter()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_cron_iter(&self) -> std::slice::Iter<Crontab> {
|
||||||
|
self.cron.iter()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_shell(&self) -> Option<String> {
|
pub fn get_shell(&self) -> Option<String> {
|
||||||
if let Some(shell) = &self.shell_path {
|
if let Some(shell) = &self.shell_path {
|
||||||
return Some(shell.clone());
|
return Some(shell.clone());
|
||||||
@@ -243,3 +303,97 @@ 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 CronTimeFieldSpec {
|
||||||
|
pub fn is_match(&self, current: u8) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::Any => { return true; },
|
||||||
|
Self::Exact(x) => { return *x == current; },
|
||||||
|
Self::MultiOccurrence(v) => {
|
||||||
|
for i in v {
|
||||||
|
if *i == current {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clone for CronTimeFieldSpec {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
match self {
|
||||||
|
Self::Any => Self::Any,
|
||||||
|
Self::Exact(x) => Self::Exact(*x),
|
||||||
|
Self::MultiOccurrence(x) => {
|
||||||
|
Self::MultiOccurrence(x.clone())
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct CronFieldCmpHelper<'a>(u8, u8, Option<&'a Vec<u8>>);
|
||||||
|
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::Exact(x) => { lhs = CronFieldCmpHelper(1, *x, None); }
|
||||||
|
CronTimeFieldSpec::MultiOccurrence(v) => { lhs = CronFieldCmpHelper(1, 0, Some(v)); }
|
||||||
|
}
|
||||||
|
|
||||||
|
match other {
|
||||||
|
CronTimeFieldSpec::Any => { rhs = CronFieldCmpHelper(0, 0, None); }
|
||||||
|
CronTimeFieldSpec::Exact(x) => { rhs = CronFieldCmpHelper(1, *x, None); }
|
||||||
|
CronTimeFieldSpec::MultiOccurrence(v) => { rhs = CronFieldCmpHelper(2, 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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,29 +3,41 @@ mod waiter;
|
|||||||
mod starter;
|
mod starter;
|
||||||
mod constants;
|
mod constants;
|
||||||
|
|
||||||
use std::error;
|
use tokio::{select, pin};
|
||||||
use tokio::task::JoinSet;
|
use tokio::task::JoinSet;
|
||||||
use tokio_util::sync::CancellationToken;
|
use tokio_util::sync::CancellationToken;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
use std::time::{Duration,Instant};
|
||||||
|
use nix::sys::signal::{Signal, kill};
|
||||||
|
use nix::unistd::Pid;
|
||||||
use crate::init::config;
|
use crate::init::config;
|
||||||
|
use crate::init::error as wmerr;
|
||||||
|
use crate::init::error::WingmateInitError;
|
||||||
|
|
||||||
pub async fn start(cfg: config::Config) -> Result<(), Box<dyn error::Error>> {
|
pub async fn start(cfg: config::Config) -> Result<(), WingmateInitError> {
|
||||||
let sync_flag = Arc::new(Mutex::new(false));
|
let sync_flag = Arc::new(Mutex::new(false));
|
||||||
let sig_sync_flag = sync_flag.clone();
|
let sig_sync_flag = sync_flag.clone();
|
||||||
|
|
||||||
let sighandler_cancel = CancellationToken::new();
|
let sighandler_cancel = CancellationToken::new();
|
||||||
let waiter_cancel_sighandler = sighandler_cancel.clone();
|
let waiter_cancel_sighandler = sighandler_cancel.clone();
|
||||||
|
let signal_pump_stop = sighandler_cancel.clone();
|
||||||
|
|
||||||
let cancel = CancellationToken::new();
|
let cancel = CancellationToken::new();
|
||||||
let starter_cancel = cancel.clone();
|
let starter_service_cancel = cancel.clone();
|
||||||
|
let starter_cron_cancel = cancel.clone();
|
||||||
|
let signal_pump_start = cancel.clone();
|
||||||
|
|
||||||
|
let mut set: JoinSet<Result<(), wmerr::WingmateInitError>> = JoinSet::new();
|
||||||
|
set.spawn(async move {
|
||||||
|
signal_pump(signal_pump_start, signal_pump_stop).await
|
||||||
|
});
|
||||||
|
|
||||||
let mut set: JoinSet<Result<(), Box<dyn error::Error + Send + Sync>>> = JoinSet::new();
|
|
||||||
set.spawn(async move {
|
set.spawn(async move {
|
||||||
sighandler::sighandler(sig_sync_flag, cancel, sighandler_cancel).await
|
sighandler::sighandler(sig_sync_flag, cancel, sighandler_cancel).await
|
||||||
});
|
});
|
||||||
|
|
||||||
//TODO: start the process starter
|
starter::start_services(&mut set, &cfg, starter_service_cancel)?;
|
||||||
starter::start_services(&mut set, &cfg, starter_cancel)?;
|
starter::start_cron(&mut set, &cfg, starter_cron_cancel)?;
|
||||||
|
|
||||||
//TODO: spawn_blocking for waiter
|
//TODO: spawn_blocking for waiter
|
||||||
set.spawn_blocking(move || {
|
set.spawn_blocking(move || {
|
||||||
@@ -38,15 +50,65 @@ pub async fn start(cfg: config::Config) -> Result<(), Box<dyn error::Error>> {
|
|||||||
Ok(v) => {
|
Ok(v) => {
|
||||||
if let Err(ev) = v {
|
if let Err(ev) = v {
|
||||||
dbg!(&ev);
|
dbg!(&ev);
|
||||||
return Err(ev as Box<dyn error::Error>);
|
match ev {
|
||||||
|
WingmateInitError::SpawnError { source, message } => {
|
||||||
|
eprintln!("{}", WingmateInitError::SpawnError { source, message });
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
return Err(ev);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
dbg!(&e);
|
dbg!(&e);
|
||||||
return Err(e.into());
|
return Err(WingmateInitError::Join { source: e });
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn signal_pump(start: CancellationToken, stop: CancellationToken) -> Result<(), WingmateInitError> {
|
||||||
|
const TERM_MODE: u8 = 0;
|
||||||
|
const KILL_MODE: u8 = 1;
|
||||||
|
const ALL_CHILDREN_PID: i32 = -1;
|
||||||
|
|
||||||
|
start.cancelled().await;
|
||||||
|
|
||||||
|
let stop_time = Instant::now();
|
||||||
|
let mut wait_time_millis: u64 = 100;
|
||||||
|
let mut mode = TERM_MODE;
|
||||||
|
|
||||||
|
'signal: loop {
|
||||||
|
let stop = stop.clone();
|
||||||
|
let s = tokio::time::sleep(Duration::from_millis(wait_time_millis));
|
||||||
|
pin!(s);
|
||||||
|
|
||||||
|
select! {
|
||||||
|
() = &mut s => {
|
||||||
|
if mode == TERM_MODE {
|
||||||
|
if let Err(e) = kill(Pid::from_raw(ALL_CHILDREN_PID), Signal::SIGTERM) {
|
||||||
|
eprintln!("daemon: sending TERM signal got {}", e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if let Err(e) = kill(Pid::from_raw(ALL_CHILDREN_PID), Signal::SIGKILL) {
|
||||||
|
eprintln!("daemon: sending KILL signal got {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let time_peek = Instant::now();
|
||||||
|
if time_peek.saturating_duration_since(stop_time).as_secs() >= config::MAX_TERM_WAIT_TIME_SECS && mode == TERM_MODE {
|
||||||
|
wait_time_millis = 10;
|
||||||
|
mode = KILL_MODE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ = stop.cancelled() => {
|
||||||
|
break 'signal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
use std::error;
|
|
||||||
use tokio::signal::unix::{signal, SignalKind};
|
use tokio::signal::unix::{signal, SignalKind};
|
||||||
use tokio::select;
|
use tokio::select;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use tokio_util::sync::CancellationToken;
|
use tokio_util::sync::CancellationToken;
|
||||||
|
use crate::init::error::WingmateInitError;
|
||||||
|
|
||||||
pub async fn sighandler(flag: Arc<Mutex<bool>>, cancel: CancellationToken, exit: CancellationToken) -> Result<(), Box<dyn error::Error + Send + Sync>> {
|
pub async fn sighandler(flag: Arc<Mutex<bool>>, cancel: CancellationToken, exit: CancellationToken) -> Result<(), WingmateInitError> {
|
||||||
let mut sigint = signal(SignalKind::interrupt())?;
|
let mut sigint = signal(SignalKind::interrupt()).map_err(|e| { WingmateInitError::Signal { source: e } })?;
|
||||||
let mut sigterm = signal(SignalKind::terminate())?;
|
let mut sigterm = signal(SignalKind::terminate()).map_err(|e| { WingmateInitError::Signal { source: e } })?;
|
||||||
let mut sigchld = signal(SignalKind::child())?;
|
let mut sigchld = signal(SignalKind::child()).map_err(|e| { WingmateInitError::Signal { source: e } })?;
|
||||||
|
|
||||||
'signal: loop {
|
'signal: loop {
|
||||||
select! {
|
select! {
|
||||||
|
|||||||
@@ -3,24 +3,27 @@ use tokio::process::{Command, Child};
|
|||||||
use tokio_util::sync::CancellationToken;
|
use tokio_util::sync::CancellationToken;
|
||||||
use tokio::select;
|
use tokio::select;
|
||||||
use tokio::io::Result as tokio_result;
|
use tokio::io::Result as tokio_result;
|
||||||
use tokio::time::sleep;
|
use tokio::time::{sleep, interval};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use std::process::ExitStatus;
|
use std::process::ExitStatus;
|
||||||
use std::error;
|
|
||||||
use nix::sys::signal::{kill, Signal};
|
use nix::sys::signal::{kill, Signal};
|
||||||
use nix::errno::Errno;
|
use nix::errno::Errno;
|
||||||
use nix::unistd::Pid;
|
use nix::unistd::Pid;
|
||||||
|
use anyhow::{Context, anyhow};
|
||||||
|
use time::{OffsetDateTime, Duration as TimeDur, Weekday};
|
||||||
use crate::init::config;
|
use crate::init::config;
|
||||||
use crate::init::error::{NoShellAvailableError, WingmateInitError};
|
use crate::init::error::{WingmateInitError, CronConfigError};
|
||||||
|
|
||||||
|
|
||||||
pub fn start_services(ts: &mut JoinSet<Result<(), Box<dyn error::Error + Send + Sync>>>, cfg: &config::Config, cancel: CancellationToken)
|
const CRON_TRIGGER_WAIT_SECS: u64 = 20;
|
||||||
-> Result<(), Box<dyn error::Error>> {
|
|
||||||
|
pub fn start_services(ts: &mut JoinSet<Result<(), WingmateInitError>>, cfg: &config::Config, cancel: CancellationToken)
|
||||||
|
-> Result<(), WingmateInitError> {
|
||||||
|
|
||||||
for svc_ in cfg.get_service_iter() {
|
for svc_ in cfg.get_service_iter() {
|
||||||
let mut shell: String = String::new();
|
let mut shell: String = String::new();
|
||||||
if let config::Command::ShellPrefixed(_) = svc_ {
|
if let config::Command::ShellPrefixed(_) = svc_ {
|
||||||
shell = cfg.get_shell().ok_or::<Box<dyn error::Error>>(NoShellAvailableError.into())?;
|
shell = cfg.get_shell().ok_or::<WingmateInitError>(WingmateInitError::NoShellAvailable)?;
|
||||||
}
|
}
|
||||||
let svc = svc_.clone();
|
let svc = svc_.clone();
|
||||||
let cancel = cancel.clone();
|
let cancel = cancel.clone();
|
||||||
@@ -32,7 +35,7 @@ pub fn start_services(ts: &mut JoinSet<Result<(), Box<dyn error::Error + Send +
|
|||||||
config::Command::Direct(c) => {
|
config::Command::Direct(c) => {
|
||||||
let exp_str = c.clone();
|
let exp_str = c.clone();
|
||||||
child = Command::new(c).spawn().map_err(|e| {
|
child = Command::new(c).spawn().map_err(|e| {
|
||||||
Box::new(WingmateInitError::SpawnError { source: e, message: exp_str })
|
WingmateInitError::SpawnError { source: e, message: exp_str }
|
||||||
})?;
|
})?;
|
||||||
},
|
},
|
||||||
config::Command::ShellPrefixed(s) => {
|
config::Command::ShellPrefixed(s) => {
|
||||||
@@ -40,7 +43,7 @@ pub fn start_services(ts: &mut JoinSet<Result<(), Box<dyn error::Error + Send +
|
|||||||
let exp_str = s.clone();
|
let exp_str = s.clone();
|
||||||
let exp_shell = shell.clone();
|
let exp_shell = shell.clone();
|
||||||
child = Command::new(shell).arg(s).spawn().map_err(|e| {
|
child = Command::new(shell).arg(s).spawn().map_err(|e| {
|
||||||
Box::new(WingmateInitError::SpawnError { source: e, message: format!("{} {}", exp_shell, exp_str) })
|
WingmateInitError::SpawnError { source: e, message: format!("{} {}", exp_shell, exp_str) }
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -51,12 +54,12 @@ pub fn start_services(ts: &mut JoinSet<Result<(), Box<dyn error::Error + Send +
|
|||||||
match kill(Pid::from_raw(id as i32), Some(Signal::SIGTERM)) {
|
match kill(Pid::from_raw(id as i32), Some(Signal::SIGTERM)) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
select! {
|
select! {
|
||||||
_ = sleep(Duration::from_secs(5)) => {
|
_ = sleep(Duration::from_secs(config::MAX_TERM_WAIT_TIME_SECS)) => {
|
||||||
child.kill().await.expect("failed to kill process");
|
child.kill().await.expect("failed to kill process");
|
||||||
},
|
},
|
||||||
result = child.wait() => {
|
result = child.wait() => {
|
||||||
if let Err(e) = result_match(result) {
|
if let Err(e) = result_match(result) {
|
||||||
return Err(e);
|
return Err(WingmateInitError::ChildExit { source: e });
|
||||||
}
|
}
|
||||||
break 'autorestart;
|
break 'autorestart;
|
||||||
}
|
}
|
||||||
@@ -64,7 +67,7 @@ pub fn start_services(ts: &mut JoinSet<Result<(), Box<dyn error::Error + Send +
|
|||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
if e != Errno::ESRCH {
|
if e != Errno::ESRCH {
|
||||||
return Err(e.into());
|
return Err(WingmateInitError::ChildNotFound);
|
||||||
} else {
|
} else {
|
||||||
break 'autorestart;
|
break 'autorestart;
|
||||||
}
|
}
|
||||||
@@ -76,7 +79,7 @@ pub fn start_services(ts: &mut JoinSet<Result<(), Box<dyn error::Error + Send +
|
|||||||
},
|
},
|
||||||
result = child.wait() => {
|
result = child.wait() => {
|
||||||
if let Err(e) = result_match(result) {
|
if let Err(e) = result_match(result) {
|
||||||
return Err(e);
|
return Err(WingmateInitError::ChildExit { source: e });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -91,18 +94,165 @@ pub fn start_services(ts: &mut JoinSet<Result<(), Box<dyn error::Error + Send +
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn result_match(result: tokio_result<ExitStatus>) -> Result<(), Box<dyn error::Error + Send + Sync>> {
|
fn result_match(result: tokio_result<ExitStatus>) -> Result<(), anyhow::Error> {
|
||||||
if let Err(e) = result {
|
if let Err(e) = result {
|
||||||
if let Some(eos) = e.raw_os_error() {
|
if let Some(eos) = e.raw_os_error() {
|
||||||
if eos != nix::Error::ECHILD as i32 {
|
if eos != nix::Error::ECHILD as i32 {
|
||||||
return Err(e.into());
|
return Err(e).context("unexpected child exit status");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return Err(e.into());
|
return Err(e).context("unexpected child error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn start_cron(ts: &mut JoinSet<Result<(), WingmateInitError>>, cfg: &config::Config, cancel: CancellationToken)
|
||||||
|
-> Result<(), WingmateInitError> {
|
||||||
|
|
||||||
|
for c_ in cfg.get_cron_iter() {
|
||||||
|
let cron = c_.clone();
|
||||||
|
let in_loop_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 cron_interval = interval(Duration::from_secs(CRON_TRIGGER_WAIT_SECS));
|
||||||
|
let mut cron_procs: JoinSet<Result<(), WingmateInitError>> = JoinSet::new();
|
||||||
|
let mut last_running: Option<OffsetDateTime> = None;
|
||||||
|
'continuous: loop {
|
||||||
|
let cron = cron.clone();
|
||||||
|
let cron_proc_cancel = in_loop_cancel.clone();
|
||||||
|
|
||||||
|
let mut flag = true;
|
||||||
|
|
||||||
|
if let Ok(local_time) = OffsetDateTime::now_local() {
|
||||||
|
if let Some(last) = last_running {
|
||||||
|
if local_time - last < TimeDur::minutes(1) {
|
||||||
|
flag = false;
|
||||||
|
} else {
|
||||||
|
flag = flag && cron.minute.is_match(local_time.minute()) &&
|
||||||
|
cron.hour.is_match(local_time.hour()) &&
|
||||||
|
cron.day_of_month.is_match(local_time.day()) &&
|
||||||
|
cron.day_of_week.is_match(weekday_map(local_time.weekday()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if flag {
|
||||||
|
last_running = Some(local_time);
|
||||||
|
cron_procs.spawn(async move {
|
||||||
|
run_cron_command(cron.command.clone(), cron_proc_cancel).await
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if cron_procs.is_empty() {
|
||||||
|
select! {
|
||||||
|
_ = in_loop_cancel.cancelled() => {
|
||||||
|
break 'continuous;
|
||||||
|
},
|
||||||
|
_ = cron_interval.tick() => {},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
'task: while !cron_procs.is_empty() {
|
||||||
|
select! {
|
||||||
|
opt_res = cron_procs.join_next() => {
|
||||||
|
if let Some(res) = opt_res {
|
||||||
|
if let Err(e) = res {
|
||||||
|
eprintln!("running cron got problem {:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ = in_loop_cancel.cancelled() => {
|
||||||
|
while let Some(res) = cron_procs.join_next().await {
|
||||||
|
if let Err(e) = res {
|
||||||
|
eprintln!("running cron got problem {:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break 'continuous;
|
||||||
|
},
|
||||||
|
_ = cron_interval.tick() => {
|
||||||
|
break 'task;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn weekday_map(wd: Weekday) -> u8 {
|
||||||
|
match wd {
|
||||||
|
Weekday::Sunday => 0,
|
||||||
|
Weekday::Monday => 1,
|
||||||
|
Weekday::Tuesday => 2,
|
||||||
|
Weekday::Wednesday => 3,
|
||||||
|
Weekday::Thursday => 4,
|
||||||
|
Weekday::Friday => 5,
|
||||||
|
Weekday::Saturday => 6
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run_cron_command(command: String, cancel: CancellationToken) -> Result<(), WingmateInitError> {
|
||||||
|
let mut args: Vec<&str> = Vec::new();
|
||||||
|
for part in command.split(' ') {
|
||||||
|
if part.len() > 0 {
|
||||||
|
args.push(part);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if args.is_empty() {
|
||||||
|
return Err(WingmateInitError::Other { source: anyhow!("parsed as empty: {}", command) });
|
||||||
|
}
|
||||||
|
|
||||||
|
let cmd = args.swap_remove(0);
|
||||||
|
let mut child: Child;
|
||||||
|
if args.is_empty() {
|
||||||
|
child = Command::new(cmd).spawn().map_err(|e| {
|
||||||
|
WingmateInitError::SpawnError { source: e, message: command }
|
||||||
|
})?;
|
||||||
|
} else {
|
||||||
|
child = Command::new(cmd).args(args.as_slice()).spawn().map_err(|e| {
|
||||||
|
WingmateInitError::SpawnError { source: e, message: command }
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
|
||||||
|
select! {
|
||||||
|
_ = cancel.cancelled() => {
|
||||||
|
if let Some(id) = child.id() {
|
||||||
|
match kill(Pid::from_raw(id as i32), Some(Signal::SIGTERM)) {
|
||||||
|
Ok(_) => {
|
||||||
|
if let Err(e) = result_match(child.wait().await) {
|
||||||
|
return Err(WingmateInitError::ChildExit { source: e });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
match e {
|
||||||
|
Errno::ESRCH => {
|
||||||
|
return Err(WingmateInitError::ChildNotFound);
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
return Err(WingmateInitError::FromNix { source: e });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
result = child.wait() => {
|
||||||
|
if let Err(e) = result_match(result) {
|
||||||
|
return Err(WingmateInitError::ChildExit { source: e });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dbg!("starter: sleep exited");
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -37,7 +37,6 @@ pub fn wait_all(flag: Arc<Mutex<bool>>, stop_sighandler: CancellationToken) {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
// dbg!("sanity");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,5 @@
|
|||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use std::fmt;
|
|
||||||
use std::error;
|
|
||||||
|
|
||||||
|
|
||||||
#[derive(Error,Debug)]
|
#[derive(Error,Debug)]
|
||||||
pub enum WingmateInitError {
|
pub enum WingmateInitError {
|
||||||
#[error("invalid config search path")]
|
#[error("invalid config search path")]
|
||||||
@@ -17,38 +13,107 @@ pub enum WingmateInitError {
|
|||||||
#[source]
|
#[source]
|
||||||
source: std::io::Error,
|
source: std::io::Error,
|
||||||
message: String,
|
message: String,
|
||||||
|
},
|
||||||
|
|
||||||
|
#[error("parsing cron")]
|
||||||
|
Cron {
|
||||||
|
#[source]
|
||||||
|
source: CronParseError,
|
||||||
|
},
|
||||||
|
|
||||||
|
#[error("looking for shell")]
|
||||||
|
FindShell {
|
||||||
|
#[source]
|
||||||
|
source: FindShellError,
|
||||||
|
},
|
||||||
|
|
||||||
|
#[error("child exited")]
|
||||||
|
ChildExit {
|
||||||
|
#[source]
|
||||||
|
source: anyhow::Error,
|
||||||
|
},
|
||||||
|
|
||||||
|
#[error("cannot find the child process")]
|
||||||
|
ChildNotFound,
|
||||||
|
|
||||||
|
#[error("failed to setup signal handler")]
|
||||||
|
Signal {
|
||||||
|
#[source]
|
||||||
|
source: std::io::Error,
|
||||||
|
},
|
||||||
|
|
||||||
|
#[error("no shell available")]
|
||||||
|
NoShellAvailable,
|
||||||
|
|
||||||
|
#[error("problem when join task")]
|
||||||
|
Join {
|
||||||
|
#[source]
|
||||||
|
source: tokio::task::JoinError,
|
||||||
|
},
|
||||||
|
|
||||||
|
#[error("cron config")]
|
||||||
|
CronConfig {
|
||||||
|
#[source]
|
||||||
|
source: CronConfigError,
|
||||||
|
},
|
||||||
|
|
||||||
|
#[error("from nix")]
|
||||||
|
FromNix {
|
||||||
|
#[source]
|
||||||
|
source: nix::Error,
|
||||||
|
},
|
||||||
|
|
||||||
|
#[error("tripped over")]
|
||||||
|
Other {
|
||||||
|
#[source]
|
||||||
|
source: anyhow::Error,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug,Clone)]
|
#[derive(Error,Debug)]
|
||||||
pub struct CronSyntaxError(pub String);
|
pub enum CronConfigError {
|
||||||
|
#[error("setting day of week and day of month at the same time will lead to unexpected behavior")]
|
||||||
|
ClashingConfig,
|
||||||
|
|
||||||
impl fmt::Display for CronSyntaxError {
|
#[error("when setting time for higher order, the smallest (minute) muste be set")]
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
MissingMinute,
|
||||||
write!(f, "cron syntax error at: {}", self.0)
|
|
||||||
|
#[error("something went wrong")]
|
||||||
|
Other {
|
||||||
|
#[source]
|
||||||
|
source: anyhow::Error,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl error::Error for CronSyntaxError {}
|
#[derive(Error,Debug)]
|
||||||
|
pub enum CronParseError {
|
||||||
|
#[error("invalid cron syntax: {}", .0)]
|
||||||
|
InvalidSyntax(String),
|
||||||
|
|
||||||
#[derive(Debug,Clone)]
|
#[error("cannot capture {} in \"{}\"", field_name, cron_line)]
|
||||||
pub struct ShellNotFoundError(pub String);
|
FieldMatch {
|
||||||
|
cron_line: String,
|
||||||
|
field_name: String,
|
||||||
|
},
|
||||||
|
|
||||||
impl fmt::Display for ShellNotFoundError {
|
#[error("failed to parse {} \"{}\" in \"{}\"", field_name, matched, cron_line)]
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
Parse {
|
||||||
write!(f, "shell not found: {}", self.0)
|
#[source]
|
||||||
|
source: anyhow::Error,
|
||||||
|
cron_line: String,
|
||||||
|
matched: String,
|
||||||
|
field_name: String,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl error::Error for ShellNotFoundError {}
|
#[derive(Error,Debug)]
|
||||||
|
pub enum FindShellError {
|
||||||
|
#[error("shell not found")]
|
||||||
|
ShellNotFound,
|
||||||
|
|
||||||
#[derive(Debug,Clone)]
|
#[error("when finding shell")]
|
||||||
pub struct NoShellAvailableError;
|
Other {
|
||||||
|
#[source]
|
||||||
impl fmt::Display for NoShellAvailableError {
|
source: anyhow::Error
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
write!(f, "no shell available")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl error::Error for NoShellAvailableError {}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user