Compare commits
No commits in common. "1371e0e637b2884ebd3d7fcbb62cf16145511e5d" and "14066e19b602c166d8e6ca7192b9354e86483f38" have entirely different histories.
1371e0e637
...
14066e19b6
@ -10,8 +10,7 @@
|
|||||||
"dustypomerleau.rust-syntax",
|
"dustypomerleau.rust-syntax",
|
||||||
"1YiB.rust-bundle",
|
"1YiB.rust-bundle",
|
||||||
"serayuzgur.crates",
|
"serayuzgur.crates",
|
||||||
"tamasfe.even-better-toml",
|
"tamasfe.even-better-toml"
|
||||||
"ms-azuretools.vscode-docker"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
165
Cargo.lock
generated
165
Cargo.lock
generated
@ -17,21 +17,6 @@ version = "1.0.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "aho-corasick"
|
|
||||||
version = "1.1.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
|
|
||||||
dependencies = [
|
|
||||||
"memchr",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "anyhow"
|
|
||||||
version = "1.0.75"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "autocfg"
|
name = "autocfg"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
@ -86,27 +71,6 @@ 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]]
|
|
||||||
name = "futures-core"
|
|
||||||
version = "0.3.29"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "futures-sink"
|
|
||||||
version = "0.3.29"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gimli"
|
name = "gimli"
|
||||||
version = "0.28.0"
|
version = "0.28.0"
|
||||||
@ -119,12 +83,6 @@ version = "0.3.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7"
|
checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "lazy_static"
|
|
||||||
version = "1.4.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.150"
|
version = "0.2.150"
|
||||||
@ -188,15 +146,6 @@ 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"
|
||||||
@ -235,12 +184,6 @@ 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"
|
||||||
@ -268,35 +211,6 @@ dependencies = [
|
|||||||
"bitflags 1.3.2",
|
"bitflags 1.3.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "regex"
|
|
||||||
version = "1.10.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343"
|
|
||||||
dependencies = [
|
|
||||||
"aho-corasick",
|
|
||||||
"memchr",
|
|
||||||
"regex-automata",
|
|
||||||
"regex-syntax",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "regex-automata"
|
|
||||||
version = "0.4.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
|
|
||||||
dependencies = [
|
|
||||||
"aho-corasick",
|
|
||||||
"memchr",
|
|
||||||
"regex-syntax",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "regex-syntax"
|
|
||||||
version = "0.8.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustc-demangle"
|
name = "rustc-demangle"
|
||||||
version = "0.1.23"
|
version = "0.1.23"
|
||||||
@ -309,26 +223,6 @@ 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"
|
||||||
@ -365,46 +259,6 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[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",
|
|
||||||
"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"
|
||||||
@ -435,19 +289,6 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tokio-util"
|
|
||||||
version = "0.7.10"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15"
|
|
||||||
dependencies = [
|
|
||||||
"bytes",
|
|
||||||
"futures-core",
|
|
||||||
"futures-sink",
|
|
||||||
"pin-project-lite",
|
|
||||||
"tokio",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.12"
|
version = "1.0.12"
|
||||||
@ -530,12 +371,6 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
|||||||
name = "wingmate-rs"
|
name = "wingmate-rs"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
|
||||||
"lazy_static",
|
|
||||||
"nix",
|
"nix",
|
||||||
"regex",
|
|
||||||
"thiserror",
|
|
||||||
"time",
|
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-util",
|
|
||||||
]
|
]
|
||||||
|
|||||||
@ -6,11 +6,5 @@ edition = "2021"
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.75"
|
nix = {version = "0.27.1", features = ["process"]}
|
||||||
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 = { version = "1.34.0", features = ["full"] }
|
||||||
tokio-util = "0.7.10"
|
|
||||||
|
|||||||
9
LICENSE
9
LICENSE
@ -1,9 +0,0 @@
|
|||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2023 suyono
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
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" ]
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
17 * * * * sleep 1
|
|
||||||
*/12 * * * * sleep 1
|
|
||||||
12,17,27 * * * * sleep 1
|
|
||||||
@ -1 +0,0 @@
|
|||||||
you cannot run this file
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
exec sleep 1
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
exec sleep 1
|
|
||||||
@ -4,10 +4,5 @@ use wingmate_rs::init;
|
|||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<(), Box<dyn error::Error>> {
|
async fn main() -> Result<(), Box<dyn error::Error>> {
|
||||||
if let Err(e) = init::start().await {
|
init::daemon::start().await
|
||||||
eprintln!("{}", e);
|
|
||||||
return Err(e.into());
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
33
src/init.rs
33
src/init.rs
@ -1,32 +1 @@
|
|||||||
mod daemon;
|
pub mod daemon;
|
||||||
mod config;
|
|
||||||
pub(crate) mod error;
|
|
||||||
|
|
||||||
use std::env;
|
|
||||||
use anyhow::Context;
|
|
||||||
|
|
||||||
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) {
|
|
||||||
Ok(paths) => {
|
|
||||||
for p in paths.split(':') {
|
|
||||||
vec_search.push(String::from(p));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(e) => {
|
|
||||||
if let env::VarError::NotUnicode(_) = e {
|
|
||||||
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"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let config = config::Config::find(vec_search)?;
|
|
||||||
dbg!(&config);
|
|
||||||
daemon::start(config).await
|
|
||||||
}
|
|
||||||
@ -1,399 +0,0 @@
|
|||||||
use std::fs;
|
|
||||||
use std::env;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::io::{BufReader, BufRead};
|
|
||||||
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 {
|
|
||||||
ShellPrefixed(String),
|
|
||||||
Direct(String)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum CronTimeFieldSpec {
|
|
||||||
Any,
|
|
||||||
Exact(u8),
|
|
||||||
MultiOccurrence(Vec<u8>)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Crontab {
|
|
||||||
pub minute: CronTimeFieldSpec,
|
|
||||||
pub hour: CronTimeFieldSpec,
|
|
||||||
pub day_of_month: CronTimeFieldSpec,
|
|
||||||
pub month: CronTimeFieldSpec,
|
|
||||||
pub day_of_week: CronTimeFieldSpec,
|
|
||||||
pub command: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Config {
|
|
||||||
pub services: Vec<Command>,
|
|
||||||
pub cron: Vec<Crontab>,
|
|
||||||
shell_path: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Config {
|
|
||||||
pub fn find(search_path: Vec<String>) -> Result<Config, wingmate_error::WingmateInitError> {
|
|
||||||
if search_path.is_empty() {
|
|
||||||
return Err(wingmate_error::WingmateInitError::InvalidConfigSearchPath.into());
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut svc_commands: Vec<Command> = Vec::new();
|
|
||||||
let mut cron : Vec<Crontab> = Vec::new();
|
|
||||||
'search: for p in search_path {
|
|
||||||
let mut buf = PathBuf::new();
|
|
||||||
buf.push(p);
|
|
||||||
if let Ok(m) = fs::metadata(buf.as_path()) {
|
|
||||||
if m.is_dir() {
|
|
||||||
let svc = buf.join("services");
|
|
||||||
if let Ok(svc_iter) = fs::read_dir(svc.as_path()) {
|
|
||||||
for entry in svc_iter {
|
|
||||||
if let Ok(dirent) = entry {
|
|
||||||
let ep = dirent.path();
|
|
||||||
if let Ok(_) = access(ep.as_path(), AccessFlags::X_OK) {
|
|
||||||
// execute directly
|
|
||||||
svc_commands.push(Command::Direct(String::from(ep.to_string_lossy())));
|
|
||||||
} else {
|
|
||||||
// call with shell
|
|
||||||
svc_commands.push(Command::ShellPrefixed(String::from(ep.to_string_lossy())));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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() {
|
|
||||||
break 'search;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// reserve for future use; when we have a centralized config file
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if svc_commands.is_empty() && cron.is_empty() {
|
|
||||||
return Err(wingmate_error::WingmateInitError::NoServiceOrCron.into());
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut config = Config {
|
|
||||||
services: svc_commands,
|
|
||||||
cron,
|
|
||||||
shell_path: None,
|
|
||||||
};
|
|
||||||
config.find_shell().map_err(|e| { wingmate_error::WingmateInitError::FindShell { source: e } })?;
|
|
||||||
|
|
||||||
Ok(config)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_crontab(path: &mut PathBuf) -> Result<Vec<Crontab>, wingmate_error::CronParseError> {
|
|
||||||
lazy_static! {
|
|
||||||
static ref CRON_REGEX: Regex = Regex::new(CRON_REGEX_STR).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
let cron_path = path.join("crontab");
|
|
||||||
let mut ret_vec: Vec<Crontab> = Vec::new();
|
|
||||||
|
|
||||||
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::<wingmate_error::CronParseError>(
|
|
||||||
wingmate_error::CronParseError::InvalidSyntax(String::from(&l))
|
|
||||||
)?;
|
|
||||||
|
|
||||||
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::<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, 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(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, 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::<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, 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(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, 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::<wingmate_error::CronParseError>(
|
|
||||||
wingmate_error::CronParseError::FieldMatch { cron_line: String::from(&l), field_name: String::from(COMMAND) }
|
|
||||||
)?;
|
|
||||||
|
|
||||||
ret_vec.push(Crontab {
|
|
||||||
minute,
|
|
||||||
hour,
|
|
||||||
day_of_month: dom,
|
|
||||||
month,
|
|
||||||
day_of_week: dow,
|
|
||||||
command: String::from(match_str.as_str())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(ret_vec)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn to_cron_time_field_spec(match_str: ®ex::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>().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>().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>().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<(), wingmate_error::FindShellError> {
|
|
||||||
|
|
||||||
let shell: String;
|
|
||||||
match env::var(WINGMATE_SHELL_ENV) {
|
|
||||||
Ok(sh) => {
|
|
||||||
shell = sh;
|
|
||||||
},
|
|
||||||
Err(e) => {
|
|
||||||
match e {
|
|
||||||
env::VarError::NotPresent => {
|
|
||||||
shell = String::from("sh");
|
|
||||||
},
|
|
||||||
env::VarError::NotUnicode(_) => {
|
|
||||||
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").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 {
|
|
||||||
let mut search_path = PathBuf::new();
|
|
||||||
search_path.push(p);
|
|
||||||
|
|
||||||
let shell_path = search_path.join(&shell);
|
|
||||||
if let Ok(_) = fs::metadata(shell_path.as_path()) {
|
|
||||||
self.shell_path = Some(String::from(shell_path.to_string_lossy()));
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Clone for Command {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
match self {
|
|
||||||
Command::Direct(d) => Command::Direct(String::from(d)),
|
|
||||||
Command::ShellPrefixed(s) => Command::ShellPrefixed(String::from(s))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,113 +1,19 @@
|
|||||||
mod sighandler;
|
mod sighandler;
|
||||||
mod waiter;
|
mod waiter;
|
||||||
mod starter;
|
|
||||||
mod constants;
|
|
||||||
|
|
||||||
use tokio::{select, pin};
|
use std::error;
|
||||||
use tokio::task::JoinSet;
|
use tokio::task::{self, JoinHandle};
|
||||||
use tokio_util::sync::CancellationToken;
|
use tokio::sync::watch;
|
||||||
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<(), WingmateInitError> {
|
pub async fn start() -> Result<(), Box<dyn error::Error>> {
|
||||||
let sync_flag = Arc::new(Mutex::new(false));
|
let (tx, mut _rx) = watch::channel::<i32>(1);
|
||||||
let sig_sync_flag = sync_flag.clone();
|
|
||||||
|
|
||||||
let sighandler_cancel = CancellationToken::new();
|
let sig_handler_fn: JoinHandle<Result<(), Box<dyn error::Error + Send + Sync>>> = task::spawn(async move {
|
||||||
let waiter_cancel_sighandler = sighandler_cancel.clone();
|
sighandler::sighandler(tx).await
|
||||||
let signal_pump_stop = sighandler_cancel.clone();
|
|
||||||
|
|
||||||
let cancel = CancellationToken::new();
|
|
||||||
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
|
|
||||||
});
|
});
|
||||||
|
|
||||||
set.spawn(async move {
|
if let Err(v) = sig_handler_fn.await? {
|
||||||
sighandler::sighandler(sig_sync_flag, cancel, sighandler_cancel).await
|
return Err(v as Box<dyn error::Error>)
|
||||||
});
|
|
||||||
|
|
||||||
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 || {
|
|
||||||
waiter::wait_all(sync_flag, waiter_cancel_sighandler);
|
|
||||||
Ok(())
|
|
||||||
});
|
|
||||||
|
|
||||||
while let Some(res) = set.join_next().await {
|
|
||||||
match res {
|
|
||||||
Ok(v) => {
|
|
||||||
if let Err(ev) = v {
|
|
||||||
dbg!(&ev);
|
|
||||||
match ev {
|
|
||||||
WingmateInitError::SpawnError { source, message } => {
|
|
||||||
eprintln!("{}", WingmateInitError::SpawnError { source, message });
|
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
return Err(ev);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(e) => {
|
|
||||||
dbg!(&e);
|
|
||||||
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(())
|
Ok(())
|
||||||
|
|||||||
@ -1,40 +1,31 @@
|
|||||||
|
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 tokio::sync::watch::Sender;
|
||||||
use tokio_util::sync::CancellationToken;
|
|
||||||
use crate::init::error::WingmateInitError;
|
|
||||||
|
|
||||||
pub async fn sighandler(flag: Arc<Mutex<bool>>, cancel: CancellationToken, exit: CancellationToken) -> Result<(), WingmateInitError> {
|
pub async fn sighandler(s: Sender<i32>) -> Result<(), Box<dyn error::Error + Send + Sync>> {
|
||||||
let mut sigint = signal(SignalKind::interrupt()).map_err(|e| { WingmateInitError::Signal { source: e } })?;
|
let mut sigint = signal(SignalKind::interrupt())?;
|
||||||
let mut sigterm = signal(SignalKind::terminate()).map_err(|e| { WingmateInitError::Signal { source: e } })?;
|
let mut sigterm = signal(SignalKind::terminate())?;
|
||||||
let mut sigchld = signal(SignalKind::child()).map_err(|e| { WingmateInitError::Signal { source: e } })?;
|
let mut sigchld = signal(SignalKind::child())?;
|
||||||
|
|
||||||
'signal: loop {
|
'signal: loop {
|
||||||
select! {
|
select! {
|
||||||
_ = sigint.recv() => {
|
_ = sigint.recv() => {
|
||||||
println!("got SIGINT");
|
println!("got SIGINT");
|
||||||
initiate_stop(flag.clone(), cancel.clone());
|
drop(s);
|
||||||
|
break 'signal;
|
||||||
},
|
},
|
||||||
_ = sigterm.recv() => {
|
_ = sigterm.recv() => {
|
||||||
println!("got SIGTERM");
|
println!("got SIGTERM");
|
||||||
initiate_stop(flag.clone(), cancel.clone());
|
drop(s);
|
||||||
|
break 'signal;
|
||||||
},
|
},
|
||||||
_ = sigchld.recv() => {
|
_ = sigchld.recv() => {
|
||||||
// do nothing intentionally
|
// do nothing intentionally
|
||||||
|
// return Err(())
|
||||||
},
|
},
|
||||||
_ = exit.cancelled() => {
|
|
||||||
break 'signal;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
|
||||||
|
|
||||||
fn initiate_stop(flag: Arc<Mutex<bool>>, cancel: CancellationToken) {
|
|
||||||
{
|
|
||||||
let mut fl = flag.lock().unwrap();
|
|
||||||
*fl = true;
|
|
||||||
}
|
|
||||||
cancel.cancel();
|
|
||||||
}
|
}
|
||||||
@ -1,258 +0,0 @@
|
|||||||
use tokio::task::JoinSet;
|
|
||||||
use tokio::process::{Command, Child};
|
|
||||||
use tokio_util::sync::CancellationToken;
|
|
||||||
use tokio::select;
|
|
||||||
use tokio::io::Result as tokio_result;
|
|
||||||
use tokio::time::{sleep, interval};
|
|
||||||
use std::time::Duration;
|
|
||||||
use std::process::ExitStatus;
|
|
||||||
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::{WingmateInitError, CronConfigError};
|
|
||||||
|
|
||||||
|
|
||||||
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 mut shell: String = String::new();
|
|
||||||
if let config::Command::ShellPrefixed(_) = svc_ {
|
|
||||||
shell = cfg.get_shell().ok_or::<WingmateInitError>(WingmateInitError::NoShellAvailable)?;
|
|
||||||
}
|
|
||||||
let svc = svc_.clone();
|
|
||||||
let cancel = cancel.clone();
|
|
||||||
ts.spawn(async move {
|
|
||||||
'autorestart: loop {
|
|
||||||
let mut child: Child;
|
|
||||||
let svc = svc.clone();
|
|
||||||
match svc {
|
|
||||||
config::Command::Direct(c) => {
|
|
||||||
let exp_str = c.clone();
|
|
||||||
child = Command::new(c).spawn().map_err(|e| {
|
|
||||||
WingmateInitError::SpawnError { source: e, message: exp_str }
|
|
||||||
})?;
|
|
||||||
},
|
|
||||||
config::Command::ShellPrefixed(s) => {
|
|
||||||
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) }
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
select! {
|
|
||||||
_ = cancel.cancelled() => {
|
|
||||||
if let Some(id) = child.id() {
|
|
||||||
match kill(Pid::from_raw(id as i32), Some(Signal::SIGTERM)) {
|
|
||||||
Ok(_) => {
|
|
||||||
select! {
|
|
||||||
_ = 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(WingmateInitError::ChildExit { source: e });
|
|
||||||
}
|
|
||||||
break 'autorestart;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(e) => {
|
|
||||||
if e != Errno::ESRCH {
|
|
||||||
return Err(WingmateInitError::ChildNotFound);
|
|
||||||
} else {
|
|
||||||
break 'autorestart;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
break 'autorestart;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
result = child.wait() => {
|
|
||||||
if let Err(e) = result_match(result) {
|
|
||||||
return Err(WingmateInitError::ChildExit { source: e });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dbg!("starter: task completed");
|
|
||||||
Ok(())
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
dbg!("starter: spawning completed");
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
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).context("unexpected child exit status");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
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 });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
@ -1,42 +1,10 @@
|
|||||||
use nix::errno::Errno;
|
use nix::sys::wait;
|
||||||
use nix::sys::wait::{self, WaitStatus};
|
|
||||||
use nix::unistd::Pid;
|
use nix::unistd::Pid;
|
||||||
use std::sync::{Mutex, Arc};
|
|
||||||
use std::{thread, time};
|
|
||||||
use tokio_util::sync::CancellationToken;
|
|
||||||
|
|
||||||
pub fn wait_all(flag: Arc<Mutex<bool>>, stop_sighandler: CancellationToken) {
|
#[allow(dead_code)]
|
||||||
'wait: loop {
|
fn wait_all() {
|
||||||
match wait::waitpid(Pid::from_raw(-1), None) {
|
match wait::waitpid(Pid::from_raw(-1), Some(wait::WaitPidFlag::WNOHANG)) {
|
||||||
Ok(x) => {
|
Ok(_x) => {},
|
||||||
// dbg!(x);
|
Err(_err) => {},
|
||||||
match x {
|
|
||||||
WaitStatus::Exited(pid, v) => {
|
|
||||||
println!("wait_all: pid {}: exited with status {}", pid, v);
|
|
||||||
},
|
|
||||||
WaitStatus::Signaled(pid, sig, _dumped) => {
|
|
||||||
println!("wait_all: pid {} killed with signal {}", pid, sig);
|
|
||||||
},
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(err) => {
|
|
||||||
dbg!(err);
|
|
||||||
match err {
|
|
||||||
Errno::ECHILD => {
|
|
||||||
let fl = flag.lock().unwrap();
|
|
||||||
if *fl {
|
|
||||||
stop_sighandler.cancel();
|
|
||||||
break 'wait;
|
|
||||||
} else {
|
|
||||||
drop(fl);
|
|
||||||
thread::sleep(time::Duration::from_millis(100));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -1,119 +0,0 @@
|
|||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
#[derive(Error,Debug)]
|
|
||||||
pub enum WingmateInitError {
|
|
||||||
#[error("invalid config search path")]
|
|
||||||
InvalidConfigSearchPath,
|
|
||||||
|
|
||||||
#[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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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)]
|
|
||||||
InvalidSyntax(String),
|
|
||||||
|
|
||||||
#[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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Error,Debug)]
|
|
||||||
pub enum FindShellError {
|
|
||||||
#[error("shell not found")]
|
|
||||||
ShellNotFound,
|
|
||||||
|
|
||||||
#[error("when finding shell")]
|
|
||||||
Other {
|
|
||||||
#[source]
|
|
||||||
source: anyhow::Error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user