treewide: going no_std

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: Ia1c001eb099ea8cae9bdf76642b873376a6a6964
This commit is contained in:
raf 2026-03-27 17:29:49 +03:00
commit 472dbfc7e7
Signed by: NotAShelf
GPG key ID: 29D95B64378DB4BF
14 changed files with 856 additions and 143 deletions

View file

@ -1,3 +1,3 @@
# https://github.com/rui314/mold?tab=readme-ov-file#how-to-use
[target.'cfg(target_os = "linux")']
rustflags = [ "-C", "link-arg=-fuse-ld=mold" ]
rustflags = [ "-C", "link-arg=-fuse-ld=mold", "-C", "link-arg=-lc", "-C", "link-arg=-lgcc_s" ]

6
Cargo.lock generated
View file

@ -582,9 +582,15 @@ name = "microfetch"
version = "0.4.13"
dependencies = [
"hotpath",
"microfetch-alloc",
"microfetch-asm",
"microfetch-lib",
]
[[package]]
name = "microfetch-alloc"
version = "0.4.13"
[[package]]
name = "microfetch-asm"
version = "0.4.13"

View file

@ -10,6 +10,7 @@ rust-version = "1.92.0"
version = "0.4.13"
[workspace.dependencies]
microfetch-alloc = { path = "./crates/alloc" }
microfetch-asm = { path = "./crates/asm" }
microfetch-lib = { path = "./crates/lib" }
@ -18,6 +19,7 @@ criterion-cycles-per-byte = "0.8.0"
[profile.dev]
opt-level = 1
panic = "abort"
[profile.release]
codegen-units = 1

12
crates/alloc/Cargo.toml Normal file
View file

@ -0,0 +1,12 @@
[package]
name = "microfetch-alloc"
description = "Simple, std-free bump allocator for Microfetch"
version.workspace = true
edition.workspace = true
authors.workspace = true
rust-version.workspace = true
license.workspace = true
publish = false
[lints]
workspace = true

106
crates/alloc/src/lib.rs Normal file
View file

@ -0,0 +1,106 @@
//! Simple bump allocator for `no_std` environments. Uses a statically allocated
//! 32KB buffer and provides O(1) allocation with no deallocation support
//! (memory is never freed).
#![no_std]
use core::{
alloc::{GlobalAlloc, Layout},
cell::UnsafeCell,
ptr::null_mut,
};
/// Default heap size is 32KB, should be plenty for Microfetch. Technically it
/// can be invoked with more (or less) depending on our needs but I am quite
/// sure 32KB is more than enough.
pub const DEFAULT_HEAP_SIZE: usize = 32 * 1024;
/// A simple bump allocator that never frees memory.
///
/// This allocator maintains a static buffer and a bump pointer. Allocations are
/// fast (just bump the pointer), but memory is never reclaimed. While you might
/// be inclined to point out that this is ugly, it's suitable for a short-lived
/// program with bounded memory usage.
pub struct BumpAllocator<const N: usize = DEFAULT_HEAP_SIZE> {
heap: UnsafeCell<[u8; N]>,
next: UnsafeCell<usize>,
}
// SAFETY: BumpAllocator is thread-safe because it uses UnsafeCell
// and the allocator is only used in single-threaded contexts (i.e., no_std).
unsafe impl<const N: usize> Sync for BumpAllocator<N> {}
impl<const N: usize> BumpAllocator<N> {
/// Creates a new bump allocator with the specified heap size.
#[must_use]
pub const fn new() -> Self {
Self {
heap: UnsafeCell::new([0; N]),
next: UnsafeCell::new(0),
}
}
/// Returns the number of bytes currently allocated.
#[must_use]
pub fn used(&self) -> usize {
// SAFETY: We're just reading the value, and this is only called
// in single-threaded contexts.
unsafe { *self.next.get() }
}
/// Returns the total heap size.
#[must_use]
pub const fn capacity(&self) -> usize {
N
}
/// Returns the number of bytes remaining.
#[must_use]
pub fn remaining(&self) -> usize {
N - self.used()
}
}
impl<const N: usize> Default for BumpAllocator<N> {
fn default() -> Self {
Self::new()
}
}
unsafe impl<const N: usize> GlobalAlloc for BumpAllocator<N> {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
unsafe {
let next = self.next.get();
let heap = self.heap.get();
// Align the current position
let align = layout.align();
let start = (*next + align - 1) & !(align - 1);
let end = start + layout.size();
if end > N {
// Out of memory
null_mut()
} else {
*next = end;
(*heap).as_mut_ptr().add(start)
}
}
}
unsafe fn dealloc(&self, _ptr: *mut u8, _layout: Layout) {
// Bump allocator doesn't support deallocation
// Memory is reclaimed when the program exits
}
}
/// Static bump allocator instance with 32KB heap.
///
/// # Example
///
/// Use this with `#[global_allocator]` in your binary:
///
///
/// ```rust,ignore
/// #[global_allocator]
/// static ALLOCATOR: BumpAllocator = BumpAllocator::new();
/// ```
pub type BumpAlloc = BumpAllocator<DEFAULT_HEAP_SIZE>;

View file

@ -507,6 +507,7 @@ pub fn read_file_fast(path: &str, buffer: &mut [u8]) -> Result<usize, i32> {
let _ = sys_close(fd);
if bytes_read < 0 {
#[allow(clippy::cast_possible_truncation)]
return Err(bytes_read as i32);
}
@ -598,3 +599,41 @@ pub unsafe fn sys_sysinfo(info: *mut SysInfo) -> i64 {
ret
}
}
/// Direct syscall to exit the process
///
/// # Safety
///
/// This syscall never returns. The process will terminate immediately.
#[inline]
pub unsafe fn sys_exit(code: i32) -> ! {
#[cfg(target_arch = "x86_64")]
unsafe {
core::arch::asm!(
"syscall",
in("rax") 60i64, // SYS_exit
in("rdi") code,
options(noreturn, nostack)
);
}
#[cfg(target_arch = "aarch64")]
unsafe {
core::arch::asm!(
"svc #0",
in("x8") 93i64, // SYS_exit
in("x0") code,
options(noreturn, nostack)
);
}
#[cfg(target_arch = "riscv64")]
unsafe {
core::arch::asm!(
"ecall",
in("a7") 93i64, // SYS_exit
in("a0") code,
options(noreturn, nostack)
);
}
}

View file

@ -1,5 +1,6 @@
use std::sync::LazyLock;
use alloc::string::String;
/// Color codes for terminal output
pub struct Colors {
pub reset: &'static str,
pub blue: &'static str,
@ -11,7 +12,8 @@ pub struct Colors {
}
impl Colors {
const fn new(is_no_color: bool) -> Self {
#[must_use]
pub const fn new(is_no_color: bool) -> Self {
if is_no_color {
Self {
reset: "",
@ -36,46 +38,68 @@ impl Colors {
}
}
pub static COLORS: LazyLock<Colors> = LazyLock::new(|| {
// 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)
});
use core::sync::atomic::{AtomicBool, Ordering};
// Check if NO_COLOR is set (only once, lazily)
// Only presence matters; value is irrelevant per the NO_COLOR spec
static NO_COLOR_CHECKED: AtomicBool = AtomicBool::new(false);
static NO_COLOR_SET: AtomicBool = AtomicBool::new(false);
/// Checks if `NO_COLOR` environment variable is set.
pub(crate) fn is_no_color() -> bool {
// Fast path: already checked
if NO_COLOR_CHECKED.load(Ordering::Acquire) {
return NO_COLOR_SET.load(Ordering::Relaxed);
}
// Slow path: check environment
let is_set = crate::env_exists("NO_COLOR");
NO_COLOR_SET.store(is_set, Ordering::Relaxed);
NO_COLOR_CHECKED.store(true, Ordering::Release);
is_set
}
#[must_use]
#[cfg_attr(feature = "hotpath", hotpath::measure)]
pub fn print_dots() -> String {
// Pre-calculate capacity: 6 color codes + " " (glyph + 2 spaces) per color
const GLYPH: &str = "";
let capacity = COLORS.blue.len()
+ COLORS.cyan.len()
+ COLORS.green.len()
+ COLORS.yellow.len()
+ COLORS.red.len()
+ COLORS.magenta.len()
+ COLORS.reset.len()
let colors = if is_no_color() {
Colors::new(true)
} else {
Colors::new(false)
};
// Pre-calculate capacity: 6 color codes + " " (glyph + 2 spaces) per color
let capacity = colors.blue.len()
+ colors.cyan.len()
+ colors.green.len()
+ colors.yellow.len()
+ colors.red.len()
+ colors.magenta.len()
+ colors.reset.len()
+ (GLYPH.len() + 2) * 6;
let mut result = String::with_capacity(capacity);
result.push_str(COLORS.blue);
result.push_str(colors.blue);
result.push_str(GLYPH);
result.push_str(" ");
result.push_str(COLORS.cyan);
result.push_str(colors.cyan);
result.push_str(GLYPH);
result.push_str(" ");
result.push_str(COLORS.green);
result.push_str(colors.green);
result.push_str(GLYPH);
result.push_str(" ");
result.push_str(COLORS.yellow);
result.push_str(colors.yellow);
result.push_str(GLYPH);
result.push_str(" ");
result.push_str(COLORS.red);
result.push_str(colors.red);
result.push_str(GLYPH);
result.push_str(" ");
result.push_str(COLORS.magenta);
result.push_str(colors.magenta);
result.push_str(GLYPH);
result.push_str(" ");
result.push_str(COLORS.reset);
result.push_str(colors.reset);
result
}

View file

@ -1,18 +1,15 @@
use std::{ffi::OsStr, fmt::Write};
use alloc::string::String;
use crate::getenv_str;
#[must_use]
#[cfg_attr(feature = "hotpath", hotpath::measure)]
pub fn get_desktop_info() -> String {
let desktop_os = std::env::var_os("XDG_CURRENT_DESKTOP");
let session_os = std::env::var_os("XDG_SESSION_TYPE");
let desktop_raw = getenv_str("XDG_CURRENT_DESKTOP").unwrap_or("Unknown");
let session_raw = getenv_str("XDG_SESSION_TYPE").unwrap_or("");
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 {
@ -27,9 +24,15 @@ pub fn get_desktop_info() -> String {
result.push_str(" (");
// Capitalize first character of backend
if let Some(first_char) = backend_str.chars().next() {
let _ = write!(result, "{}", first_char.to_ascii_uppercase());
result.push_str(&backend_str[first_char.len_utf8()..]);
if let Some(first_byte) = backend_str.as_bytes().first() {
// Convert first byte to uppercase if it's ASCII lowercase
let upper = if first_byte.is_ascii_lowercase() {
(first_byte - b'a' + b'A') as char
} else {
*first_byte as char
};
result.push(upper);
result.push_str(&backend_str[1..]);
}
result.push(')');

View file

@ -1,13 +1,17 @@
#![no_std]
extern crate alloc;
pub mod colors;
pub mod desktop;
pub mod release;
pub mod system;
pub mod uptime;
use std::{
use alloc::string::String;
use core::{
ffi::CStr,
io::{self, Cursor, Write},
mem::MaybeUninit,
sync::atomic::{AtomicPtr, Ordering},
};
pub use microfetch_asm as syscall;
@ -25,6 +29,200 @@ pub use microfetch_asm::{
sys_write,
};
/// A simple error type for microfetch operations.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Error {
/// An OS error occurred, containing the errno value.
OsError(i32),
/// Invalid data or encoding error.
InvalidData,
/// Not found.
NotFound,
/// Write operation failed or partial write.
WriteError,
}
impl Error {
/// Creates an error from the last OS error (reads errno).
#[inline]
#[must_use]
pub const fn last_os_error() -> Self {
// This is a simplified version - in a real implementation,
// we'd need to get the actual errno from the syscall return
Self::OsError(0)
}
/// Creates an error from a raw OS error code (negative errno from syscall).
#[inline]
#[must_use]
pub const fn from_raw_os_error(errno: i32) -> Self {
Self::OsError(-errno)
}
}
impl core::fmt::Display for Error {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::OsError(errno) => write!(f, "OS error: {errno}"),
Self::InvalidData => write!(f, "Invalid data"),
Self::NotFound => write!(f, "Not found"),
Self::WriteError => write!(f, "Write error"),
}
}
}
// Simple OnceLock implementation for no_std
pub struct OnceLock<T> {
ptr: AtomicPtr<T>,
}
impl<T> Default for OnceLock<T> {
fn default() -> Self {
Self::new()
}
}
impl<T> OnceLock<T> {
#[must_use]
pub const fn new() -> Self {
Self {
ptr: AtomicPtr::new(core::ptr::null_mut()),
}
}
pub fn get_or_init<F>(&self, f: F) -> &T
where
F: FnOnce() -> T,
{
// Load the current pointer
let mut ptr = self.ptr.load(Ordering::Acquire);
if ptr.is_null() {
// Need to initialize
let value = f();
let boxed = alloc::boxed::Box::new(value);
let new_ptr = alloc::boxed::Box::into_raw(boxed);
// Try to set the pointer
match self.ptr.compare_exchange(
core::ptr::null_mut(),
new_ptr,
Ordering::Release,
Ordering::Acquire,
) {
Ok(_) => {
// We successfully set it
ptr = new_ptr;
},
Err(existing) => {
// Someone else set it first, free our allocation
// SAFETY: We just allocated this and no one else has seen it
unsafe {
let _ = alloc::boxed::Box::from_raw(new_ptr);
}
ptr = existing;
},
}
}
// SAFETY: We know ptr is non-null and points to a valid T
unsafe { &*ptr }
}
}
impl<T> Drop for OnceLock<T> {
fn drop(&mut self) {
let ptr = self.ptr.load(Ordering::Acquire);
if !ptr.is_null() {
// SAFETY: We know this was allocated via Box::into_raw
unsafe {
let _ = alloc::boxed::Box::from_raw(ptr);
}
}
}
}
// Access to the environ pointer (provided by libc startup code)
unsafe extern "C" {
static environ: *const *const u8;
}
/// Gets an environment variable by name (without using std).
///
/// # Safety
///
/// This function reads from the environ global which is initialized
/// by the C runtime before `main()` is called.
#[must_use]
pub fn getenv(name: &str) -> Option<&'static [u8]> {
// SAFETY: environ is set up by the C runtime before main() runs
// and remains valid for the lifetime of the program
let envp = unsafe { environ };
if envp.is_null() {
return None;
}
let name_bytes = name.as_bytes();
// Walk through environment variables
let mut i = 0;
loop {
// SAFETY: environ is null-terminated array of pointers
let entry = unsafe { *envp.add(i) };
if entry.is_null() {
break;
}
// Check if this entry starts with our variable name followed by '='
let mut matches = true;
for (j, &b) in name_bytes.iter().enumerate() {
// SAFETY: entry is a valid C string
let entry_byte = unsafe { *entry.add(j) };
if entry_byte != b {
matches = false;
break;
}
}
if matches {
// Check for '=' after the name
// SAFETY: entry is a valid C string
let eq_byte = unsafe { *entry.add(name_bytes.len()) };
if eq_byte == b'=' {
// Found it! Calculate the value length
let value_start = unsafe { entry.add(name_bytes.len() + 1) };
let mut len = 0;
loop {
// SAFETY: entry is a valid C string
let b = unsafe { *value_start.add(len) };
if b == 0 {
break;
}
len += 1;
}
// SAFETY: We calculated the exact length
return Some(unsafe { core::slice::from_raw_parts(value_start, len) });
}
}
i += 1;
}
None
}
/// Gets an environment variable as a UTF-8 string.
#[must_use]
pub fn getenv_str(name: &str) -> Option<&'static str> {
getenv(name).and_then(|bytes| core::str::from_utf8(bytes).ok())
}
/// Checks if an environment variable exists (regardless of its value).
#[must_use]
pub fn env_exists(name: &str) -> bool {
getenv(name).is_some()
}
/// Wrapper for `utsname` with safe accessor methods
pub struct UtsName(UtsNameBuf);
@ -34,10 +232,10 @@ impl UtsName {
/// # Errors
///
/// Returns an error if the `uname` syscall fails
pub fn uname() -> Result<Self, std::io::Error> {
pub fn uname() -> Result<Self, Error> {
let mut uts = MaybeUninit::uninit();
if unsafe { sys_uname(uts.as_mut_ptr()) } != 0 {
return Err(std::io::Error::last_os_error());
return Err(Error::last_os_error());
}
Ok(Self(unsafe { uts.assume_init() }))
}
@ -79,9 +277,7 @@ struct Fields {
}
#[cfg_attr(feature = "hotpath", hotpath::measure)]
fn print_system_info(
fields: &Fields,
) -> Result<(), Box<dyn std::error::Error>> {
fn print_system_info(fields: &Fields) -> Result<(), Error> {
let Fields {
user_info,
os_name,
@ -94,69 +290,242 @@ fn print_system_info(
colors,
} = fields;
let cyan = colors::COLORS.cyan;
let blue = colors::COLORS.blue;
let reset = colors::COLORS.reset;
let no_color = colors::is_no_color();
let colors_obj = colors::Colors::new(no_color);
let cyan = colors_obj.cyan;
let blue = colors_obj.blue;
let reset = colors_obj.reset;
// Build output string
let mut buf = [0u8; 2048];
let mut cursor = Cursor::new(&mut buf[..]);
let mut pos = 0usize;
write!(
cursor,
"
{blue} {cyan} {user_info} ~{reset}
{blue} {cyan} {blue} {cyan} {blue}System{reset} {os_name}
{blue} {cyan} {blue} {cyan} {blue}Kernel{reset} {kernel_version}
{cyan} {cyan}{blue} {cyan} {blue}Shell{reset} {shell}
{cyan} {blue} {cyan} {blue}Uptime{reset} {uptime}
{cyan} {blue} {blue} {cyan} {blue}Desktop{reset} {desktop}
{cyan} {blue}{cyan} {cyan}󰍛 {blue}Memory{reset} {memory_usage}
{cyan} {blue}{cyan} {cyan}󱥎 {blue}Storage (/){reset} {storage}
{blue} {cyan} {cyan} {blue}Colors{reset} {colors}\n\n"
)?;
// Helper to write to buffer
let mut write_str = |s: &str| {
let bytes = s.as_bytes();
let remaining = buf.len() - pos;
let to_write = bytes.len().min(remaining);
buf[pos..pos + to_write].copy_from_slice(&bytes[..to_write]);
pos += to_write;
};
write_str("\n ");
write_str(blue);
write_str(" ▟█▖ ");
write_str(cyan);
write_str("▝█▙ ▗█▛ ");
write_str(user_info);
write_str(" ~");
write_str(reset);
write_str("\n");
write_str(" ");
write_str(blue);
write_str(" ▗▄▄▟██▄▄▄▄▄");
write_str(cyan);
write_str("▝█▙█▛ ");
write_str(blue);
write_str("");
write_str(cyan);
write_str("");
write_str(blue);
write_str("System");
write_str(reset);
write_str("");
write_str(os_name);
write_str("\n");
write_str(" ");
write_str(blue);
write_str(" ▀▀▀▀▀▀▀▀▀▀▀▘");
write_str(cyan);
write_str("▝██ ");
write_str(blue);
write_str("▟█▖ ");
write_str(cyan);
write_str("");
write_str(blue);
write_str("Kernel");
write_str(reset);
write_str("");
write_str(kernel_version);
write_str("\n");
write_str(" ");
write_str(cyan);
write_str(" ▟█▛ ");
write_str(cyan);
write_str("▝█▘");
write_str(blue);
write_str("▟█▛ ");
write_str(cyan);
write_str("");
write_str(blue);
write_str("Shell");
write_str(reset);
write_str("");
write_str(shell);
write_str("\n");
write_str(" ");
write_str(cyan);
write_str("▟█████▛ ");
write_str(blue);
write_str("▟█████▛ ");
write_str(cyan);
write_str("");
write_str(blue);
write_str("Uptime");
write_str(reset);
write_str("");
write_str(uptime);
write_str("\n");
write_str(" ");
write_str(cyan);
write_str(" ▟█▛");
write_str(blue);
write_str("▗█▖ ");
write_str(blue);
write_str("▟█▛ ");
write_str(cyan);
write_str("");
write_str(blue);
write_str("Desktop");
write_str(reset);
write_str("");
write_str(desktop);
write_str("\n");
write_str(" ");
write_str(cyan);
write_str(" ▝█▛ ");
write_str(blue);
write_str("██▖");
write_str(cyan);
write_str("▗▄▄▄▄▄▄▄▄▄▄▄ ");
write_str(cyan);
write_str("󰍛 ");
write_str(blue);
write_str("Memory");
write_str(reset);
write_str("");
write_str(memory_usage);
write_str("\n");
write_str(" ");
write_str(cyan);
write_str("");
write_str(blue);
write_str("▟█▜█▖");
write_str(cyan);
write_str("▀▀▀▀▀██▛▀▀▘ ");
write_str(cyan);
write_str("󱥎 ");
write_str(blue);
write_str("Storage (/)");
write_str(reset);
write_str("");
write_str(storage);
write_str("\n");
write_str(" ");
write_str(blue);
write_str(" ▟█▘ ▜█▖ ");
write_str(cyan);
write_str("▝█▛ ");
write_str(cyan);
write_str("");
write_str(blue);
write_str("Colors");
write_str(reset);
write_str("");
write_str(colors);
write_str("\n\n");
let len =
usize::try_from(cursor.position()).expect("cursor position fits usize");
// Direct syscall to avoid stdout buffering allocation
let written = unsafe { sys_write(1, buf.as_ptr(), len) };
let written = unsafe { sys_write(1, buf.as_ptr(), pos) };
if written < 0 {
return Err(io::Error::last_os_error().into());
#[allow(clippy::cast_possible_truncation)]
return Err(Error::OsError(written as i32));
}
#[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")
.into(),
);
#[allow(clippy::cast_sign_loss)]
if written as usize != pos {
return Err(Error::WriteError);
}
Ok(())
}
/// Print version information using direct syscall.
fn print_version() {
const VERSION: &str = concat!("Microfetch ", env!("CARGO_PKG_VERSION"), "\n");
unsafe {
let _ = sys_write(1, VERSION.as_ptr(), VERSION.len());
}
}
/// Check if --version was passed via argc/argv.
///
/// # Safety
///
/// This function must be called with valid argc and argv from the program entry
/// point.
unsafe fn check_version_flag(argc: i32, argv: *const *const u8) -> bool {
if argc < 2 {
return false;
}
// SAFETY: argv is a valid array of argc pointers
let arg1 = unsafe { *argv.add(1) };
if arg1.is_null() {
return false;
}
// Check if arg1 is "--version"
let version_flag = b"--version\0";
for (i, &b) in version_flag.iter().enumerate() {
// SAFETY: arg1 is a valid C string
let arg_byte = unsafe { *arg1.add(i) };
if arg_byte != b {
return false;
}
}
true
}
/// Main entry point for microfetch - can be called by the binary crate
/// or by other consumers of the library
/// or by other consumers of the library.
///
/// # Arguments
///
/// * `argc` - Argument count from main
/// * `argv` - Argument vector from main
///
/// # Errors
///
/// Returns an error if any system call fails
///
/// # Safety
///
/// argv must be a valid null-terminated array of C strings.
#[cfg_attr(feature = "hotpath", hotpath::main)]
pub fn run() -> Result<(), Box<dyn std::error::Error>> {
if Some("--version") == std::env::args().nth(1).as_deref() {
println!("Microfetch {}", env!("CARGO_PKG_VERSION"));
} else {
let utsname = UtsName::uname()?;
let fields = Fields {
user_info: system::get_username_and_hostname(&utsname),
os_name: release::get_os_pretty_name()?,
kernel_version: release::get_system_info(&utsname),
shell: system::get_shell(),
desktop: desktop::get_desktop_info(),
uptime: uptime::get_current()?,
memory_usage: system::get_memory_usage()?,
storage: system::get_root_disk_usage()?,
colors: colors::print_dots(),
};
print_system_info(&fields)?;
pub unsafe fn run(argc: i32, argv: *const *const u8) -> Result<(), Error> {
if unsafe { check_version_flag(argc, argv) } {
print_version();
return Ok(());
}
let utsname = UtsName::uname()?;
let fields = Fields {
user_info: system::get_username_and_hostname(&utsname),
os_name: release::get_os_pretty_name()?,
kernel_version: release::get_system_info(&utsname),
shell: system::get_shell(),
desktop: desktop::get_desktop_info(),
uptime: uptime::get_current()?,
memory_usage: system::get_memory_usage()?,
storage: system::get_root_disk_usage()?,
colors: colors::print_dots(),
};
print_system_info(&fields)?;
Ok(())
}

View file

@ -1,6 +1,6 @@
use std::{fmt::Write as _, io};
use alloc::string::String;
use crate::{UtsName, syscall::read_file_fast};
use crate::{Error, UtsName, syscall::read_file_fast};
#[must_use]
#[cfg_attr(feature = "hotpath", hotpath::measure)]
@ -13,7 +13,14 @@ pub fn get_system_info(utsname: &UtsName) -> String {
let capacity = sysname.len() + 1 + release.len() + 2 + machine.len() + 1;
let mut result = String::with_capacity(capacity);
write!(result, "{sysname} {release} ({machine})").unwrap();
// Manual string construction instead of write! macro
result.push_str(sysname);
result.push(' ');
result.push_str(release);
result.push_str(" (");
result.push_str(machine);
result.push(')');
result
}
@ -23,7 +30,7 @@ pub fn get_system_info(utsname: &UtsName) -> String {
///
/// Returns an error if `/etc/os-release` cannot be read.
#[cfg_attr(feature = "hotpath", hotpath::measure)]
pub fn get_os_pretty_name() -> Result<String, io::Error> {
pub fn get_os_pretty_name() -> Result<String, Error> {
// Fast byte-level scanning for PRETTY_NAME=
const PREFIX: &[u8] = b"PRETTY_NAME=";
@ -31,7 +38,7 @@ pub fn get_os_pretty_name() -> Result<String, io::Error> {
// Use fast syscall-based file reading
let bytes_read = read_file_fast("/etc/os-release", &mut buffer)
.map_err(|e| io::Error::from_raw_os_error(-e))?;
.map_err(Error::from_raw_os_error)?;
let content = &buffer[..bytes_read];
let mut offset = 0;
@ -66,5 +73,5 @@ pub fn get_os_pretty_name() -> Result<String, io::Error> {
offset += line_end + 1;
}
Ok("Unknown".to_owned())
Ok(String::from("Unknown"))
}

View file

@ -1,37 +1,39 @@
use std::{ffi::OsStr, fmt::Write as _, io, mem::MaybeUninit};
use alloc::string::String;
use core::mem::MaybeUninit;
use crate::{
Error,
UtsName,
colors::COLORS,
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_os = std::env::var_os("USER");
let username = username_os
.as_deref()
.and_then(OsStr::to_str)
.unwrap_or("unknown_user");
let username = crate::getenv_str("USER").unwrap_or("unknown_user");
let hostname = utsname.nodename().to_str().unwrap_or("unknown_host");
let capacity = COLORS.yellow.len()
// Get colors (checking NO_COLOR only once)
let no_color = crate::colors::is_no_color();
let colors = Colors::new(no_color);
let capacity = colors.yellow.len()
+ username.len()
+ COLORS.red.len()
+ colors.red.len()
+ 1
+ COLORS.green.len()
+ colors.green.len()
+ hostname.len()
+ COLORS.reset.len();
+ colors.reset.len();
let mut result = String::with_capacity(capacity);
result.push_str(COLORS.yellow);
result.push_str(colors.yellow);
result.push_str(username);
result.push_str(COLORS.red);
result.push_str(colors.red);
result.push('@');
result.push_str(COLORS.green);
result.push_str(colors.green);
result.push_str(hostname);
result.push_str(COLORS.reset);
result.push_str(colors.reset);
result
}
@ -39,13 +41,12 @@ pub fn get_username_and_hostname(utsname: &UtsName) -> String {
#[must_use]
#[cfg_attr(feature = "hotpath", hotpath::measure)]
pub fn get_shell() -> String {
let shell_os = std::env::var_os("SHELL");
let shell = shell_os.as_deref().and_then(OsStr::to_str).unwrap_or("");
let shell = crate::getenv_str("SHELL").unwrap_or("");
let start = shell.rfind('/').map_or(0, |i| i + 1);
if shell.is_empty() {
"unknown_shell".into()
String::from("unknown_shell")
} else {
shell[start..].into()
String::from(&shell[start..])
}
}
@ -56,12 +57,12 @@ pub fn get_shell() -> String {
/// Returns an error if the filesystem information cannot be retrieved.
#[cfg_attr(feature = "hotpath", hotpath::measure)]
#[allow(clippy::cast_precision_loss)]
pub fn get_root_disk_usage() -> Result<String, io::Error> {
pub fn get_root_disk_usage() -> Result<String, Error> {
let mut vfs = MaybeUninit::<StatfsBuf>::uninit();
let path = b"/\0";
if unsafe { sys_statfs(path.as_ptr(), vfs.as_mut_ptr()) } != 0 {
return Err(io::Error::last_os_error());
return Err(Error::last_os_error());
}
let vfs = unsafe { vfs.assume_init() };
@ -77,18 +78,96 @@ pub fn get_root_disk_usage() -> Result<String, io::Error> {
let used_size = used_size as f64 / (1024.0 * 1024.0 * 1024.0);
let usage = (used_size / total_size) * 100.0;
let no_color = crate::colors::is_no_color();
let colors = Colors::new(no_color);
let mut result = String::with_capacity(64);
write!(
result,
"{used_size:.2} GiB / {total_size:.2} GiB ({cyan}{usage:.0}%{reset})",
cyan = COLORS.cyan,
reset = COLORS.reset,
)
.unwrap();
// Manual float formatting
write_float(&mut result, used_size, 2);
result.push_str(" GiB / ");
write_float(&mut result, total_size, 2);
result.push_str(" GiB (");
result.push_str(colors.cyan);
write_float(&mut result, usage, 0);
result.push('%');
result.push_str(colors.reset);
result.push(')');
Ok(result)
}
/// Write a float to string with specified decimal places
#[allow(
clippy::cast_sign_loss,
clippy::cast_possible_truncation,
clippy::cast_precision_loss
)]
fn write_float(s: &mut String, val: f64, decimals: u32) {
// Handle integer part
let int_part = val as u64;
write_u64(s, int_part);
if decimals > 0 {
s.push('.');
// Calculate fractional part
let mut frac = val - int_part as f64;
for _ in 0..decimals {
frac *= 10.0;
let digit = frac as u8;
s.push((b'0' + digit) as char);
frac -= f64::from(digit);
}
}
}
/// Round an f64 to nearest integer (`f64::round` is not in core)
#[allow(
clippy::cast_precision_loss,
clippy::cast_possible_truncation,
clippy::cast_sign_loss
)]
fn round_f64(x: f64) -> f64 {
if x >= 0.0 {
let int_part = x as u64 as f64;
let frac = x - int_part;
if frac >= 0.5 {
int_part + 1.0
} else {
int_part
}
} else {
let int_part = (-x) as u64 as f64;
let frac = -x - int_part;
if frac >= 0.5 {
-(int_part + 1.0)
} else {
-int_part
}
}
}
/// Write a u64 to string
fn write_u64(s: &mut String, mut n: u64) {
if n == 0 {
s.push('0');
return;
}
let mut buf = [0u8; 20];
let mut i = 20;
while n > 0 {
i -= 1;
buf[i] = b'0' + (n % 10) as u8;
n /= 10;
}
// SAFETY: buf contains only ASCII digits
s.push_str(unsafe { core::str::from_utf8_unchecked(&buf[i..]) });
}
/// Fast integer parsing without stdlib overhead
#[inline]
fn parse_u64_fast(s: &[u8]) -> u64 {
@ -109,16 +188,16 @@ fn parse_u64_fast(s: &[u8]) -> u64 {
///
/// Returns an error if `/proc/meminfo` cannot be read.
#[cfg_attr(feature = "hotpath", hotpath::measure)]
pub fn get_memory_usage() -> Result<String, io::Error> {
pub fn get_memory_usage() -> Result<String, Error> {
#[cfg_attr(feature = "hotpath", hotpath::measure)]
fn parse_memory_info() -> Result<(f64, f64), io::Error> {
fn parse_memory_info() -> Result<(f64, f64), Error> {
let mut total_memory_kb = 0u64;
let mut available_memory_kb = 0u64;
let mut buffer = [0u8; 1024];
// Use fast syscall-based file reading
let bytes_read = read_file_fast("/proc/meminfo", &mut buffer)
.map_err(|e| io::Error::from_raw_os_error(-e))?;
.map_err(Error::from_raw_os_error)?;
let meminfo = &buffer[..bytes_read];
// Fast scanning for MemTotal and MemAvailable
@ -168,17 +247,22 @@ pub fn get_memory_usage() -> Result<String, io::Error> {
let (used_memory, total_memory) = parse_memory_info()?;
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
let percentage_used = (used_memory / total_memory * 100.0).round() as u64;
let percentage_used = round_f64(used_memory / total_memory * 100.0) as u64;
let no_color = crate::colors::is_no_color();
let colors = Colors::new(no_color);
let mut result = String::with_capacity(64);
write!(
result,
"{used_memory:.2} GiB / {total_memory:.2} GiB \
({cyan}{percentage_used}%{reset})",
cyan = COLORS.cyan,
reset = COLORS.reset,
)
.unwrap();
write_float(&mut result, used_memory, 2);
result.push_str(" GiB / ");
write_float(&mut result, total_memory, 2);
result.push_str(" GiB (");
result.push_str(colors.cyan);
write_u64(&mut result, percentage_used);
result.push('%');
result.push_str(colors.reset);
result.push(')');
Ok(result)
}

View file

@ -1,6 +1,7 @@
use std::{io, mem::MaybeUninit};
use alloc::string::String;
use core::mem::MaybeUninit;
use crate::syscall::sys_sysinfo;
use crate::{Error, syscall::sys_sysinfo};
/// Faster integer to string conversion without the formatting overhead.
#[inline]
@ -16,7 +17,8 @@ fn itoa(mut n: u64, buf: &mut [u8]) -> &str {
n /= 10;
}
unsafe { std::str::from_utf8_unchecked(&buf[i..]) }
// SAFETY: We only wrote ASCII digits
unsafe { core::str::from_utf8_unchecked(&buf[i..]) }
}
/// Gets the current system uptime.
@ -25,11 +27,11 @@ fn itoa(mut n: u64, buf: &mut [u8]) -> &str {
///
/// Returns an error if the system uptime cannot be retrieved.
#[cfg_attr(feature = "hotpath", hotpath::measure)]
pub fn get_current() -> Result<String, io::Error> {
pub fn get_current() -> Result<String, Error> {
let uptime_seconds = {
let mut info = MaybeUninit::uninit();
if unsafe { sys_sysinfo(info.as_mut_ptr()) } != 0 {
return Err(io::Error::last_os_error());
return Err(Error::last_os_error());
}
#[allow(clippy::cast_sign_loss)]
unsafe {

View file

@ -11,12 +11,14 @@ repository = "https://github.com/notashelf/microfetch"
publish = false
[dependencies]
hotpath = { optional = true, version = "0.14.0" }
hotpath = { optional = true, version = "0.14.0" }
microfetch-alloc.workspace = true
microfetch-lib.workspace = true
microfetch-asm.workspace = true
[features]
hotpath = [ "dep:hotpath" ]
hotpath-alloc = [ "hotpath/hotpath-alloc" ]
[lints]
workspace = true
workspace = true

View file

@ -1,3 +1,60 @@
fn main() -> Result<(), Box<dyn std::error::Error>> {
microfetch_lib::run()
#![no_std]
#![no_main]
extern crate alloc;
use microfetch_alloc::BumpAlloc;
use microfetch_asm::sys_write;
#[cfg(not(test))]
use {core::panic::PanicInfo, microfetch_asm::sys_exit};
#[global_allocator]
static ALLOCATOR: BumpAlloc = BumpAlloc::new();
/// Receives argc and argv directly. The C runtime will call this after
/// initializing the environment. Cool right?
///
/// # Safety
///
/// argv must be a valid pointer to an array of argc C strings.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn main(argc: i32, argv: *const *const u8) -> i32 {
// SAFETY: argc and argv are provided by the C runtime and are valid
unsafe {
match microfetch_lib::run(argc, argv) {
Ok(()) => 0,
Err(e) => {
// Print error message to stderr (fd 2)
let msg = alloc::format!("Error: {e}\n");
let _ = sys_write(2, msg.as_ptr(), msg.len());
1
},
}
}
}
#[cfg(not(test))]
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
// Write "panic" to stderr and exit
const PANIC_MSG: &[u8] = b"panic\n";
unsafe {
let _ = sys_write(2, PANIC_MSG.as_ptr(), PANIC_MSG.len());
sys_exit(1)
}
}
// FIXME: Stubs for Rust exception handling symbols needed when using alloc with
// panic=abort These are normally provided by the unwinding runtime, but we're
// using panic=abort. I don't actually think this is the correct approach, but I
// cannot think of anything better.
#[cfg(not(test))]
#[unsafe(no_mangle)]
const extern "C" fn rust_eh_personality() {}
#[cfg(not(test))]
#[unsafe(no_mangle)]
extern "C" fn _Unwind_Resume() -> ! {
unsafe { sys_exit(1) }
}