Skip to content

Commit 64ba35b

Browse files
authored
Merge pull request #8396 from drinkcat/du-bigtime
`du`/`ls`: Unify file metadata time handling
2 parents 5244b5e + b8d81ad commit 64ba35b

File tree

6 files changed

+115
-160
lines changed

6 files changed

+115
-160
lines changed

src/uu/du/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ path = "src/du.rs"
2121
# For the --exclude & --exclude-from options
2222
glob = { workspace = true }
2323
clap = { workspace = true }
24-
uucore = { workspace = true, features = ["format", "parser", "time"] }
24+
uucore = { workspace = true, features = ["format", "fsext", "parser", "time"] }
2525
thiserror = { workspace = true }
2626

2727
[target.'cfg(target_os = "windows")'.dependencies]

src/uu/du/src/du.rs

Lines changed: 39 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,21 @@ use clap::{Arg, ArgAction, ArgMatches, Command, builder::PossibleValue};
77
use glob::Pattern;
88
use std::collections::{HashMap, HashSet};
99
use std::env;
10-
#[cfg(not(windows))]
1110
use std::fs::Metadata;
1211
use std::fs::{self, DirEntry, File};
1312
use std::io::{BufRead, BufReader, stdout};
1413
#[cfg(not(windows))]
1514
use std::os::unix::fs::MetadataExt;
1615
#[cfg(windows)]
17-
use std::os::windows::fs::MetadataExt;
18-
#[cfg(windows)]
1916
use std::os::windows::io::AsRawHandle;
2017
use std::path::{Path, PathBuf};
2118
use std::str::FromStr;
2219
use std::sync::mpsc;
2320
use std::thread;
24-
use std::time::{Duration, UNIX_EPOCH};
2521
use thiserror::Error;
2622
use uucore::display::{Quotable, print_verbatim};
2723
use uucore::error::{FromIo, UError, UResult, USimpleError, set_exit_code};
24+
use uucore::fsext::{MetadataTimeField, metadata_get_time};
2825
use uucore::line_ending::LineEnding;
2926
use uucore::locale::{get_message, get_message_with_args};
3027
use uucore::parser::parse_glob;
@@ -87,7 +84,7 @@ struct StatPrinter {
8784
threshold: Option<Threshold>,
8885
apparent_size: bool,
8986
size_format: SizeFormat,
90-
time: Option<Time>,
87+
time: Option<MetadataTimeField>,
9188
time_format: String,
9289
line_ending: LineEnding,
9390
summarize: bool,
@@ -101,13 +98,6 @@ enum Deref {
10198
None,
10299
}
103100

104-
#[derive(Clone, Copy)]
105-
enum Time {
106-
Accessed,
107-
Modified,
108-
Created,
109-
}
110-
111101
#[derive(Clone)]
112102
enum SizeFormat {
113103
HumanDecimal,
@@ -123,14 +113,11 @@ struct FileInfo {
123113

124114
struct Stat {
125115
path: PathBuf,
126-
is_dir: bool,
127116
size: u64,
128117
blocks: u64,
129118
inodes: u64,
130119
inode: Option<FileInfo>,
131-
created: Option<u64>,
132-
accessed: u64,
133-
modified: u64,
120+
metadata: Metadata,
134121
}
135122

136123
impl Stat {
@@ -157,69 +144,27 @@ impl Stat {
157144
fs::symlink_metadata(path)
158145
}?;
159146

160-
#[cfg(not(windows))]
161-
{
162-
let file_info = FileInfo {
163-
file_id: metadata.ino() as u128,
164-
dev_id: metadata.dev(),
165-
};
166-
167-
Ok(Self {
168-
path: path.to_path_buf(),
169-
is_dir: metadata.is_dir(),
170-
size: if metadata.is_dir() { 0 } else { metadata.len() },
171-
blocks: metadata.blocks(),
172-
inodes: 1,
173-
inode: Some(file_info),
174-
created: birth_u64(&metadata),
175-
accessed: metadata.atime() as u64,
176-
modified: metadata.mtime() as u64,
177-
})
178-
}
179-
180-
#[cfg(windows)]
181-
{
182-
let size_on_disk = get_size_on_disk(path);
183-
let file_info = get_file_info(path);
184-
185-
Ok(Self {
186-
path: path.to_path_buf(),
187-
is_dir: metadata.is_dir(),
188-
size: if metadata.is_dir() { 0 } else { metadata.len() },
189-
blocks: size_on_disk / 1024 * 2,
190-
inodes: 1,
191-
inode: file_info,
192-
created: windows_creation_time_to_unix_time(metadata.creation_time()),
193-
accessed: windows_time_to_unix_time(metadata.last_access_time()),
194-
modified: windows_time_to_unix_time(metadata.last_write_time()),
195-
})
196-
}
147+
let file_info = get_file_info(path, &metadata);
148+
let blocks = get_blocks(path, &metadata);
149+
150+
Ok(Self {
151+
path: path.to_path_buf(),
152+
size: if metadata.is_dir() { 0 } else { metadata.len() },
153+
blocks,
154+
inodes: 1,
155+
inode: file_info,
156+
metadata,
157+
})
197158
}
198159
}
199160

200-
#[cfg(windows)]
201-
/// <https://doc.rust-lang.org/std/os/windows/fs/trait.MetadataExt.html#tymethod.last_access_time>
202-
/// "The returned 64-bit value [...] which represents the number of 100-nanosecond intervals since January 1, 1601 (UTC)."
203-
/// "If the underlying filesystem does not support last access time, the returned value is 0."
204-
fn windows_time_to_unix_time(win_time: u64) -> u64 {
205-
(win_time / 10_000_000).saturating_sub(11_644_473_600)
206-
}
207-
208-
#[cfg(windows)]
209-
fn windows_creation_time_to_unix_time(win_time: u64) -> Option<u64> {
210-
(win_time / 10_000_000).checked_sub(11_644_473_600)
211-
}
212-
213161
#[cfg(not(windows))]
214-
fn birth_u64(meta: &Metadata) -> Option<u64> {
215-
meta.created()
216-
.ok()
217-
.and_then(|t| t.duration_since(UNIX_EPOCH).ok())
218-
.map(|e| e.as_secs())
162+
fn get_blocks(_path: &Path, metadata: &Metadata) -> u64 {
163+
metadata.blocks()
219164
}
220165

221166
#[cfg(windows)]
222-
fn get_size_on_disk(path: &Path) -> u64 {
167+
fn get_blocks(path: &Path, _metadata: &Metadata) -> u64 {
223168
let mut size_on_disk = 0;
224169

225170
// bind file so it stays in scope until end of function
@@ -244,11 +189,19 @@ fn get_size_on_disk(path: &Path) -> u64 {
244189
}
245190
}
246191

247-
size_on_disk
192+
size_on_disk / 1024 * 2
193+
}
194+
195+
#[cfg(not(windows))]
196+
fn get_file_info(_path: &Path, metadata: &Metadata) -> Option<FileInfo> {
197+
Some(FileInfo {
198+
file_id: metadata.ino() as u128,
199+
dev_id: metadata.dev(),
200+
})
248201
}
249202

250203
#[cfg(windows)]
251-
fn get_file_info(path: &Path) -> Option<FileInfo> {
204+
fn get_file_info(path: &Path, _metadata: &Metadata) -> Option<FileInfo> {
252205
let mut result = None;
253206

254207
let Ok(file) = File::open(path) else {
@@ -306,7 +259,7 @@ fn du(
306259
seen_inodes: &mut HashSet<FileInfo>,
307260
print_tx: &mpsc::Sender<UResult<StatPrintInfo>>,
308261
) -> Result<Stat, Box<mpsc::SendError<UResult<StatPrintInfo>>>> {
309-
if my_stat.is_dir {
262+
if my_stat.metadata.is_dir() {
310263
let read = match fs::read_dir(&my_stat.path) {
311264
Ok(read) => read,
312265
Err(e) => {
@@ -367,7 +320,7 @@ fn du(
367320
seen_inodes.insert(inode);
368321
}
369322

370-
if this_stat.is_dir {
323+
if this_stat.metadata.is_dir() {
371324
if options.one_file_system {
372325
if let (Some(this_inode), Some(my_inode)) =
373326
(this_stat.inode, my_stat.inode)
@@ -435,9 +388,6 @@ enum DuError {
435388
])))]
436389
InvalidTimeStyleArg(String),
437390

438-
#[error("{}", get_message("du-error-invalid-time-arg"))]
439-
InvalidTimeArg,
440-
441391
#[error("{}", get_message_with_args("du-error-invalid-glob", HashMap::from([("error".to_string(), _0.to_string())])))]
442392
InvalidGlob(String),
443393
}
@@ -448,7 +398,6 @@ impl UError for DuError {
448398
Self::InvalidMaxDepthArg(_)
449399
| Self::SummarizeDepthConflict(_)
450400
| Self::InvalidTimeStyleArg(_)
451-
| Self::InvalidTimeArg
452401
| Self::InvalidGlob(_) => 1,
453402
}
454403
}
@@ -577,11 +526,13 @@ impl StatPrinter {
577526
fn print_stat(&self, stat: &Stat, size: u64) -> UResult<()> {
578527
print!("{}\t", self.convert_size(size));
579528

580-
if let Some(time) = self.time {
581-
let secs = get_time_secs(time, stat)?;
582-
let time = UNIX_EPOCH + Duration::from_secs(secs);
583-
uucore::time::format_system_time(&mut stdout(), time, &self.time_format, true)?;
584-
print!("\t");
529+
if let Some(md_time) = &self.time {
530+
if let Some(time) = metadata_get_time(&stat.metadata, *md_time) {
531+
uucore::time::format_system_time(&mut stdout(), time, &self.time_format, true)?;
532+
print!("\t");
533+
} else {
534+
print!("???\t");
535+
}
585536
}
586537

587538
print_verbatim(&stat.path).unwrap();
@@ -697,12 +648,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
697648
};
698649

699650
let time = matches.contains_id(options::TIME).then(|| {
700-
match matches.get_one::<String>(options::TIME).map(AsRef::as_ref) {
701-
None | Some("ctime" | "status") => Time::Modified,
702-
Some("access" | "atime" | "use") => Time::Accessed,
703-
Some("birth" | "creation") => Time::Created,
704-
_ => unreachable!("should be caught by clap"),
705-
}
651+
matches
652+
.get_one::<String>(options::TIME)
653+
.map_or(MetadataTimeField::Modification, |s| s.as_str().into())
706654
});
707655

708656
let size_format = if matches.get_flag(options::HUMAN_READABLE) {
@@ -853,14 +801,6 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
853801
Ok(())
854802
}
855803

856-
fn get_time_secs(time: Time, stat: &Stat) -> Result<u64, DuError> {
857-
match time {
858-
Time::Modified => Ok(stat.modified),
859-
Time::Accessed => Ok(stat.accessed),
860-
Time::Created => stat.created.ok_or(DuError::InvalidTimeArg),
861-
}
862-
}
863-
864804
fn parse_time_style(s: Option<&str>) -> UResult<&str> {
865805
match s {
866806
Some(s) => match s {

src/uu/ls/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ uucore = { workspace = true, features = [
3333
"entries",
3434
"format",
3535
"fs",
36+
"fsext",
3637
"fsxattr",
3738
"parser",
3839
"quoting-style",

src/uu/ls/src/ls.rs

Lines changed: 11 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ use uucore::entries;
3939
use uucore::error::USimpleError;
4040
use uucore::format::human::{SizeFormat, human_readable};
4141
use uucore::fs::FileInformation;
42+
use uucore::fsext::{MetadataTimeField, metadata_get_time};
4243
#[cfg(all(unix, not(any(target_os = "android", target_os = "macos"))))]
4344
use uucore::fsxattr::has_acl;
4445
#[cfg(unix)]
@@ -248,13 +249,6 @@ enum Files {
248249
Normal,
249250
}
250251

251-
enum Time {
252-
Modification,
253-
Access,
254-
Change,
255-
Birth,
256-
}
257-
258252
fn parse_time_style(options: &clap::ArgMatches) -> Result<(String, Option<String>), LsError> {
259253
const TIME_STYLES: [(&str, (&str, Option<&str>)); 4] = [
260254
("full-iso", ("%Y-%m-%d %H:%M:%S.%f %z", None)),
@@ -332,7 +326,7 @@ pub struct Config {
332326
ignore_patterns: Vec<Pattern>,
333327
size_format: SizeFormat,
334328
directory: bool,
335-
time: Time,
329+
time: MetadataTimeField,
336330
#[cfg(unix)]
337331
inode: bool,
338332
color: Option<LsColors>,
@@ -467,23 +461,16 @@ fn extract_sort(options: &clap::ArgMatches) -> Sort {
467461
///
468462
/// # Returns
469463
///
470-
/// A Time variant representing the time to use.
471-
fn extract_time(options: &clap::ArgMatches) -> Time {
464+
/// A `MetadataTimeField` variant representing the time to use.
465+
fn extract_time(options: &clap::ArgMatches) -> MetadataTimeField {
472466
if let Some(field) = options.get_one::<String>(options::TIME) {
473-
match field.as_str() {
474-
"ctime" | "status" => Time::Change,
475-
"access" | "atime" | "use" => Time::Access,
476-
"mtime" | "modification" => Time::Modification,
477-
"birth" | "creation" => Time::Birth,
478-
// below should never happen as clap already restricts the values.
479-
_ => unreachable!("Invalid field for --time"),
480-
}
467+
field.as_str().into()
481468
} else if options.get_flag(options::time::ACCESS) {
482-
Time::Access
469+
MetadataTimeField::Access
483470
} else if options.get_flag(options::time::CHANGE) {
484-
Time::Change
471+
MetadataTimeField::Change
485472
} else {
486-
Time::Modification
473+
MetadataTimeField::Modification
487474
}
488475
}
489476

@@ -2099,7 +2086,7 @@ fn sort_entries(entries: &mut [PathData], config: &Config, out: &mut BufWriter<S
20992086
Sort::Time => entries.sort_by_key(|k| {
21002087
Reverse(
21012088
k.get_metadata(out)
2102-
.and_then(|md| get_system_time(md, config))
2089+
.and_then(|md| metadata_get_time(md, config.time))
21032090
.unwrap_or(UNIX_EPOCH),
21042091
)
21052092
}),
@@ -2685,7 +2672,7 @@ fn display_grid(
26852672
/// * `group` ([`display_group`], config-optional)
26862673
/// * `author` ([`display_uname`], config-optional)
26872674
/// * `size / rdev` ([`display_len_or_rdev`])
2688-
/// * `system_time` ([`get_system_time`])
2675+
/// * `system_time` ([`display_date`])
26892676
/// * `item_name` ([`display_item_name`])
26902677
///
26912678
/// This function needs to display information in columns:
@@ -2963,35 +2950,13 @@ fn display_group(_metadata: &Metadata, _config: &Config, _state: &mut ListState)
29632950
"somegroup"
29642951
}
29652952

2966-
// The implementations for get_system_time are separated because some options, such
2967-
// as ctime will not be available
2968-
#[cfg(unix)]
2969-
fn get_system_time(md: &Metadata, config: &Config) -> Option<SystemTime> {
2970-
match config.time {
2971-
Time::Change => Some(UNIX_EPOCH + Duration::new(md.ctime() as u64, md.ctime_nsec() as u32)),
2972-
Time::Modification => md.modified().ok(),
2973-
Time::Access => md.accessed().ok(),
2974-
Time::Birth => md.created().ok(),
2975-
}
2976-
}
2977-
2978-
#[cfg(not(unix))]
2979-
fn get_system_time(md: &Metadata, config: &Config) -> Option<SystemTime> {
2980-
match config.time {
2981-
Time::Modification => md.modified().ok(),
2982-
Time::Access => md.accessed().ok(),
2983-
Time::Birth => md.created().ok(),
2984-
Time::Change => None,
2985-
}
2986-
}
2987-
29882953
fn display_date(
29892954
metadata: &Metadata,
29902955
config: &Config,
29912956
state: &mut ListState,
29922957
out: &mut Vec<u8>,
29932958
) -> UResult<()> {
2994-
let Some(time) = get_system_time(metadata, config) else {
2959+
let Some(time) = metadata_get_time(metadata, config.time) else {
29952960
out.extend(b"???");
29962961
return Ok(());
29972962
};

0 commit comments

Comments
 (0)