sysinfo/unix/linux/
system.rs

1// Take a look at the license at the top of the repository in the LICENSE file.
2
3use crate::sys::cpu::{get_physical_core_count, CpusWrapper};
4use crate::sys::process::{_get_process_data, compute_cpu_usage, refresh_procs, unset_updated};
5use crate::sys::utils::{get_all_data, to_u64};
6use crate::{Cpu, CpuRefreshKind, LoadAvg, MemoryRefreshKind, Pid, Process, ProcessRefreshKind};
7
8use libc::{self, c_char, sysconf, _SC_CLK_TCK, _SC_HOST_NAME_MAX, _SC_PAGESIZE};
9use std::cmp::min;
10use std::collections::HashMap;
11use std::ffi::CStr;
12use std::fs::File;
13use std::io::Read;
14use std::path::Path;
15use std::str::FromStr;
16use std::sync::atomic::AtomicIsize;
17
18// This whole thing is to prevent having too many files open at once. It could be problematic
19// for processes using a lot of files and using sysinfo at the same time.
20pub(crate) static REMAINING_FILES: once_cell::sync::Lazy<AtomicIsize> =
21    once_cell::sync::Lazy::new(|| {
22        unsafe {
23            let mut limits = libc::rlimit {
24                rlim_cur: 0,
25                rlim_max: 0,
26            };
27            if libc::getrlimit(libc::RLIMIT_NOFILE, &mut limits) != 0 {
28                // Most Linux system now defaults to 1024.
29                return AtomicIsize::new(1024 / 2);
30            }
31            // We save the value in case the update fails.
32            let current = limits.rlim_cur;
33
34            // The set the soft limit to the hard one.
35            limits.rlim_cur = limits.rlim_max;
36            // In this part, we leave minimum 50% of the available file descriptors to the process
37            // using sysinfo.
38            AtomicIsize::new(if libc::setrlimit(libc::RLIMIT_NOFILE, &limits) == 0 {
39                limits.rlim_cur / 2
40            } else {
41                current / 2
42            } as _)
43        }
44    });
45
46pub(crate) fn get_max_nb_fds() -> isize {
47    unsafe {
48        let mut limits = libc::rlimit {
49            rlim_cur: 0,
50            rlim_max: 0,
51        };
52        if libc::getrlimit(libc::RLIMIT_NOFILE, &mut limits) != 0 {
53            // Most Linux system now defaults to 1024.
54            1024 / 2
55        } else {
56            limits.rlim_max as isize / 2
57        }
58    }
59}
60
61fn boot_time() -> u64 {
62    if let Ok(buf) = File::open("/proc/stat").and_then(|mut f| {
63        let mut buf = Vec::new();
64        f.read_to_end(&mut buf)?;
65        Ok(buf)
66    }) {
67        let line = buf.split(|c| *c == b'\n').find(|l| l.starts_with(b"btime"));
68
69        if let Some(line) = line {
70            return line
71                .split(|x| *x == b' ')
72                .filter(|s| !s.is_empty())
73                .nth(1)
74                .map(to_u64)
75                .unwrap_or(0);
76        }
77    }
78    // Either we didn't find "btime" or "/proc/stat" wasn't available for some reason...
79    unsafe {
80        let mut up: libc::timespec = std::mem::zeroed();
81        if libc::clock_gettime(libc::CLOCK_BOOTTIME, &mut up) == 0 {
82            up.tv_sec as u64
83        } else {
84            sysinfo_debug!("clock_gettime failed: boot time cannot be retrieve...");
85            0
86        }
87    }
88}
89
90pub(crate) struct SystemInfo {
91    pub(crate) page_size_b: u64,
92    pub(crate) clock_cycle: u64,
93    pub(crate) boot_time: u64,
94}
95
96impl SystemInfo {
97    fn new() -> Self {
98        unsafe {
99            Self {
100                page_size_b: sysconf(_SC_PAGESIZE) as _,
101                clock_cycle: sysconf(_SC_CLK_TCK) as _,
102                boot_time: boot_time(),
103            }
104        }
105    }
106}
107
108pub(crate) struct SystemInner {
109    process_list: HashMap<Pid, Process>,
110    mem_total: u64,
111    mem_free: u64,
112    mem_available: u64,
113    mem_buffers: u64,
114    mem_page_cache: u64,
115    mem_shmem: u64,
116    mem_slab_reclaimable: u64,
117    swap_total: u64,
118    swap_free: u64,
119    info: SystemInfo,
120    cpus: CpusWrapper,
121}
122
123impl SystemInner {
124    /// It is sometime possible that a CPU usage computation is bigger than
125    /// `"number of CPUs" * 100`.
126    ///
127    /// To prevent that, we compute ahead of time this maximum value and ensure that processes'
128    /// CPU usage don't go over it.
129    fn get_max_process_cpu_usage(&self) -> f32 {
130        self.cpus.len() as f32 * 100.
131    }
132
133    fn clear_procs(&mut self, refresh_kind: ProcessRefreshKind) {
134        let (total_time, compute_cpu, max_value) = if refresh_kind.cpu() {
135            self.cpus
136                .refresh_if_needed(true, CpuRefreshKind::new().with_cpu_usage());
137
138            if self.cpus.is_empty() {
139                sysinfo_debug!("cannot compute processes CPU usage: no CPU found...");
140                (0., false, 0.)
141            } else {
142                let (new, old) = self.cpus.get_global_raw_times();
143                let total_time = if old > new { 1 } else { new - old };
144                (
145                    total_time as f32 / self.cpus.len() as f32,
146                    true,
147                    self.get_max_process_cpu_usage(),
148                )
149            }
150        } else {
151            (0., false, 0.)
152        };
153
154        self.process_list.retain(|_, proc_| {
155            let proc_ = &mut proc_.inner;
156            if !proc_.updated {
157                return false;
158            }
159            if compute_cpu {
160                compute_cpu_usage(proc_, total_time, max_value);
161            }
162            unset_updated(proc_);
163            true
164        });
165    }
166
167    fn refresh_cpus(&mut self, only_update_global_cpu: bool, refresh_kind: CpuRefreshKind) {
168        self.cpus.refresh(only_update_global_cpu, refresh_kind);
169    }
170}
171
172impl SystemInner {
173    pub(crate) fn new() -> Self {
174        Self {
175            process_list: HashMap::new(),
176            mem_total: 0,
177            mem_free: 0,
178            mem_available: 0,
179            mem_buffers: 0,
180            mem_page_cache: 0,
181            mem_shmem: 0,
182            mem_slab_reclaimable: 0,
183            swap_total: 0,
184            swap_free: 0,
185            cpus: CpusWrapper::new(),
186            info: SystemInfo::new(),
187        }
188    }
189
190    pub(crate) fn refresh_memory_specifics(&mut self, refresh_kind: MemoryRefreshKind) {
191        if !refresh_kind.ram() && !refresh_kind.swap() {
192            return;
193        }
194        let mut mem_available_found = false;
195        read_table("/proc/meminfo", ':', |key, value_kib| {
196            let field = match key {
197                "MemTotal" => &mut self.mem_total,
198                "MemFree" => &mut self.mem_free,
199                "MemAvailable" => {
200                    mem_available_found = true;
201                    &mut self.mem_available
202                }
203                "Buffers" => &mut self.mem_buffers,
204                "Cached" => &mut self.mem_page_cache,
205                "Shmem" => &mut self.mem_shmem,
206                "SReclaimable" => &mut self.mem_slab_reclaimable,
207                "SwapTotal" => &mut self.swap_total,
208                "SwapFree" => &mut self.swap_free,
209                _ => return,
210            };
211            // /proc/meminfo reports KiB, though it says "kB". Convert it.
212            *field = value_kib.saturating_mul(1_024);
213        });
214
215        // Linux < 3.14 may not have MemAvailable in /proc/meminfo
216        // So it should fallback to the old way of estimating available memory
217        // https://github.com/KittyKatt/screenFetch/issues/386#issuecomment-249312716
218        if !mem_available_found {
219            self.mem_available = self
220                .mem_free
221                .saturating_add(self.mem_buffers)
222                .saturating_add(self.mem_page_cache)
223                .saturating_add(self.mem_slab_reclaimable)
224                .saturating_sub(self.mem_shmem);
225        }
226    }
227
228    pub(crate) fn cgroup_limits(&self) -> Option<crate::CGroupLimits> {
229        crate::CGroupLimits::new(self)
230    }
231
232    pub(crate) fn refresh_cpu_specifics(&mut self, refresh_kind: CpuRefreshKind) {
233        self.refresh_cpus(false, refresh_kind);
234    }
235
236    pub(crate) fn refresh_processes_specifics(
237        &mut self,
238        filter: Option<&[Pid]>,
239        refresh_kind: ProcessRefreshKind,
240    ) {
241        let uptime = Self::uptime();
242        refresh_procs(
243            &mut self.process_list,
244            Path::new("/proc"),
245            uptime,
246            &self.info,
247            filter,
248            refresh_kind,
249        );
250        self.clear_procs(refresh_kind);
251        self.cpus.set_need_cpus_update();
252    }
253
254    pub(crate) fn refresh_process_specifics(
255        &mut self,
256        pid: Pid,
257        refresh_kind: ProcessRefreshKind,
258    ) -> bool {
259        let uptime = Self::uptime();
260        match _get_process_data(
261            &Path::new("/proc/").join(pid.to_string()),
262            &mut self.process_list,
263            pid,
264            None,
265            uptime,
266            &self.info,
267            refresh_kind,
268        ) {
269            Ok((Some(p), pid)) => {
270                self.process_list.insert(pid, p);
271            }
272            Ok(_) => {}
273            Err(_e) => {
274                sysinfo_debug!("Cannot get information for PID {:?}: {:?}", pid, _e);
275                return false;
276            }
277        };
278        if refresh_kind.cpu() {
279            self.refresh_cpus(true, CpuRefreshKind::new().with_cpu_usage());
280
281            if self.cpus.is_empty() {
282                eprintln!("Cannot compute process CPU usage: no cpus found...");
283                return true;
284            }
285            let (new, old) = self.cpus.get_global_raw_times();
286            let total_time = (if old >= new { 1 } else { new - old }) as f32;
287            let total_time = total_time / self.cpus.len() as f32;
288
289            let max_cpu_usage = self.get_max_process_cpu_usage();
290            if let Some(p) = self.process_list.get_mut(&pid) {
291                let p = &mut p.inner;
292                compute_cpu_usage(p, total_time, max_cpu_usage);
293                unset_updated(p);
294            }
295        } else if let Some(p) = self.process_list.get_mut(&pid) {
296            unset_updated(&mut p.inner);
297        }
298        true
299    }
300
301    // COMMON PART
302    //
303    // Need to be moved into a "common" file to avoid duplication.
304
305    pub(crate) fn processes(&self) -> &HashMap<Pid, Process> {
306        &self.process_list
307    }
308
309    pub(crate) fn process(&self, pid: Pid) -> Option<&Process> {
310        self.process_list.get(&pid)
311    }
312
313    pub(crate) fn global_cpu_info(&self) -> &Cpu {
314        &self.cpus.global_cpu
315    }
316
317    pub(crate) fn cpus(&self) -> &[Cpu] {
318        &self.cpus.cpus
319    }
320
321    pub(crate) fn physical_core_count(&self) -> Option<usize> {
322        get_physical_core_count()
323    }
324
325    pub(crate) fn total_memory(&self) -> u64 {
326        self.mem_total
327    }
328
329    pub(crate) fn free_memory(&self) -> u64 {
330        self.mem_free
331    }
332
333    pub(crate) fn available_memory(&self) -> u64 {
334        self.mem_available
335    }
336
337    pub(crate) fn used_memory(&self) -> u64 {
338        self.mem_total - self.mem_available
339    }
340
341    pub(crate) fn total_swap(&self) -> u64 {
342        self.swap_total
343    }
344
345    pub(crate) fn free_swap(&self) -> u64 {
346        self.swap_free
347    }
348
349    // need to be checked
350    pub(crate) fn used_swap(&self) -> u64 {
351        self.swap_total - self.swap_free
352    }
353
354    pub(crate) fn uptime() -> u64 {
355        let content = get_all_data("/proc/uptime", 50).unwrap_or_default();
356        content
357            .split('.')
358            .next()
359            .and_then(|t| t.parse().ok())
360            .unwrap_or_default()
361    }
362
363    pub(crate) fn boot_time() -> u64 {
364        boot_time()
365    }
366
367    pub(crate) fn load_average() -> LoadAvg {
368        let mut s = String::new();
369        if File::open("/proc/loadavg")
370            .and_then(|mut f| f.read_to_string(&mut s))
371            .is_err()
372        {
373            return LoadAvg::default();
374        }
375        let loads = s
376            .trim()
377            .split(' ')
378            .take(3)
379            .map(|val| val.parse::<f64>().unwrap())
380            .collect::<Vec<f64>>();
381        LoadAvg {
382            one: loads[0],
383            five: loads[1],
384            fifteen: loads[2],
385        }
386    }
387
388    #[cfg(not(target_os = "android"))]
389    pub(crate) fn name() -> Option<String> {
390        get_system_info_linux(
391            InfoType::Name,
392            Path::new("/etc/os-release"),
393            Path::new("/etc/lsb-release"),
394        )
395    }
396
397    #[cfg(target_os = "android")]
398    pub(crate) fn name() -> Option<String> {
399        get_system_info_android(InfoType::Name)
400    }
401
402    pub(crate) fn long_os_version() -> Option<String> {
403        #[cfg(target_os = "android")]
404        let system_name = "Android";
405
406        #[cfg(not(target_os = "android"))]
407        let system_name = "Linux";
408
409        Some(format!(
410            "{} {} {}",
411            system_name,
412            Self::os_version().unwrap_or_default(),
413            Self::name().unwrap_or_default()
414        ))
415    }
416
417    pub(crate) fn host_name() -> Option<String> {
418        unsafe {
419            let hostname_max = sysconf(_SC_HOST_NAME_MAX);
420            let mut buffer = vec![0_u8; hostname_max as usize];
421            if libc::gethostname(buffer.as_mut_ptr() as *mut c_char, buffer.len()) == 0 {
422                if let Some(pos) = buffer.iter().position(|x| *x == 0) {
423                    // Shrink buffer to terminate the null bytes
424                    buffer.resize(pos, 0);
425                }
426                String::from_utf8(buffer).ok()
427            } else {
428                sysinfo_debug!("gethostname failed: hostname cannot be retrieved...");
429                None
430            }
431        }
432    }
433
434    pub(crate) fn kernel_version() -> Option<String> {
435        let mut raw = std::mem::MaybeUninit::<libc::utsname>::zeroed();
436
437        unsafe {
438            if libc::uname(raw.as_mut_ptr()) == 0 {
439                let info = raw.assume_init();
440
441                let release = info
442                    .release
443                    .iter()
444                    .filter(|c| **c != 0)
445                    .map(|c| *c as u8 as char)
446                    .collect::<String>();
447
448                Some(release)
449            } else {
450                None
451            }
452        }
453    }
454
455    #[cfg(not(target_os = "android"))]
456    pub(crate) fn os_version() -> Option<String> {
457        get_system_info_linux(
458            InfoType::OsVersion,
459            Path::new("/etc/os-release"),
460            Path::new("/etc/lsb-release"),
461        )
462    }
463
464    #[cfg(target_os = "android")]
465    pub(crate) fn os_version() -> Option<String> {
466        get_system_info_android(InfoType::OsVersion)
467    }
468
469    #[cfg(not(target_os = "android"))]
470    pub(crate) fn distribution_id() -> String {
471        get_system_info_linux(
472            InfoType::DistributionID,
473            Path::new("/etc/os-release"),
474            Path::new(""),
475        )
476        .unwrap_or_else(|| std::env::consts::OS.to_owned())
477    }
478
479    #[cfg(target_os = "android")]
480    pub(crate) fn distribution_id() -> String {
481        // Currently get_system_info_android doesn't support InfoType::DistributionID and always
482        // returns None. This call is done anyway for consistency with non-Android implementation
483        // and to suppress dead-code warning for DistributionID on Android.
484        get_system_info_android(InfoType::DistributionID)
485            .unwrap_or_else(|| std::env::consts::OS.to_owned())
486    }
487
488    pub(crate) fn cpu_arch() -> Option<String> {
489        let mut raw = std::mem::MaybeUninit::<libc::utsname>::uninit();
490
491        unsafe {
492            if libc::uname(raw.as_mut_ptr()) != 0 {
493                return None;
494            }
495            let info = raw.assume_init();
496            // Converting `&[i8]` to `&[u8]`.
497            let machine: &[u8] =
498                std::slice::from_raw_parts(info.machine.as_ptr() as *const _, info.machine.len());
499
500            CStr::from_bytes_until_nul(machine)
501                .ok()
502                .and_then(|res| match res.to_str() {
503                    Ok(arch) => Some(arch.to_string()),
504                    Err(_) => None,
505                })
506        }
507    }
508}
509
510fn read_u64(filename: &str) -> Option<u64> {
511    get_all_data(filename, 16_635)
512        .ok()
513        .and_then(|d| u64::from_str(d.trim()).ok())
514}
515
516fn read_table<F>(filename: &str, colsep: char, mut f: F)
517where
518    F: FnMut(&str, u64),
519{
520    if let Ok(content) = get_all_data(filename, 16_635) {
521        content
522            .split('\n')
523            .flat_map(|line| {
524                let mut split = line.split(colsep);
525                let key = split.next()?;
526                let value = split.next()?;
527                let value0 = value.trim_start().split(' ').next()?;
528                let value0_u64 = u64::from_str(value0).ok()?;
529                Some((key, value0_u64))
530            })
531            .for_each(|(k, v)| f(k, v));
532    }
533}
534
535impl crate::CGroupLimits {
536    fn new(sys: &SystemInner) -> Option<Self> {
537        assert!(
538            sys.mem_total != 0,
539            "You need to call System::refresh_memory before trying to get cgroup limits!",
540        );
541        if let (Some(mem_cur), Some(mem_max)) = (
542            read_u64("/sys/fs/cgroup/memory.current"),
543            read_u64("/sys/fs/cgroup/memory.max"),
544        ) {
545            // cgroups v2
546
547            let mut limits = Self {
548                total_memory: sys.mem_total,
549                free_memory: sys.mem_free,
550                free_swap: sys.swap_free,
551            };
552
553            limits.total_memory = min(mem_max, sys.mem_total);
554            limits.free_memory = limits.total_memory.saturating_sub(mem_cur);
555
556            if let Some(swap_cur) = read_u64("/sys/fs/cgroup/memory.swap.current") {
557                limits.free_swap = sys.swap_total.saturating_sub(swap_cur);
558            }
559
560            Some(limits)
561        } else if let (Some(mem_cur), Some(mem_max)) = (
562            // cgroups v1
563            read_u64("/sys/fs/cgroup/memory/memory.usage_in_bytes"),
564            read_u64("/sys/fs/cgroup/memory/memory.limit_in_bytes"),
565        ) {
566            let mut limits = Self {
567                total_memory: sys.mem_total,
568                free_memory: sys.mem_free,
569                free_swap: sys.swap_free,
570            };
571
572            limits.total_memory = min(mem_max, sys.mem_total);
573            limits.free_memory = limits.total_memory.saturating_sub(mem_cur);
574
575            Some(limits)
576        } else {
577            None
578        }
579    }
580}
581
582#[derive(PartialEq, Eq)]
583enum InfoType {
584    /// The end-user friendly name of:
585    /// - Android: The device model
586    /// - Linux: The distributions name
587    Name,
588    OsVersion,
589    /// Machine-parseable ID of a distribution, see
590    /// https://www.freedesktop.org/software/systemd/man/os-release.html#ID=
591    DistributionID,
592}
593
594#[cfg(not(target_os = "android"))]
595fn get_system_info_linux(info: InfoType, path: &Path, fallback_path: &Path) -> Option<String> {
596    if let Ok(buf) = File::open(path).and_then(|mut f| {
597        let mut buf = String::new();
598        f.read_to_string(&mut buf)?;
599        Ok(buf)
600    }) {
601        let info_str = match info {
602            InfoType::Name => "NAME=",
603            InfoType::OsVersion => "VERSION_ID=",
604            InfoType::DistributionID => "ID=",
605        };
606
607        for line in buf.lines() {
608            if let Some(stripped) = line.strip_prefix(info_str) {
609                return Some(stripped.replace('"', ""));
610            }
611        }
612    }
613
614    // Fallback to `/etc/lsb-release` file for systems where VERSION_ID is not included.
615    // VERSION_ID is not required in the `/etc/os-release` file
616    // per https://www.linux.org/docs/man5/os-release.html
617    // If this fails for some reason, fallback to None
618    let buf = File::open(fallback_path)
619        .and_then(|mut f| {
620            let mut buf = String::new();
621            f.read_to_string(&mut buf)?;
622            Ok(buf)
623        })
624        .ok()?;
625
626    let info_str = match info {
627        InfoType::OsVersion => "DISTRIB_RELEASE=",
628        InfoType::Name => "DISTRIB_ID=",
629        InfoType::DistributionID => {
630            // lsb-release is inconsistent with os-release and unsupported.
631            return None;
632        }
633    };
634    for line in buf.lines() {
635        if let Some(stripped) = line.strip_prefix(info_str) {
636            return Some(stripped.replace('"', ""));
637        }
638    }
639    None
640}
641
642#[cfg(target_os = "android")]
643fn get_system_info_android(info: InfoType) -> Option<String> {
644    // https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/core/java/android/os/Build.java#58
645    let name: &'static [u8] = match info {
646        InfoType::Name => b"ro.product.model\0",
647        InfoType::OsVersion => b"ro.build.version.release\0",
648        InfoType::DistributionID => {
649            // Not supported.
650            return None;
651        }
652    };
653
654    let mut value_buffer = vec![0u8; libc::PROP_VALUE_MAX as usize];
655    unsafe {
656        let len = libc::__system_property_get(
657            name.as_ptr() as *const c_char,
658            value_buffer.as_mut_ptr() as *mut c_char,
659        );
660
661        if len != 0 {
662            if let Some(pos) = value_buffer.iter().position(|c| *c == 0) {
663                value_buffer.resize(pos, 0);
664            }
665            String::from_utf8(value_buffer).ok()
666        } else {
667            None
668        }
669    }
670}
671
672#[cfg(test)]
673mod test {
674    #[cfg(target_os = "android")]
675    use super::get_system_info_android;
676    #[cfg(not(target_os = "android"))]
677    use super::get_system_info_linux;
678    use super::InfoType;
679
680    #[test]
681    #[cfg(target_os = "android")]
682    fn lsb_release_fallback_android() {
683        assert!(get_system_info_android(InfoType::OsVersion).is_some());
684        assert!(get_system_info_android(InfoType::Name).is_some());
685        assert!(get_system_info_android(InfoType::DistributionID).is_none());
686    }
687
688    #[test]
689    #[cfg(not(target_os = "android"))]
690    fn lsb_release_fallback_not_android() {
691        use std::path::Path;
692
693        let dir = tempfile::tempdir().expect("failed to create temporary directory");
694        let tmp1 = dir.path().join("tmp1");
695        let tmp2 = dir.path().join("tmp2");
696
697        // /etc/os-release
698        std::fs::write(
699            &tmp1,
700            r#"NAME="Ubuntu"
701VERSION="20.10 (Groovy Gorilla)"
702ID=ubuntu
703ID_LIKE=debian
704PRETTY_NAME="Ubuntu 20.10"
705VERSION_ID="20.10"
706VERSION_CODENAME=groovy
707UBUNTU_CODENAME=groovy
708"#,
709        )
710        .expect("Failed to create tmp1");
711
712        // /etc/lsb-release
713        std::fs::write(
714            &tmp2,
715            r#"DISTRIB_ID=Ubuntu
716DISTRIB_RELEASE=20.10
717DISTRIB_CODENAME=groovy
718DISTRIB_DESCRIPTION="Ubuntu 20.10"
719"#,
720        )
721        .expect("Failed to create tmp2");
722
723        // Check for the "normal" path: "/etc/os-release"
724        assert_eq!(
725            get_system_info_linux(InfoType::OsVersion, &tmp1, Path::new("")),
726            Some("20.10".to_owned())
727        );
728        assert_eq!(
729            get_system_info_linux(InfoType::Name, &tmp1, Path::new("")),
730            Some("Ubuntu".to_owned())
731        );
732        assert_eq!(
733            get_system_info_linux(InfoType::DistributionID, &tmp1, Path::new("")),
734            Some("ubuntu".to_owned())
735        );
736
737        // Check for the "fallback" path: "/etc/lsb-release"
738        assert_eq!(
739            get_system_info_linux(InfoType::OsVersion, Path::new(""), &tmp2),
740            Some("20.10".to_owned())
741        );
742        assert_eq!(
743            get_system_info_linux(InfoType::Name, Path::new(""), &tmp2),
744            Some("Ubuntu".to_owned())
745        );
746        assert_eq!(
747            get_system_info_linux(InfoType::DistributionID, Path::new(""), &tmp2),
748            None
749        );
750    }
751}