Skip to content
Open
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
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions src/uu/touch/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ thiserror = { workspace = true }
uucore = { workspace = true, features = ["libc", "parser"] }
fluent = { workspace = true }

[target.'cfg(unix)'.dependencies]
nix = { workspace = true, features = ["fs"] }

[dev-dependencies]
tempfile = { workspace = true }

[target.'cfg(target_os = "windows")'.dependencies]
windows-sys = { workspace = true, features = [
"Win32_Storage_FileSystem",
Expand Down
86 changes: 81 additions & 5 deletions src/uu/touch/src/touch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.

// spell-checker:ignore (ToDO) filetime datetime lpszfilepath mktime DATETIME datelike timelike
// spell-checker:ignore (ToDO) filetime datetime lpszfilepath mktime DATETIME datelike timelike futimens
// spell-checker:ignore (FORMATS) MMDDhhmm YYYYMMDDHHMM YYMMDDHHMM YYYYMMDDHHMMS

pub mod error;
Expand All @@ -16,8 +16,14 @@ use clap::builder::{PossibleValue, ValueParser};
use clap::{Arg, ArgAction, ArgGroup, ArgMatches, Command};
use filetime::{FileTime, set_file_times, set_symlink_file_times};
use jiff::{Timestamp, Zoned};
#[cfg(unix)]
use nix::sys::stat::futimens;
#[cfg(unix)]
use nix::sys::time::TimeSpec;
use std::borrow::Cow;
use std::ffi::OsString;
#[cfg(unix)]
use std::fs::OpenOptions;
use std::fs::{self, File};
use std::io::{Error, ErrorKind};
use std::path::{Path, PathBuf};
Expand Down Expand Up @@ -566,11 +572,41 @@ fn update_times(
// The filename, access time (atime), and modification time (mtime) are provided as inputs.

if opts.no_deref && !is_stdout {
set_symlink_file_times(path, atime, mtime)
} else {
set_file_times(path, atime, mtime)
return set_symlink_file_times(path, atime, mtime).map_err_context(
|| translate!("touch-error-setting-times-of-path", "path" => path.quote()),
);
}

#[cfg(unix)]
{
// Open write-only and use futimens to trigger IN_CLOSE_WRITE on Linux.
if !is_stdout && try_futimens_via_write_fd(path, atime, mtime).is_ok() {
return Ok(());
}
}
.map_err_context(|| translate!("touch-error-setting-times-of-path", "path" => path.quote()))

set_file_times(path, atime, mtime)
.map_err_context(|| translate!("touch-error-setting-times-of-path", "path" => path.quote()))
}

#[cfg(unix)]
/// Set file times via file descriptor using `futimens`.
///
/// This opens the file write-only and uses the POSIX `futimens` call to set
/// access and modification times on the open FD (not by path), which also
/// triggers `IN_CLOSE_WRITE` on Linux when the FD is closed.
fn try_futimens_via_write_fd(path: &Path, atime: FileTime, mtime: FileTime) -> std::io::Result<()> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please document this function. and explain what is futimens

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add

let metadata = fs::metadata(path)?;
if !metadata.is_file() {
return Err(Error::other("not a regular file"));
}

let file = OpenOptions::new().write(true).open(path)?;
let atime_spec = TimeSpec::new(atime.unix_seconds() as _, atime.nanoseconds() as _);
let mtime_spec = TimeSpec::new(mtime.unix_seconds() as _, mtime.nanoseconds() as _);

futimens(&file, &atime_spec, &mtime_spec)
.map_err(|err| std::io::Error::from_raw_os_error(err as i32))
}

/// Get metadata of the provided path
Expand Down Expand Up @@ -852,6 +888,13 @@ mod tests {
uu_app,
};

#[cfg(unix)]
use std::fs;
#[cfg(unix)]
use std::io::ErrorKind;
#[cfg(unix)]
use tempfile::tempdir;

#[cfg(windows)]
use std::env;
#[cfg(windows)]
Expand Down Expand Up @@ -923,4 +966,37 @@ mod tests {
Ok(_) => panic!("Expected to error with TouchError::InvalidFiletime but succeeded"),
}
}

#[cfg(unix)]
#[test]
fn test_try_futimens_via_write_fd_sets_times() {
let dir = tempdir().unwrap();
let path = dir.path().join("futimens-file");
fs::write(&path, b"data").unwrap();

let atime = FileTime::from_unix_time(1_600_000_000, 123_456_789);
let mtime = FileTime::from_unix_time(1_600_000_100, 987_654_321);

super::try_futimens_via_write_fd(&path, atime, mtime).unwrap();

let metadata = fs::metadata(&path).unwrap();
let actual_atime = FileTime::from_last_access_time(&metadata);
let actual_mtime = FileTime::from_last_modification_time(&metadata);

assert_eq!(actual_atime, atime);
assert_eq!(actual_mtime, mtime);
}

#[cfg(unix)]
#[test]
fn test_try_futimens_via_write_fd_rejects_non_file() {
let dir = tempdir().unwrap();
let atime = FileTime::from_unix_time(1_600_000_000, 0);
let mtime = FileTime::from_unix_time(1_600_000_001, 0);

let err = super::try_futimens_via_write_fd(dir.path(), atime, mtime)
.expect_err("expected error for non-regular file");
assert_eq!(err.kind(), ErrorKind::Other);
assert!(err.to_string().contains("not a regular file"));
}
}
Loading