diff --git a/Makefile b/Makefile index e6312c2a1..08f722cd5 100644 --- a/Makefile +++ b/Makefile @@ -191,6 +191,7 @@ TEST_PROGS := \ stdbuf \ tac \ test \ + touch \ tr \ true \ truncate \ diff --git a/deps/Cargo.toml b/deps/Cargo.toml index 9607091c3..0e48c371f 100644 --- a/deps/Cargo.toml +++ b/deps/Cargo.toml @@ -22,3 +22,4 @@ winapi = "0.2" advapi32-sys = "0.1" kernel32-sys = "0.1" walker = "^1.0.0" +filetime = "0.1" diff --git a/src/mv/deps.mk b/src/mv/deps.mk index b6534caec..0e162f8f0 100644 --- a/src/mv/deps.mk +++ b/src/mv/deps.mk @@ -1 +1 @@ -DEPLIBS += time +DEPLIBS += kernel32 winapi filetime time diff --git a/src/test/test.rs b/src/test/test.rs index f1d587c75..7cd446e69 100644 --- a/src/test/test.rs +++ b/src/test/test.rs @@ -1,5 +1,4 @@ #![crate_name = "test"] -#![feature(convert)] /* * This file is part of the uutils coreutils package. @@ -24,7 +23,7 @@ static NAME: &'static str = "test"; pub fn uumain(_: Vec) -> i32 { let args = args_os().collect::>(); // This is completely disregarding valid windows paths that aren't valid unicode - let args = args.iter().map(|a| a.to_bytes().unwrap()).collect::>(); + let args = args.iter().map(|a| a.to_str().unwrap().as_bytes()).collect::>(); if args.len() == 0 { return 2; } diff --git a/src/touch/deps.mk b/src/touch/deps.mk index b6534caec..0e162f8f0 100644 --- a/src/touch/deps.mk +++ b/src/touch/deps.mk @@ -1 +1 @@ -DEPLIBS += time +DEPLIBS += kernel32 winapi filetime time diff --git a/src/touch/touch.rs b/src/touch/touch.rs index 05d2157c2..9f51f9837 100644 --- a/src/touch/touch.rs +++ b/src/touch/touch.rs @@ -1,5 +1,4 @@ #![crate_name = "touch"] -#![feature(fs_time)] /* * This file is part of the uutils coreutils package. @@ -13,14 +12,11 @@ extern crate getopts; extern crate libc; extern crate time; +extern crate filetime; -use libc::types::os::arch::c95::c_char; -use libc::types::os::arch::posix01::stat as stat_t; -use libc::funcs::posix88::stat_::stat as c_stat; -use libc::funcs::posix01::stat_::lstat as c_lstat; -use std::fs::{set_file_times, File}; +use filetime::*; +use std::fs::{self, File}; use std::io::{Error, Write}; -use std::mem::uninitialized; use std::path::Path; #[path = "../common/util.rs"] @@ -35,6 +31,24 @@ use filesystem::UUPathExt; static NAME: &'static str = "touch"; static VERSION: &'static str = "1.0.0"; +// Since touch's date/timestamp parsing doesn't account for timezone, the +// returned value from time::strptime() is UTC. We get system's timezone to +// localize the time. +macro_rules! to_local( + ($exp:expr) => ({ + let mut tm = $exp; + tm.tm_utcoff = time::now().tm_utcoff; + tm + }) +); + +macro_rules! local_tm_to_filetime( + ($exp:expr) => ({ + let ts = $exp.to_timespec(); + FileTime::from_seconds_since_1970(ts.sec as u64, ts.nsec as u32) + }) +); + pub fn uumain(args: Vec) -> i32 { let mut opts = getopts::Options::new(); @@ -92,8 +106,7 @@ pub fn uumain(args: Vec) -> i32 { }; (timestamp, timestamp) } else { - // FIXME: Should use Timespec. https://github.com/mozilla/rust/issues/10301 - let now = (time::get_time().sec * 1000) as u64; + let now = local_tm_to_filetime!(time::now()); (now, now) }; @@ -142,7 +155,7 @@ pub fn uumain(args: Vec) -> i32 { // this follows symlinks and thus does not work correctly for the -h flag // need to use lutimes() c function on supported platforms - match set_file_times(path, atime, mtime) { + match filetime::set_file_times(path, atime, mtime) { Err(e) => show_warning!("cannot touch '{}': {}", path, e), _ => (), }; @@ -151,60 +164,47 @@ pub fn uumain(args: Vec) -> i32 { 0 } -fn stat(path: &str, follow: bool) -> (u64, u64) { - let stat_fn = if follow { - c_stat +fn stat(path: &str, follow: bool) -> (FileTime, FileTime) { + let metadata = if follow { + fs::symlink_metadata(path) } else { - c_lstat + fs::metadata(path) }; - let mut st: stat_t = unsafe { uninitialized() }; - let result = unsafe { stat_fn(path.as_ptr() as *const c_char, &mut st as *mut stat_t) }; - if result < 0 { - crash!(1, "failed to get attributes of '{}': {}", path, Error::last_os_error()); + 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()) } - - // set_file_times expects milliseconds - let atime = if st.st_atime_nsec == 0 { - st.st_atime * 1000 - } else { - st.st_atime_nsec / 1000 - } as u64; - - // set_file_times expects milliseconds - let mtime = if st.st_mtime_nsec == 0 { - st.st_mtime * 1000 - } else { - st.st_mtime_nsec / 1000 - } as u64; - - (atime, mtime) } -fn parse_date(str: &str) -> u64 { +fn parse_date(str: &str) -> FileTime { // This isn't actually compatible with GNU touch, but there doesn't seem to // be any simple specification for what format this parameter allows and I'm // not about to implement GNU parse_datetime. // http://git.savannah.gnu.org/gitweb/?p=gnulib.git;a=blob_plain;f=lib/parse-datetime.y match time::strptime(str, "%c") { - Ok(tm) => (tm.to_timespec().sec * 1000) as u64, + Ok(tm) => local_tm_to_filetime!(to_local!(tm)), Err(e) => panic!("Unable to parse date\n{}", e) } } -fn parse_timestamp(str: &str) -> u64 { - let format = match str.chars().count() { - 15 => "%Y%m%d%H%M.%S", - 12 => "%Y%m%d%H%M", - 13 => "%y%m%d%H%M.%S", - 10 => "%y%m%d%H%M", - 11 => "%m%d%H%M.%S", - 8 => "%m%d%H%M", +fn parse_timestamp(s: &str) -> FileTime { + let now = time::now(); + let (format, ts) = match s.chars().count() { + 15 => ("%Y%m%d%H%M.%S", s.to_string()), + 12 => ("%Y%m%d%H%M", s.to_string()), + 13 => ("%y%m%d%H%M.%S", s.to_string()), + 10 => ("%y%m%d%H%M", s.to_string()), + 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") }; - match time::strptime(str, format) { - Ok(tm) => (tm.to_timespec().sec * 1000) as u64, + match time::strptime(&ts, format) { + Ok(tm) => local_tm_to_filetime!(to_local!(tm)), Err(e) => panic!("Unable to parse timestamp\n{}", e) } } diff --git a/test/common/util.rs b/test/common/util.rs index 690e7a20a..af86c26ba 100644 --- a/test/common/util.rs +++ b/test/common/util.rs @@ -97,6 +97,13 @@ pub fn resolve_link(path: &str) -> String { } } +pub fn metadata(path: &str) -> fs::Metadata { + match fs::metadata(path) { + Ok(m) => m, + Err(e) => panic!("{}", e) + } +} + pub fn file_exists(path: &str) -> bool { match fs::metadata(path) { Ok(m) => m.is_file(), diff --git a/test/mv.rs b/test/mv.rs index 74f2943b9..9ab811ae9 100644 --- a/test/mv.rs +++ b/test/mv.rs @@ -1,10 +1,10 @@ -#![feature(fs_time)] - extern crate libc; extern crate time; +extern crate kernel32; +extern crate winapi; +extern crate filetime; -use std::fs; -use std::path::Path; +use filetime::*; use std::process::Command; use util::*; @@ -272,9 +272,11 @@ fn test_mv_update_option() { touch(file_a); touch(file_b); - let now = (time::get_time().sec * 1000) as u64; - fs::set_file_times(Path::new(file_a), now, now).unwrap(); - fs::set_file_times(Path::new(file_b), now, now+3600).unwrap(); + let ts = time::now().to_timespec(); + let now = FileTime::from_seconds_since_1970(ts.sec as u64, ts.nsec as u32); + let later = FileTime::from_seconds_since_1970(ts.sec as u64 + 3600, ts.nsec as u32); + filetime::set_file_times(file_a, now, now).unwrap(); + filetime::set_file_times(file_b, now, later).unwrap(); let result1 = run(Command::new(PROGNAME).arg("--update").arg(file_a).arg(file_b)); diff --git a/test/touch.rs b/test/touch.rs new file mode 100644 index 000000000..642844f18 --- /dev/null +++ b/test/touch.rs @@ -0,0 +1,258 @@ +extern crate libc; +extern crate time; +extern crate kernel32; +extern crate winapi; +extern crate filetime; + +use filetime::FileTime; +use std::process::Command; +use util::*; + +static PROGNAME: &'static str = "./touch"; + +#[path = "common/util.rs"] +#[macro_use] +mod util; + +fn get_file_times(path: &str) -> (FileTime, FileTime) { + let m = metadata(path); + (FileTime::from_last_access_time(&m), FileTime::from_last_modification_time(&m)) +} + +fn set_file_times(path: &str, atime: FileTime, mtime: FileTime) { + filetime::set_file_times(path, atime, mtime).unwrap() +} + +// Adjusts for local timezone +fn str_to_filetime(format: &str, s: &str) -> FileTime { + let mut tm = time::strptime(s, format).unwrap(); + tm.tm_utcoff = time::now().tm_utcoff; + let ts = tm.to_timespec(); + FileTime::from_seconds_since_1970(ts.sec as u64, ts.nsec as u32) +} + +#[test] +fn test_touch_default() { + let file = "test_touch_default_file"; + + let result = run(Command::new(PROGNAME).arg(file)); + assert_empty_stderr!(result); + assert!(result.success); + + assert!(file_exists(file)); +} + +#[test] +fn test_touch_no_create_file_absent() { + let file = "test_touch_no_create_file_absent"; + + let result = run(Command::new(PROGNAME).arg("-c").arg(file)); + assert_empty_stderr!(result); + assert!(result.success); + + assert!(!file_exists(file)); +} + +#[test] +fn test_touch_no_create_file_exists() { + let file = "test_touch_no_create_file_exists"; + + touch(file); + assert!(file_exists(file)); + + let result = run(Command::new(PROGNAME).arg("-c").arg(file)); + assert_empty_stderr!(result); + assert!(result.success); + + assert!(file_exists(file)); +} + +#[test] +fn test_touch_set_mdhm_time() { + let file = "test_touch_set_mdhm_time"; + + let result = run(Command::new(PROGNAME).args(&["-t", "01011234", file])); + assert_empty_stderr!(result); + assert!(result.success); + + assert!(file_exists(file)); + + let start_of_year = str_to_filetime("%Y%m%d%H%M", "201501010000"); + let (atime, mtime) = get_file_times(file); + assert_eq!(atime, mtime); + assert_eq!(atime.seconds_relative_to_1970() - start_of_year.seconds_relative_to_1970(), 45240); + assert_eq!(mtime.seconds_relative_to_1970() - start_of_year.seconds_relative_to_1970(), 45240); +} + +#[test] +fn test_touch_set_mdhms_time() { + let file = "test_touch_set_mdhms_time"; + + let result = run(Command::new(PROGNAME).args(&["-t", "01011234.56", file])); + assert_empty_stderr!(result); + assert!(result.success); + + assert!(file_exists(file)); + + let start_of_year = str_to_filetime("%Y%m%d%H%M.%S", "201501010000.00"); + let (atime, mtime) = get_file_times(file); + assert_eq!(atime, mtime); + assert_eq!(atime.seconds_relative_to_1970() - start_of_year.seconds_relative_to_1970(), 45296); + assert_eq!(mtime.seconds_relative_to_1970() - start_of_year.seconds_relative_to_1970(), 45296); +} + +#[test] +fn test_touch_set_ymdhm_time() { + let file = "test_touch_set_ymdhm_time"; + + let result = run(Command::new(PROGNAME).args(&["-t", "1501011234", file])); + assert_empty_stderr!(result); + assert!(result.success); + + assert!(file_exists(file)); + + let start_of_year = str_to_filetime("%y%m%d%H%M", "1501010000"); + let (atime, mtime) = get_file_times(file); + assert_eq!(atime, mtime); + assert_eq!(atime.seconds_relative_to_1970() - start_of_year.seconds_relative_to_1970(), 45240); + assert_eq!(mtime.seconds_relative_to_1970() - start_of_year.seconds_relative_to_1970(), 45240); +} + +#[test] +fn test_touch_set_ymdhms_time() { + let file = "test_touch_set_ymdhms_time"; + + let result = run(Command::new(PROGNAME).args(&["-t", "1501011234.56", file])); + assert_empty_stderr!(result); + assert!(result.success); + + assert!(file_exists(file)); + + let start_of_year = str_to_filetime("%y%m%d%H%M.%S", "1501010000.00"); + let (atime, mtime) = get_file_times(file); + assert_eq!(atime, mtime); + assert_eq!(atime.seconds_relative_to_1970() - start_of_year.seconds_relative_to_1970(), 45296); + assert_eq!(mtime.seconds_relative_to_1970() - start_of_year.seconds_relative_to_1970(), 45296); +} + +#[test] +fn test_touch_set_cymdhm_time() { + let file = "test_touch_set_cymdhm_time"; + + let result = run(Command::new(PROGNAME).args(&["-t", "201501011234", file])); + assert_empty_stderr!(result); + assert!(result.success); + + assert!(file_exists(file)); + + let start_of_year = str_to_filetime("%Y%m%d%H%M", "201501010000"); + let (atime, mtime) = get_file_times(file); + assert_eq!(atime, mtime); + assert_eq!(atime.seconds_relative_to_1970() - start_of_year.seconds_relative_to_1970(), 45240); + assert_eq!(mtime.seconds_relative_to_1970() - start_of_year.seconds_relative_to_1970(), 45240); +} + +#[test] +fn test_touch_set_cymdhms_time() { + let file = "test_touch_set_cymdhms_time"; + + let result = run(Command::new(PROGNAME).args(&["-t", "201501011234.56", file])); + assert_empty_stderr!(result); + assert!(result.success); + + assert!(file_exists(file)); + + let start_of_year = str_to_filetime("%Y%m%d%H%M.%S", "201501010000.00"); + let (atime, mtime) = get_file_times(file); + assert_eq!(atime, mtime); + assert_eq!(atime.seconds_relative_to_1970() - start_of_year.seconds_relative_to_1970(), 45296); + assert_eq!(mtime.seconds_relative_to_1970() - start_of_year.seconds_relative_to_1970(), 45296); +} + +#[test] +fn test_touch_set_only_atime() { + let file = "test_touch_set_only_atime"; + + let result = run(Command::new(PROGNAME).args(&["-t", "201501011234", "-a", file])); + assert_empty_stderr!(result); + assert!(result.success); + + assert!(file_exists(file)); + + let start_of_year = str_to_filetime("%Y%m%d%H%M", "201501010000"); + let (atime, mtime) = get_file_times(file); + assert!(atime != mtime); + assert_eq!(atime.seconds_relative_to_1970() - start_of_year.seconds_relative_to_1970(), 45240); +} + +#[test] +fn test_touch_set_only_mtime() { + let file = "test_touch_set_only_mtime"; + + let result = run(Command::new(PROGNAME).args(&["-t", "201501011234", "-m", file])); + assert_empty_stderr!(result); + assert!(result.success); + + assert!(file_exists(file)); + + let start_of_year = str_to_filetime("%Y%m%d%H%M", "201501010000"); + let (atime, mtime) = get_file_times(file); + assert!(atime != mtime); + assert_eq!(mtime.seconds_relative_to_1970() - start_of_year.seconds_relative_to_1970(), 45240); +} + +#[test] +fn test_touch_set_both() { + let file = "test_touch_set_both"; + + let result = run(Command::new(PROGNAME).args(&["-t", "201501011234", "-a", "-m", file])); + assert_empty_stderr!(result); + assert!(result.success); + + assert!(file_exists(file)); + + let start_of_year = str_to_filetime("%Y%m%d%H%M", "201501010000"); + let (atime, mtime) = get_file_times(file); + assert_eq!(atime, mtime); + assert_eq!(atime.seconds_relative_to_1970() - start_of_year.seconds_relative_to_1970(), 45240); + assert_eq!(mtime.seconds_relative_to_1970() - start_of_year.seconds_relative_to_1970(), 45240); +} + +#[test] +fn test_touch_reference() { + let file_a = "test_touch_reference_a"; + let file_b = "test_touch_reference_b"; + let start_of_year = str_to_filetime("%Y%m%d%H%M", "201501010000"); + + touch(file_a); + set_file_times(file_a, start_of_year, start_of_year); + assert!(file_exists(file_a)); + + let result = run(Command::new(PROGNAME).args(&["-r", file_a, file_b])); + assert_empty_stderr!(result); + assert!(result.success); + + assert!(file_exists(file_b)); + + let (atime, mtime) = get_file_times(file_b); + assert_eq!(atime, mtime); + assert_eq!(atime, start_of_year); + assert_eq!(mtime, start_of_year); +} + +#[test] +fn test_touch_set_date() { + let file = "test_touch_set_date"; + + let result = run(Command::new(PROGNAME).args(&["-d", "Thu Jan 01 12:34:00 2015", file])); + assert_empty_stderr!(result); + assert!(result.success); + + assert!(file_exists(file)); + + let start_of_year = str_to_filetime("%Y%m%d%H%M", "201501011234"); + let (atime, mtime) = get_file_times(file); + assert_eq!(atime, mtime); + assert_eq!(atime, start_of_year); + assert_eq!(mtime, start_of_year); +}