Skip to main content

rustpython_vm/stdlib/
pwd.rs

1// spell-checker:disable
2
3pub(crate) use pwd::module_def;
4
5#[pymodule]
6mod pwd {
7    use crate::{
8        PyResult, VirtualMachine,
9        builtins::{PyIntRef, PyUtf8StrRef},
10        convert::IntoPyException,
11        exceptions,
12        types::PyStructSequence,
13    };
14    use nix::unistd::{self, User};
15
16    #[cfg(not(target_os = "android"))]
17    use crate::{PyObjectRef, convert::ToPyObject};
18
19    #[pystruct_sequence_data]
20    struct PasswdData {
21        pw_name: String,
22        pw_passwd: String,
23        pw_uid: u32,
24        pw_gid: u32,
25        pw_gecos: String,
26        pw_dir: String,
27        pw_shell: String,
28    }
29
30    #[pyattr]
31    #[pystruct_sequence(name = "struct_passwd", module = "pwd", data = "PasswdData")]
32    struct PyPasswd;
33
34    #[pyclass(with(PyStructSequence))]
35    impl PyPasswd {}
36
37    impl From<User> for PasswdData {
38        fn from(user: User) -> Self {
39            // this is just a pain...
40            let cstr_lossy = |s: alloc::ffi::CString| {
41                s.into_string()
42                    .unwrap_or_else(|e| e.into_cstring().to_string_lossy().into_owned())
43            };
44            let pathbuf_lossy = |p: std::path::PathBuf| {
45                p.into_os_string()
46                    .into_string()
47                    .unwrap_or_else(|s| s.to_string_lossy().into_owned())
48            };
49            PasswdData {
50                pw_name: user.name,
51                pw_passwd: cstr_lossy(user.passwd),
52                pw_uid: user.uid.as_raw(),
53                pw_gid: user.gid.as_raw(),
54                pw_gecos: cstr_lossy(user.gecos),
55                pw_dir: pathbuf_lossy(user.dir),
56                pw_shell: pathbuf_lossy(user.shell),
57            }
58        }
59    }
60
61    #[pyfunction]
62    fn getpwnam(name: PyUtf8StrRef, vm: &VirtualMachine) -> PyResult<PasswdData> {
63        let pw_name = name.as_str();
64        if pw_name.contains('\0') {
65            return Err(exceptions::cstring_error(vm));
66        }
67        let user = User::from_name(name.as_str()).ok().flatten();
68        let user = user.ok_or_else(|| {
69            vm.new_key_error(
70                vm.ctx
71                    .new_str(format!("getpwnam(): name not found: {pw_name}"))
72                    .into(),
73            )
74        })?;
75        Ok(PasswdData::from(user))
76    }
77
78    #[pyfunction]
79    fn getpwuid(uid: PyIntRef, vm: &VirtualMachine) -> PyResult<PasswdData> {
80        let uid_t = libc::uid_t::try_from(uid.as_bigint())
81            .map(unistd::Uid::from_raw)
82            .ok();
83        let user = uid_t
84            .map(User::from_uid)
85            .transpose()
86            .map_err(|err| err.into_pyexception(vm))?
87            .flatten();
88        let user = user.ok_or_else(|| {
89            vm.new_key_error(
90                vm.ctx
91                    .new_str(format!("getpwuid(): uid not found: {}", uid.as_bigint()))
92                    .into(),
93            )
94        })?;
95        Ok(PasswdData::from(user))
96    }
97
98    // TODO: maybe merge this functionality into nix?
99    #[cfg(not(target_os = "android"))]
100    #[pyfunction]
101    fn getpwall(vm: &VirtualMachine) -> PyResult<Vec<PyObjectRef>> {
102        // setpwent, getpwent, etc are not thread safe. Could use fgetpwent_r, but this is easier
103        static GETPWALL: parking_lot::Mutex<()> = parking_lot::Mutex::new(());
104        let _guard = GETPWALL.lock();
105        let mut list = Vec::new();
106
107        unsafe { libc::setpwent() };
108        while let Some(ptr) = core::ptr::NonNull::new(unsafe { libc::getpwent() }) {
109            let user = User::from(unsafe { ptr.as_ref() });
110            let passwd = PasswdData::from(user).to_pyobject(vm);
111            list.push(passwd);
112        }
113        unsafe { libc::endpwent() };
114
115        Ok(list)
116    }
117}