rustpython-vm 0.5.0

RustPython virtual machine.
Documentation
// spell-checker:disable

pub(crate) use pwd::module_def;

#[pymodule]
mod pwd {
    use crate::{
        PyResult, VirtualMachine,
        builtins::{PyIntRef, PyUtf8StrRef},
        convert::IntoPyException,
        exceptions,
        types::PyStructSequence,
    };
    use nix::unistd::{self, User};

    #[cfg(not(target_os = "android"))]
    use crate::{PyObjectRef, convert::ToPyObject};

    #[pystruct_sequence_data]
    struct PasswdData {
        pw_name: String,
        pw_passwd: String,
        pw_uid: u32,
        pw_gid: u32,
        pw_gecos: String,
        pw_dir: String,
        pw_shell: String,
    }

    #[pyattr]
    #[pystruct_sequence(name = "struct_passwd", module = "pwd", data = "PasswdData")]
    struct PyPasswd;

    #[pyclass(with(PyStructSequence))]
    impl PyPasswd {}

    impl From<User> for PasswdData {
        fn from(user: User) -> Self {
            // this is just a pain...
            let cstr_lossy = |s: alloc::ffi::CString| {
                s.into_string()
                    .unwrap_or_else(|e| e.into_cstring().to_string_lossy().into_owned())
            };
            let pathbuf_lossy = |p: std::path::PathBuf| {
                p.into_os_string()
                    .into_string()
                    .unwrap_or_else(|s| s.to_string_lossy().into_owned())
            };
            PasswdData {
                pw_name: user.name,
                pw_passwd: cstr_lossy(user.passwd),
                pw_uid: user.uid.as_raw(),
                pw_gid: user.gid.as_raw(),
                pw_gecos: cstr_lossy(user.gecos),
                pw_dir: pathbuf_lossy(user.dir),
                pw_shell: pathbuf_lossy(user.shell),
            }
        }
    }

    #[pyfunction]
    fn getpwnam(name: PyUtf8StrRef, vm: &VirtualMachine) -> PyResult<PasswdData> {
        let pw_name = name.as_str();
        if pw_name.contains('\0') {
            return Err(exceptions::cstring_error(vm));
        }
        let user = User::from_name(name.as_str()).ok().flatten();
        let user = user.ok_or_else(|| {
            vm.new_key_error(
                vm.ctx
                    .new_str(format!("getpwnam(): name not found: {pw_name}"))
                    .into(),
            )
        })?;
        Ok(PasswdData::from(user))
    }

    #[pyfunction]
    fn getpwuid(uid: PyIntRef, vm: &VirtualMachine) -> PyResult<PasswdData> {
        let uid_t = libc::uid_t::try_from(uid.as_bigint())
            .map(unistd::Uid::from_raw)
            .ok();
        let user = uid_t
            .map(User::from_uid)
            .transpose()
            .map_err(|err| err.into_pyexception(vm))?
            .flatten();
        let user = user.ok_or_else(|| {
            vm.new_key_error(
                vm.ctx
                    .new_str(format!("getpwuid(): uid not found: {}", uid.as_bigint()))
                    .into(),
            )
        })?;
        Ok(PasswdData::from(user))
    }

    // TODO: maybe merge this functionality into nix?
    #[cfg(not(target_os = "android"))]
    #[pyfunction]
    fn getpwall(vm: &VirtualMachine) -> PyResult<Vec<PyObjectRef>> {
        // setpwent, getpwent, etc are not thread safe. Could use fgetpwent_r, but this is easier
        static GETPWALL: parking_lot::Mutex<()> = parking_lot::Mutex::new(());
        let _guard = GETPWALL.lock();
        let mut list = Vec::new();

        unsafe { libc::setpwent() };
        while let Some(ptr) = core::ptr::NonNull::new(unsafe { libc::getpwent() }) {
            let user = User::from(unsafe { ptr.as_ref() });
            let passwd = PasswdData::from(user).to_pyobject(vm);
            list.push(passwd);
        }
        unsafe { libc::endpwent() };

        Ok(list)
    }
}