#![allow(
clippy::wildcard_imports,
clippy::too_many_arguments,
clippy::needless_pass_by_value,
dead_code,
reason = "split-out module preserves existing code while keeping files under the linecheck limit"
)]
use super::*;
pub(crate) fn wait_for_crontab_write(child: &mut Child) -> Result<ExitStatus, SyncError> {
let timeout = crontab_write_timeout();
let deadline = Instant::now() + timeout;
loop {
if let Some(status) = child.try_wait()? {
return Ok(status);
}
if Instant::now() >= deadline {
let pid = child.id();
let _ = child.kill();
let status = child.wait()?;
return Err(SyncError::CrontabCommand(format!(
"crontab - timed out after {}s; killed pid {pid} ({status})",
timeout.as_secs()
)));
}
thread::sleep(CRONTAB_WAIT_POLL_INTERVAL);
}
}
pub(crate) fn crontab_write_timeout() -> Duration {
std::env::var(CRONTAB_WRITE_TIMEOUT_ENV)
.ok()
.and_then(|value| value.parse::<u64>().ok())
.filter(|seconds| *seconds > 0)
.map_or(DEFAULT_CRONTAB_WRITE_TIMEOUT, Duration::from_secs)
}
pub(crate) fn find_marker_line(crontab: &str, marker: &str) -> Option<(usize, usize)> {
let mut offset = 0;
for line in crontab.split_inclusive('\n') {
let content = line.trim_end_matches('\n');
if content.trim() == marker {
return Some((offset, offset + content.trim_end().len()));
}
offset += line.len();
}
None
}
pub(crate) fn replace_block_with(
crontab: &str,
block: &str,
begin_marker: &str,
end_marker: &str,
) -> String {
let begin_pos = find_marker_line(crontab, begin_marker).map(|(start, _)| start);
let end_pos = find_marker_line(crontab, end_marker).map(|(_, marker_end)| marker_end);
match (begin_pos, end_pos) {
(Some(begin), Some(end)) if begin < end => {
let after = end;
let mut result = crontab[..begin].to_string();
result.push_str(block);
result.push('\n');
let rest = crontab[after..].trim_start_matches('\n');
if !rest.is_empty() {
result.push('\n');
result.push_str(rest);
if !result.ends_with('\n') {
result.push('\n');
}
}
result
}
(Some(begin), _) => {
let mut result = crontab[..begin].to_string();
result.push_str(block);
result.push('\n');
result
}
_ => {
let mut result = crontab.trim_end_matches('\n').to_string();
if !result.is_empty() {
result.push('\n');
}
result.push_str(block);
result.push('\n');
result
}
}
}
#[cfg(not(test))]
pub(crate) const fn clear_managed_crontab_blocks() -> Result<usize, SyncError> {
Ok(0)
}
#[cfg(test)]
pub(crate) fn clear_managed_crontab_blocks() -> Result<usize, SyncError> {
let current = read_crontab()?;
let removed = current
.lines()
.filter(|line| line.contains(routines::ROUTINE_LINE_MARKER))
.count();
if !current.contains(routines::BLOCK_BEGIN) {
return Ok(0);
}
let updated = replace_block_with(¤t, "", routines::BLOCK_BEGIN, routines::BLOCK_END);
write_crontab(&updated)?;
Ok(removed)
}