lxcrond 0.3.0

cron and entr/inotify server for lxc containers
Documentation
use crate::config::Job;
use crate::config::VERBOSE;
use std::process::{Command, Stdio};
use std::os::unix::process::CommandExt;
use std::thread;
use std::ffi::{CStr, CString};
use std::sync::atomic::Ordering;
use log::*;
use crate::builtins::{match_builtin, execute_builtin};

/// Contains code relating to executing jobs.
/// Code here is unsafe and Linux specific, (might work on other nix flavours)
/// Sets standard env vars that might break stuff if they are missing.
/// - `PATH` - copied from user that runs lxcrond
/// - `USER` and `USERNAME` - copied from the `lxcrontab` entry
/// - `SHELL` - hardcoded to /bin/sh, in lxinitd containers it expected to be missing or symlinked to /bin/false or /bin/rosh
/// - `HOME` - set to / or if possible copied from the /etc/password entry for this user.
///
pub fn execute_job(job: &Job) -> bool {

    if match_builtin(job) {
        return execute_builtin(job);
    }

    let mut command = Command::new(job.command());
    for arg in job.args() {
        command.arg(arg);
    }
    command.stdin(Stdio::null());
    command.stdout(Stdio::null());
    command.stderr(Stdio::null());

    String::from("");
    command = set_env_vars(command, job);


    unsafe {
        // read /etc/passwd uid, gid and home directory

        // fugly, rust, fugly
        let mut pwd = std::mem::MaybeUninit::<libc::passwd>::uninit();
        let mut result: *mut libc::passwd = 0 as *mut libc::passwd;
        let mut buf_len = libc::sysconf(libc::_SC_GETPW_R_SIZE_MAX);
        if buf_len == -1 {
            buf_len = 16384;
        }
        let mut buf: Box<[i8]> = vec![0 as i8; buf_len as usize].into_boxed_slice();

        libc::getpwnam_r(CString::from_vec_unchecked(job.user().clone().into_bytes()).as_ptr(),
                         pwd.as_mut_ptr(),
                         buf.as_mut_ptr(), buf_len as usize,
                         &mut result);

        if std::ptr::null() != result {
            command.uid(pwd.assume_init().pw_uid);
            command.gid(pwd.assume_init().pw_gid);

            let cd_string = CStr::from_ptr(pwd.assume_init().pw_dir).to_str().unwrap().to_owned();
            command.current_dir(&cd_string);
            command.env("HOME", cd_string);
        } else {
            if VERBOSE.load(Ordering::Relaxed) {
                println!("user not found {}", job.user());
            }
            warn!("user not found {}", job.user());
            // if we cant set the current directory, fail fast, e.g. rm in the wrong directory as root would be nasty
            return false;
        }
    }

    // this kills the app  builder.exec();  I think its like bash's exec()
    let child = command.spawn();
    if let Err(err) = child {
        if VERBOSE.load(Ordering::Relaxed) {
            println!("error spawning: '{}' {} {}", err, job.command(), job.args().join(" "));
        }
        warn!("error spawning: '{}' {} {}", err, job.command(), job.args().join(" "));
        false
    } else {
        if VERBOSE.load(Ordering::Relaxed) {
            println!("spawned: {} {}", job.command(), job.args().join(" "));
        }
        debug!("spawned: {} {}", job.command(), job.args().join(" "));
        // now we _have_ to wait() on the spawned process or it hangs around in the ps list consuming a process id
        thread::spawn(|| child.unwrap().wait() );
        true
    }

}

fn set_env_vars(mut builder: Command, job: &Job) -> Command {

    builder
        .env_clear()
        .env("USERNAME", job.user())
        .env("USER", job.user())
        .env("SHELL", "/bin/sh")
        .env("HOME", "/");

    if let Ok(path) = std::env::var("PATH") {
        builder.env("PATH", path);
    } else {
        builder.env("PATH", "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin");
    }

    builder
}


#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    pub fn test_execute_job() {
        VERBOSE.store(true, Ordering::Relaxed);
        let job = Job::new(None, None, std::env::var("USER").unwrap().as_str(), String::from("/bin/echo hello world"));
        assert!(execute_job(&job));
        let job = Job::new(None, None, std::env::var("USER").unwrap().as_str(), String::from("non_existing_bin"));
        assert!(!execute_job(&job));
        let job = Job::new(None, None, "non_existing_user", String::from("/bin/echo hello world"));
        assert!(!execute_job(&job));
    }
}