From b1efa05d595ca4bbf5e7b40e5514f17e8d3aeabf Mon Sep 17 00:00:00 2001 From: Nicolas Boichat Date: Sat, 26 Jul 2025 17:49:25 +0800 Subject: [PATCH 1/7] uucore: fsext: Move metadata_get_time from ls This function is useful for more than `ls` (`du` can use it too). --- src/uu/ls/Cargo.toml | 1 + src/uu/ls/src/ls.rs | 56 +++++++--------------------- src/uucore/src/lib/features/fsext.rs | 42 ++++++++++++++++++++- 3 files changed, 55 insertions(+), 44 deletions(-) diff --git a/src/uu/ls/Cargo.toml b/src/uu/ls/Cargo.toml index c117c78748b..706615d72c6 100644 --- a/src/uu/ls/Cargo.toml +++ b/src/uu/ls/Cargo.toml @@ -33,6 +33,7 @@ uucore = { workspace = true, features = [ "entries", "format", "fs", + "fsext", "fsxattr", "parser", "quoting-style", diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 00ebbb0eff4..dc021828621 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -39,6 +39,7 @@ use uucore::entries; use uucore::error::USimpleError; use uucore::format::human::{SizeFormat, human_readable}; use uucore::fs::FileInformation; +use uucore::fsext::{MetadataTimeField, metadata_get_time}; #[cfg(all(unix, not(any(target_os = "android", target_os = "macos"))))] use uucore::fsxattr::has_acl; #[cfg(unix)] @@ -248,13 +249,6 @@ enum Files { Normal, } -enum Time { - Modification, - Access, - Change, - Birth, -} - fn parse_time_style(options: &clap::ArgMatches) -> Result<(String, Option), LsError> { const TIME_STYLES: [(&str, (&str, Option<&str>)); 4] = [ ("full-iso", ("%Y-%m-%d %H:%M:%S.%f %z", None)), @@ -332,7 +326,7 @@ pub struct Config { ignore_patterns: Vec, size_format: SizeFormat, directory: bool, - time: Time, + time: MetadataTimeField, #[cfg(unix)] inode: bool, color: Option, @@ -467,23 +461,23 @@ fn extract_sort(options: &clap::ArgMatches) -> Sort { /// /// # Returns /// -/// A Time variant representing the time to use. -fn extract_time(options: &clap::ArgMatches) -> Time { +/// A `MetadataTimeField` variant representing the time to use. +fn extract_time(options: &clap::ArgMatches) -> MetadataTimeField { if let Some(field) = options.get_one::(options::TIME) { match field.as_str() { - "ctime" | "status" => Time::Change, - "access" | "atime" | "use" => Time::Access, - "mtime" | "modification" => Time::Modification, - "birth" | "creation" => Time::Birth, + "ctime" | "status" => MetadataTimeField::Change, + "access" | "atime" | "use" => MetadataTimeField::Access, + "mtime" | "modification" => MetadataTimeField::Modification, + "birth" | "creation" => MetadataTimeField::Birth, // below should never happen as clap already restricts the values. _ => unreachable!("Invalid field for --time"), } } else if options.get_flag(options::time::ACCESS) { - Time::Access + MetadataTimeField::Access } else if options.get_flag(options::time::CHANGE) { - Time::Change + MetadataTimeField::Change } else { - Time::Modification + MetadataTimeField::Modification } } @@ -2099,7 +2093,7 @@ fn sort_entries(entries: &mut [PathData], config: &Config, out: &mut BufWriter entries.sort_by_key(|k| { Reverse( k.get_metadata(out) - .and_then(|md| get_system_time(md, config)) + .and_then(|md| metadata_get_time(md, config.time)) .unwrap_or(UNIX_EPOCH), ) }), @@ -2685,7 +2679,7 @@ fn display_grid( /// * `group` ([`display_group`], config-optional) /// * `author` ([`display_uname`], config-optional) /// * `size / rdev` ([`display_len_or_rdev`]) -/// * `system_time` ([`get_system_time`]) +/// * `system_time` ([`display_date`]) /// * `item_name` ([`display_item_name`]) /// /// This function needs to display information in columns: @@ -2963,35 +2957,13 @@ fn display_group(_metadata: &Metadata, _config: &Config, _state: &mut ListState) "somegroup" } -// The implementations for get_system_time are separated because some options, such -// as ctime will not be available -#[cfg(unix)] -fn get_system_time(md: &Metadata, config: &Config) -> Option { - match config.time { - Time::Change => Some(UNIX_EPOCH + Duration::new(md.ctime() as u64, md.ctime_nsec() as u32)), - Time::Modification => md.modified().ok(), - Time::Access => md.accessed().ok(), - Time::Birth => md.created().ok(), - } -} - -#[cfg(not(unix))] -fn get_system_time(md: &Metadata, config: &Config) -> Option { - match config.time { - Time::Modification => md.modified().ok(), - Time::Access => md.accessed().ok(), - Time::Birth => md.created().ok(), - Time::Change => None, - } -} - fn display_date( metadata: &Metadata, config: &Config, state: &mut ListState, out: &mut Vec, ) -> UResult<()> { - let Some(time) = get_system_time(metadata, config) else { + let Some(time) = metadata_get_time(metadata, config.time) else { out.extend(b"???"); return Ok(()); }; diff --git a/src/uucore/src/lib/features/fsext.rs b/src/uucore/src/lib/features/fsext.rs index 072aaeeb147..4bc76cd608b 100644 --- a/src/uucore/src/lib/features/fsext.rs +++ b/src/uucore/src/lib/features/fsext.rs @@ -69,9 +69,15 @@ use std::io::Error as IOError; use std::mem; #[cfg(windows)] use std::path::Path; -use std::time::UNIX_EPOCH; +use std::time::{SystemTime, UNIX_EPOCH}; use std::{borrow::Cow, ffi::OsString}; +use std::fs::Metadata; +#[cfg(unix)] +use std::os::unix::fs::MetadataExt; +#[cfg(unix)] +use std::time::Duration; + #[cfg(any( target_os = "linux", target_os = "android", @@ -112,7 +118,6 @@ pub trait BirthTime { fn birth(&self) -> Option<(u64, u32)>; } -use std::fs::Metadata; impl BirthTime for Metadata { fn birth(&self) -> Option<(u64, u32)> { self.created() @@ -122,6 +127,39 @@ impl BirthTime for Metadata { } } +#[derive(Debug, Copy, Clone)] +pub enum MetadataTimeField { + Modification, + Access, + Change, + Birth, +} + +// The implementations for get_system_time are separated because some options, such +// as ctime will not be available +#[cfg(unix)] +pub fn metadata_get_time(md: &Metadata, md_time: MetadataTimeField) -> Option { + match md_time { + MetadataTimeField::Change => { + // TODO: This is incorrect for negative timestamps. + Some(UNIX_EPOCH + Duration::new(md.ctime() as u64, md.ctime_nsec() as u32)) + } + MetadataTimeField::Modification => md.modified().ok(), + MetadataTimeField::Access => md.accessed().ok(), + MetadataTimeField::Birth => md.created().ok(), + } +} + +#[cfg(not(unix))] +pub fn metadata_get_time(md: &Metadata, md_time: MetadataTimeField) -> Option { + match md_time { + MetadataTimeField::Modification => md.modified().ok(), + MetadataTimeField::Access => md.accessed().ok(), + MetadataTimeField::Birth => md.created().ok(), + MetadataTimeField::Change => None, + } +} + // TODO: Types for this struct are probably mostly wrong. Possibly, most of them // should be OsString. #[derive(Debug, Clone)] From 51dbe090a0bbdd220849f2e4b663f7b295924252 Mon Sep 17 00:00:00 2001 From: Nicolas Boichat Date: Sat, 26 Jul 2025 18:35:05 +0800 Subject: [PATCH 2/7] du: Keep metadata in Stat, and make use of uucore::fsext::metadata_get_time Removes some duplicated code, with slight, but incorrect, differences. --- src/uu/du/Cargo.toml | 2 +- src/uu/du/src/du.rs | 77 +++++++++----------------------------------- 2 files changed, 16 insertions(+), 63 deletions(-) diff --git a/src/uu/du/Cargo.toml b/src/uu/du/Cargo.toml index b9285e5a827..a9f4786f94a 100644 --- a/src/uu/du/Cargo.toml +++ b/src/uu/du/Cargo.toml @@ -21,7 +21,7 @@ path = "src/du.rs" # For the --exclude & --exclude-from options glob = { workspace = true } clap = { workspace = true } -uucore = { workspace = true, features = ["format", "parser", "time"] } +uucore = { workspace = true, features = ["format", "fsext", "parser", "time"] } thiserror = { workspace = true } [target.'cfg(target_os = "windows")'.dependencies] diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index 01b115482b7..f259b19ea02 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -7,24 +7,21 @@ use clap::{Arg, ArgAction, ArgMatches, Command, builder::PossibleValue}; use glob::Pattern; use std::collections::{HashMap, HashSet}; use std::env; -#[cfg(not(windows))] use std::fs::Metadata; use std::fs::{self, DirEntry, File}; use std::io::{BufRead, BufReader, stdout}; #[cfg(not(windows))] use std::os::unix::fs::MetadataExt; #[cfg(windows)] -use std::os::windows::fs::MetadataExt; -#[cfg(windows)] use std::os::windows::io::AsRawHandle; use std::path::{Path, PathBuf}; use std::str::FromStr; use std::sync::mpsc; use std::thread; -use std::time::{Duration, UNIX_EPOCH}; use thiserror::Error; use uucore::display::{Quotable, print_verbatim}; use uucore::error::{FromIo, UError, UResult, USimpleError, set_exit_code}; +use uucore::fsext::{MetadataTimeField, metadata_get_time}; use uucore::line_ending::LineEnding; use uucore::locale::{get_message, get_message_with_args}; use uucore::parser::parse_glob; @@ -87,7 +84,7 @@ struct StatPrinter { threshold: Option, apparent_size: bool, size_format: SizeFormat, - time: Option