From d11d595fda83f738a20d292ead5dfa14493cb351 Mon Sep 17 00:00:00 2001 From: Yash Thakur <45539777+ysthakur@users.noreply.github.com> Date: Sun, 10 Mar 2024 03:05:59 -0400 Subject: [PATCH] touch: Respect -h when getting metadata (#5951) * Add tests that stat symlinks * Check follow first in stat * Don't run tests on FreeBSD It would be possible to get them to run on FreeBSD by avoiding get_symlink_times, but the behavior we're testing is not platform-specific, so it's fine to not test it on FreeBSD. --------- Co-authored-by: Sylvestre Ledru --- src/uu/touch/src/touch.rs | 12 ++++++------ tests/by-util/test_touch.rs | 39 ++++++++++++++++++++++++++++++++++++- 2 files changed, 44 insertions(+), 7 deletions(-) diff --git a/src/uu/touch/src/touch.rs b/src/uu/touch/src/touch.rs index e4dd4076d..fe1783b21 100644 --- a/src/uu/touch/src/touch.rs +++ b/src/uu/touch/src/touch.rs @@ -326,12 +326,12 @@ fn update_times( // If `follow` is `true`, the function will try to follow symlinks // If `follow` is `false` or the symlink is broken, the function will return metadata of the symlink itself fn stat(path: &Path, follow: bool) -> UResult<(FileTime, FileTime)> { - let metadata = match fs::metadata(path) { - Ok(metadata) => metadata, - Err(e) if e.kind() == std::io::ErrorKind::NotFound && !follow => fs::symlink_metadata(path) - .map_err_context(|| format!("failed to get attributes of {}", path.quote()))?, - Err(e) => return Err(e.into()), - }; + let metadata = if follow { + fs::metadata(path).or_else(|_| fs::symlink_metadata(path)) + } else { + fs::symlink_metadata(path) + } + .map_err_context(|| format!("failed to get attributes of {}", path.quote()))?; Ok(( FileTime::from_last_access_time(&metadata), diff --git a/tests/by-util/test_touch.rs b/tests/by-util/test_touch.rs index eead33836..3af129d49 100644 --- a/tests/by-util/test_touch.rs +++ b/tests/by-util/test_touch.rs @@ -5,7 +5,7 @@ // spell-checker:ignore (formats) cymdhm cymdhms mdhm mdhms ymdhm ymdhms datetime mktime use crate::common::util::{AtPath, TestScenario}; -use filetime::FileTime; +use filetime::{self, set_symlink_file_times, FileTime}; use std::fs::remove_file; use std::path::PathBuf; @@ -854,3 +854,40 @@ fn test_touch_invalid_date_format() { .fails() .stderr_contains("touch: invalid date format '+1000000000000 years'"); } + +#[test] +#[cfg(not(target_os = "freebsd"))] +fn test_touch_symlink_with_no_deref() { + let (at, mut ucmd) = at_and_ucmd!(); + let target = "foo.txt"; + let symlink = "bar.txt"; + let time = FileTime::from_unix_time(123, 0); + + at.touch(target); + at.relative_symlink_file(target, symlink); + set_symlink_file_times(at.plus(symlink), time, time).unwrap(); + + ucmd.args(&["-a", "--no-dereference", symlink]).succeeds(); + // Modification time shouldn't be set to the destination's modification time + assert_eq!(time, get_symlink_times(&at, symlink).1); +} + +#[test] +#[cfg(not(target_os = "freebsd"))] +fn test_touch_reference_symlink_with_no_deref() { + let (at, mut ucmd) = at_and_ucmd!(); + let target = "foo.txt"; + let symlink = "bar.txt"; + let arg = "baz.txt"; + let time = FileTime::from_unix_time(123, 0); + + at.touch(target); + at.relative_symlink_file(target, symlink); + set_symlink_file_times(at.plus(symlink), time, time).unwrap(); + at.touch(arg); + + ucmd.args(&["--reference", symlink, "--no-dereference", arg]) + .succeeds(); + // Times should be taken from the symlink, not the destination + assert_eq!((time, time), get_symlink_times(&at, arg)); +}