From 90ba703506dcd5df19df20fa7cdb0fd5f27e0fd8 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Wed, 25 Mar 2026 15:55:24 +0300 Subject: [PATCH 1/9] colors: drop libc invocations for `var_os` Signed-off-by: NotAShelf Change-Id: I572acd49ef687c8049d43a7ba42bd12c6a6a6964 --- src/colors.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/colors.rs b/src/colors.rs index 7c65944..0fca89e 100644 --- a/src/colors.rs +++ b/src/colors.rs @@ -37,8 +37,8 @@ impl Colors { } pub static COLORS: LazyLock = LazyLock::new(|| { - const NO_COLOR: *const libc::c_char = c"NO_COLOR".as_ptr(); - let is_no_color = unsafe { !libc::getenv(NO_COLOR).is_null() }; + // Only presence matters; value is irrelevant per the NO_COLOR spec + let is_no_color = std::env::var_os("NO_COLOR").is_some(); Colors::new(is_no_color) }); From 72de00771f3e238bc04c72c84e4b808e41e518ce Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Wed, 25 Mar 2026 16:58:16 +0300 Subject: [PATCH 2/9] desktop: replace `libc::getenv` with `env::var_os` Signed-off-by: NotAShelf Change-Id: Ie94268934435b70bfbcd81e4676600206a6a6964 --- src/desktop.rs | 33 ++++++++++++++------------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/src/desktop.rs b/src/desktop.rs index 501e967..ea863b4 100644 --- a/src/desktop.rs +++ b/src/desktop.rs @@ -1,27 +1,22 @@ -use std::{ffi::CStr, fmt::Write}; +use std::{ffi::OsStr, fmt::Write}; #[must_use] #[cfg_attr(feature = "hotpath", hotpath::measure)] pub fn get_desktop_info() -> String { - // Retrieve the environment variables and handle Result types - let desktop_str = unsafe { - let ptr = libc::getenv(c"XDG_CURRENT_DESKTOP".as_ptr()); - if ptr.is_null() { - "Unknown" - } else { - let s = CStr::from_ptr(ptr).to_str().unwrap_or("Unknown"); - s.strip_prefix("none+").unwrap_or(s) - } - }; + let desktop_os = std::env::var_os("XDG_CURRENT_DESKTOP"); + let session_os = std::env::var_os("XDG_SESSION_TYPE"); - let backend_str = unsafe { - let ptr = libc::getenv(c"XDG_SESSION_TYPE".as_ptr()); - if ptr.is_null() { - "Unknown" - } else { - let s = CStr::from_ptr(ptr).to_str().unwrap_or("Unknown"); - if s.is_empty() { "Unknown" } else { s } - } + let desktop_raw = desktop_os + .as_deref() + .and_then(OsStr::to_str) + .unwrap_or("Unknown"); + let desktop_str = desktop_raw.strip_prefix("none+").unwrap_or(desktop_raw); + + let session_raw = session_os.as_deref().and_then(OsStr::to_str).unwrap_or(""); + let backend_str = if session_raw.is_empty() { + "Unknown" + } else { + session_raw }; // Pre-calculate capacity: desktop_len + " (" + backend_len + ")" From f6f1b3003a725c40216dd5ead395472bfca99ae7 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Wed, 25 Mar 2026 16:59:04 +0300 Subject: [PATCH 3/9] syscall: wrap Linux syscalls directly to replace libc Signed-off-by: NotAShelf Change-Id: I8bb8f3b2e3966cfa2324c0cf8eb447386a6a6964 --- src/syscall.rs | 200 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 200 insertions(+) diff --git a/src/syscall.rs b/src/syscall.rs index 0c8634b..cedc68a 100644 --- a/src/syscall.rs +++ b/src/syscall.rs @@ -123,6 +123,63 @@ pub unsafe fn sys_read(fd: i32, buf: *mut u8, count: usize) -> isize { } } +/// Direct syscall to write to a file descriptor +/// +/// # Returns +/// +/// Number of bytes written or -1 on error +/// +/// # Safety +/// +/// The caller must ensure that: +/// +/// - `buf` points to a valid readable buffer of at least `count` bytes +/// - `fd` is a valid open file descriptor +#[inline] +#[must_use] +pub unsafe fn sys_write(fd: i32, buf: *const u8, count: usize) -> isize { + #[cfg(target_arch = "x86_64")] + unsafe { + let ret: i64; + std::arch::asm!( + "syscall", + in("rax") 1i64, // SYS_write + in("rdi") fd, + in("rsi") buf, + in("rdx") count, + lateout("rax") ret, + lateout("rcx") _, + lateout("r11") _, + options(nostack) + ); + #[allow(clippy::cast_possible_truncation)] + { + ret as isize + } + } + #[cfg(target_arch = "aarch64")] + unsafe { + let ret: i64; + std::arch::asm!( + "svc #0", + in("x8") 64i64, // SYS_write + in("x0") fd, + in("x1") buf, + in("x2") count, + lateout("x0") ret, + options(nostack) + ); + #[allow(clippy::cast_possible_truncation)] + { + ret as isize + } + } + #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))] + { + compile_error!("Unsupported architecture for inline assembly syscalls"); + } +} + /// Direct syscall to close a file descriptor /// /// # Safety @@ -169,6 +226,149 @@ pub unsafe fn sys_close(fd: i32) -> i32 { } } +/// Raw buffer for the `uname(2)` syscall. +/// +/// Linux ABI hasfive fields of `[i8; 65]`: sysname, nodename, release, version, +/// machine. The `domainname` field (GNU extension, `[i8; 65]`) follows but is +/// not used, nor any useful to us here. +#[repr(C)] +#[allow(dead_code)] +pub struct UtsNameBuf { + pub sysname: [i8; 65], + pub nodename: [i8; 65], + pub release: [i8; 65], + pub version: [i8; 65], + pub machine: [i8; 65], + pub domainname: [i8; 65], // GNU extension, included for correct struct size +} + +/// Direct `uname(2)` syscall +/// +/// # Returns +/// +/// 0 on success, negative on error +/// +/// # Safety +/// +/// The caller must ensure `buf` points to a valid `UtsNameBuf`. +#[inline] +#[allow(dead_code)] +pub unsafe fn sys_uname(buf: *mut UtsNameBuf) -> i32 { + #[cfg(target_arch = "x86_64")] + unsafe { + let ret: i64; + std::arch::asm!( + "syscall", + in("rax") 63i64, // SYS_uname + in("rdi") buf, + lateout("rax") ret, + lateout("rcx") _, + lateout("r11") _, + options(nostack) + ); + #[allow(clippy::cast_possible_truncation)] + { + ret as i32 + } + } + #[cfg(target_arch = "aarch64")] + unsafe { + let ret: i64; + std::arch::asm!( + "svc #0", + in("x8") 160i64, // SYS_uname + in("x0") buf, + lateout("x0") ret, + options(nostack) + ); + #[allow(clippy::cast_possible_truncation)] + { + ret as i32 + } + } + #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))] + { + compile_error!("Unsupported architecture for inline assembly syscalls"); + } +} + +/// Raw buffer for the `statfs(2)` syscall. +/// +/// Linux ABI (`x86_64` and `aarch64`): the fields we use are at the same +/// offsets on both architectures. Only the fields needed for disk usage are +/// declared; the remainder of the 120-byte struct is covered by `_pad`. +#[repr(C)] +pub struct StatfsBuf { + pub f_type: i64, + pub f_bsize: i64, + pub f_blocks: u64, + pub f_bfree: u64, + pub f_bavail: u64, + pub f_files: u64, + pub f_ffree: u64, + pub f_fsid: [i32; 2], + pub f_namelen: i64, + pub f_frsize: i64, + pub f_flags: i64, + + #[allow(clippy::pub_underscore_fields, reason = "This is not a public API")] + pub _pad: [i64; 4], +} + +/// Direct `statfs(2)` syscall +/// +/// # Returns +/// +/// 0 on success, negative errno on error +/// +/// # Safety +/// +/// The caller must ensure that: +/// +/// - `path` points to a valid null-terminated string +/// - `buf` points to a valid `StatfsBuf` +#[inline] +pub unsafe fn sys_statfs(path: *const u8, buf: *mut StatfsBuf) -> i32 { + #[cfg(target_arch = "x86_64")] + unsafe { + let ret: i64; + std::arch::asm!( + "syscall", + in("rax") 137i64, // SYS_statfs + in("rdi") path, + in("rsi") buf, + lateout("rax") ret, + lateout("rcx") _, + lateout("r11") _, + options(nostack) + ); + #[allow(clippy::cast_possible_truncation)] + { + ret as i32 + } + } + #[cfg(target_arch = "aarch64")] + unsafe { + let ret: i64; + std::arch::asm!( + "svc #0", + in("x8") 43i64, // SYS_statfs + in("x0") path, + in("x1") buf, + lateout("x0") ret, + options(nostack) + ); + #[allow(clippy::cast_possible_truncation)] + { + ret as i32 + } + } + #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))] + { + compile_error!("Unsupported architecture for inline assembly syscalls"); + } +} + /// Read entire file using direct syscalls. This avoids libc overhead and can be /// significantly faster for small files. /// From 99f7be5aac84c4982d807211f821867abfd8af01 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Wed, 25 Mar 2026 17:02:26 +0300 Subject: [PATCH 4/9] microfetch-lib: use home-made syscall wrappers over `libc::uname` Signed-off-by: NotAShelf Change-Id: I162c771dce2714fa73db3e2e5dc8dcb36a6a6964 --- src/lib.rs | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 1e0f9f3..b989c97 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,42 +5,44 @@ pub mod syscall; pub mod system; pub mod uptime; -use std::mem::MaybeUninit; +use std::{ffi::CStr, mem::MaybeUninit}; -/// Wrapper for `libc::utsname` with safe accessor methods -pub struct UtsName(libc::utsname); +use crate::syscall::{UtsNameBuf, sys_uname}; + +/// Wrapper for `utsname` with safe accessor methods +pub struct UtsName(UtsNameBuf); impl UtsName { - /// Calls `uname` syscall and returns a `UtsName` wrapper + /// Calls `uname(2)` syscall and returns a `UtsName` wrapper /// /// # Errors /// /// Returns an error if the `uname` syscall fails pub fn uname() -> Result { let mut uts = MaybeUninit::uninit(); - if unsafe { libc::uname(uts.as_mut_ptr()) } != 0 { + if unsafe { sys_uname(uts.as_mut_ptr()) } != 0 { return Err(std::io::Error::last_os_error()); } Ok(Self(unsafe { uts.assume_init() })) } #[must_use] - pub const fn nodename(&self) -> &std::ffi::CStr { - unsafe { std::ffi::CStr::from_ptr(self.0.nodename.as_ptr()) } + pub const fn nodename(&self) -> &CStr { + unsafe { CStr::from_ptr(self.0.nodename.as_ptr()) } } #[must_use] - pub const fn sysname(&self) -> &std::ffi::CStr { - unsafe { std::ffi::CStr::from_ptr(self.0.sysname.as_ptr()) } + pub const fn sysname(&self) -> &CStr { + unsafe { CStr::from_ptr(self.0.sysname.as_ptr()) } } #[must_use] - pub const fn release(&self) -> &std::ffi::CStr { - unsafe { std::ffi::CStr::from_ptr(self.0.release.as_ptr()) } + pub const fn release(&self) -> &CStr { + unsafe { CStr::from_ptr(self.0.release.as_ptr()) } } #[must_use] - pub const fn machine(&self) -> &std::ffi::CStr { - unsafe { std::ffi::CStr::from_ptr(self.0.machine.as_ptr()) } + pub const fn machine(&self) -> &CStr { + unsafe { CStr::from_ptr(self.0.machine.as_ptr()) } } } From 17cd7530d61646e881b2523f5111ef422fa971f7 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Wed, 25 Mar 2026 17:03:09 +0300 Subject: [PATCH 5/9] uptime: construct a raw buffer for the sysinfo syscall; drop `libc::sysinfo` Signed-off-by: NotAShelf Change-Id: I63887d9f73e6fed670e497086d30d2a46a6a6964 --- src/uptime.rs | 35 +++++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/src/uptime.rs b/src/uptime.rs index 095af7d..c6c4b26 100644 --- a/src/uptime.rs +++ b/src/uptime.rs @@ -17,14 +17,41 @@ fn itoa(mut n: u64, buf: &mut [u8]) -> &str { unsafe { std::str::from_utf8_unchecked(&buf[i..]) } } -/// Direct `sysinfo` syscall using inline assembly +/// Raw buffer for the `sysinfo(2)` syscall. +/// +/// In the Linux ABI `uptime` is a `long` at offset 0. The remaining fields are +/// not needed, but are declared to give the struct its correct size (112 bytes +/// on 64-bit Linux). +/// +/// The layout matches the kernel's `struct sysinfo` *exactly*: +/// `mem_unit` ends at offset 108, then 4 bytes of implicit padding to 112. +#[repr(C)] +struct SysInfo { + uptime: i64, + loads: [u64; 3], + totalram: u64, + freeram: u64, + sharedram: u64, + bufferram: u64, + totalswap: u64, + freeswap: u64, + procs: u16, + _pad: u16, + _pad2: u32, // alignment padding to reach 8-byte boundary for totalhigh + totalhigh: u64, + freehigh: u64, + mem_unit: u32, + // 4 bytes implicit trailing padding to reach 112 bytes total; no field + // needed +} + +/// Direct `sysinfo(2)` syscall using inline assembly /// /// # Safety /// -/// This function uses inline assembly to make a direct syscall. /// The caller must ensure the sysinfo pointer is valid. #[inline] -unsafe fn sys_sysinfo(info: *mut libc::sysinfo) -> i64 { +unsafe fn sys_sysinfo(info: *mut SysInfo) -> i64 { #[cfg(target_arch = "x86_64")] { let ret: i64; @@ -59,7 +86,7 @@ unsafe fn sys_sysinfo(info: *mut libc::sysinfo) -> i64 { #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))] { - unsafe { libc::sysinfo(info) as i64 } + compile_error!("Unsupported architecture for inline assembly syscalls"); } } From 99e033c41530ea1793bfdd4081ddeebd9fc92e82 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Wed, 25 Mar 2026 17:05:09 +0300 Subject: [PATCH 6/9] system: use `std::env::var_os` over `libc` helpers Signed-off-by: NotAShelf Change-Id: I2747d4b065095314a1d454a204a251c96a6a6964 --- src/main.rs | 7 +++++-- src/system.rs | 45 ++++++++++++++++++++++----------------------- 2 files changed, 27 insertions(+), 25 deletions(-) diff --git a/src/main.rs b/src/main.rs index caa58ec..1ee5d7c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -101,12 +101,15 @@ fn print_system_info( let len = cursor.position() as usize; // Direct syscall to avoid stdout buffering allocation - let written = unsafe { libc::write(libc::STDOUT_FILENO, buf.as_ptr().cast(), len) }; + let written = unsafe { syscall::sys_write(1, buf.as_ptr(), len) }; if written < 0 { return Err(io::Error::last_os_error().into()); } if written as usize != len { - return Err(io::Error::new(io::ErrorKind::WriteZero, "partial write to stdout").into()); + return Err( + io::Error::new(io::ErrorKind::WriteZero, "partial write to stdout") + .into(), + ); } Ok(()) } diff --git a/src/system.rs b/src/system.rs index ba8fe79..e75daa8 100644 --- a/src/system.rs +++ b/src/system.rs @@ -1,18 +1,19 @@ -use std::{ffi::CStr, fmt::Write as _, io, mem::MaybeUninit}; +use std::{ffi::OsStr, fmt::Write as _, io, mem::MaybeUninit}; -use crate::{UtsName, colors::COLORS, syscall::read_file_fast}; +use crate::{ + UtsName, + colors::COLORS, + syscall::{StatfsBuf, read_file_fast, sys_statfs}, +}; #[must_use] #[cfg_attr(feature = "hotpath", hotpath::measure)] pub fn get_username_and_hostname(utsname: &UtsName) -> String { - let username = unsafe { - let ptr = libc::getenv(c"USER".as_ptr()); - if ptr.is_null() { - "unknown_user" - } else { - CStr::from_ptr(ptr).to_str().unwrap_or("unknown_user") - } - }; + let username_os = std::env::var_os("USER"); + let username = username_os + .as_deref() + .and_then(OsStr::to_str) + .unwrap_or("unknown_user"); let hostname = utsname.nodename().to_str().unwrap_or("unknown_host"); let capacity = COLORS.yellow.len() @@ -38,16 +39,13 @@ pub fn get_username_and_hostname(utsname: &UtsName) -> String { #[must_use] #[cfg_attr(feature = "hotpath", hotpath::measure)] pub fn get_shell() -> String { - unsafe { - let ptr = libc::getenv(c"SHELL".as_ptr()); - if ptr.is_null() { - return "unknown_shell".into(); - } - - let bytes = CStr::from_ptr(ptr).to_bytes(); - let start = bytes.iter().rposition(|&b| b == b'/').map_or(0, |i| i + 1); - let name = std::str::from_utf8_unchecked(&bytes[start..]); - name.into() + let shell_os = std::env::var_os("SHELL"); + let shell = shell_os.as_deref().and_then(OsStr::to_str).unwrap_or(""); + let start = shell.rfind('/').map_or(0, |i| i + 1); + if shell.is_empty() { + "unknown_shell".into() + } else { + shell[start..].into() } } @@ -59,15 +57,16 @@ pub fn get_shell() -> String { #[cfg_attr(feature = "hotpath", hotpath::measure)] #[allow(clippy::cast_precision_loss)] pub fn get_root_disk_usage() -> Result { - let mut vfs = MaybeUninit::uninit(); + let mut vfs = MaybeUninit::::uninit(); let path = b"/\0"; - if unsafe { libc::statvfs(path.as_ptr().cast(), vfs.as_mut_ptr()) } != 0 { + if unsafe { sys_statfs(path.as_ptr(), vfs.as_mut_ptr()) } != 0 { return Err(io::Error::last_os_error()); } let vfs = unsafe { vfs.assume_init() }; - let block_size = vfs.f_bsize; + #[allow(clippy::cast_sign_loss)] + let block_size = vfs.f_bsize as u64; let total_blocks = vfs.f_blocks; let available_blocks = vfs.f_bavail; From a798c5d3e88a42267c3206a79c13a72d08f2677a Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Wed, 25 Mar 2026 17:06:51 +0300 Subject: [PATCH 7/9] chore: drop libc from crate dependencies Signed-off-by: NotAShelf Change-Id: I1a4110c5b79f7cbb3a7d964e3a5a80c66a6a6964 --- Cargo.lock | 1 - Cargo.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4deada2..48a6768 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -571,7 +571,6 @@ version = "0.4.13" dependencies = [ "criterion", "hotpath", - "libc", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 6b1ae09..5db56fc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,6 @@ path = "src/main.rs" [dependencies] hotpath = { optional = true, version = "0.13.0" } -libc = "0.2.183" [dev-dependencies] criterion = "0.8.1" From 0c294d348bbd649e9bfe791a0f6a7855ecfef6bf Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Wed, 25 Mar 2026 17:08:06 +0300 Subject: [PATCH 8/9] various: fix clippy lints Signed-off-by: NotAShelf Change-Id: Icbd2ba104c66907208cb37adf0c3915a6a6a6964 --- src/main.rs | 4 +++- src/system.rs | 8 ++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/main.rs b/src/main.rs index 1ee5d7c..ebc1e83 100644 --- a/src/main.rs +++ b/src/main.rs @@ -99,12 +99,14 @@ fn print_system_info( {blue} ▟█▘ ▜█▖ {cyan}▝█▛ {cyan} {blue}Colors{reset}  {colors}\n\n" )?; - let len = cursor.position() as usize; + let len = + usize::try_from(cursor.position()).expect("cursor position fits usize"); // Direct syscall to avoid stdout buffering allocation let written = unsafe { syscall::sys_write(1, buf.as_ptr(), len) }; if written < 0 { return Err(io::Error::last_os_error().into()); } + #[allow(clippy::cast_sign_loss)] // non-negative verified by the guard above if written as usize != len { return Err( io::Error::new(io::ErrorKind::WriteZero, "partial write to stdout") diff --git a/src/system.rs b/src/system.rs index e75daa8..9dd1ab3 100644 --- a/src/system.rs +++ b/src/system.rs @@ -157,12 +157,12 @@ pub fn get_memory_usage() -> Result { } #[allow(clippy::cast_precision_loss)] - let total_memory_gb = total_memory_kb as f64 / 1024.0 / 1024.0; + let total_gb = total_memory_kb as f64 / 1024.0 / 1024.0; #[allow(clippy::cast_precision_loss)] - let available_memory_gb = available_memory_kb as f64 / 1024.0 / 1024.0; - let used_memory_gb = total_memory_gb - available_memory_gb; + let available_gb = available_memory_kb as f64 / 1024.0 / 1024.0; + let used_memory_gb = total_gb - available_gb; - Ok((used_memory_gb, total_memory_gb)) + Ok((used_memory_gb, total_gb)) } let (used_memory, total_memory) = parse_memory_info()?; From 1dd02def4bbb10f495e94ee626ab7ee6f608ad60 Mon Sep 17 00:00:00 2001 From: Amaan Qureshi Date: Thu, 26 Mar 2026 15:43:40 -0400 Subject: [PATCH 9/9] lib: fix aarch64 build by casting `UtsNameBuf` field pointers to `c_char` (#55) --- src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index b989c97..ab21d03 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,21 +28,21 @@ impl UtsName { #[must_use] pub const fn nodename(&self) -> &CStr { - unsafe { CStr::from_ptr(self.0.nodename.as_ptr()) } + unsafe { CStr::from_ptr(self.0.nodename.as_ptr().cast()) } } #[must_use] pub const fn sysname(&self) -> &CStr { - unsafe { CStr::from_ptr(self.0.sysname.as_ptr()) } + unsafe { CStr::from_ptr(self.0.sysname.as_ptr().cast()) } } #[must_use] pub const fn release(&self) -> &CStr { - unsafe { CStr::from_ptr(self.0.release.as_ptr()) } + unsafe { CStr::from_ptr(self.0.release.as_ptr().cast()) } } #[must_use] pub const fn machine(&self) -> &CStr { - unsafe { CStr::from_ptr(self.0.machine.as_ptr()) } + unsafe { CStr::from_ptr(self.0.machine.as_ptr().cast()) } } }