Skip to content

Commit 16fd0b7

Browse files
committed
feat(#1384): change timestamps args of utimensat and futimens to be optional
1 parent 33b5f92 commit 16fd0b7

File tree

3 files changed

+131
-12
lines changed

3 files changed

+131
-12
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ usage.
1717

1818
As an example of what Nix provides, examine the differences between what is
1919
exposed by libc and nix for the
20-
[gethostname](https://man7.org/linux/man-pages/man2/gethostname.2.html) system
20+
[gethostname](https://pubs.opengroup.org/onlinepubs/9699919799/functions/gethostname.html) system
2121
call:
2222

2323
```rust,ignore

src/sys/stat.rs

+48-6
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ pub use libc::c_uint;
77
))]
88
pub use libc::c_ulong;
99
pub use libc::stat as FileStat;
10+
pub use libc::UTIME_NOW;
1011
pub use libc::{dev_t, mode_t};
1112

1213
#[cfg(not(target_os = "redox"))]
@@ -402,14 +403,51 @@ pub fn lutimes<P: ?Sized + NixPath>(
402403
Errno::result(res).map(drop)
403404
}
404405

405-
/// Change the access and modification times of the file specified by a file descriptor.
406+
/// A helper function to convert `atime: Option<&TimeSpec>, mtime: Option<&TimeSpec>`
407+
/// to `[libc::timespec; 2], used in the implementation of [`utimnsat(3)`]
408+
/// and [`futimens(3)`]
409+
fn time_convert(
410+
atime: Option<&TimeSpec>,
411+
mtime: Option<&TimeSpec>,
412+
) -> [libc::timespec; 2] {
413+
let mut times = [
414+
libc::timespec {
415+
tv_sec: 0,
416+
tv_nsec: libc::UTIME_OMIT,
417+
},
418+
libc::timespec {
419+
tv_sec: 0,
420+
tv_nsec: libc::UTIME_OMIT,
421+
},
422+
];
423+
if let Some(atime) = atime {
424+
times[0].tv_sec = atime.tv_sec();
425+
times[0].tv_nsec = atime.tv_nsec();
426+
}
427+
if let Some(mtime) = mtime {
428+
times[1].tv_sec = mtime.tv_sec();
429+
times[1].tv_nsec = mtime.tv_nsec();
430+
}
431+
times
432+
}
433+
434+
/// Change the access and modification times of the file specified by a file
435+
/// descriptor.
436+
///
437+
/// When a timestamp argument is set to `None`, it remains unchanged. If you
438+
/// would like to change a timestamp to the special value `Now`, set the `st_nsec`
439+
/// field of that timestamp to [`UTIME_NOW`].
406440
///
407441
/// # References
408442
///
409443
/// [futimens(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/futimens.html).
410444
#[inline]
411-
pub fn futimens(fd: RawFd, atime: &TimeSpec, mtime: &TimeSpec) -> Result<()> {
412-
let times: [libc::timespec; 2] = [*atime.as_ref(), *mtime.as_ref()];
445+
pub fn futimens(
446+
fd: RawFd,
447+
atime: Option<&TimeSpec>,
448+
mtime: Option<&TimeSpec>,
449+
) -> Result<()> {
450+
let times = time_convert(atime, mtime);
413451
let res = unsafe { libc::futimens(fd, &times[0]) };
414452

415453
Errno::result(res).map(drop)
@@ -429,6 +467,10 @@ pub enum UtimensatFlags {
429467
/// with the file descriptor `dirfd` or the current working directory
430468
/// if `dirfd` is `None`.
431469
///
470+
/// When a timestamp argument is set to `None`, it remains unchanged. If you
471+
/// would like to change a timestamp to the special value `Now`, set the `st_nsec`
472+
/// field of that timestamp to [`UTIME_NOW`].
473+
///
432474
/// If `flag` is `UtimensatFlags::NoFollowSymlink` and `path` names a symbolic link,
433475
/// then the mode of the symbolic link is changed.
434476
///
@@ -444,15 +486,15 @@ pub enum UtimensatFlags {
444486
pub fn utimensat<P: ?Sized + NixPath>(
445487
dirfd: Option<RawFd>,
446488
path: &P,
447-
atime: &TimeSpec,
448-
mtime: &TimeSpec,
489+
atime: Option<&TimeSpec>,
490+
mtime: Option<&TimeSpec>,
449491
flag: UtimensatFlags,
450492
) -> Result<()> {
451493
let atflag = match flag {
452494
UtimensatFlags::FollowSymlink => AtFlags::empty(),
453495
UtimensatFlags::NoFollowSymlink => AtFlags::AT_SYMLINK_NOFOLLOW,
454496
};
455-
let times: [libc::timespec; 2] = [*atime.as_ref(), *mtime.as_ref()];
497+
let times = time_convert(atime, mtime);
456498
let res = path.with_nix_path(|cstr| unsafe {
457499
libc::utimensat(
458500
at_rawfd(dirfd),

test/test_stat.rs

+82-5
Original file line numberDiff line numberDiff line change
@@ -275,10 +275,47 @@ fn test_futimens() {
275275
let fd = fcntl::open(&fullpath, fcntl::OFlag::empty(), stat::Mode::empty())
276276
.unwrap();
277277

278-
futimens(fd, &TimeSpec::seconds(10), &TimeSpec::seconds(20)).unwrap();
278+
futimens(
279+
fd,
280+
Some(&TimeSpec::seconds(10)),
281+
Some(&TimeSpec::seconds(20)),
282+
)
283+
.unwrap();
279284
assert_times_eq(10, 20, &fs::metadata(&fullpath).unwrap());
280285
}
281286

287+
#[test]
288+
#[cfg(not(any(target_os = "redox", target_os = "haiku")))]
289+
fn test_futimens_unchanged() {
290+
let tempdir = tempfile::tempdir().unwrap();
291+
let fullpath = tempdir.path().join("file");
292+
drop(File::create(&fullpath).unwrap());
293+
let fd = fcntl::open(&fullpath, fcntl::OFlag::empty(), stat::Mode::empty())
294+
.unwrap();
295+
296+
let old_atime = fs::metadata(fullpath.as_path())
297+
.unwrap()
298+
.accessed()
299+
.unwrap();
300+
let old_mtime = fs::metadata(fullpath.as_path())
301+
.unwrap()
302+
.modified()
303+
.unwrap();
304+
305+
futimens(fd, None, None).unwrap();
306+
307+
let new_atime = fs::metadata(fullpath.as_path())
308+
.unwrap()
309+
.accessed()
310+
.unwrap();
311+
let new_mtime = fs::metadata(fullpath.as_path())
312+
.unwrap()
313+
.modified()
314+
.unwrap();
315+
assert_eq!(old_atime, new_atime);
316+
assert_eq!(old_mtime, new_mtime);
317+
}
318+
282319
#[test]
283320
#[cfg(not(any(target_os = "redox", target_os = "haiku")))]
284321
fn test_utimensat() {
@@ -295,8 +332,8 @@ fn test_utimensat() {
295332
utimensat(
296333
Some(dirfd),
297334
filename,
298-
&TimeSpec::seconds(12345),
299-
&TimeSpec::seconds(678),
335+
Some(&TimeSpec::seconds(12345)),
336+
Some(&TimeSpec::seconds(678)),
300337
UtimensatFlags::FollowSymlink,
301338
)
302339
.unwrap();
@@ -307,14 +344,54 @@ fn test_utimensat() {
307344
utimensat(
308345
None,
309346
filename,
310-
&TimeSpec::seconds(500),
311-
&TimeSpec::seconds(800),
347+
Some(&TimeSpec::seconds(500)),
348+
Some(&TimeSpec::seconds(800)),
312349
UtimensatFlags::FollowSymlink,
313350
)
314351
.unwrap();
315352
assert_times_eq(500, 800, &fs::metadata(&fullpath).unwrap());
316353
}
317354

355+
#[test]
356+
#[cfg(not(any(target_os = "redox", target_os = "haiku")))]
357+
fn test_utimensat_unchanged() {
358+
let _dr = crate::DirRestore::new();
359+
let tempdir = tempfile::tempdir().unwrap();
360+
let filename = "foo.txt";
361+
let fullpath = tempdir.path().join(filename);
362+
drop(File::create(&fullpath).unwrap());
363+
let dirfd =
364+
fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty())
365+
.unwrap();
366+
367+
let old_atime = fs::metadata(fullpath.as_path())
368+
.unwrap()
369+
.accessed()
370+
.unwrap();
371+
let old_mtime = fs::metadata(fullpath.as_path())
372+
.unwrap()
373+
.modified()
374+
.unwrap();
375+
utimensat(
376+
Some(dirfd),
377+
filename,
378+
None,
379+
None,
380+
UtimensatFlags::NoFollowSymlink,
381+
)
382+
.unwrap();
383+
let new_atime = fs::metadata(fullpath.as_path())
384+
.unwrap()
385+
.accessed()
386+
.unwrap();
387+
let new_mtime = fs::metadata(fullpath.as_path())
388+
.unwrap()
389+
.modified()
390+
.unwrap();
391+
assert_eq!(old_atime, new_atime);
392+
assert_eq!(old_mtime, new_mtime);
393+
}
394+
318395
#[test]
319396
#[cfg(not(target_os = "redox"))]
320397
fn test_mkdirat_success_path() {

0 commit comments

Comments
 (0)