use std::io::Write;
use std::process::{Child, Command, ExitStatus, Stdio};
use std::sync::{Mutex, OnceLock};
use std::thread;
use std::time::{Duration, Instant};
use crate::utils::lock::LockRecover;
use crate::utils::time::now_secs;
#[allow(
clippy::missing_docs_in_private_items,
reason = "split-out module keeps the file under the linecheck limit"
)]
mod wait_for_crontab_write;
pub(crate) use wait_for_crontab_write::*;
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct CrontabSyncStatus {
pub ok: bool,
pub last_error: Option<String>,
pub last_error_at: Option<u64>,
}
impl Default for CrontabSyncStatus {
fn default() -> Self {
Self {
ok: true,
last_error: None,
last_error_at: None,
}
}
}
fn crontab_sync_state() -> &'static Mutex<CrontabSyncStatus> {
static STATE: OnceLock<Mutex<CrontabSyncStatus>> = OnceLock::new();
STATE.get_or_init(|| Mutex::new(CrontabSyncStatus::default()))
}
pub(crate) fn crontab_sync_status() -> CrontabSyncStatus {
crontab_sync_state().lock_recover().clone()
}
pub(crate) fn record_crontab_sync_success() {
*crontab_sync_state().lock_recover() = CrontabSyncStatus::default();
}
pub(crate) fn record_crontab_sync_failure(err: &SyncError) {
*crontab_sync_state().lock_recover() = CrontabSyncStatus {
ok: false,
last_error: Some(err.to_string()),
last_error_at: Some(now_secs()),
};
}
#[cfg(test)]
pub(crate) fn reset_crontab_sync_status_for_tests() {
record_crontab_sync_success();
}
const CRONTAB_WRITE_TIMEOUT_ENV: &str = "MOADIM_CRONTAB_WRITE_TIMEOUT_SECS";
const DEFAULT_CRONTAB_WRITE_TIMEOUT: Duration = Duration::from_secs(15);
const CRONTAB_WAIT_POLL_INTERVAL: Duration = Duration::from_millis(100);
pub mod routines;
#[derive(Debug)]
pub enum SyncError {
CrontabCommand(String),
Io(std::io::Error),
}
impl std::fmt::Display for SyncError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::CrontabCommand(msg) => write!(f, "crontab: {msg}"),
Self::Io(err) => write!(f, "io: {err}"),
}
}
}
impl From<std::io::Error> for SyncError {
fn from(err: std::io::Error) -> Self {
Self::Io(err)
}
}
pub(crate) fn to_os_schedule(schedule: &str) -> String {
let trimmed = schedule.trim();
if trimmed.starts_with('@') {
return trimmed.to_string();
}
let fields: Vec<&str> = trimmed.split_ascii_whitespace().collect();
match fields.len() {
6 | 7 => fields[1..6].join(" "),
_ => trimmed.to_string(),
}
}
fn crontab_bin() -> String {
if let Ok(bin) = std::env::var("MOADIM_CRONTAB_BIN") {
return bin;
}
#[cfg(test)]
let fallback = "/nonexistent/moadim-test-crontab-guard".to_string();
#[cfg(not(test))]
let fallback = "crontab".to_string();
fallback
}
include!("read_crontab.rs");