use std::io;
use crate::paths::JobPaths;
use fs2::FileExt;
use std::fs::OpenOptions;
pub(crate) fn do_job(
job_name: &str,
cmd: &[String],
timeout: Option<u64>,
retries: Option<u32>,
) -> io::Result<()> {
if job_name.trim().is_empty() {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"job name cannot be empty",
));
}
if job_name.contains('/') || job_name.contains('\\') {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"job name must not contain path separators",
));
}
if job_name.chars().count() > 100 {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"job name must not exceed 100 characters",
));
}
if job_name.starts_with('.') || job_name.contains("..") {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"job name must not start with a dot or contain repeated dots",
));
}
if !job_name.chars().all(|c| {
if c.is_ascii() {
c.is_ascii_alphanumeric() || c == '-' || c == '_' || c == '.'
} else {
!c.is_control()
}
}) {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"job name contains invalid characters",
));
}
use unicode_normalization::UnicodeNormalization;
if job_name.nfc().collect::<String>() != job_name {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"job name must be Unicode NFC normalised",
));
}
if cmd.is_empty() {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"command cannot be empty",
));
}
let paths = JobPaths::new(job_name)?;
let lock_file = OpenOptions::new()
.create(true)
.write(true)
.truncate(false)
.open(&paths.lock)?;
if let Err(err) = lock_file.try_lock_exclusive() {
if err.kind() == io::ErrorKind::WouldBlock {
return Err(io::Error::new(
io::ErrorKind::AlreadyExists,
format!("job '{job_name}' is already running"),
));
} else {
return Err(err);
}
}
if paths.any_exist() {
let mut last_err: Option<io::Error> = None;
for p in [
&paths.out,
&paths.err,
&paths.exit,
&paths.meta,
&paths.log,
&paths.signal,
] {
if p.exists() {
if let Err(e) = std::fs::remove_file(p) {
last_err = Some(e);
}
}
}
if let Some(err) = last_err {
return Err(err);
}
}
super::worker::spawn_worker(job_name, cmd, timeout, retries)
}