From d0196b82adf8c3b41fbaad6c1ddeea0ceaedc51f Mon Sep 17 00:00:00 2001 From: knight42 Date: Sun, 28 Aug 2016 06:48:59 +0800 Subject: [PATCH 1/2] touch: respect -h --- src/touch/Cargo.toml | 7 +- src/touch/touch.rs | 148 +++++++++++++++++++++++++++---------------- 2 files changed, 97 insertions(+), 58 deletions(-) diff --git a/src/touch/Cargo.toml b/src/touch/Cargo.toml index 6dffc3f00..efe94b554 100644 --- a/src/touch/Cargo.toml +++ b/src/touch/Cargo.toml @@ -10,9 +10,12 @@ path = "touch.rs" [dependencies] filetime = "*" getopts = "*" -libc = "*" time = "*" -uucore = { path="../uucore" } + +[dependencies.uucore] +path = "../uucore" +default-features = false +features = ["libc"] [[bin]] name = "touch" diff --git a/src/touch/touch.rs b/src/touch/touch.rs index b96f61a77..dd6907ae4 100644 --- a/src/touch/touch.rs +++ b/src/touch/touch.rs @@ -1,25 +1,24 @@ #![crate_name = "uu_touch"] -/* - * This file is part of the uutils coreutils package. - * - * (c) Nick Platt - * - * For the full copyright and license information, please view the LICENSE file - * that was distributed with this source code. - */ +// This file is part of the uutils coreutils package. +// +// (c) Nick Platt +// (c) Jian Zeng +// +// For the full copyright and license information, please view the LICENSE file +// that was distributed with this source code. +// extern crate getopts; -extern crate libc; extern crate time; -extern crate filetime; +pub extern crate filetime; #[macro_use] extern crate uucore; use filetime::*; use std::fs::{self, File}; -use std::io::{Error, Write}; +use std::io::{self, Error, Write}; use std::path::Path; static NAME: &'static str = "touch"; @@ -43,26 +42,65 @@ macro_rules! local_tm_to_filetime( }) ); +macro_rules! to_timeval { + ($ft:expr) => ( + timeval { + tv_sec: $ft.seconds() as time_t, + tv_usec: ($ft.nanoseconds() / 1000) as suseconds_t, + } + ) +} + +#[cfg(unix)] +fn set_symlink_times(p: &str, atime: FileTime, mtime: FileTime) -> io::Result<()> { + use std::ffi::CString; + use uucore::libc::{lutimes, timeval, time_t, suseconds_t}; + + let times = [to_timeval!(atime), to_timeval!(mtime)]; + let p = try!(CString::new(p)); + return unsafe { + if lutimes(p.as_ptr() as *const _, times.as_ptr()) == 0 { + Ok(()) + } else { + Err(io::Error::last_os_error()) + } + }; +} + pub fn uumain(args: Vec) -> i32 { let mut opts = getopts::Options::new(); - opts.optflag("a", "", "change only the access time"); - opts.optflag("c", "no-create", "do not create any files"); - opts.optopt( "d", "date", "parse argument and use it instead of current time", "STRING"); - opts.optflag("h", "no-dereference", "affect each symbolic link instead of any referenced file \ + opts.optflag("a", "", "change only the access time"); + opts.optflag("c", "no-create", "do not create any files"); + opts.optopt("d", + "date", + "parse argument and use it instead of current time", + "STRING"); + opts.optflag("h", + "no-dereference", + "affect each symbolic link instead of any referenced file \ (only for systems that can change the timestamps of a symlink)"); - opts.optflag("m", "", "change only the modification time"); - opts.optopt( "r", "reference", "use this file's times instead of the current time", "FILE"); - opts.optopt( "t", "", "use [[CC]YY]MMDDhhmm[.ss] instead of the current time", "STAMP"); - opts.optopt( "", "time", "change only the specified time: \"access\", \"atime\", or \ + opts.optflag("m", "", "change only the modification time"); + opts.optopt("r", + "reference", + "use this file's times instead of the current time", + "FILE"); + opts.optopt("t", + "", + "use [[CC]YY]MMDDhhmm[.ss] instead of the current time", + "STAMP"); + opts.optopt("", + "time", + "change only the specified time: \"access\", \"atime\", or \ \"use\" are equivalent to -a; \"modify\" or \"mtime\" are \ - equivalent to -m", "WORD"); - opts.optflag("h", "help", "display this help and exit"); - opts.optflag("V", "version", "output version information and exit"); + equivalent to -m", + "WORD"); + opts.optflag("h", "help", "display this help and exit"); + opts.optflag("V", "version", "output version information and exit"); let matches = match opts.parse(&args[1..]) { - Ok(m) => m, - Err(e) => panic!("Invalid options\n{}", e) + Ok(m) => m, + Err(e) => panic!("Invalid options\n{}", e), }; if matches.opt_present("version") { @@ -89,20 +127,20 @@ pub fn uumain(args: Vec) -> i32 { panic!("Invalid options: cannot specify reference time from more than one source"); } - let (mut atime, mut mtime) = - if matches.opt_present("reference") { - stat(&matches.opt_str("reference").unwrap()[..], !matches.opt_present("no-dereference")) - } else if matches.opts_present(&["date".to_owned(), "t".to_owned()]) { - let timestamp = if matches.opt_present("date") { - parse_date(matches.opt_str("date").unwrap().as_ref()) - } else { - parse_timestamp(matches.opt_str("t").unwrap().as_ref()) - }; - (timestamp, timestamp) + let (mut atime, mut mtime) = if matches.opt_present("reference") { + stat(&matches.opt_str("reference").unwrap()[..], + !matches.opt_present("no-dereference")) + } else if matches.opts_present(&["date".to_owned(), "t".to_owned()]) { + let timestamp = if matches.opt_present("date") { + parse_date(matches.opt_str("date").unwrap().as_ref()) } else { - let now = local_tm_to_filetime!(time::now()); - (now, now) + parse_timestamp(matches.opt_str("t").unwrap().as_ref()) }; + (timestamp, timestamp) + } else { + let now = local_tm_to_filetime!(time::now()); + (now, now) + }; for filename in &matches.free { let path = &filename[..]; @@ -130,25 +168,26 @@ pub fn uumain(args: Vec) -> i32 { let st = stat(path, !matches.opt_present("no-dereference")); let time = matches.opt_strs("time"); - if !(matches.opt_present("a") || - time.contains(&"access".to_owned()) || - time.contains(&"atime".to_owned()) || - time.contains(&"use".to_owned())) { + if !(matches.opt_present("a") || time.contains(&"access".to_owned()) || + time.contains(&"atime".to_owned()) || time.contains(&"use".to_owned())) { atime = st.0; } - if !(matches.opt_present("m") || - time.contains(&"modify".to_owned()) || + if !(matches.opt_present("m") || time.contains(&"modify".to_owned()) || time.contains(&"mtime".to_owned())) { mtime = st.1; } } - // this follows symlinks and thus does not work correctly for the -h flag - // need to use lutimes() c function on supported platforms - if let Err(e) = filetime::set_file_times(path, atime, mtime) { - show_warning!("cannot touch '{}': {}", path, e); - }; + if matches.opt_present("h") { + if let Err(e) = set_symlink_times(path, atime, mtime) { + show_warning!("cannot touch '{}': {}", path, e); + } + } else { + if let Err(e) = filetime::set_file_times(path, atime, mtime) { + show_warning!("cannot touch '{}': {}", path, e); + } + } } 0 @@ -162,11 +201,8 @@ fn stat(path: &str, follow: bool) -> (FileTime, FileTime) { }; match metadata { - Ok(m) => ( - FileTime::from_last_access_time(&m), - FileTime::from_last_modification_time(&m) - ), - Err(_) => crash!(1, "failed to get attributes of '{}': {}", path, Error::last_os_error()) + Ok(m) => (FileTime::from_last_access_time(&m), FileTime::from_last_modification_time(&m)), + Err(_) => crash!(1, "failed to get attributes of '{}': {}", path, Error::last_os_error()), } } @@ -177,7 +213,7 @@ fn parse_date(str: &str) -> FileTime { // http://git.savannah.gnu.org/gitweb/?p=gnulib.git;a=blob_plain;f=lib/parse-datetime.y match time::strptime(str, "%c") { Ok(tm) => local_tm_to_filetime!(to_local!(tm)), - Err(e) => panic!("Unable to parse date\n{}", e) + Err(e) => panic!("Unable to parse date\n{}", e), } } @@ -189,12 +225,12 @@ fn parse_timestamp(s: &str) -> FileTime { 13 => ("%y%m%d%H%M.%S", s.to_owned()), 10 => ("%y%m%d%H%M", s.to_owned()), 11 => ("%Y%m%d%H%M.%S", format!("{}{}", now.tm_year + 1900, s)), - 8 => ("%Y%m%d%H%M", format!("{}{}", now.tm_year + 1900, s)), - _ => panic!("Unknown timestamp format") + 8 => ("%Y%m%d%H%M", format!("{}{}", now.tm_year + 1900, s)), + _ => panic!("Unknown timestamp format"), }; match time::strptime(&ts, format) { Ok(tm) => local_tm_to_filetime!(to_local!(tm)), - Err(e) => panic!("Unable to parse timestamp\n{}", e) + Err(e) => panic!("Unable to parse timestamp\n{}", e), } } From c277793f3883e4a4357231b1ad068914c05eb28c Mon Sep 17 00:00:00 2001 From: knight42 Date: Sun, 28 Aug 2016 07:12:58 +0800 Subject: [PATCH 2/2] touch: add test --- tests/common/util.rs | 7 +++++++ tests/test_touch.rs | 41 +++++++++++++++++++++++++++++++++++++---- 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/tests/common/util.rs b/tests/common/util.rs index 3c20fdb2c..d5b5ac476 100644 --- a/tests/common/util.rs +++ b/tests/common/util.rs @@ -291,6 +291,13 @@ impl AtPath { } } + pub fn symlink_metadata(&self, path: &str) -> fs::Metadata { + match fs::symlink_metadata(&self.plus(path)) { + Ok(m) => m, + Err(e) => panic!("{}", e), + } + } + pub fn metadata(&self, path: &str) -> fs::Metadata { match fs::metadata(&self.plus(path)) { Ok(m) => m, diff --git a/tests/test_touch.rs b/tests/test_touch.rs index 17069dc3a..1296dacdb 100644 --- a/tests/test_touch.rs +++ b/tests/test_touch.rs @@ -1,9 +1,9 @@ -extern crate filetime; +extern crate uu_touch; +use self::uu_touch::filetime::{self, FileTime}; + extern crate time; use common::util::*; -use self::filetime::FileTime; - fn get_file_times(at: &AtPath, path: &str) -> (FileTime, FileTime) { let m = at.metadata(path); @@ -11,6 +11,12 @@ fn get_file_times(at: &AtPath, path: &str) -> (FileTime, FileTime) { FileTime::from_last_modification_time(&m)) } +fn get_symlink_times(at: &AtPath, path: &str) -> (FileTime, FileTime) { + let m = at.symlink_metadata(path); + (FileTime::from_last_access_time(&m), + FileTime::from_last_modification_time(&m)) +} + fn set_file_times(at: &AtPath, path: &str, atime: FileTime, mtime: FileTime) { filetime::set_file_times(&at.plus_as_string(path), atime, mtime).unwrap() } @@ -29,7 +35,7 @@ fn test_touch_default() { let file = "test_touch_default_file"; ucmd.arg(file).succeeds().no_stderr(); - + assert!(at.file_exists(file)); } @@ -215,6 +221,33 @@ fn test_touch_set_both() { 45240); } +#[test] +fn test_touch_no_dereference() { + let (at, mut ucmd) = at_and_ucmd!(); + let file_a = "test_touch_no_dereference_a"; + let file_b = "test_touch_no_dereference_b"; + let start_of_year = str_to_filetime("%Y%m%d%H%M", "201501010000"); + let end_of_year = str_to_filetime("%Y%m%d%H%M", "201512312359"); + + at.touch(file_a); + set_file_times(&at, file_a, start_of_year, start_of_year); + at.symlink(file_a, file_b); + assert!(at.file_exists(file_a)); + assert!(at.is_symlink(file_b)); + + ucmd.args(&["-t", "201512312359", "-h", file_b]).succeeds().no_stderr(); + + let (atime, mtime) = get_symlink_times(&at, file_b); + assert_eq!(atime, mtime); + assert_eq!(atime, end_of_year); + assert_eq!(mtime, end_of_year); + + let (atime, mtime) = get_file_times(&at, file_a); + assert_eq!(atime, mtime); + assert_eq!(atime, start_of_year); + assert_eq!(mtime, start_of_year); +} + #[test] fn test_touch_reference() { let (at, mut ucmd) = at_and_ucmd!();