shell-rs 0.2.6

Rust reimplementation of common coreutils APIs
Documentation
// Copyright (c) 2021 Xu Shaohua <shaohua@biofan.org>. All rights reserved.
// Use of this source is governed by Apache-2.0 License that can be found
// in the LICENSE file.

use std::fs::File;
use std::io::{BufRead, BufReader};
use std::str::FromStr;

use crate::error::{Error, ErrorKind};

#[derive(Debug, Default, Clone)]
pub struct Passwd {
    /// Username.
    pub name: String,

    /// Passsword, commonly empty.
    pub passwd: String,

    /// User ID.
    pub uid: nc::uid_t,

    /// User default group ID.
    pub gid: nc::gid_t,

    /// Real name.
    pub gecos: String,

    /// Home directory.
    pub home_dir: String,

    /// Default shell.
    pub shell: String,
}

impl FromStr for Passwd {
    type Err = Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let parts: Vec<&str> = s.split(':').collect();
        if parts.len() != 7 {
            return Err(Error::from_string(
                ErrorKind::PwdError,
                format!("Invalid passwd entry: {}", s),
            ));
        }

        let uid = parts[2].parse()?;
        let gid = parts[3].parse()?;
        Ok(Self {
            name: parts[0].to_string(),
            passwd: parts[1].to_string(),
            uid,
            gid,
            gecos: parts[4].to_string(),
            home_dir: parts[5].to_string(),
            shell: parts[6].to_string(),
        })
    }
}

#[derive(Debug)]
pub struct PasswdIter {
    reader: BufReader<File>,
}

pub fn getpwent() -> Result<PasswdIter, Error> {
    let fd = File::open("/etc/passwd")?;
    let reader = BufReader::new(fd);
    Ok(PasswdIter { reader })
}

impl Iterator for PasswdIter {
    type Item = Passwd;

    fn next(&mut self) -> Option<Self::Item> {
        let mut line = String::new();
        let len = self.reader.read_line(&mut line);
        if len.is_err() {
            return None;
        }

        match Passwd::from_str(&line.trim()) {
            Ok(p) => Some(p),
            Err(err) => {
                log::error!("{:?}", err);
                None
            }
        }
    }
}

pub fn getpwuid(uid: nc::uid_t) -> Result<Option<Passwd>, Error> {
    let iter = getpwent()?;
    for p in iter {
        if p.uid == uid {
            return Ok(Some(p));
        }
    }
    Ok(None)
}

pub fn getpwname(name: &str) -> Result<Option<Passwd>, Error> {
    let iter = getpwent()?;
    for p in iter {
        if p.name == name {
            return Ok(Some(p));
        }
    }
    Ok(None)
}

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

    #[test]
    fn test_passwd_iter() {
        let iter = getpwent();
        assert!(iter.is_ok());
        let mut iter = iter.unwrap();
        let p0 = iter.next();
        println!("p0: {:#?}", p0);
        assert!(p0.is_some());
    }

    #[test]
    fn test_getpwuid() {
        let mail_uid = 6;
        let p = getpwuid(mail_uid);
        assert!(p.is_ok());
        let p = p.unwrap();
        assert!(p.is_some());
        let p = p.unwrap();
        assert_eq!(p.uid, mail_uid);
    }

    #[test]
    fn test_getpwname() {
        let mail_name = "mail";
        let p = getpwname(mail_name);
        assert!(p.is_ok());
        let p = p.unwrap();
        assert!(p.is_some());
        let p = p.unwrap();
        assert_eq!(p.name, mail_name);
    }
}