I just discovered that opening a file with O_PATH gives an fd that works with utimensat(fd, "", times, O_EMPTY_PATH) but does *not* work with what futimens calls, which is: utimensat(fd, NULL, times, 0) The former will go through do_utimes_fd, while the latter goes through do_utimes_path. I would have expected these two cases to end up in the same codepath once they'd discovered they were operating on a file descriptor, and I would have expected both to support O_PATH file descriptors if either does. This is true for both symlinks (with O_NOFOLLOW | O_PATH) and regular files (with just O_PATH). This is on 6.12, in case it matters. Quick and dirty test program (in Rust, using rustix to make syscalls): ``` use rustix::fs::{AtFlags, OFlags, Timespec, Timestamps, UTIME_OMIT}; fn main() -> std::io::Result<()> { let f = rustix::fs::open("oldfile", OFlags::PATH | OFlags::CLOEXEC, 0o666.into())?; let times = Timestamps { last_access: Timespec { tv_sec: 0, tv_nsec: UTIME_OMIT }, last_modification: Timespec { tv_sec: 0, tv_nsec: 0 }, }; let ret = rustix::fs::utimensat(&f, "", ×, AtFlags::EMPTY_PATH); println!("utimensat: {ret:?}"); let ret = rustix::fs::futimens(&f, ×); println!("futimens: {ret:?}"); Ok(()) } ``` Is this something that would be reasonable to fix? Would a patch be welcome that makes both cases work identically and support O_PATH file descriptors?