Files
sausage/userland/fs-tools/src/stat.rs
T

109 lines
3.4 KiB
Rust

//! Print portable filesystem metadata and the world's fixed root identity.
use chrono::{DateTime, Utc};
use clap::Parser;
use std::{fs, io, path::PathBuf, time::SystemTime};
#[derive(Parser)]
struct Args {
#[arg(short = 'c', long = "format")]
format: Option<String>,
#[arg(short = 'L', long)]
dereference: bool,
#[arg(required = true)]
paths: Vec<PathBuf>,
}
fn timestamp(value: io::Result<SystemTime>, seconds: bool) -> String {
match value {
Ok(time) => {
let date: DateTime<Utc> = time.into();
if seconds {
date.timestamp().to_string()
} else {
date.format("%Y-%m-%d %H:%M:%S%.9f +0000").to_string()
}
}
Err(_) => {
if seconds {
"0".into()
} else {
"-".into()
}
}
}
}
fn render(path: &PathBuf, meta: &fs::Metadata, format: &str) -> io::Result<String> {
let mut out = String::new();
let mut chars = format.chars();
while let Some(ch) = chars.next() {
if ch != '%' {
out.push(ch);
continue;
}
let field = chars.next().unwrap_or('%');
let value = match field {
'%' => "%".into(),
'n' => path.display().to_string(),
'N' => {
if meta.is_symlink() {
format!(
"'{}' -> '{}'",
path.display(),
fs::read_link(path)?.display()
)
} else {
format!("'{}'", path.display())
}
}
's' => meta.len().to_string(),
'F' => if meta.is_dir() {
"directory"
} else if meta.is_symlink() {
"symbolic link"
} else if meta.len() == 0 {
"regular empty file"
} else {
"regular file"
}
.into(),
'Y' => timestamp(meta.modified(), true),
'y' => timestamp(meta.modified(), false),
'X' => timestamp(meta.accessed(), true),
'x' => timestamp(meta.accessed(), false),
'a' => if meta.is_dir() { "755" } else { "644" }.into(),
'A' => if meta.is_dir() {
"drwxr-xr-x"
} else if meta.is_symlink() {
"lrw-r--r--"
} else {
"-rw-r--r--"
}
.into(),
'u' | 'g' => "0".into(),
'U' | 'G' => "root".into(),
_ => return Err(io::Error::other(format!("unsupported format %{field}"))),
};
out.push_str(&value);
}
Ok(out)
}
fn main() {
let args = Args::parse();
let format = args.format.as_deref().unwrap_or(" File: %N\n Size: %s\tType: %F\nAccess: (%a/%A) Uid: (%u/%U) Gid: (%g/%G)\nAccess: %x\nModify: %y");
let mut failed = false;
for path in &args.paths {
let result = if args.dereference {
fs::metadata(path)
} else {
fs::symlink_metadata(path)
}
.and_then(|meta| render(path, &meta, format));
match result {
Ok(text) => println!("{text}"),
Err(e) => {
eprintln!("stat: {}: {e}", path.display());
failed = true;
}
}
}
std::process::exit(i32::from(failed));
}