1#[cfg(target_os = "illumos")]
2use std::convert::TryInto;
3#[cfg(any(
4 target_os = "linux",
5 target_os = "dragonfly",
6 target_os = "freebsd",
7 target_os = "netbsd",
8 target_os = "openbsd",
9 target_os = "illumos",
10))]
11use std::env;
12use std::{
13 ffi::{c_void, CStr, OsString},
14 fs,
15 io::{Error, ErrorKind},
16 mem,
17 os::{
18 raw::{c_char, c_int},
19 unix::ffi::OsStringExt,
20 },
21 slice,
22};
23#[cfg(target_os = "macos")]
24use std::{
25 os::{
26 raw::{c_long, c_uchar},
27 unix::ffi::OsStrExt,
28 },
29 ptr::null_mut,
30};
31
32use crate::{
33 os::{Os, Target},
34 Arch, DesktopEnv, Platform, Result,
35};
36
37#[cfg(target_os = "linux")]
38#[repr(C)]
39struct PassWd {
40 pw_name: *const c_void,
41 pw_passwd: *const c_void,
42 pw_uid: u32,
43 pw_gid: u32,
44 pw_gecos: *const c_void,
45 pw_dir: *const c_void,
46 pw_shell: *const c_void,
47}
48
49#[cfg(any(
50 target_os = "macos",
51 target_os = "dragonfly",
52 target_os = "freebsd",
53 target_os = "openbsd",
54 target_os = "netbsd"
55))]
56#[repr(C)]
57struct PassWd {
58 pw_name: *const c_void,
59 pw_passwd: *const c_void,
60 pw_uid: u32,
61 pw_gid: u32,
62 pw_change: isize,
63 pw_class: *const c_void,
64 pw_gecos: *const c_void,
65 pw_dir: *const c_void,
66 pw_shell: *const c_void,
67 pw_expire: isize,
68 pw_fields: i32,
69}
70
71#[cfg(target_os = "illumos")]
72#[repr(C)]
73struct PassWd {
74 pw_name: *const c_void,
75 pw_passwd: *const c_void,
76 pw_uid: u32,
77 pw_gid: u32,
78 pw_age: *const c_void,
79 pw_comment: *const c_void,
80 pw_gecos: *const c_void,
81 pw_dir: *const c_void,
82 pw_shell: *const c_void,
83}
84
85#[cfg(target_os = "illumos")]
86extern "system" {
87 fn getpwuid_r(
88 uid: u32,
89 pwd: *mut PassWd,
90 buf: *mut c_void,
91 buflen: c_int,
92 ) -> *mut PassWd;
93}
94
95#[cfg(any(
96 target_os = "linux",
97 target_os = "macos",
98 target_os = "dragonfly",
99 target_os = "freebsd",
100 target_os = "netbsd",
101 target_os = "openbsd",
102))]
103extern "system" {
104 fn getpwuid_r(
105 uid: u32,
106 pwd: *mut PassWd,
107 buf: *mut c_void,
108 buflen: usize,
109 result: *mut *mut PassWd,
110 ) -> i32;
111}
112
113extern "system" {
114 fn geteuid() -> u32;
115 fn gethostname(name: *mut c_void, len: usize) -> i32;
116}
117
118#[cfg(target_os = "macos")]
119#[link(name = "CoreFoundation", kind = "framework")]
120#[link(name = "SystemConfiguration", kind = "framework")]
121extern "system" {
122 fn CFStringGetCString(
123 the_string: *mut c_void,
124 buffer: *mut u8,
125 buffer_size: c_long,
126 encoding: u32,
127 ) -> c_uchar;
128 fn CFStringGetLength(the_string: *mut c_void) -> c_long;
129 fn CFStringGetMaximumSizeForEncoding(
130 length: c_long,
131 encoding: u32,
132 ) -> c_long;
133 fn SCDynamicStoreCopyComputerName(
134 store: *mut c_void,
135 encoding: *mut u32,
136 ) -> *mut c_void;
137 fn CFRelease(cf: *const c_void);
138}
139
140enum Name {
141 User,
142 Real,
143}
144
145unsafe fn strlen(cs: *const c_void) -> usize {
146 let mut len = 0;
147 let mut cs: *const u8 = cs.cast();
148 while *cs != 0 {
149 len += 1;
150 cs = cs.offset(1);
151 }
152 len
153}
154
155unsafe fn strlen_gecos(cs: *const c_void) -> usize {
156 let mut len = 0;
157 let mut cs: *const u8 = cs.cast();
158 while *cs != 0 && *cs != b',' {
159 len += 1;
160 cs = cs.offset(1);
161 }
162 len
163}
164
165fn os_from_cstring_gecos(string: *const c_void) -> Result<OsString> {
166 if string.is_null() {
167 return Err(super::err_null_record());
168 }
169
170 let slice = unsafe {
172 let length = strlen_gecos(string);
173
174 if length == 0 {
175 return Err(super::err_empty_record());
176 }
177
178 slice::from_raw_parts(string.cast(), length)
179 };
180
181 Ok(OsString::from_vec(slice.to_vec()))
183}
184
185fn os_from_cstring(string: *const c_void) -> Result<OsString> {
186 if string.is_null() {
187 return Err(super::err_null_record());
188 }
189
190 let slice = unsafe {
192 let length = strlen(string);
193
194 if length == 0 {
195 return Err(super::err_empty_record());
196 }
197
198 slice::from_raw_parts(string.cast(), length)
199 };
200
201 Ok(OsString::from_vec(slice.to_vec()))
203}
204
205#[cfg(target_os = "macos")]
206fn os_from_cfstring(string: *mut c_void) -> OsString {
207 if string.is_null() {
208 return "".to_string().into();
209 }
210
211 unsafe {
212 let len = CFStringGetLength(string);
213 let capacity =
214 CFStringGetMaximumSizeForEncoding(len, 134_217_984 ) + 1;
215 let mut out = Vec::with_capacity(capacity as usize);
216 if CFStringGetCString(
217 string,
218 out.as_mut_ptr(),
219 capacity,
220 134_217_984, ) != 0
222 {
223 out.set_len(strlen(out.as_ptr().cast())); out.shrink_to_fit();
225 CFRelease(string);
226 OsString::from_vec(out)
227 } else {
228 CFRelease(string);
229 "".to_string().into()
230 }
231 }
232}
233
234#[inline(always)]
237fn getpwuid(name: Name) -> Result<OsString> {
238 const BUF_SIZE: usize = 16_384; let mut buffer = mem::MaybeUninit::<[u8; BUF_SIZE]>::uninit();
240 let mut passwd = mem::MaybeUninit::<PassWd>::uninit();
241
242 let passwd = unsafe {
244 #[cfg(any(
245 target_os = "linux",
246 target_os = "macos",
247 target_os = "dragonfly",
248 target_os = "freebsd",
249 target_os = "netbsd",
250 target_os = "openbsd",
251 ))]
252 {
253 let mut _passwd = mem::MaybeUninit::<*mut PassWd>::uninit();
254 let ret = getpwuid_r(
255 geteuid(),
256 passwd.as_mut_ptr(),
257 buffer.as_mut_ptr() as *mut c_void,
258 BUF_SIZE,
259 _passwd.as_mut_ptr(),
260 );
261
262 if ret != 0 {
263 return Err(Error::last_os_error());
264 }
265
266 let _passwd = _passwd.assume_init();
267
268 if _passwd.is_null() {
269 return Err(super::err_null_record());
270 }
271 passwd.assume_init()
272 }
273
274 #[cfg(target_os = "illumos")]
275 {
276 let ret = getpwuid_r(
277 geteuid(),
278 passwd.as_mut_ptr(),
279 buffer.as_mut_ptr() as *mut c_void,
280 BUF_SIZE.try_into().unwrap_or(c_int::MAX),
281 );
282
283 if ret.is_null() {
284 return Err(Error::last_os_error());
285 }
286 passwd.assume_init()
287 }
288 };
289
290 if let Name::Real = name {
292 os_from_cstring_gecos(passwd.pw_gecos)
293 } else {
294 os_from_cstring(passwd.pw_name)
295 }
296}
297
298#[cfg(target_os = "macos")]
299fn distro_xml(data: String) -> Result<String> {
300 let mut product_name = None;
301 let mut user_visible_version = None;
302
303 if let Some(start) = data.find("<dict>") {
304 if let Some(end) = data.find("</dict>") {
305 let mut set_product_name = false;
306 let mut set_user_visible_version = false;
307
308 for line in data[start + "<dict>".len()..end].lines() {
309 let line = line.trim();
310
311 if line.starts_with("<key>") {
312 match line["<key>".len()..].trim_end_matches("</key>") {
313 "ProductName" => set_product_name = true,
314 "ProductUserVisibleVersion" => {
315 set_user_visible_version = true
316 }
317 "ProductVersion" => {
318 if user_visible_version.is_none() {
319 set_user_visible_version = true
320 }
321 }
322 _ => {}
323 }
324 } else if line.starts_with("<string>") {
325 if set_product_name {
326 product_name = Some(
327 line["<string>".len()..]
328 .trim_end_matches("</string>"),
329 );
330 set_product_name = false;
331 } else if set_user_visible_version {
332 user_visible_version = Some(
333 line["<string>".len()..]
334 .trim_end_matches("</string>"),
335 );
336 set_user_visible_version = false;
337 }
338 }
339 }
340 }
341 }
342
343 Ok(if let Some(product_name) = product_name {
344 if let Some(user_visible_version) = user_visible_version {
345 format!("{} {}", product_name, user_visible_version)
346 } else {
347 product_name.to_string()
348 }
349 } else {
350 user_visible_version
351 .map(|v| format!("Mac OS (Unknown) {}", v))
352 .ok_or_else(|| {
353 Error::new(ErrorKind::InvalidData, "Parsing failed")
354 })?
355 })
356}
357
358#[cfg(any(
359 target_os = "macos",
360 target_os = "freebsd",
361 target_os = "netbsd",
362 target_os = "openbsd",
363))]
364#[repr(C)]
365struct UtsName {
366 sysname: [c_char; 256],
367 nodename: [c_char; 256],
368 release: [c_char; 256],
369 version: [c_char; 256],
370 machine: [c_char; 256],
371}
372
373#[cfg(target_os = "illumos")]
374#[repr(C)]
375struct UtsName {
376 sysname: [c_char; 257],
377 nodename: [c_char; 257],
378 release: [c_char; 257],
379 version: [c_char; 257],
380 machine: [c_char; 257],
381}
382
383#[cfg(target_os = "dragonfly")]
384#[repr(C)]
385struct UtsName {
386 sysname: [c_char; 32],
387 nodename: [c_char; 32],
388 release: [c_char; 32],
389 version: [c_char; 32],
390 machine: [c_char; 32],
391}
392
393#[cfg(any(target_os = "linux", target_os = "android",))]
394#[repr(C)]
395struct UtsName {
396 sysname: [c_char; 65],
397 nodename: [c_char; 65],
398 release: [c_char; 65],
399 version: [c_char; 65],
400 machine: [c_char; 65],
401 domainname: [c_char; 65],
402}
403
404impl Default for UtsName {
406 fn default() -> Self {
407 unsafe { mem::zeroed() }
408 }
409}
410
411#[inline(always)]
412unsafe fn uname(buf: *mut UtsName) -> c_int {
413 extern "C" {
414 #[cfg(any(
415 target_os = "linux",
416 target_os = "macos",
417 target_os = "dragonfly",
418 target_os = "netbsd",
419 target_os = "openbsd",
420 target_os = "illumos",
421 ))]
422 fn uname(buf: *mut UtsName) -> c_int;
423
424 #[cfg(target_os = "freebsd")]
425 fn __xuname(nmln: c_int, buf: *mut c_void) -> c_int;
426 }
427
428 #[inline(always)]
430 #[cfg(target_os = "freebsd")]
431 unsafe extern "C" fn uname(buf: *mut UtsName) -> c_int {
432 __xuname(256, buf.cast())
433 }
434
435 uname(buf)
436}
437
438impl Target for Os {
439 fn langs(self) -> Result<String> {
440 super::unix_lang()
441 }
442
443 fn realname(self) -> Result<OsString> {
444 getpwuid(Name::Real)
445 }
446
447 fn username(self) -> Result<OsString> {
448 getpwuid(Name::User)
449 }
450
451 fn devicename(self) -> Result<OsString> {
452 #[cfg(target_os = "macos")]
453 {
454 let out = os_from_cfstring(unsafe {
455 SCDynamicStoreCopyComputerName(null_mut(), null_mut())
456 });
457
458 if out.as_bytes().is_empty() {
459 return Err(super::err_empty_record());
460 }
461
462 Ok(out)
463 }
464
465 #[cfg(target_os = "illumos")]
466 {
467 let mut nodename = fs::read("/etc/nodename")?;
468
469 if let Some(slice) = nodename.split(|x| *x == b'\n').next() {
471 nodename.drain(slice.len()..);
472 }
473
474 if nodename.is_empty() {
475 return Err(super::err_empty_record());
476 }
477
478 Ok(OsString::from_vec(nodename))
479 }
480
481 #[cfg(any(
482 target_os = "linux",
483 target_os = "dragonfly",
484 target_os = "freebsd",
485 target_os = "netbsd",
486 target_os = "openbsd",
487 ))]
488 {
489 let machine_info = fs::read("/etc/machine-info")?;
490
491 for i in machine_info.split(|b| *b == b'\n') {
492 let mut j = i.split(|b| *b == b'=');
493
494 if j.next() == Some(b"PRETTY_HOSTNAME") {
495 if let Some(value) = j.next() {
496 return Ok(OsString::from_vec(value.to_vec()));
498 }
499 }
500 }
501
502 Err(super::err_missing_record())
503 }
504 }
505
506 fn hostname(self) -> Result<String> {
507 let mut string = Vec::<u8>::with_capacity(256);
509
510 unsafe {
511 if gethostname(string.as_mut_ptr().cast(), 255) == -1 {
512 return Err(Error::last_os_error());
513 }
514
515 string.set_len(strlen(string.as_ptr().cast()));
516 };
517
518 String::from_utf8(string).map_err(|_| {
519 Error::new(ErrorKind::InvalidData, "Hostname not valid UTF-8")
520 })
521 }
522
523 fn distro(self) -> Result<String> {
524 #[cfg(target_os = "macos")]
525 {
526 if let Ok(data) = fs::read_to_string(
527 "/System/Library/CoreServices/ServerVersion.plist",
528 ) {
529 distro_xml(data)
530 } else if let Ok(data) = fs::read_to_string(
531 "/System/Library/CoreServices/SystemVersion.plist",
532 ) {
533 distro_xml(data)
534 } else {
535 Err(super::err_missing_record())
536 }
537 }
538
539 #[cfg(any(
540 target_os = "linux",
541 target_os = "dragonfly",
542 target_os = "freebsd",
543 target_os = "netbsd",
544 target_os = "openbsd",
545 target_os = "illumos",
546 ))]
547 {
548 let program = fs::read("/etc/os-release")?;
549 let distro = String::from_utf8_lossy(&program);
550 let err = || Error::new(ErrorKind::InvalidData, "Parsing failed");
551 let mut fallback = None;
552
553 for i in distro.split('\n') {
554 let mut j = i.split('=');
555
556 match j.next().ok_or_else(err)? {
557 "PRETTY_NAME" => {
558 return Ok(j
559 .next()
560 .ok_or_else(err)?
561 .trim_matches('"')
562 .to_string());
563 }
564 "NAME" => {
565 fallback = Some(
566 j.next()
567 .ok_or_else(err)?
568 .trim_matches('"')
569 .to_string(),
570 )
571 }
572 _ => {}
573 }
574 }
575
576 fallback.ok_or_else(err)
577 }
578 }
579
580 fn desktop_env(self) -> DesktopEnv {
581 #[cfg(target_os = "macos")]
582 let env = "Aqua";
583
584 #[cfg(any(
586 target_os = "linux",
587 target_os = "dragonfly",
588 target_os = "freebsd",
589 target_os = "netbsd",
590 target_os = "openbsd",
591 target_os = "illumos",
592 ))]
593 let env = env::var_os("DESKTOP_SESSION");
594 #[cfg(any(
595 target_os = "linux",
596 target_os = "dragonfly",
597 target_os = "freebsd",
598 target_os = "netbsd",
599 target_os = "openbsd",
600 target_os = "illumos",
601 ))]
602 let env = if let Some(ref env) = env {
603 env.to_string_lossy()
604 } else {
605 return DesktopEnv::Unknown("Unknown".to_string());
606 };
607
608 if env.eq_ignore_ascii_case("AQUA") {
609 DesktopEnv::Aqua
610 } else if env.eq_ignore_ascii_case("GNOME") {
611 DesktopEnv::Gnome
612 } else if env.eq_ignore_ascii_case("LXDE") {
613 DesktopEnv::Lxde
614 } else if env.eq_ignore_ascii_case("OPENBOX") {
615 DesktopEnv::Openbox
616 } else if env.eq_ignore_ascii_case("I3") {
617 DesktopEnv::I3
618 } else if env.eq_ignore_ascii_case("UBUNTU") {
619 DesktopEnv::Ubuntu
620 } else if env.eq_ignore_ascii_case("PLASMA5") {
621 DesktopEnv::Kde
622 } else {
624 DesktopEnv::Unknown(env.to_string())
625 }
626 }
627
628 #[inline(always)]
629 fn platform(self) -> Platform {
630 #[cfg(target_os = "linux")]
631 {
632 Platform::Linux
633 }
634
635 #[cfg(target_os = "macos")]
636 {
637 Platform::MacOS
638 }
639
640 #[cfg(any(
641 target_os = "dragonfly",
642 target_os = "freebsd",
643 target_os = "netbsd",
644 target_os = "openbsd",
645 ))]
646 {
647 Platform::Bsd
648 }
649
650 #[cfg(target_os = "illumos")]
651 {
652 Platform::Illumos
653 }
654 }
655
656 #[inline(always)]
657 fn arch(self) -> Result<Arch> {
658 let mut buf = UtsName::default();
659
660 if unsafe { uname(&mut buf) } == -1 {
661 return Err(Error::last_os_error());
662 }
663
664 let arch_str =
665 unsafe { CStr::from_ptr(buf.machine.as_ptr()) }.to_string_lossy();
666
667 Ok(match arch_str.as_ref() {
668 "aarch64" | "arm64" | "aarch64_be" | "armv8b" | "armv8l" => {
669 Arch::Arm64
670 }
671 "armv5" => Arch::ArmV5,
672 "armv6" | "arm" => Arch::ArmV6,
673 "armv7" => Arch::ArmV7,
674 "i386" => Arch::I386,
675 "i586" => Arch::I586,
676 "i686" | "i686-AT386" => Arch::I686,
677 "mips" => Arch::Mips,
678 "mipsel" => Arch::MipsEl,
679 "mips64" => Arch::Mips64,
680 "mips64el" => Arch::Mips64El,
681 "powerpc" | "ppc" | "ppcle" => Arch::PowerPc,
682 "powerpc64" | "ppc64" | "ppc64le" => Arch::PowerPc64,
683 "powerpc64le" => Arch::PowerPc64Le,
684 "riscv32" => Arch::Riscv32,
685 "riscv64" => Arch::Riscv64,
686 "s390x" => Arch::S390x,
687 "sparc" => Arch::Sparc,
688 "sparc64" => Arch::Sparc64,
689 "x86_64" | "amd64" => Arch::X64,
690 _ => Arch::Unknown(arch_str.into_owned()),
691 })
692 }
693}