18 Commits

Author SHA1 Message Date
4cfee4c454 experiment: ignore SIGCHLD 2023-12-04 14:19:34 +11:00
03c0c0401e test: initial 2023-12-04 11:49:36 +11:00
d1c78f7b71 fix: logger usage 2023-12-04 11:23:08 +11:00
da71569517 Merge branch 'wip2' into multi-docker 2023-12-04 11:06:40 +11:00
4195eadc9a test(spawner): added logs 2023-12-04 11:04:24 +11:00
190479d796 wip: alpine image 2023-12-03 12:56:26 +11:00
171c799a31 wip: cron shell script for testing 2023-12-02 15:54:41 +11:00
11dbe7d4f0 Merge branch 'wip2' into multi-docker 2023-12-02 14:53:02 +11:00
b4f81ccf30 chore: remove tmp file from git index 2023-12-02 14:45:01 +11:00
64fa76d39b add: log test helper 2023-12-02 14:04:58 +11:00
342ac49bea add: test helper binaries 2023-12-01 22:33:52 +00:00
d26cc87713 wip: using different versions of rust images 2023-12-02 09:11:12 +11:00
212ae4847a wip: rearrange docker directory 2023-12-02 09:09:02 +11:00
05dde284a7 wip: ready to test 2023-12-01 13:26:09 +11:00
5a0c901281 wip: working on cron 2023-11-28 00:08:27 +11:00
6e60f58145 wip: replace dyn Error with concrete 2023-11-27 13:09:53 +11:00
aabf39f041 wip: convert to thiserror and anyhow 2023-11-26 11:34:26 +11:00
c03adb808a wip: implement error kind 2023-11-25 19:09:01 +11:00
34 changed files with 955 additions and 133 deletions

1
.gitignore vendored
View File

@@ -1 +1,2 @@
/target
/coba.txt

217
Cargo.lock generated
View File

@@ -26,6 +26,12 @@ dependencies = [
"memchr",
]
[[package]]
name = "anyhow"
version = "1.0.75"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
[[package]]
name = "autocfg"
version = "1.1.0"
@@ -80,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"
@@ -92,6 +107,17 @@ version = "0.3.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817"
[[package]]
name = "getrandom"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "gimli"
version = "0.28.0"
@@ -104,6 +130,12 @@ version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7"
[[package]]
name = "itoa"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
[[package]]
name = "lazy_static"
version = "1.4.0"
@@ -126,6 +158,12 @@ dependencies = [
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
[[package]]
name = "memchr"
version = "2.6.4"
@@ -173,6 +211,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"
@@ -211,6 +258,18 @@ 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 = "ppv-lite86"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]]
name = "proc-macro2"
version = "1.0.69"
@@ -229,6 +288,36 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom",
]
[[package]]
name = "redox_syscall"
version = "0.4.1"
@@ -279,6 +368,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"
@@ -288,6 +397,17 @@ dependencies = [
"libc",
]
[[package]]
name = "simplelog"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acee08041c5de3d5048c8b3f6f13fafb3026b24ba43c6a695a0c76179b844369"
dependencies = [
"log",
"termcolor",
"time",
]
[[package]]
name = "smallvec"
version = "1.11.2"
@@ -315,6 +435,66 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "termcolor"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
dependencies = [
"winapi-util",
]
[[package]]
name = "thiserror"
version = "1.0.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "time"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5"
dependencies = [
"deranged",
"itoa",
"libc",
"num_threads",
"powerfmt",
"serde",
"time-core",
"time-macros",
]
[[package]]
name = "time-core"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
[[package]]
name = "time-macros"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20"
dependencies = [
"time-core",
]
[[package]]
name = "tokio"
version = "1.34.0"
@@ -370,6 +550,37 @@ version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
dependencies = [
"winapi",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.48.0"
@@ -440,9 +651,15 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
name = "wingmate-rs"
version = "0.1.0"
dependencies = [
"anyhow",
"lazy_static",
"log",
"nix",
"rand",
"regex",
"simplelog",
"thiserror",
"time",
"tokio",
"tokio-util",
]

View File

@@ -5,9 +5,28 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[[bin]]
name = "wmtest-helper-dummy"
path = "src/bin/test-helper/dummy.rs"
[[bin]]
name = "wmtest-helper-spawner"
path = "src/bin/test-helper/spawner.rs"
[[bin]]
name = "wmtest-helper-log"
path = "src/bin/test-helper/log.rs"
[dependencies]
anyhow = "1.0.75"
lazy_static = "1.4.0"
nix = {version = "0.27.1", features = ["process", "signal", "fs"]}
log = { version = "0.4.20", features = ["std"]}
nix = { version = "0.27.1", features = ["process", "signal", "fs"]}
rand = "0.8.5"
regex = "1.10.2"
simplelog = "0.12.1"
thiserror = "1.0.50"
time = { version = "0.3.30", features = ["local-offset"]}
tokio = { version = "1.34.0", features = ["full"] }
tokio-util = "0.7.10"

25
docker/alpine/Dockerfile Normal file
View File

@@ -0,0 +1,25 @@
FROM rust:alpine as builder
ADD . /root/wingmate
WORKDIR /root/wingmate
RUN apk add musl-dev && cargo clean && \
cargo build --release
FROM alpine
COPY --from=builder /root/wingmate/target/release/wingmate-rs /usr/local/bin/wingmate
COPY --from=builder /root/wingmate/target/release/wmtest-helper-dummy /usr/local/bin/wmtest-helper-dummy
COPY --from=builder /root/wingmate/target/release/wmtest-helper-spawner /usr/local/bin/wmtest-helper-spawner
COPY --from=builder /root/wingmate/target/release/wmtest-helper-log /usr/local/bin/wmtest-helper-log
ADD docker/alpine/etc/ /etc/
ADD docker/alpine/entry.sh /usr/local/bin/entry.sh
RUN chmod -R ugo+x /etc/wingmate && chmod +x /usr/local/bin/entry.sh && apk add tzdata && \
ln -sv /usr/share/zoneinfo/Australia/Sydney /etc/localtime
ENTRYPOINT [ "/usr/local/bin/entry.sh" ]
CMD [ "/usr/local/bin/wingmate" ]

7
docker/alpine/entry.sh Normal file
View File

@@ -0,0 +1,7 @@
#!/bin/sh
if [ $# -gt 0 ];then
exec "$@"
else
exec /usr/local/bin/wingmate
fi

View File

@@ -0,0 +1,3 @@
17 * * * * /etc/wingmate/crontab.d/cron1.sh
*/5 * * * * /etc/wingmate/crontab.d/cron2.sh
21,41 3,6,14,17,20,22 * * * /etc/wingmate/crontab.d/cron3.sh

View File

@@ -0,0 +1,3 @@
#!/bin/sh
exec wmtest-helper-log /var/log/cron1.log "this cron runs every hour on minute 17"

View File

@@ -0,0 +1,3 @@
#!/bin/sh
exec wmtest-helper-log /var/log/cron2.log "this cron runs every hour on minute 5,10,15,20,25,30,35,40,45,50,55"

View File

@@ -0,0 +1,3 @@
#!/bin/sh
exec wmtest-helper-log /var/log/cron3.log "this cron runs based on this specification: 21,41 3,6,14,17,20,22 * * *"

View File

@@ -0,0 +1,4 @@
#!/bin/sh
export LOG_PATH=/var/log/one.log
exec /usr/local/bin/wmtest-helper-spawner 10

View File

@@ -0,0 +1,4 @@
#!/bin/sh
export LOG_PATH=/var/log/three.log
exec /usr/local/bin/wmtest-helper-spawner 13

View File

@@ -0,0 +1,4 @@
#!/bin/sh
export LOG_PATH=/var/log/two.log
exec /usr/local/bin/wmtest-helper-spawner 4

View File

@@ -0,0 +1 @@
you cannot run this file

9
docker/dev/Dockerfile Normal file
View File

@@ -0,0 +1,9 @@
FROM ubuntu:22.04
ADD target/debug/init /usr/local/bin/init
ADD docker/etc/ /etc/
RUN chmod ugo+x /etc/wingmate/services/one && chmod ugo+x /etc/wingmate/services/two.sh && \
chmod ugo-x /etc/wingmate/services/three.sh
CMD [ "/usr/local/bin/init" ]

View File

@@ -0,0 +1,3 @@
17 * * * * sleep 1
*/12 * * * * sleep 1
12,17,27 * * * * sleep 1

View File

@@ -0,0 +1 @@
you cannot run this file

View File

@@ -0,0 +1,3 @@
#!/bin/bash
exec sleep 1

View File

@@ -0,0 +1,15 @@
use std::{env, thread, time};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let myi: u64;
let args: Vec<String> = env::args().collect();
if args.len() > 1 {
myi = args[1].parse().unwrap();
thread::sleep(time::Duration::from_secs(myi));
} else {
return Err(anyhow::anyhow!("invalid arguments").into());
}
Ok(())
}

View File

@@ -0,0 +1,19 @@
use std::env;
use std::fs;
use std::io;
use std::io::Write;
use time::OffsetDateTime;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let args: Vec<String> = env::args().collect();
if args.len() == 3 {
let file = fs::OpenOptions::new().create(true).append(true).open(&args[1])?;
let mut buf = io::BufWriter::new(file);
let local_time = OffsetDateTime::now_local()?;
buf.write_all(format!("{} {}\n", local_time, &args[2]).as_bytes())?;
} else {
return Err(anyhow::anyhow!("invalid argument").into());
}
Ok(())
}

View File

@@ -0,0 +1,46 @@
#[macro_use] extern crate log;
extern crate simplelog;
use simplelog::*;
use std::error::Error;
use std::fs::OpenOptions;
use std::process::Command;
use std::env;
use rand::Rng;
use nix::sys::signal;
fn main() -> Result<(), Box<dyn Error>> {
let args: Vec<String> = env::args().collect();
unsafe {
signal::signal(signal::Signal::SIGCHLD, signal::SigHandler::SigIgn)
}.unwrap();
let mut rng = rand::thread_rng();
let log_path = env::var("LOG_PATH")?;
let file = OpenOptions::new().append(true).create(true).open(log_path)?;
WriteLogger::init(LevelFilter::Debug, Config::default(), file)?;
if args.len() > 1 {
let x: u64 = args[1].parse()?;
loop {
for _i in 0..x {
let sleep_time = rng.gen_range(10..20);
info!("starting wmtest-helper-dummy {}", &sleep_time);
let child = Command::new("/usr/local/bin/wmtest-helper-dummy").arg(format!("{}", sleep_time)).spawn();
if let Err(e) = child {
error!("error spawning child: {e}");
}
}
let pause_time = rng.gen_range(10..20);
info!("going to sleep for {}", &pause_time);
std::thread::sleep(std::time::Duration::from_secs(pause_time));
info!("waking up")
}
} else {
return Err(anyhow::anyhow!("invalid arguments").into());
}
}

View File

@@ -3,12 +3,14 @@ mod config;
pub(crate) mod error;
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();
match env::var("WINGMATE_CONFIG_PATH") {
match env::var(WINGMATE_CONFIG_PATH) {
Ok(paths) => {
for p in paths.split(':') {
vec_search.push(String::from(p));
@@ -16,7 +18,8 @@ pub async fn start() -> Result<(), Box<dyn std_err::Error>> {
},
Err(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 {
vec_search.push(String::from("/etc/wingmate"));
}

View File

@@ -2,11 +2,26 @@ use std::fs;
use std::env;
use std::path::PathBuf;
use std::io::{BufReader, BufRead};
use std::error as std_error;
use crate::init::error as wingmate_error;
use anyhow::anyhow;
use nix::unistd::{access, AccessFlags};
use lazy_static::lazy_static;
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)]
pub enum Command {
@@ -18,8 +33,7 @@ pub enum Command {
pub enum CronTimeFieldSpec {
Any,
Exact(u8),
MultiOccurrence(Vec<u8>),
Every(u8)
MultiOccurrence(Vec<u8>)
}
#[derive(Debug)]
@@ -40,9 +54,9 @@ pub struct 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() {
return Err(wingmate_error::InvalidConfigSearchPathError.into());
return Err(wingmate_error::WingmateInitError::InvalidConfigSearchPath.into());
}
let mut svc_commands: Vec<Command> = Vec::new();
@@ -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
if !svc_commands.is_empty() || !cron.is_empty() {
@@ -81,7 +95,7 @@ impl Config {
}
if svc_commands.is_empty() && cron.is_empty() {
return Err(wingmate_error::NoServiceOrCronFoundError.into());
return Err(wingmate_error::WingmateInitError::NoServiceOrCron.into());
}
let mut config = Config {
@@ -89,16 +103,14 @@ impl Config {
cron,
shell_path: None,
};
config.find_shell()?;
config.find_shell().map_err(|e| { wingmate_error::WingmateInitError::FindShell { source: e } })?;
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! {
static ref CRON_REGEX: Regex = Regex::new(
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();
static ref CRON_REGEX: Regex = Regex::new(CRON_REGEX_STR).unwrap();
}
let cron_path = path.join("crontab");
@@ -107,45 +119,72 @@ impl Config {
if let Ok(f) = fs::File::open(cron_path.as_path()) {
for line in BufReader::new(f).lines() {
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 mut match_str = cap.name("minute").ok_or::<Box<dyn std_error::Error>>(
wingmate_error::CronSyntaxError(format!("cannot capture minute in \"{}\"", &l)).into()
let cap = CRON_REGEX.captures(&l).ok_or::<wingmate_error::CronParseError>(
wingmate_error::CronParseError::InvalidSyntax(String::from(&l))
)?;
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>>(
wingmate_error::CronSyntaxError(format!("cannot capture hour in \"{}\"", &l)).into()
match_str = cap.name(HOUR).ok_or::<wingmate_error::CronParseError>(
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| {
Box::new(wingmate_error::CronSyntaxError(format!("failed to parse hour \"{}\" in \"{}\": {}", &match_str.as_str(), &l, e)))
let hour = Self::to_cron_time_field_spec(&match_str, 24u8).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(HOUR)
}
})?;
match_str = cap.name("dom").ok_or::<Box<dyn std_error::Error>>(
wingmate_error::CronSyntaxError(format!("cannot capture day of month in \"{}\"", &l)).into()
match_str = cap.name(DAY_OF_MONTH_ABBRV).ok_or::<wingmate_error::CronParseError>(
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| {
Box::new(wingmate_error::CronSyntaxError(format!("failed to parse day of month \"{}\" in \"{}\": {}", &match_str.as_str(), &l, e)))
let dom = Self::to_cron_time_field_spec(&match_str, 31u8).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(DAY_OF_MONTH)
}
})?;
match_str = cap.name("month").ok_or::<Box<dyn std_error::Error>>(
wingmate_error::CronSyntaxError(format!("cannot capture month in \"{}\"", &l)).into()
match_str = cap.name(MONTH).ok_or::<wingmate_error::CronParseError>(
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| {
Box::new(wingmate_error::CronSyntaxError(format!("failed to parse month \"{}\" in \"{}\": {}", &match_str.as_str(), &l, e)))
let month = Self::to_cron_time_field_spec(&match_str, 12u8).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(MONTH)
}
})?;
match_str = cap.name("dow").ok_or::<Box<dyn std_error::Error>>(
wingmate_error::CronSyntaxError(format!("cannot capture day of week in \"{}\"", &l)).into()
match_str = cap.name(DAY_OF_WEEK_ABBRV).ok_or::<wingmate_error::CronParseError>(
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| {
Box::new(wingmate_error::CronSyntaxError(format!("failed to parse day of week \"{}\" in \"{}\": {}", &match_str.as_str(), &l, e)))
let dow = Self::to_cron_time_field_spec(&match_str, 7u8).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(DAY_OF_WEEK)
}
})?;
match_str = cap.name("command").ok_or::<Box<dyn std_error::Error>>(
wingmate_error::CronSyntaxError(format!("cannot capture command in \"{}\"", &l)).into()
match_str = cap.name(COMMAND).ok_or::<wingmate_error::CronParseError>(
wingmate_error::CronParseError::FieldMatch { cron_line: String::from(&l), field_name: String::from(COMMAND) }
)?;
ret_vec.push(Crontab {
@@ -163,34 +202,49 @@ impl Config {
Ok(ret_vec)
}
fn to_cron_time_field_spec(match_str: &regex::Match) -> Result<CronTimeFieldSpec, Box<dyn std_error::Error>> {
fn to_cron_time_field_spec(match_str: &regex::Match, max: u8) -> Result<CronTimeFieldSpec, anyhow::Error> {
let field = match_str.as_str();
if field == "*" {
return Ok(CronTimeFieldSpec::Any);
} else if field.starts_with("*/") {
let every = field[2..].parse::<u8>()?;
return Ok(CronTimeFieldSpec::Every(every));
let every = field[2..].parse::<u8>().context("parsing on field matching \"every\" pattern")?;
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(",") {
let multi: Vec<&str> = field.split(",").collect();
let mut multi_occurrence: Vec<u8> = Vec::new();
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);
}
return Ok(CronTimeFieldSpec::MultiOccurrence(multi_occurrence));
} 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));
}
}
fn find_shell(&mut self) -> Result<(), Box<dyn std_error::Error>> {
fn find_shell(&mut self) -> Result<(), wingmate_error::FindShellError> {
let shell: String;
match env::var("WINGMATE_SHELL") {
match env::var(WINGMATE_SHELL_ENV) {
Ok(sh) => {
shell = sh;
},
@@ -200,13 +254,15 @@ impl Config {
shell = String::from("sh");
},
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();
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> {
self.services.iter()
}
pub fn get_cron_iter(&self) -> std::slice::Iter<Crontab> {
self.cron.iter()
}
pub fn get_shell(&self) -> Option<String> {
if let Some(shell) = &self.shell_path {
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
}
}

View File

@@ -3,29 +3,41 @@ mod waiter;
mod starter;
mod constants;
use std::error;
use tokio::{select, pin};
use tokio::task::JoinSet;
use tokio_util::sync::CancellationToken;
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::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 sig_sync_flag = sync_flag.clone();
let sighandler_cancel = CancellationToken::new();
let waiter_cancel_sighandler = sighandler_cancel.clone();
let signal_pump_stop = 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 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 {
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 || {
@@ -38,15 +50,65 @@ pub async fn start(cfg: config::Config) -> Result<(), Box<dyn error::Error>> {
Ok(v) => {
if let Err(ev) = v {
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) => {
dbg!(&e);
return Err(e.into());
return Err(WingmateInitError::Join { source: e });
},
}
}
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(())
}

View File

@@ -1,13 +1,13 @@
use std::error;
use tokio::signal::unix::{signal, SignalKind};
use tokio::select;
use std::sync::{Arc, Mutex};
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>> {
let mut sigint = signal(SignalKind::interrupt())?;
let mut sigterm = signal(SignalKind::terminate())?;
let mut sigchld = signal(SignalKind::child())?;
pub async fn sighandler(flag: Arc<Mutex<bool>>, cancel: CancellationToken, exit: CancellationToken) -> Result<(), WingmateInitError> {
let mut sigint = signal(SignalKind::interrupt()).map_err(|e| { WingmateInitError::Signal { source: e } })?;
let mut sigterm = signal(SignalKind::terminate()).map_err(|e| { WingmateInitError::Signal { source: e } })?;
let mut sigchld = signal(SignalKind::child()).map_err(|e| { WingmateInitError::Signal { source: e } })?;
'signal: loop {
select! {

View File

@@ -3,38 +3,48 @@ use tokio::process::{Command, Child};
use tokio_util::sync::CancellationToken;
use tokio::select;
use tokio::io::Result as tokio_result;
use tokio::time::sleep;
use tokio::time::{sleep, interval};
use std::time::Duration;
use std::process::ExitStatus;
use std::error;
use nix::sys::signal::{kill, Signal};
use nix::errno::Errno;
use nix::unistd::Pid;
use anyhow::{Context, anyhow};
use time::{OffsetDateTime, Duration as TimeDur, Weekday};
use crate::init::config;
use crate::init::error::NoShellAvailableError;
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)
-> Result<(), Box<dyn error::Error>> {
const CRON_TRIGGER_WAIT_SECS: u64 = 20;
pub fn start_services(ts: &mut JoinSet<Result<(), WingmateInitError>>, cfg: &config::Config, cancel: CancellationToken)
-> Result<(), WingmateInitError> {
for svc_ in cfg.get_service_iter() {
let shell: String = cfg.get_shell().ok_or::<Box<dyn error::Error>>(NoShellAvailableError.into())?;
let mut shell: String = String::new();
if let config::Command::ShellPrefixed(_) = svc_ {
shell = cfg.get_shell().ok_or::<WingmateInitError>(WingmateInitError::NoShellAvailable)?;
}
let svc = svc_.clone();
// if let config::Command::ShellPrefixed(_) = svc {
// shell = cfg.get_shell().ok_or::<Box<dyn error::Error>>(NoShellAvailableError.into())?;
// }
let cancel = cancel.clone();
ts.spawn(async move {
'autorestart: loop {
let mut child: Child;
let shell = shell.clone();
let svc = svc.clone();
match svc {
config::Command::Direct(c) => {
child = Command::new(c).spawn().expect("change me");
let exp_str = c.clone();
child = Command::new(c).spawn().map_err(|e| {
WingmateInitError::SpawnError { source: e, message: exp_str }
})?;
},
config::Command::ShellPrefixed(s) => {
child = Command::new(shell).arg(s).spawn().expect("change me");
let shell = shell.clone();
let exp_str = s.clone();
let exp_shell = shell.clone();
child = Command::new(shell).arg(s).spawn().map_err(|e| {
WingmateInitError::SpawnError { source: e, message: format!("{} {}", exp_shell, exp_str) }
})?;
}
}
@@ -44,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)) {
Ok(_) => {
select! {
_ = sleep(Duration::from_secs(5)) => {
_ = sleep(Duration::from_secs(config::MAX_TERM_WAIT_TIME_SECS)) => {
child.kill().await.expect("failed to kill process");
},
result = child.wait() => {
if let Err(e) = result_match(result) {
return Err(e);
return Err(WingmateInitError::ChildExit { source: e });
}
break 'autorestart;
}
@@ -57,7 +67,7 @@ pub fn start_services(ts: &mut JoinSet<Result<(), Box<dyn error::Error + Send +
},
Err(e) => {
if e != Errno::ESRCH {
return Err(e.into());
return Err(WingmateInitError::ChildNotFound);
} else {
break 'autorestart;
}
@@ -69,34 +79,180 @@ pub fn start_services(ts: &mut JoinSet<Result<(), Box<dyn error::Error + Send +
},
result = child.wait() => {
if let Err(e) = result_match(result) {
return Err(e);
return Err(WingmateInitError::ChildExit { source: e });
}
},
}
}
println!("starter: task completed");
dbg!("starter: task completed");
Ok(())
});
}
println!("starter: spawning completed");
dbg!("starter: spawning completed");
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 Some(eos) = e.raw_os_error() {
if eos != nix::Error::ECHILD as i32 {
return Err(e.into());
return Err(e).context("unexpected child exit status");
}
} 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 });
}
}
}
//TODO: remove me! this is for debug + tracing purpose
println!("starter: sleep exited");
Ok(())
}

View File

@@ -37,7 +37,6 @@ pub fn wait_all(flag: Arc<Mutex<bool>>, stop_sighandler: CancellationToken) {
}
},
}
// dbg!("sanity");
}
}

View File

@@ -1,58 +1,116 @@
use std::fmt;
use std::error;
use thiserror::Error;
#[derive(Debug, Clone)]
pub struct InvalidConfigSearchPathError;
#[derive(Error,Debug)]
pub enum WingmateInitError {
#[error("invalid config search path")]
InvalidConfigSearchPath,
impl fmt::Display for InvalidConfigSearchPathError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "invalid config search path")
#[error("no service or cron found")]
NoServiceOrCron,
#[error("failed to spawn: {}", message)]
SpawnError {
#[source]
source: std::io::Error,
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,
}
}
impl error::Error for InvalidConfigSearchPathError {}
#[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("something went wrong")]
// Other {
// #[source]
// source: anyhow::Error,
// }
}
#[derive(Debug,Clone)]
pub struct NoServiceOrCronFoundError;
#[derive(Error,Debug)]
pub enum CronParseError {
#[error("invalid cron syntax: {}", .0)]
InvalidSyntax(String),
impl fmt::Display for NoServiceOrCronFoundError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "no service or cron found")
#[error("cannot capture {} in \"{}\"", field_name, cron_line)]
FieldMatch {
cron_line: String,
field_name: String,
},
#[error("failed to parse {} \"{}\" in \"{}\"", field_name, matched, cron_line)]
Parse {
#[source]
source: anyhow::Error,
cron_line: String,
matched: String,
field_name: String,
}
}
impl error::Error for NoServiceOrCronFoundError {}
#[derive(Error,Debug)]
pub enum FindShellError {
#[error("shell not found")]
ShellNotFound,
#[derive(Debug,Clone)]
pub struct CronSyntaxError(pub String);
impl fmt::Display for CronSyntaxError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "cron syntax error at: {}", self.0)
#[error("when finding shell")]
Other {
#[source]
source: anyhow::Error
}
}
impl error::Error for CronSyntaxError {}
#[derive(Debug,Clone)]
pub struct ShellNotFoundError(pub String);
impl fmt::Display for ShellNotFoundError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "shell not found: {}", self.0)
}
}
impl error::Error for ShellNotFoundError {}
#[derive(Debug,Clone)]
pub struct NoShellAvailableError;
impl fmt::Display for NoShellAvailableError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "no shell available")
}
}
impl error::Error for NoShellAvailableError {}

View File

@@ -1,12 +1,12 @@
mod init;
use std::error;
use wingmate_rs::init;
#[tokio::main]
async fn main() -> Result<(), Box<dyn error::Error>> {
if let Err(e) = init::start().await {
eprintln!("{}", e);
return Err(e);
return Err(e.into());
}
Ok(())