whoami/
unix.rs

1use crate::{DesktopEnv, Platform};
2
3use std::ffi::c_void;
4use std::mem;
5use std::process::Command;
6use std::process::Stdio;
7
8#[repr(C)]
9struct PassWd {
10    pw_name: *const c_void,
11    pw_passwd: *const c_void,
12    pw_uid: u32,
13    pw_gid: u32,
14    #[cfg(target_os = "macos")]
15    pw_change: isize,
16    #[cfg(target_os = "macos")]
17    pw_class: *const c_void,
18    pw_gecos: *const c_void,
19    pw_dir: *const c_void,
20    pw_shell: *const c_void,
21    #[cfg(target_os = "macos")]
22    pw_expire: isize,
23    #[cfg(target_os = "macos")]
24    pw_fields: i32,
25}
26
27extern "system" {
28    fn getpwuid_r(
29        uid: u32,
30        pwd: *mut PassWd,
31        buf: *mut c_void,
32        buflen: usize,
33        result: *mut *mut PassWd,
34    ) -> i32;
35    fn geteuid() -> u32;
36    fn strlen(cs: *const c_void) -> usize;
37    fn gethostname(name: *mut c_void, len: usize) -> i32;
38}
39
40fn string_from_cstring(string: *const c_void) -> String {
41    if string.is_null() {
42        return "".to_string();
43    }
44
45    // Get a byte slice of the c string.
46    let slice = unsafe {
47        let length = strlen(string);
48        std::slice::from_raw_parts(string as *const u8, length)
49    };
50
51    // Turn byte slice into Rust String.
52    String::from_utf8_lossy(slice).to_string()
53}
54
55// This function must return `String`s, because a slice or Cow<str> would still
56// reference `passwd` which is dropped when this function returns.
57#[inline(always)]
58fn getpwuid() -> (String, String) {
59    const BUF_SIZE: usize = 16_384; // size from the man page
60    let mut buffer = mem::MaybeUninit::<[u8; BUF_SIZE]>::uninit();
61    let mut passwd = mem::MaybeUninit::<PassWd>::uninit();
62    let mut _passwd = mem::MaybeUninit::<*mut PassWd>::uninit();
63
64    // Get PassWd `struct`.
65    let passwd = unsafe {
66        getpwuid_r(
67            geteuid(),
68            passwd.as_mut_ptr(),
69            buffer.as_mut_ptr() as *mut c_void,
70            BUF_SIZE,
71            _passwd.as_mut_ptr(),
72        );
73
74        passwd.assume_init()
75    };
76
77    // Extract names.
78    let a = string_from_cstring(passwd.pw_name);
79    let b = string_from_cstring(passwd.pw_gecos);
80
81    (a, b)
82}
83
84pub fn username() -> String {
85    let pwent = getpwuid();
86
87    pwent.0
88}
89
90fn fancy_fallback(mut computer: String, fallback_fn: fn() -> String) -> String {
91    let mut cap = true;
92
93    if computer.is_empty() {
94        let fallback = fallback_fn();
95
96        for c in fallback.chars() {
97            match c {
98                '.' | '-' | '_' => {
99                    computer.push(' ');
100                    cap = true;
101                }
102                a => {
103                    if cap {
104                        cap = false;
105                        for i in a.to_uppercase() {
106                            computer.push(i);
107                        }
108                    } else {
109                        computer.push(a);
110                    }
111                }
112            }
113        }
114    }
115
116    computer
117}
118
119pub fn realname() -> String {
120    let pwent = getpwuid();
121    let realname = pwent.1;
122
123    // If no real name is provided, guess based on username.
124    fancy_fallback(realname, username)
125}
126
127pub fn computer() -> String {
128    let mut computer = String::new();
129
130    let program = if cfg!(not(target_os = "macos")) {
131        Command::new("hostnamectl")
132            .arg("--pretty")
133            .stdout(Stdio::piped())
134            .output()
135            .expect("Couldn't Find `hostnamectl`")
136    } else {
137        Command::new("scutil")
138            .arg("--get")
139            .arg("ComputerName")
140            .output()
141            .expect("Couldn't find `scutil`")
142    };
143
144    computer.push_str(String::from_utf8(program.stdout).unwrap().as_str());
145
146    //    let mut pretty = BufReader::new(program.stdout.as_mut().unwrap());
147
148    //    pretty.read_to_string(&mut computer).unwrap();
149
150    computer.pop();
151
152    fancy_fallback(computer, hostname)
153}
154
155pub fn hostname() -> String {
156    // Maximum hostname length = 255, plus a NULL byte.
157    let mut string = mem::MaybeUninit::<[u8; 256]>::uninit();
158    let string = unsafe {
159        gethostname(string.as_mut_ptr() as *mut c_void, 255);
160        &string.assume_init()[..strlen(string.as_ptr() as *const c_void)]
161    };
162
163    String::from_utf8_lossy(string).to_string()
164}
165
166#[cfg(target_os = "macos")]
167pub fn os() -> String {
168    let mut distro = String::new();
169
170    let name = Command::new("sw_vers")
171        .arg("-productName")
172        .output()
173        .expect("Couldn't find `sw_vers`");
174
175    let version = Command::new("sw_vers")
176        .arg("-productVersion")
177        .output()
178        .expect("Couldn't find `sw_vers`");
179
180    let build = Command::new("sw_vers")
181        .arg("-buildVersion")
182        .output()
183        .expect("Couldn't find `sw_vers`");
184
185    distro.push_str(String::from_utf8(name.stdout).unwrap().as_str());
186    distro.pop();
187    distro.push(' ');
188    distro.push_str(String::from_utf8(version.stdout).unwrap().as_str());
189    distro.pop();
190    distro.push(' ');
191    distro.push_str(String::from_utf8(build.stdout).unwrap().as_str());
192    distro.pop();
193
194    distro
195}
196
197#[cfg(not(target_os = "macos"))]
198pub fn os() -> String {
199    let mut distro = String::new();
200
201    let program = std::fs::read_to_string("/etc/os-release")
202        .expect("Couldn't read file /etc/os-release")
203        .into_bytes();
204
205    distro.push_str(String::from_utf8(program).unwrap().as_str());
206
207    let mut fallback = None;
208
209    for i in distro.split('\n') {
210        let mut j = i.split('=');
211
212        match j.next().unwrap() {
213            "PRETTY_NAME" => {
214                return j.next().unwrap().trim_matches('"').to_string()
215            }
216            "NAME" => {
217                fallback = Some(j.next().unwrap().trim_matches('"').to_string())
218            }
219            _ => {}
220        }
221    }
222
223    if let Some(x) = fallback {
224        x
225    } else {
226        "unknown".to_string()
227    }
228}
229
230#[cfg(target_os = "macos")]
231#[inline(always)]
232pub const fn env() -> DesktopEnv {
233    DesktopEnv::Mac
234}
235
236#[cfg(not(target_os = "macos"))]
237#[inline(always)]
238pub fn env() -> DesktopEnv {
239    match std::env::var_os("DESKTOP_SESSION") {
240        Some(env) => {
241            let env = env.to_str().unwrap().to_uppercase();
242
243            if env.contains("GNOME") {
244                DesktopEnv::Gnome
245            } else if env.contains("LXDE") {
246                DesktopEnv::Lxde
247            } else if env.contains("OPENBOX") {
248                DesktopEnv::Openbox
249            } else if env.contains("I3") {
250                DesktopEnv::I3
251            } else if env.contains("UBUNTU") {
252                DesktopEnv::Ubuntu
253            } else {
254                DesktopEnv::Unknown(env)
255            }
256        }
257        // TODO: Other Linux Desktop Environments
258        None => DesktopEnv::Unknown("Unknown".to_string()),
259    }
260}
261
262#[cfg(target_os = "macos")]
263#[inline(always)]
264pub const fn platform() -> Platform {
265    Platform::MacOS
266}
267
268#[cfg(not(target_os = "macos"))]
269#[inline(always)]
270pub const fn platform() -> Platform {
271    Platform::Linux
272}