Skip to content

Commit 891a3fe

Browse files
committed
refactor: use an enum to represent the timestamp update operation
1 parent a989fd5 commit 891a3fe

File tree

3 files changed

+164
-12
lines changed

3 files changed

+164
-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

+79-4
Original file line numberDiff line numberDiff line change
@@ -402,12 +402,87 @@ pub fn lutimes<P: ?Sized + NixPath>(
402402
Errno::result(res).map(drop)
403403
}
404404

405+
/// Timestamp update operation specifier used in [`utimensat`] and [`futimens`].
406+
#[cfg(not(target_os = "redox"))] // Redox does not support `UTIME_OMIT` and `UTIME_NOW`
407+
#[derive(Copy, Clone, Debug)]
408+
pub enum TimestampSpec {
409+
/// Leave the timestamp unchanged.
410+
Omit,
411+
/// Update the timestamp to `Now`
412+
Now,
413+
/// Update the timestamp to the specific value specified in `TimeSpec`.
414+
Set(TimeSpec),
415+
}
416+
417+
/// A helper function to construct an instance of `libc::timespec` according to
418+
/// the operation specified in `spec`.
419+
#[cfg(not(target_os = "redox"))] // Redox does not support `UTIME_OMIT` and `UTIME_NOW`
420+
fn create_timespec(spec: &TimestampSpec) -> libc::timespec {
421+
let mut default_value = libc::timespec {
422+
tv_sec: 0,
423+
tv_nsec: 0,
424+
};
425+
426+
match spec {
427+
#[cfg(all(
428+
target_os = "linux",
429+
target_arch = "x86_64",
430+
target_pointer_width = "32"
431+
))]
432+
TimestampSpec::Omit => default_value.tv_nsec = libc::UTIME_OMIT as i64,
433+
#[cfg(not(all(
434+
target_os = "linux",
435+
target_arch = "x86_64",
436+
target_pointer_width = "32"
437+
)))]
438+
TimestampSpec::Omit => default_value.tv_nsec = libc::UTIME_OMIT,
439+
#[cfg(all(
440+
target_os = "linux",
441+
target_arch = "x86_64",
442+
target_pointer_width = "32"
443+
))]
444+
TimestampSpec::Now => default_value.tv_nsec = libc::UTIME_NOW as i64,
445+
#[cfg(not(all(
446+
target_os = "linux",
447+
target_arch = "x86_64",
448+
target_pointer_width = "32"
449+
)))]
450+
TimestampSpec::Now => default_value.tv_nsec = libc::UTIME_NOW,
451+
TimestampSpec::Set(time) => {
452+
default_value.tv_sec = time.tv_sec();
453+
default_value.tv_nsec = time.tv_nsec();
454+
}
455+
}
456+
457+
default_value
458+
}
459+
460+
/// Change the access and modification times of the file specified by a file
461+
/// descriptor.
462+
///
463+
/// # References
464+
///
465+
/// [futimens(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/futimens.html).
466+
#[cfg(not(target_os = "redox"))] // Redox does not support `UTIME_OMIT` and `UTIME_NOW`
467+
#[inline]
468+
pub fn futimens(
469+
fd: RawFd,
470+
atime: TimestampSpec,
471+
mtime: TimestampSpec,
472+
) -> Result<()> {
473+
let times = [create_timespec(&atime), create_timespec(&mtime)];
474+
let res = unsafe { libc::futimens(fd, &times[0]) };
475+
476+
Errno::result(res).map(drop)
477+
}
478+
405479
/// Change the access and modification times of the file specified by a file descriptor.
406480
///
407481
/// # References
408482
///
409483
/// [futimens(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/futimens.html).
410484
#[inline]
485+
#[cfg(target_os = "redox")]
411486
pub fn futimens(fd: RawFd, atime: &TimeSpec, mtime: &TimeSpec) -> Result<()> {
412487
let times: [libc::timespec; 2] = [*atime.as_ref(), *mtime.as_ref()];
413488
let res = unsafe { libc::futimens(fd, &times[0]) };
@@ -439,20 +514,20 @@ pub enum UtimensatFlags {
439514
/// # References
440515
///
441516
/// [utimensat(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/utimens.html).
442-
#[cfg(not(target_os = "redox"))]
517+
#[cfg(not(target_os = "redox"))] // Redox does not support `utimensat(2)` syscall
443518
#[cfg_attr(docsrs, doc(cfg(all())))]
444519
pub fn utimensat<P: ?Sized + NixPath>(
445520
dirfd: Option<RawFd>,
446521
path: &P,
447-
atime: &TimeSpec,
448-
mtime: &TimeSpec,
522+
atime: TimestampSpec,
523+
mtime: TimestampSpec,
449524
flag: UtimensatFlags,
450525
) -> Result<()> {
451526
let atflag = match flag {
452527
UtimensatFlags::FollowSymlink => AtFlags::empty(),
453528
UtimensatFlags::NoFollowSymlink => AtFlags::AT_SYMLINK_NOFOLLOW,
454529
};
455-
let times: [libc::timespec; 2] = [*atime.as_ref(), *mtime.as_ref()];
530+
let times = [create_timespec(&atime), create_timespec(&mtime)];
456531
let res = path.with_nix_path(|cstr| unsafe {
457532
libc::utimensat(
458533
at_rawfd(dirfd),

test/test_stat.rs

+84-7
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,15 @@ use nix::sys::stat::utimensat;
3232
#[cfg(not(target_os = "redox"))]
3333
use nix::sys::stat::FchmodatFlags;
3434
use nix::sys::stat::Mode;
35-
#[cfg(not(any(target_os = "redox", target_os = "haiku")))]
36-
use nix::sys::stat::UtimensatFlags;
3735
#[cfg(not(target_os = "redox"))]
3836
use nix::sys::stat::{self};
3937
use nix::sys::stat::{fchmod, stat};
4038
#[cfg(not(target_os = "redox"))]
4139
use nix::sys::stat::{fchmodat, mkdirat};
4240
#[cfg(not(any(target_os = "redox", target_os = "haiku")))]
4341
use nix::sys::stat::{futimens, utimes};
42+
#[cfg(not(any(target_os = "redox", target_os = "haiku")))]
43+
use nix::sys::stat::{TimestampSpec, UtimensatFlags};
4444

4545
#[cfg(not(any(target_os = "netbsd", target_os = "redox")))]
4646
use nix::sys::stat::FileStat;
@@ -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+
TimestampSpec::Set(TimeSpec::seconds(10)),
281+
TimestampSpec::Set(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, TimestampSpec::Omit, TimestampSpec::Omit).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+
TimestampSpec::Set(TimeSpec::seconds(12345)),
336+
TimestampSpec::Set(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+
TimestampSpec::Set(TimeSpec::seconds(500)),
348+
TimestampSpec::Set(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+
TimestampSpec::Omit,
379+
TimestampSpec::Omit,
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)