pgdo/
util.rs

1use std::env;
2use std::ffi::OsString;
3use std::path::Path;
4
5type PrependedPath = Result<OsString, env::JoinPathsError>;
6
7/// Prepend the given `dir` to the given `path`.
8///
9/// If `dir` is already in `path` it is moved to first place. Note that this does
10/// *not* update `PATH` in the environment.
11pub(crate) fn prepend_to_path(dir: &Path, path: Option<OsString>) -> PrependedPath {
12    Ok(match path {
13        None => env::join_paths([dir])?,
14        Some(path) => {
15            let mut paths = vec![dir.to_path_buf()];
16            paths.extend(env::split_paths(&path).filter(|path| path != dir));
17            env::join_paths(paths)?
18        }
19    })
20}
21
22#[derive(thiserror::Error, Debug)]
23pub enum CurrentUserError {
24    #[error("User name in {0:?} environment variable cannot be decoded: {1:?}")]
25    NotUnicode(&'static str, std::ffi::OsString),
26    #[error("System error")]
27    System(#[from] nix::Error),
28    #[error("User unknown")]
29    Unknown,
30}
31
32/// Determine the current user name to use.
33///
34/// Checks the `PGUSER` then `USER` environment variables first, which allows
35/// the invoking user to override the current user name. If those are not set,
36/// it obtains the user name from the OS.
37pub fn current_user() -> Result<String, CurrentUserError> {
38    use nix::unistd::{getuid, User};
39    use std::env::{var, VarError::*};
40    match var("PGUSER") {
41        Ok(user) if !user.trim().is_empty() => Ok(user),
42        Err(NotUnicode(value)) => Err(CurrentUserError::NotUnicode("PGUSER", value)),
43        Ok(_) | Err(NotPresent) => match var("USER") {
44            Ok(user) if !user.trim().is_empty() => Ok(user),
45            Err(NotUnicode(value)) => Err(CurrentUserError::NotUnicode("USER", value)),
46            Ok(_) | Err(NotPresent) => User::from_uid(getuid())?
47                .map(|user| user.name)
48                .ok_or(CurrentUserError::Unknown),
49        },
50    }
51}
52
53/// Calculate `numerator` divided by `denominator` as a percentage.
54///
55/// When `numerator` is very large we cannot multiply it by 100 without risking
56/// wrapping, so this is careful to use checked arithmetic to avoid wrapping or
57/// overflow. It scales down `numerator` and `denominator` by powers of two
58/// until a percentage can be calculated. If `denominator` is zero, returns
59/// `None`.
60///
61/// ```rust
62/// # use pgdo::util::percent;
63/// assert_eq!(percent(100, 1000), Some(10));
64/// assert_eq!(percent(104, 1000), Some(10));
65/// assert_eq!(percent(105, 1000), Some(11)); // <-- Rounds.
66/// assert_eq!(percent(u64::MAX, 1), None); // Overflow.
67/// assert_eq!(percent(0, u64::MAX), Some(0));
68/// assert_eq!(percent(1, u64::MAX), Some(0));
69/// assert_eq!(percent(u64::MAX, u64::MAX), Some(100));
70/// assert_eq!(percent(u64::MAX / 100, u64::MAX), Some(1));
71/// assert_eq!(percent(u64::MAX >> 1, u64::MAX), Some(50));
72/// ```
73///
74pub fn percent(numerator: u64, denominator: u64) -> Option<u64> {
75    // The 7 is calculated as: 100u8.ilog2() + 1;
76    (0..=7).find_map(|shift| {
77        (numerator >> shift)
78            .checked_mul(100)
79            .and_then(|numerator| match denominator >> shift {
80                0 => None,
81                1 => Some(numerator),
82                d if (d >> 1) > numerator.rem_euclid(d) => Some(numerator.div_euclid(d)),
83                d => Some(numerator.div_euclid(d) + 1),
84            })
85    })
86}
87
88#[cfg(test)]
89mod tests {
90    use std::env;
91
92    type TestResult = Result<(), Box<dyn std::error::Error>>;
93
94    #[test]
95    fn test_prepend_to_path_prepends_given_dir_to_path() -> TestResult {
96        let path = env::join_paths([tempfile::tempdir()?.path(), tempfile::tempdir()?.path()])?;
97        let tempdir = tempfile::tempdir()?;
98        let expected = {
99            let mut tmp = vec![tempdir.path().to_path_buf()];
100            tmp.extend(env::split_paths(&path));
101            env::join_paths(tmp)?
102        };
103        let observed = { super::prepend_to_path(tempdir.path(), Some(path))? };
104        assert_eq!(expected, observed);
105        Ok(())
106    }
107
108    #[test]
109    fn test_prepend_to_path_moves_dir_to_front_of_path() -> TestResult {
110        let tempdir = tempfile::tempdir()?;
111        let path = env::join_paths([
112            tempfile::tempdir()?.path(),
113            tempfile::tempdir()?.path(),
114            tempdir.path(),
115        ])?;
116        let expected = {
117            let mut tmp = vec![tempdir.path().to_path_buf()];
118            tmp.extend(env::split_paths(&path).take(2));
119            env::join_paths(tmp)?
120        };
121        let observed = { super::prepend_to_path(tempdir.path(), Some(path))? };
122        assert_eq!(expected, observed);
123        Ok(())
124    }
125
126    #[test]
127    fn test_prepend_to_path_returns_given_dir_if_path_is_empty() -> TestResult {
128        let tempdir = tempfile::tempdir()?;
129        let expected = tempdir.path();
130        let observed = super::prepend_to_path(tempdir.path(), None)?;
131        assert_eq!(expected, observed);
132        Ok(())
133    }
134}