pwd_grp/
generic.rs

1//! Interface generic over returned string type, for non-UTF-8 etc.
2
3use super::*;
4
5/// Strings that can we can work with
6///
7/// Implemented for `String`, which is the usual case.
8/// `Box<str>` is also useable.
9///
10/// If you want to work with non-unicode data
11/// use `Box<[u8]>` or `Vec<u8>`.
12pub trait PwdGrpString: SealedString + Deref {}
13
14impl PwdGrpString for Box<str> {}
15impl PwdGrpString for Box<[u8]> {}
16impl PwdGrpString for String {}
17impl PwdGrpString for Vec<u8> {}
18
19impl PwdGrpProvider for PwdGrp {}
20
21define_derive_deftly! {
22    Blank for struct, expect items:
23
24    impl<S: Default> $tname<S> {
25        /// Make a new blank entry (useful for testing)
26        ///
27        /// All the contained strings and arrays are empty.
28        /// The contained id(s) are zero.
29        pub fn blank() -> Self {
30            Self { $( ${select1 fmeta(dummy) {
31                $fname: NonExhaustive {},
32            } else {
33                $fname: Default::default(),
34            }})}
35        }
36    }
37}
38
39/// Provider of passwd and group information, from system databases
40///
41/// The actual functionality is provided by methods of
42/// [`PwdGrpProvider`].
43///
44/// These are generic over the returned string type.
45/// This enables working with non-UTF-8 data in passwd/group files,
46/// and also with `Box<str>`.
47/// If you just want to work with `String`,
48/// use the plain functions in the [module toplevel](crate).
49///
50/// # Examples
51///
52/// ```
53/// use pwd_grp::Passwd;
54/// use pwd_grp::PwdGrpProvider as _;
55/// use pwd_grp::PwdGrp;
56///
57/// let pwent: Passwd<Vec<u8>> = PwdGrp.getpwuid(0).unwrap().unwrap();
58/// assert_eq!(pwent.uid, 0);
59/// match std::str::from_utf8(&pwent.gecos) {
60///     Ok(s) => println!("root user gecos: {}", s),
61///     Err(_) => println!("root user gecos is not valid UTF-8"),
62/// }
63/// ```
64#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Default)]
65#[allow(clippy::exhaustive_structs)] // callers use the unit constructor
66pub struct PwdGrp;
67
68/// Define a generic entry point
69macro_rules! define_generic_entrypoint {
70    // Handle entrypoints with plain key type (Id)
71    {
72        fn $getfoobar:ident($key:ident: $keytype:ty) -> $out:ty;
73    } => { define_generic_entrypoint! {
74        fn $getfoobar($key: $keytype, $keytype; ) -> $out { }
75    } };
76
77    // Handle entrypoints which are strings (names)
78    {
79        fn $getfoobar:ident($key:ident) -> $out:ty;
80    } => { define_generic_entrypoint! {
81        fn $getfoobar(
82            $key:
83            impl AsRef<str>,
84            impl AsRef<<S as Deref>::Target>;
85            .as_ref()
86        ) -> $out {
87            let $key: &[u8] = <S as SealedString>::as_u8($key.as_ref());
88        }
89    } };
90
91    // Actual implementation
92    {
93        fn $getfoobar:ident(
94            $key:ident:
95            $ktsim:ty,
96            $ktgen:ty;
97            $( $k_as_ref:tt )*
98        ) -> $out:ty {
99            $( $bind_key:tt )*
100        }
101    } => { paste!{
102        /// Look up a
103        #[doc = stringify!([< $out:lower >])]
104        /// entry by
105        #[doc = concat!(stringify!([< $key >]), ",")]
106        /// returning strings as `S`
107        fn $getfoobar<S: PwdGrpString>(
108            &self,
109            $key: $ktgen,
110        ) -> io::Result<Option<$out<S>>> {
111            $( $bind_key )*
112
113            let value = {
114                if let Some(mock) = self.with_mocks(|data| {
115                    data.0.[< $out:lower >].iter().find(|ent| {
116                        $key == ent.$key $( $k_as_ref )*
117                    }).cloned().into()
118                }) {
119                    mock
120                } else {
121                    [< $getfoobar _inner >](RealLibc, $key)?
122                }
123            };
124
125            value
126                .map(|v| $out::<S>::try_convert_from(v, ""))
127                .transpose()
128                .map_err(<S as SealedString>::handle_error_as_io)
129        }
130    } };
131}
132
133macro_rules! define_getid_wrapper { {
134    $fn:ident: $id:ident. $f:ident, $doc:literal $( $real:literal )?
135} => { define_getid_wrapper! {
136    @ $fn: $id, $doc $($real)?,
137    Id,
138    |mock: mock::RealEffectiveSavedIds| mock.$f,
139} }; {
140    $fn:ident: $id:ident. ($( $f:ident )*), $doc:literal $( $real:literal )?
141} => { define_getid_wrapper! {
142    @ $fn: $id, $doc $($real)?,
143    (Id, Id, Id),
144    |mock: mock::RealEffectiveSavedIds| ( $( mock.$f, )* ),
145} }; {
146    @ $fn:ident: $id:ident, $doc:literal $( $want_real_warn:literal )?,
147    $ret:ty,
148    $if_mock:expr,
149} => { paste!{
150    /// Get the current process's
151    #[doc = $doc]
152    ///
153    /// This may be mocked,
154    /// via [`mock::MockPwdGrpProvider`].
155    #[doc = $doc]
156    $(
157        /// "Real" is the Unix technical term: ruid vs euid/suid/fsuid.
158        #[doc = $want_real_warn]
159    )?
160    fn $fn(&self) -> $ret {
161        match self.with_mocks(|data| data.0.[< $id s >]) {
162            Some(mock) => ($if_mock)(mock),
163            None => [<$fn _inner>](RealLibc),
164        }
165    }
166} } }
167
168macro_rules! for_getid_wrappers { { $call:ident } => {
169    $call! { getuid: uid. r, "(real) uid" ""}
170    $call! { geteuid: uid. e, "effective uid"}
171 if_cfg_getresuid! {
172    $call! { getresuid: uid. (r e s), "real, effective and saved set-uid" ""}
173 }
174    $call! { getgid: gid. r, "(real) gid" ""}
175    $call! { getegid: gid. e, "effective gid"}
176 if_cfg_getresuid! {
177    $call! { getresgid: gid. (r e s), "real, effective and saved set-gid" ""}
178 }
179} }
180
181/// Provider of passwd and group information.
182///
183/// Normally, use [`PwdGrp`].
184///
185/// This may be mocked,
186/// via [`mock::MockPwdGrpProvider`].
187pub trait PwdGrpProvider: SealedProvider {
188    define_generic_entrypoint! { fn getpwnam(name) -> Passwd; }
189    define_generic_entrypoint! { fn getpwuid(uid: Id) -> Passwd; }
190    define_generic_entrypoint! { fn getgrnam(name) -> Group; }
191    define_generic_entrypoint! { fn getgrgid(gid: Id) -> Group; }
192    for_getid_wrappers! { define_getid_wrapper }
193
194    /// Get the current process's supplementary group list
195    fn getgroups(&self) -> io::Result<Vec<Id>> {
196        if let Some(mock) =
197            self.with_mocks(|data| data.0.supplementary_groups.clone())
198        {
199            return Ok(mock);
200        }
201
202        getgroups_inner(RealLibc)
203    }
204}