Skip to content

Conversation

@mattsu2020
Copy link
Contributor

Summary

Make touch open regular files for write and call futimens on the fd (Unix only). This matches GNU touch behavior and ensures inotify emits IN_CLOSE_WRITE when timestamps are updated.

Motivation

Some inotify-based reloaders watch only IN_CLOSE_WRITE. With the current path-based timestamp update, touch emits only IN_ATTRIB, so those watchers miss the change. This caused infinite loops in downstream tests (e.g., 0 A.D. hotload tests on Ubuntu Questing).

Changes

  • On Unix, attempt OpenOptions::new().write(true) and call futimens on the fd for regular files.
  • Fallback to the existing set_file_times path-based update when open/futimens fails or the target is not a regular file.
  • Keep existing symlink handling intact.

Testing

  • cargo test -p uu_touch

Notes

  • Behavior is Unix-only and preserves current semantics on non-Unix platforms.

related
#9812

…inux

- Add `try_futimens_via_write_fd` function for Unix systems to open files write-only and set times using `futimens`, ensuring inotify watchers detect file closure after touch.
- Modify `update_times` to attempt this method before falling back to `set_file_times`, improving compatibility with file monitoring tools on Linux.
- Include necessary imports for Unix-specific operations.
- Updated the spell-checker comment to include 'futimens', likely a system call or function name, to avoid false positives in code spell-checking.
@sylvestre
Copy link
Contributor

is it possible to add tests ? thanks

…time setting

- Add nix and tempfile dependencies to Cargo.toml and Cargo.lock
- Replace unsafe libc::futimens calls with nix::sys::stat::futimens for better safety and portability
- Introduce try_futimens_via_write_fd function using nix abstractions
- Add unit tests to verify futimens functionality on Unix systems

This change reduces unsafe code usage and leverages a Rust-friendly library for Unix-specific operations.
@mattsu2020
Copy link
Contributor Author

is it possible to add tests ? thanks

add

@sylvestre
Copy link
Contributor

sorry, it needs to be rebased

@mattsu2020 mattsu2020 requested a review from sylvestre January 5, 2026 10:16
@github-actions
Copy link

github-actions bot commented Jan 5, 2026

GNU testsuite comparison:

Skipping an intermittent issue tests/timeout/timeout (passes in this run but fails in the 'main' branch)

@github-actions
Copy link

GNU testsuite comparison:

Congrats! The gnu test tests/stty/bad-speed is now passing!

@codspeed-hq
Copy link

codspeed-hq bot commented Jan 19, 2026

Merging this PR will not alter performance

✅ 284 untouched benchmarks
⏩ 38 skipped benchmarks1


Comparing mattsu2020:bug_analystic (deef781) with main (ec7e81e)

Open in CodSpeed

Footnotes

  1. 38 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@github-actions
Copy link

GNU testsuite comparison:

Skipping an intermittent issue tests/shuf/shuf-reservoir (passes in this run but fails in the 'main' branch)
Skipping an intermittent issue tests/sort/sort-stale-thread-mem (passes in this run but fails in the 'main' branch)

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))
Copy link
Contributor

Choose a reason for hiding this comment

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

he conversion from nix::Error to std::io::Error is incorrect

#[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() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Silent error swallowing with is_ok() loses information about why futimens failed.

/// 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<()> {
let metadata = fs::metadata(path)?;
Copy link
Contributor

Choose a reason for hiding this comment

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

Unnecessary metadata check before open. Opens file twice (metadata + open). Should check metadata after opening via file.metadata() to avoid TOCTOU and reduce syscalls.

fn try_futimens_via_write_fd(path: &Path, atime: FileTime, mtime: FileTime) -> std::io::Result<()> {
let metadata = fs::metadata(path)?;
if !metadata.is_file() {
return Err(Error::other("not a regular file"));
Copy link
Contributor

Choose a reason for hiding this comment

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

please use translate!()

}

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

Choose a reason for hiding this comment

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

Unsafe type cast 'as _' for nanoseconds. Should explicitly cast to i32: atime.nanoseconds() as i32

@github-actions
Copy link

GNU testsuite comparison:

Congrats! The gnu test tests/pr/bounded-memory is no longer failing!

uu_app,
};

#[cfg(unix)]
Copy link
Contributor

Choose a reason for hiding this comment

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

Redundant import - std::fs is already imported unconditionally at line 26

… fallback

Enhanced error handling in the touch utility by:
- Adding proper error message translation for non-regular files
- Implementing fallback mechanism when futimens fails
- Improving error context and messaging for better user experience
- Adding proper handling for special files like FIFOs and directories
- Ensuring consistent error reporting across different platforms
@github-actions
Copy link

GNU testsuite comparison:

GNU test failed: tests/touch/not-owner. tests/touch/not-owner is passing on 'main'. Maybe you have to rebase?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants