Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions src/uu/touch/src/touch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
39 changes: 38 additions & 1 deletion tests/by-util/test_touch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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));
}