Skip to content

Commit 952befd

Browse files
committed
refactor(uucore): replace unsafe sysctl with safe command-line approach for macOS boot time
- Remove unsafe libc::sysctl() system call entirely - Replace with safe std::process::Command executing 'sysctl -n kern.boottime' - Parse sysctl output format to extract boot time seconds - Maintains same API and functionality while eliminating unsafe blocks - Addresses reviewer feedback to completely remove unsafe code
1 parent 256dbd3 commit 952befd

File tree

1 file changed

+56
-64
lines changed

1 file changed

+56
-64
lines changed

src/uucore/src/lib/features/uptime.rs

Lines changed: 56 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,48 @@ pub fn get_formatted_time() -> String {
4141
Local::now().time().format("%H:%M:%S").to_string()
4242
}
4343

44+
/// Safely get macOS boot time using sysctl command
45+
///
46+
/// This function uses the sysctl command-line tool to retrieve the kernel
47+
/// boot time on macOS, avoiding any unsafe code. It parses the output
48+
/// of the sysctl command to extract the boot time.
49+
///
50+
/// # Returns
51+
///
52+
/// Returns Some(time_t) if successful, None if the call fails.
53+
#[cfg(target_os = "macos")]
54+
fn get_macos_boot_time_sysctl() -> Option<time_t> {
55+
use std::process::Command;
56+
57+
// Execute sysctl command to get boot time
58+
let output = Command::new("sysctl")
59+
.arg("-n")
60+
.arg("kern.boottime")
61+
.output();
62+
63+
if let Ok(output) = output {
64+
if output.status.success() {
65+
// Parse output like: "Wed Oct 19 08:25:52 2025"
66+
// The actual format is: { sec = 1729338352, usec = 0 } Wed Oct 19 08:25:52 2025
67+
let stdout = String::from_utf8_lossy(&output.stdout);
68+
69+
// Extract the seconds from the output
70+
// Look for "sec = " pattern
71+
if let Some(sec_start) = stdout.find("sec = ") {
72+
let sec_part = &stdout[sec_start + 6..];
73+
if let Some(sec_end) = sec_part.find(',') {
74+
let sec_str = &sec_part[..sec_end];
75+
if let Ok(boot_time) = sec_str.trim().parse::<i64>() {
76+
return Some(boot_time as time_t);
77+
}
78+
}
79+
}
80+
}
81+
}
82+
83+
None
84+
}
85+
4486
/// Get the system uptime
4587
///
4688
/// # Arguments
@@ -135,38 +177,11 @@ pub fn get_uptime(boot_time: Option<time_t>) -> UResult<i64> {
135177
// This fallback only runs if utmpx failed to provide a boot time.
136178
#[cfg(target_os = "macos")]
137179
let derived_boot_time = {
138-
use libc::{c_int, c_void, size_t, sysctl, timeval};
139-
use std::mem::size_of;
140-
use std::ptr;
141-
142180
let mut t = derived_boot_time;
143181
if t.is_none() {
144-
// MIB for kern.boottime - the macOS-specific way to get boot time
145-
let mut mib: [c_int; 2] = [libc::CTL_KERN, libc::KERN_BOOTTIME];
146-
let mut tv: timeval = timeval {
147-
tv_sec: 0,
148-
tv_usec: 0,
149-
};
150-
let mut tv_len: size_t = size_of::<timeval>() as size_t;
151-
152-
// SAFETY: We're calling sysctl with valid parameters:
153-
// - mib is a valid 2-element array for kern.boottime
154-
// - tv is a properly initialized timeval struct
155-
// - tv_len correctly reflects the size of tv
156-
// - All pointers are valid and properly aligned
157-
// - We check the return value before using the result
158-
let ret = unsafe {
159-
sysctl(
160-
mib.as_mut_ptr(),
161-
2u32, // namelen
162-
std::ptr::from_mut::<timeval>(&mut tv) as *mut c_void,
163-
std::ptr::from_mut::<size_t>(&mut tv_len),
164-
ptr::null_mut(),
165-
0,
166-
)
167-
};
168-
if ret == 0 && tv.tv_sec > 0 {
169-
t = Some(tv.tv_sec as time_t);
182+
// Use a safe wrapper function to get boot time via sysctl
183+
if let Some(boot_time) = get_macos_boot_time_sysctl() {
184+
t = Some(boot_time);
170185
}
171186
}
172187
t
@@ -446,47 +461,24 @@ mod tests {
446461
#[test]
447462
#[cfg(target_os = "macos")]
448463
fn test_macos_sysctl_boottime_available() {
449-
use libc::{c_int, c_void, size_t, sysctl, timeval};
450-
use std::mem::size_of;
451-
use std::ptr;
452-
453-
// Attempt to get boot time directly via sysctl
454-
let mut mib: [c_int; 2] = [libc::CTL_KERN, libc::KERN_BOOTTIME];
455-
let mut tv: timeval = timeval {
456-
tv_sec: 0,
457-
tv_usec: 0,
458-
};
459-
let mut tv_len: size_t = size_of::<timeval>() as size_t;
460-
461-
// SAFETY: We're calling sysctl with valid parameters:
462-
// - mib is a valid 2-element array for kern.boottime
463-
// - tv is a properly initialized timeval struct
464-
// - tv_len correctly reflects the size of tv
465-
// - We check the return value before using the result
466-
let ret = unsafe {
467-
sysctl(
468-
mib.as_mut_ptr(),
469-
2u32,
470-
std::ptr::from_mut::<timeval>(&mut tv) as *mut c_void,
471-
std::ptr::from_mut::<size_t>(&mut tv_len),
472-
ptr::null_mut(),
473-
0,
474-
)
475-
};
476-
477-
// Verify sysctl succeeded
478-
assert_eq!(ret, 0, "sysctl kern.boottime should succeed on macOS");
479-
464+
// Test the safe wrapper function
465+
let boot_time = get_macos_boot_time_sysctl();
466+
467+
// Verify the safe wrapper succeeded
468+
assert!(boot_time.is_some(), "get_macos_boot_time_sysctl should succeed on macOS");
469+
470+
let boot_time = boot_time.unwrap();
471+
480472
// Verify boot time is valid (positive, reasonable value)
481-
assert!(tv.tv_sec > 0, "Boot time should be positive");
473+
assert!(boot_time > 0, "Boot time should be positive");
482474

483475
// Boot time should be after 2000-01-01 (946684800 seconds since epoch)
484-
assert!(tv.tv_sec > 946684800, "Boot time should be after year 2000");
476+
assert!(boot_time > 946684800, "Boot time should be after year 2000");
485477

486478
// Boot time should be before current time
487479
let now = chrono::Local::now().timestamp();
488480
assert!(
489-
(tv.tv_sec as i64) < now,
481+
(boot_time as i64) < now,
490482
"Boot time should be before current time"
491483
);
492484
}

0 commit comments

Comments
 (0)