lxcrond 0.3.0

cron and entr/inotify server for lxc containers
Documentation
use crate::config::Job;
use std::io;
use log::*;
use libc::{kill, pid_t};
use std::fs;
use std::fs::OpenOptions;

pub fn match_builtin(job: &Job) -> bool {
    return match job.command().as_str() {
        "kill" => {
            true
        }
        "reboot" => {
            true
        }
        "truncate" => {
            true
        }
        "copy" => {
            true
        }
        _ => {
            false
        }
    };
}

pub fn execute_builtin(job: &Job) -> bool {
    return match job.command().as_str() {
        "kill" => {
            execute_kill(job)
        }
        "reboot" => {
            execute_reboot(job)
        }
        "truncate" => {
            execute_truncate(job)
        }
        "copy" => {
            execute_copy(job)
        }
        _ => {
            false
        }
    };
}
pub fn execute_kill(job: &Job) -> bool {
    if job.args().len() == 2 {
        let signal = job.args().get(0).unwrap();
        if let Ok(mut sig_int) = signal.parse::<pid_t>() {
            // kill -3  and kill 3 accepted
            if sig_int < 0 {
                sig_int = sig_int * -1;
            }

            let pid_file = job.args().get(1).unwrap();
            if let Ok(pid_string) = fs::read_to_string(pid_file) {
                if let Ok(pid_int) = pid_string.trim().parse::<i32>() {
                    let result = unsafe {
                        kill(pid_int, sig_int)
                    };
                    if result == 0 {
                        return true;
                    }
                }
            } else {
                warn!("builtin kill pid file error: {}", job.args().join(" "));
                return false;
            }
        }
    }
    warn!("builtin kill error: {}", job.args().join(" "));
    false
}

pub fn execute_reboot(_job: &Job) -> bool {
    unsafe {
        kill(1, 15);
    };
    true
}

fn truncate_file(path: &String) -> io::Result<()> {
    OpenOptions::new()
        .write(true)
        .truncate(true)
        .open(path)?;
    Ok(())
}

/**
 * Truncates one or more files.
 * returns false if any of the files could not be truncated
 */
pub fn execute_truncate(job: &Job) -> bool {
    if job.args() .len() == 0 {
        return false;
    }

    let mut count = job.args() .len();
    for file_path in job.args() {
        if let Ok(_) = truncate_file(file_path) {
            count = count - 1;
        }
    }
    warn!("builtin truncate error: {}", job.args().join(" "));
    count == 0
}

pub fn execute_copy(job: &Job) -> bool {
    if job.has_file_spec() && job.args().len() == 1 && ! job.file_spec().is_dir() {
        let file_path = job.file_spec().path();
        let dest_file = job.args().get(0).unwrap();
        return if let Ok(_) = fs::copy(file_path, dest_file) {
            true
        } else {
            warn!("builtin copy error: {}", job.args().join(" "));
            false
        }
    }
    warn!("builtin copy syntax error: {}", job.args().join(" "));
    false
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::config::VERBOSE;
    use crate::config::FileSpec;
    use std::path::Path;
    use std::sync::atomic::Ordering;


    #[test]
    pub fn test_builtins_happy_paths() {
        VERBOSE.store(true, Ordering::Relaxed);

        // kill
        let job = Job::new(None, None, std::env::var("USER").unwrap().as_str(), String::from("kill -15 999999"));
        assert!(match_builtin(&job));
        assert!(! execute_kill(&job)); // wont work
        assert!(! execute_builtin(&job));

        // reboot
        let job = Job::new(None, None, std::env::var("USER").unwrap().as_str(), String::from("reboot"));
        assert!(match_builtin(&job));
        //assert!(! execute_reboot(&job)); // might work!!
        //assert!(! execute_builtin(&job));

        // copy
        let file_spec = FileSpec::new("./tests/test-truncate.txt".to_string());
        let job = Job::new(None, Some(file_spec), std::env::var("USER").unwrap().as_str(), String::from("copy ./target/to-truncate.txt"));
        assert!(match_builtin(&job));
        assert!(execute_copy(&job)); // should work
        assert!(execute_builtin(&job));
        assert!(Path::new("./tests/test-truncate.txt").exists());
        assert!(Path::new("./target/to-truncate.txt").exists());

        // truncate
        let job = Job::new(None, None, std::env::var("USER").unwrap().as_str(), String::from("truncate ./target/to-truncate.txt"));
        assert!(match_builtin(&job));
        assert!(execute_truncate(&job)); // should work
        assert!(execute_builtin(&job));
        assert!(Path::new("./target/to-truncate.txt").exists());
        if let Ok(meta) = fs::metadata("./target/to-truncate.txt") {
            assert_eq!(meta.len(), 0);
            let _ = fs::remove_file("./target/to-truncate.txt");
        }

    }


    #[test]
    pub fn test_builtins_unhappy_paths() {
        VERBOSE.store(true, Ordering::Relaxed);

        // kill
        let job = Job::new(None, None, std::env::var("USER").unwrap().as_str(), String::from("/bin/updatedb"));
        assert!(! match_builtin(&job));

        // kill
        let job = Job::new(None, None, std::env::var("USER").unwrap().as_str(), String::from("kill -15"));
        assert!(! execute_kill(&job)); // missing arg
        let job = Job::new(None, None, std::env::var("USER").unwrap().as_str(), String::from("kill"));
        assert!(! execute_kill(&job)); // missing both args

        // copy
        let file_spec = Some(FileSpec::new("./tests/test-truncate.txt".to_string()));
        let job = Job::new(None, None, std::env::var("USER").unwrap().as_str(), String::from("copy ./tests/to-truncate.txt"));
        assert!(! execute_copy(&job)); // no src file
        let job = Job::new(None, file_spec, std::env::var("USER").unwrap().as_str(), String::from("copy"));
        assert!(! execute_copy(&job)); // no dest file

        // truncate
        let job = Job::new(None, None, std::env::var("USER").unwrap().as_str(), String::from("truncate"));
        assert!(! execute_truncate(&job)); // no file to truncate

    }
}