diff --git a/Cargo.lock b/Cargo.lock index ebb288d3d..838f28a26 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1687,6 +1687,7 @@ name = "uu_install" version = "0.0.4" dependencies = [ "clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)", + "filetime 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", "uucore 0.0.7", diff --git a/src/uu/install/Cargo.toml b/src/uu/install/Cargo.toml index 5280184fe..9841ac64a 100644 --- a/src/uu/install/Cargo.toml +++ b/src/uu/install/Cargo.toml @@ -19,6 +19,7 @@ path = "src/install.rs" [dependencies] clap = "2.33" +filetime = "0.2" libc = ">= 0.2" uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["mode", "perms", "entries"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/install/src/install.rs b/src/uu/install/src/install.rs index b41b16ef6..9d1acdc7e 100644 --- a/src/uu/install/src/install.rs +++ b/src/uu/install/src/install.rs @@ -13,6 +13,7 @@ mod mode; extern crate uucore; use clap::{App, Arg, ArgMatches}; +use filetime::{set_file_times, FileTime}; use uucore::entries::{grp2gid, usr2uid}; use uucore::perms::{wrap_chgrp, wrap_chown, Verbosity}; @@ -32,6 +33,7 @@ pub struct Behavior { owner: String, group: String, verbose: bool, + preserve_timestamps: bool, } #[derive(Clone, Eq, PartialEq)] @@ -154,11 +156,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .takes_value(true) ) .arg( - // TODO implement flag Arg::with_name(OPT_PRESERVE_TIMESTAMPS) .short("p") .long(OPT_PRESERVE_TIMESTAMPS) - .help("(unimplemented) apply access/modification times of SOURCE files to corresponding destination files") + .help("apply access/modification times of SOURCE files to corresponding destination files") ) .arg( // TODO implement flag @@ -265,8 +266,6 @@ fn check_unimplemented<'a>(matches: &ArgMatches) -> Result<(), &'a str> { Err("--compare, -C") } else if matches.is_present(OPT_CREATED) { Err("-D") - } else if matches.is_present(OPT_PRESERVE_TIMESTAMPS) { - Err("--preserve-timestamps, -p") } else if matches.is_present(OPT_STRIP) { Err("--strip, -s") } else if matches.is_present(OPT_STRIP_PROGRAM) { @@ -338,6 +337,7 @@ fn behavior(matches: &ArgMatches) -> Result { owner: matches.value_of(OPT_OWNER).unwrap_or("").to_string(), group: matches.value_of(OPT_GROUP).unwrap_or("").to_string(), verbose: matches.is_present(OPT_VERBOSE), + preserve_timestamps: matches.is_present(OPT_PRESERVE_TIMESTAMPS), }) } @@ -555,6 +555,21 @@ fn copy(from: &PathBuf, to: &PathBuf, b: &Behavior) -> Result<(), ()> { } } + if b.preserve_timestamps { + let meta = match fs::metadata(from) { + Ok(meta) => meta, + Err(f) => crash!(1, "{}", f.to_string()), + }; + + let modified_time = FileTime::from_last_modification_time(&meta); + let accessed_time = FileTime::from_last_access_time(&meta); + + match set_file_times(to.as_path(), accessed_time, modified_time) { + Ok(_) => {} + Err(e) => show_info!("{}", e), + } + } + if b.verbose { show_info!("'{}' -> '{}'", from.display(), to.display()); } diff --git a/tests/by-util/test_install.rs b/tests/by-util/test_install.rs index a04c0ddfc..7b3706f9e 100644 --- a/tests/by-util/test_install.rs +++ b/tests/by-util/test_install.rs @@ -309,6 +309,31 @@ fn test_install_target_new_file_failing_nonexistent_parent() { assert!(err.contains("not a directory")) } +#[test] +fn test_install_preserve_timestamps() { + let (at, mut ucmd) = at_and_ucmd!(); + let file1 = "test_install_target_dir_file_a1"; + let file2 = "test_install_target_dir_file_a2"; + at.touch(file1); + + ucmd.arg(file1).arg(file2).arg("-p").succeeds().no_stderr(); + + assert!(at.file_exists(file1)); + assert!(at.file_exists(file2)); + + let file1_metadata = at.metadata(file1); + let file2_metadata = at.metadata(file2); + + assert_eq!( + file1_metadata.accessed().ok(), + file2_metadata.accessed().ok() + ); + assert_eq!( + file1_metadata.modified().ok(), + file2_metadata.modified().ok() + ); +} + // These two tests are failing but should work #[test] fn test_install_copy_file() {