1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
use std::env;
use std::ffi::OsString;
use std::path::Path;

type PrependedPath = Result<OsString, env::JoinPathsError>;

/// Prepend the given `dir` to the given `path`.
///
/// If `dir` is already in `path` it is moved to first place. Note that this does
/// *not* update `PATH` in the environment.
pub(crate) fn prepend_to_path(dir: &Path, path: Option<OsString>) -> PrependedPath {
    Ok(match path {
        None => env::join_paths([dir])?,
        Some(path) => {
            let mut paths = vec![dir.to_path_buf()];
            paths.extend(env::split_paths(&path).filter(|path| path != dir));
            env::join_paths(paths)?
        }
    })
}

#[derive(thiserror::Error, Debug)]
pub enum CurrentUserError {
    #[error("User name in {0:?} environment variable cannot be decoded: {1:?}")]
    NotUnicode(&'static str, std::ffi::OsString),
    #[error("System error")]
    System(#[from] nix::Error),
    #[error("User unknown")]
    Unknown,
}

/// Determine the current user name to use.
///
/// Checks the `PGUSER` then `USER` environment variables first, which allows
/// the invoking user to override the current user name. If those are not set,
/// it obtains the user name from the OS.
pub fn current_user() -> Result<String, CurrentUserError> {
    use nix::unistd::{getuid, User};
    use std::env::{var, VarError::*};
    match var("PGUSER") {
        Ok(user) if !user.trim().is_empty() => Ok(user),
        Err(NotUnicode(value)) => Err(CurrentUserError::NotUnicode("PGUSER", value)),
        Ok(_) | Err(NotPresent) => match var("USER") {
            Ok(user) if !user.trim().is_empty() => Ok(user),
            Err(NotUnicode(value)) => Err(CurrentUserError::NotUnicode("USER", value)),
            Ok(_) | Err(NotPresent) => User::from_uid(getuid())?
                .map(|user| user.name)
                .ok_or(CurrentUserError::Unknown),
        },
    }
}

/// Calculate `numerator` divided by `denominator` as a percentage.
///
/// When `numerator` is very large we cannot multiply it by 100 without risking
/// wrapping, so this is careful to use checked arithmetic to avoid wrapping or
/// overflow. It scales down `numerator` and `denominator` by powers of two
/// until a percentage can be calculated. If `denominator` is zero, returns
/// `None`.
///
/// ```rust
/// # use pgdo::util::percent;
/// assert_eq!(percent(100, 1000), Some(10));
/// assert_eq!(percent(104, 1000), Some(10));
/// assert_eq!(percent(105, 1000), Some(11)); // <-- Rounds.
/// assert_eq!(percent(u64::MAX, 1), None); // Overflow.
/// assert_eq!(percent(0, u64::MAX), Some(0));
/// assert_eq!(percent(1, u64::MAX), Some(0));
/// assert_eq!(percent(u64::MAX, u64::MAX), Some(100));
/// assert_eq!(percent(u64::MAX / 100, u64::MAX), Some(1));
/// assert_eq!(percent(u64::MAX >> 1, u64::MAX), Some(50));
/// ```
///
pub fn percent(numerator: u64, denominator: u64) -> Option<u64> {
    // The 7 is calculated as: 100u8.ilog2() + 1;
    (0..=7).find_map(|shift| {
        (numerator >> shift)
            .checked_mul(100)
            .and_then(|numerator| match denominator >> shift {
                0 => None,
                1 => Some(numerator),
                d if (d >> 1) > numerator.rem_euclid(d) => Some(numerator.div_euclid(d)),
                d => Some(numerator.div_euclid(d) + 1),
            })
    })
}

#[cfg(test)]
mod tests {
    use std::env;

    type TestResult = Result<(), Box<dyn std::error::Error>>;

    #[test]
    fn test_prepend_to_path_prepends_given_dir_to_path() -> TestResult {
        let path = env::join_paths([tempfile::tempdir()?.path(), tempfile::tempdir()?.path()])?;
        let tempdir = tempfile::tempdir()?;
        let expected = {
            let mut tmp = vec![tempdir.path().to_path_buf()];
            tmp.extend(env::split_paths(&path));
            env::join_paths(tmp)?
        };
        let observed = { super::prepend_to_path(tempdir.path(), Some(path))? };
        assert_eq!(expected, observed);
        Ok(())
    }

    #[test]
    fn test_prepend_to_path_moves_dir_to_front_of_path() -> TestResult {
        let tempdir = tempfile::tempdir()?;
        let path = env::join_paths([
            tempfile::tempdir()?.path(),
            tempfile::tempdir()?.path(),
            tempdir.path(),
        ])?;
        let expected = {
            let mut tmp = vec![tempdir.path().to_path_buf()];
            tmp.extend(env::split_paths(&path).take(2));
            env::join_paths(tmp)?
        };
        let observed = { super::prepend_to_path(tempdir.path(), Some(path))? };
        assert_eq!(expected, observed);
        Ok(())
    }

    #[test]
    fn test_prepend_to_path_returns_given_dir_if_path_is_empty() -> TestResult {
        let tempdir = tempfile::tempdir()?;
        let expected = tempdir.path();
        let observed = super::prepend_to_path(tempdir.path(), None)?;
        assert_eq!(expected, observed);
        Ok(())
    }
}