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