leenfetch_core/modules/linux/info/
os_age.rs1use std::fmt::Write;
2use std::fs;
3use std::time::{SystemTime, UNIX_EPOCH};
4
5use crate::modules::enums::OsAgeShorthand;
6
7pub fn get_os_age(shorthand: OsAgeShorthand) -> Option<String> {
10 let install_epoch = read_install_epoch_seconds()?;
11 let now = SystemTime::now().duration_since(UNIX_EPOCH).ok()?.as_secs();
12
13 let seconds = now.saturating_sub(install_epoch);
15
16 Some(format_age(seconds, shorthand))
17}
18
19fn read_install_epoch_seconds() -> Option<u64> {
24 if let Ok(md) = fs::metadata("/") {
26 if let Ok(created) = md.created() {
27 if let Ok(dur) = created.duration_since(UNIX_EPOCH) {
28 let secs = dur.as_secs();
29 if secs > 0 {
30 return Some(secs);
31 }
32 }
33 }
34 }
35
36 let root_cstr = std::ffi::CString::new("/").ok()?;
39 let mut stat_buf: libc::stat = unsafe { std::mem::zeroed() };
40
41 if unsafe { libc::stat(root_cstr.as_ptr(), &mut stat_buf) } == 0 {
42 #[cfg(any(target_os = "freebsd", target_os = "macos"))]
44 {
45 let birth = stat_buf.st_birthtime;
46 if birth > 0 {
47 return Some(birth as u64);
48 }
49 }
50
51 let ctime = stat_buf.st_ctime as u64;
54 if ctime > 0 {
55 let now = SystemTime::now().duration_since(UNIX_EPOCH).ok()?.as_secs();
57 if now > ctime && (now - ctime) > 86400 {
58 return Some(ctime);
59 }
60 }
61 }
62
63 None
64}
65
66fn format_age(seconds: u64, shorthand: OsAgeShorthand) -> String {
67 let days = seconds / 86_400;
68 let hours = (seconds / 3_600) % 24;
69 let minutes = (seconds / 60) % 60;
70
71 let mut buf = String::with_capacity(32);
72
73 match shorthand {
74 OsAgeShorthand::Full => {
75 if days > 0 {
76 let _ = write!(buf, "{} day{}, ", days, if days != 1 { "s" } else { "" });
77 }
78 if hours > 0 {
79 let _ = write!(buf, "{} hour{}, ", hours, if hours != 1 { "s" } else { "" });
80 }
81 if minutes > 0 {
82 let _ = write!(
83 buf,
84 "{} minute{}",
85 minutes,
86 if minutes != 1 { "s" } else { "" }
87 );
88 }
89 if buf.is_empty() {
90 let _ = write!(buf, "{} seconds", seconds);
91 }
92 }
93 OsAgeShorthand::Tiny => {
94 if days > 0 {
95 let _ = write!(buf, "{} days", days);
96 }
97 }
98 OsAgeShorthand::Seconds => {
99 let _ = write!(buf, "{}s", seconds);
100 }
101 }
102
103 buf.trim_end_matches([' ', ','].as_ref()).to_string()
104}
105
106#[cfg(test)]
107mod tests {
108 use super::*;
109
110 #[test]
111 fn formats_full_age() {
112 let result = format_age(86_400 * 2 + 3_600 + 120, OsAgeShorthand::Full);
113 assert!(
114 result.contains("2 days") && result.contains("1 hour") && result.contains("2 minutes"),
115 "unexpected format: {result}"
116 );
117 }
118
119 #[test]
120 fn formats_tiny_age() {
121 let result = format_age(86_400 * 5 + 300, OsAgeShorthand::Tiny);
122 assert_eq!(result, "5 days");
123 }
124
125 #[test]
126 fn formats_seconds_age() {
127 let result = format_age(42, OsAgeShorthand::Seconds);
128 assert_eq!(result, "42s");
129 }
130}