nsutils 0.0.5

Command-line utilities for making use of Linux namespaces
Documentation
use procinfo::pid;
use procinfo::pid::Stat;
use std::collections::HashMap;
use std::fs::{self, DirEntry, File};
use std::io::{Read, Result};
use std::os::unix::io::{AsRawFd, RawFd};
use std::path::{Path, PathBuf};
use unshare::Namespace;

pub struct NsCtx {
    nsid: u64,
    nstype: Namespace,
}

pub struct StatNs {
    cmdline: String,
    stat: Stat,
    nses: Vec<NsCtx>,
}

impl IntoIterator for StatNs {
    type Item = NsCtx;
    type IntoIter = ::std::vec::IntoIter<NsCtx>;

    fn into_iter(self) -> Self::IntoIter {
        self.nses.into_iter()
    }
}

pub struct ListNs {
    pub nstype: Namespace,
    pub nproc: u32,
    pub pid: i32,
    pub ppid: i32,
    pub cmdline: String,
}

pub struct NamespaceFile {
    pub nstype: Namespace,
    pub name: String,
    pub fd: RawFd,
}

impl NamespaceFile {
    pub fn open_file(&mut self) {
        let ref name = self.name;
        let file = File::open(name).unwrap();
        self.fd = file.as_raw_fd();
    }
}

pub fn statns_to_nslist(svec: Vec<StatNs>) -> HashMap<u64, ListNs> {
    let mut result_nslist: HashMap<u64, ListNs> = HashMap::new();

    for statns in svec {
        let s_nses = statns.nses;
        for nsctx in s_nses {
            let nsid = nsctx.nsid;

            if result_nslist.contains_key(&nsid) {
                let mut listns = result_nslist.get_mut(&nsid).unwrap();
                *listns = ListNs {
                    nstype: nsctx.nstype,
                    nproc: listns.nproc + 1,
                    pid: statns.stat.pid,
                    ppid: statns.stat.ppid,
                    cmdline: statns.cmdline.clone(),
                };
            } else {
                result_nslist.insert(nsid,
                                     ListNs {
                                         nstype: nsctx.nstype,
                                         nproc: 1,
                                         pid: statns.stat.pid,
                                         ppid: statns.stat.ppid,
                                         cmdline: statns.cmdline.clone(),
                                     });
            }
        }
    }

    result_nslist
}

pub fn ns_str_to_const(nsname: &str) -> Option<Namespace> {
    match nsname.as_ref() {
        "ipc" => Some(Namespace::Ipc),
        "mnt" => Some(Namespace::Mount),
        "net" => Some(Namespace::Net),
        "pid" => Some(Namespace::Pid),
        "user" => Some(Namespace::User),
        "uts" => Some(Namespace::Uts),
        _ => None,
    }
}

pub fn ns_const_to_str<'a>(ns: &Namespace) -> &'a str {
    match ns {
        &Namespace::Ipc => "ipc",
        &Namespace::Mount => "mnt",
        &Namespace::Net => "net",
        &Namespace::Pid => "pid",
        &Namespace::User => "user",
        &Namespace::Uts => "uts",
    }
}

fn ns_symlink_to_ino(symlink_ns: &str) -> u64 {
    let nstype: String;
    let nsino: u64;
    scan!(symlink_ns.bytes() => "{}:[{}]", nstype, nsino);

    nsino
}

fn get_next_pid(entry: &DirEntry) -> Option<i32> {
    let path = entry.path();
    if !path.is_dir() {
        return None;
    }

    let filename = entry.file_name().into_string().unwrap();
    if !filename.chars().nth(0).unwrap().is_digit(10) {
        return None;
    }
    match filename.parse::<i32>() {
        Ok(n) => Some(n),
        Err(_) => None,
    }
}

fn get_ns_stat(entry: &DirEntry) -> Option<Vec<NsCtx>> {
    let pathbuf = PathBuf::from(entry.path());
    let mut result_ns: Vec<NsCtx> = Vec::<NsCtx>::new();

    let pathbuf = pathbuf.join("ns");
    if !pathbuf.is_dir() {
        return None;
    }

    match fs::read_dir(pathbuf) {
        Ok(paths) => {
            for entry in paths {
                let entry = entry.unwrap();
                let path = &entry.path();
                if fs::symlink_metadata(path)
                       .unwrap()
                       .file_type()
                       .is_symlink() {
                    let fname_os = entry.file_name();
                    let filename = fname_os.to_str().unwrap();
                    let nsconst = ns_str_to_const(filename);

                    let path_link = path.read_link().unwrap();
                    let fname_path_link = path_link.to_str().unwrap();
                    let ino = ns_symlink_to_ino(fname_path_link);

                    match nsconst {
                        Some(_) => {
                            result_ns.push(NsCtx {
                                               nsid: ino,
                                               nstype: nsconst.unwrap(),
                                           })
                        },
                        None => continue,
                    }
                }
            }
        },
        Err(reason) => {
            println!("Cannot read the directory: {:?}", reason.kind());
        },
    }

    Some(result_ns)
}

pub fn read_proc_to_statns(dir: &Path) -> Result<Vec<StatNs>> {
    let mut result_svec: Vec<StatNs> = Vec::<StatNs>::new();

    if !dir.is_dir() {
        return Ok(result_svec);
    }

    for entry in fs::read_dir(dir).unwrap() {
        let entry = entry.unwrap();
        if let Some(pid) = get_next_pid(&entry) {
            match pid::stat(pid) {
                Ok(ps) => {
                    let mut data = String::new();
                    let mut pathbuf = entry.path();

                    pathbuf = pathbuf.join("cmdline");
                    if !pathbuf.is_file() {
                        continue;
                    }

                    let path = pathbuf.as_path();

                    let mut fh = File::open(path).unwrap();
                    fh.read_to_string(&mut data).unwrap();
                    let statns = StatNs {
                        cmdline: data,
                        stat: ps,
                        nses: get_ns_stat(&entry).unwrap(),
                    };
                    if !statns.nses.is_empty() {
                        result_svec.push(statns);
                    }
                },
                Err(_) => continue,
            }
        }
    }

    result_svec.sort_by_key(|k| k.stat.pid);
    Ok(result_svec)
}

#[cfg(test)]
mod tests {
    use super::{ns_const_to_str, statns_to_nslist, NsCtx, StatNs};
    use procinfo::pid::Stat;
    use unshare::Namespace;

    /// Test the statns
    #[test]
    fn test_statns_to_nslist() {
        let mut listns: Vec<NsCtx> = Vec::<NsCtx>::new();
        let test_nsid = 4000000000u64;
        let test_pid = 12345;
        let test_ppid = 1234;

        listns.push(NsCtx {
                        nsid: test_nsid,
                        nstype: Namespace::Mount,
                    });
        let statns = StatNs {
            cmdline: "/bin/bash".to_string(),
            stat: Stat {
                pid: test_pid,
                ppid: test_ppid,
                ..Default::default()
            },
            nses: listns,
        };

        let mut vec_statns: Vec<StatNs> = Vec::<StatNs>::new();
        vec_statns.push(statns);

        let map_listns = statns_to_nslist(vec_statns);

        assert_eq!(ns_const_to_str(&map_listns[&test_nsid].nstype), "mnt");
        assert_eq!(map_listns[&test_nsid].nproc, 1u32);
        assert_eq!(map_listns[&test_nsid].pid, test_pid);
        assert_eq!(map_listns[&test_nsid].ppid, test_ppid);
        assert_eq!(map_listns[&test_nsid].cmdline, "/bin/bash".to_string());
    }
}