daemonize-me 2.0.2

Rust library to ease the task of creating daemons on unix-like systems
Documentation
#![deny(warnings)]
#![allow(unsafe_code)]
extern crate libc;

use std::ffi::{CStr, CString, OsStr, OsString};
use std::os::unix::ffi::OsStrExt;

#[cfg(target_os = "linux")]
use {
    crate::DaemonError::{GetPasswdRecord, SetProcName},
    libc::{PR_SET_NAME, prctl},
};

use crate::{DaemonError, Result};
#[cfg(not(target_os = "linux"))]
use crate::DaemonError::{GetPasswdRecord, SetProcName, UnsupportedOnOS};
use crate::DaemonError::InvalidProcName;

#[repr(C)]
#[allow(dead_code)]
struct FFIGroup {
    gr_name: *const libc::c_char,
    gr_passwd: *const libc::c_char,
    gr_gid: libc::gid_t,
    gr_mem: *const *const libc::c_char,
}

#[cfg(target_os = "linux")]
#[repr(C)]
#[allow(dead_code)]
struct FFIPasswd {
    pw_name: *const libc::c_char,
    pw_passwd: *const libc::c_char,
    pw_uid: libc::uid_t,
    pw_gid: libc::gid_t,
    pw_gecos: *const libc::c_char,
    pw_dir: *const libc::c_char,
    pw_shell: *const libc::c_char,
}

// Used on MacOS and FreeBSD
#[cfg(any(target_os = "macos", target_os = "freebsd"))]
#[repr(C)]
#[allow(dead_code)]
struct FFIPasswd {
    pw_name: *const libc::c_char,
    pw_passwd: *const libc::c_char,
    pw_uid: libc::uid_t,
    pw_gid: libc::gid_t,
    pw_change: libc::time_t,
    pw_class: *const libc::c_char,
    pw_gecos: *const libc::c_char,
    pw_dir: *const libc::c_char,
    pw_shell: *const libc::c_char,
    pw_expire: libc::time_t,
    pw_fields: libc::c_int,
}

// Used on the other two supported BSDs
#[cfg(any(target_os = "openbsd", target_os = "netbsd"))]
#[repr(C)]
#[allow(dead_code)]
struct FFIPasswd {
    pw_name: *const libc::c_char,
    pw_passwd: *const libc::c_char,
    pw_uid: libc::uid_t,
    pw_gid: libc::gid_t,
    pw_change: libc::time_t,
    pw_class: *const libc::c_char,
    pw_gecos: *const libc::c_char,
    pw_dir: *const libc::c_char,
    pw_shell: *const libc::c_char,
    pw_expire: libc::time_t,
}

#[allow(dead_code)]
extern "C" {
    fn getgrnam(name: *const libc::c_char) -> *const FFIGroup;
    fn getgrgid(name: libc::gid_t) -> *const FFIGroup;
    fn getpwnam(name: *const libc::c_char) -> *const FFIPasswd;
    fn getpwuid(name: libc::uid_t) -> *const FFIPasswd;
}

#[derive(Debug)]
#[allow(dead_code)]
pub struct GroupRecord {
    pub gr_name: String,
    pub gr_passwd: String,
    pub gr_gid: u32,
}

#[derive(Debug)]
#[allow(dead_code)]
pub struct PasswdRecord {
    pub pw_name: String,
    pub pw_passwd: String,
    pub pw_uid: u32,
    pub pw_gid: u32,
    pub pw_gecos: String,
    pub pw_dir: String,
    pub pw_shell: String,
}

unsafe fn check_group_record(grp: *const FFIGroup) -> Result<GroupRecord> {
    return if grp.is_null() {
        Err(DaemonError::GetGrRecord)
    } else {
        let gr = &*grp;
        let sgr = GroupRecord {
            gr_name: CStr::from_ptr(gr.gr_name).to_string_lossy().to_string(),
            gr_passwd: CStr::from_ptr(gr.gr_passwd).to_string_lossy().to_string(),
            gr_gid: gr.gr_gid as u32,
        };
        Ok(sgr)
    };
}

unsafe fn check_passwd_record(passwd: *const FFIPasswd) -> Result<PasswdRecord> {
    return if passwd.is_null() {
        Err(GetPasswdRecord)
    } else {
        let pw = &*passwd;
        let pwr = PasswdRecord {
            pw_name: CStr::from_ptr(pw.pw_name).to_string_lossy().to_string(),
            pw_passwd: CStr::from_ptr(pw.pw_passwd).to_string_lossy().to_string(),
            pw_uid: pw.pw_uid as u32,
            pw_gid: pw.pw_gid as u32,
            pw_gecos: CStr::from_ptr(pw.pw_gecos).to_string_lossy().to_string(),
            pw_dir: CStr::from_ptr(pw.pw_dir).to_string_lossy().to_string(),
            pw_shell: CStr::from_ptr(pw.pw_shell).to_string_lossy().to_string(),
        };
        Ok(pwr)
    };
}

#[allow(dead_code)]
impl GroupRecord {
    pub fn lookup_record_by_name(name: &str) -> Result<GroupRecord> {
        let record_name = match CString::new(name) {
            Ok(s) => s,
            Err(_) => return Err(DaemonError::InvalidCstr),
        };

        unsafe {
            let raw_grp = getgrnam(record_name.as_ptr());
            return check_group_record(raw_grp);
        };
    }

    pub fn lookup_record_by_id(gid: u32) -> Result<GroupRecord> {
        let record_id = gid as libc::uid_t;

        unsafe {
            let raw_grp = getgrgid(record_id);
            return check_group_record(raw_grp);
        };
    }
}

impl PasswdRecord {
    pub fn lookup_record_by_name(name: &str) -> Result<PasswdRecord> {
        let record_name = match CString::new(name) {
            Ok(s) => s,
            Err(_) => return Err(DaemonError::InvalidCstr),
        };

        unsafe {
            let raw_passwd = getpwnam(record_name.as_ptr());
            return check_passwd_record(raw_passwd);
        };
    }

    pub fn lookup_record_by_id(uid: u32) -> Result<PasswdRecord> {
        let record_id = uid as libc::uid_t;

        unsafe {
            let raw_passwd = getpwuid(record_id);
            return check_passwd_record(raw_passwd);
        };
    }
}

#[cfg(target_os = "linux")]
/// Safe wrapper to the prctl(2) call
pub fn set_proc_name(name: &OsStr) -> Result<()> {
    let name_truncated = match CString::new(OsString::from(name).as_bytes()) {
        Ok(procname) => procname,
        Err(_) => return Err(InvalidProcName),
    };
    unsafe {
        if prctl(PR_SET_NAME, name_truncated.as_bytes_with_nul()) < 0 {
            Err(SetProcName)
        } else {
            Ok(())
        }
    }
}

// TODO: Implement this for non linux targets
#[cfg(not(target_os = "linux"))]
pub fn set_proc_name(name: &OsStr) -> Result<()> {
    Err(UnsupportedOnOS)
}

#[cfg(test)]
mod tests {
    // TODO: Improve testing because of unsafe code
    use super::*;

    #[test]
    /// Asserts if the uid returned for the uname "root" is 0
    fn test_passwd_by_name() {
        let root = PasswdRecord::lookup_record_by_name("root").unwrap();
        assert_eq!(root.pw_uid, 0)
    }

    #[test]
    /// Asserts if the uname returned by the uid 0 is "root"
    fn test_passwd_by_uid() {
        let root = PasswdRecord::lookup_record_by_id(0).unwrap();
        assert_eq!(root.pw_name, "root")
    }

    #[test]
    /// Asserts if the uid returned for the uname "root" is 0
    fn test_gr_by_name() {
        let root = GroupRecord::lookup_record_by_name("root").unwrap();
        assert_eq!(root.gr_gid, 0)
    }

    #[test]
    /// Asserts if the uname returned by the uid 0 is "root"
    fn test_gr_by_gid() {
        let root = GroupRecord::lookup_record_by_id(0).unwrap();
        assert_eq!(root.gr_name, "root")
    }
}