diff --git a/src/uu/touch/src/touch.rs b/src/uu/touch/src/touch.rs index de66e52ee..323d7a11d 100644 --- a/src/uu/touch/src/touch.rs +++ b/src/uu/touch/src/touch.rs @@ -135,12 +135,57 @@ fn filetime_to_datetime(ft: &FileTime) -> Option> { Some(DateTime::from_timestamp(ft.unix_seconds(), ft.nanoseconds())?.into()) } +/// Whether all characters in the string are digits. +fn all_digits(s: &str) -> bool { + s.as_bytes().iter().all(u8::is_ascii_digit) +} + +/// Convert a two-digit year string to the corresponding number. +fn get_year(s: &str) -> u8 { + // Pre-condition: s.len() >= 2 + let bytes = s.as_bytes(); + let y1 = bytes[0] - b'0'; + let y2 = bytes[1] - b'0'; + 10 * y1 + y2 +} + +/// Whether the first filename should be interpreted as a timestamp. +fn is_first_filename_timestamp( + reference: Option<&OsString>, + date: Option<&str>, + timestamp: Option<&String>, + files: &[&String], +) -> bool { + match std::env::var("_POSIX2_VERSION") { + Ok(s) if s == "199209" => { + if timestamp.is_none() && reference.is_none() && date.is_none() { + if files.len() >= 2 { + let s = files[0]; + if s.len() == 8 && all_digits(s) { + true + } else if s.len() == 10 && all_digits(s) { + let year = get_year(s); + (69..=99).contains(&year) + } else { + false + } + } else { + false + } + } else { + false + } + } + _ => false, + } +} + #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { let matches = uu_app().try_get_matches_from(args)?; - let files: Vec = matches - .get_many::(ARG_FILES) + let mut filenames: Vec<&String> = matches + .get_many::(ARG_FILES) .ok_or_else(|| { USimpleError::new( 1, @@ -150,19 +195,23 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { ), ) })? - .map(|filename| { - if filename == "-" { - InputFile::Stdout - } else { - InputFile::Path(PathBuf::from(filename)) - } - }) .collect(); let no_deref = matches.get_flag(options::NO_DEREF); let reference = matches.get_one::(options::sources::REFERENCE); - let timestamp = matches.get_one::(options::sources::TIMESTAMP); + let date = matches + .get_one::(options::sources::DATE) + .map(|date| date.to_owned()); + + let mut timestamp = matches.get_one::(options::sources::TIMESTAMP); + + if is_first_filename_timestamp(reference, date.as_deref(), timestamp, &filenames) { + let head = filenames[0]; + let tail = &filenames[1..]; + timestamp = Some(head); + filenames = tail.to_vec(); + } let source = if let Some(reference) = reference { Source::Reference(PathBuf::from(reference)) @@ -172,9 +221,16 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { Source::Now }; - let date = matches - .get_one::(options::sources::DATE) - .map(|date| date.to_owned()); + let files: Vec = filenames + .into_iter() + .map(|filename| { + if filename == "-" { + InputFile::Stdout + } else { + InputFile::Path(PathBuf::from(filename)) + } + }) + .collect(); let opts = Options { no_create: matches.get_flag(options::NO_CREATE), @@ -275,7 +331,6 @@ pub fn uu_app() -> Command { Arg::new(ARG_FILES) .action(ArgAction::Append) .num_args(1..) - .value_parser(ValueParser::os_string()) .value_hint(clap::ValueHint::AnyPath), ) .group( diff --git a/tests/by-util/test_touch.rs b/tests/by-util/test_touch.rs index a0d51c208..194ac0e7b 100644 --- a/tests/by-util/test_touch.rs +++ b/tests/by-util/test_touch.rs @@ -917,3 +917,27 @@ fn test_touch_reference_symlink_with_no_deref() { // Times should be taken from the symlink, not the destination assert_eq!((time, time), get_symlink_times(&at, arg)); } + +#[test] +fn test_obsolete_posix_format() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.env("_POSIX2_VERSION", "199209") + .env("POSIXLY_CORRECT", "1") + .args(&["01010000", "11111111"]) + .succeeds() + .no_output(); + assert!(at.file_exists("11111111")); + assert!(!at.file_exists("01010000")); +} + +#[test] +fn test_obsolete_posix_format_with_year() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.env("_POSIX2_VERSION", "199209") + .env("POSIXLY_CORRECT", "1") + .args(&["9001010000", "11111111"]) + .succeeds() + .no_output(); + assert!(at.file_exists("11111111")); + assert!(!at.file_exists("01010000")); +}