Skip to content

Commit 717debc

Browse files
committed
Add wrappers for futimens(2) and utimesat(2)
1 parent d302e8d commit 717debc

File tree

3 files changed

+123
-9
lines changed

3 files changed

+123
-9
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
1414
([#923](https://github.com/nix-rust/nix/pull/923))
1515
- Added a `dir` module for reading directories (wraps `fdopendir`, `readdir`, and `rewinddir`).
1616
([#916](https://github.com/nix-rust/nix/pull/916))
17+
- Added `futimens` and `utimesat` wrappers.
1718

1819
### Changed
1920
- Increased required Rust version to 1.22.1/

src/sys/stat.rs

+72-6
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ use errno::Errno;
66
use fcntl::AtFlags;
77
use libc::{self, mode_t};
88
use std::mem;
9+
use std::os::raw;
910
use std::os::unix::io::RawFd;
11+
use sys::time::TimeSpec;
1012

1113
libc_bitflags!(
1214
pub struct SFlag: mode_t {
@@ -133,6 +135,15 @@ pub fn fchmod(fd: RawFd, mode: Mode) -> Result<()> {
133135
Errno::result(res).map(|_| ())
134136
}
135137

138+
/// Computes the raw fd consumed by a function of the form `*at`.
139+
#[inline]
140+
fn actual_atfd(fd: Option<RawFd>) -> raw::c_int {
141+
match fd {
142+
None => libc::AT_FDCWD,
143+
Some(fd) => fd,
144+
}
145+
}
146+
136147
/// Flags for `fchmodat` function.
137148
#[derive(Clone, Copy, Debug)]
138149
pub enum FchmodatFlags {
@@ -162,19 +173,14 @@ pub fn fchmodat<P: ?Sized + NixPath>(
162173
mode: Mode,
163174
flag: FchmodatFlags,
164175
) -> Result<()> {
165-
let actual_dirfd =
166-
match dirfd {
167-
None => libc::AT_FDCWD,
168-
Some(fd) => fd,
169-
};
170176
let atflag =
171177
match flag {
172178
FchmodatFlags::FollowSymlink => AtFlags::empty(),
173179
FchmodatFlags::NoFollowSymlink => AtFlags::AT_SYMLINK_NOFOLLOW,
174180
};
175181
let res = path.with_nix_path(|cstr| unsafe {
176182
libc::fchmodat(
177-
actual_dirfd,
183+
actual_atfd(dirfd),
178184
cstr.as_ptr(),
179185
mode.bits() as mode_t,
180186
atflag.bits() as libc::c_int,
@@ -183,3 +189,63 @@ pub fn fchmodat<P: ?Sized + NixPath>(
183189

184190
Errno::result(res).map(|_| ())
185191
}
192+
193+
/// Change the access and modification times of the file specified by a file descriptor.
194+
///
195+
/// # References
196+
///
197+
/// [futimens(2)](http://pubs.opengroup.org/onlinepubs/9699919799/functions/futimens.html).
198+
#[inline]
199+
pub fn futimens(fd: RawFd, atime: &TimeSpec, mtime: &TimeSpec) -> Result<()> {
200+
let times: [libc::timespec; 2] = [*atime.as_ref(), *mtime.as_ref()];
201+
let res = unsafe { libc::futimens(fd, &times[0]) };
202+
203+
Errno::result(res).map(|_| ())
204+
}
205+
206+
/// Flags for `utimensat` function.
207+
#[derive(Clone, Copy, Debug)]
208+
pub enum UtimensatFlags {
209+
FollowSymlink,
210+
NoFollowSymlink,
211+
}
212+
213+
/// Change the access and modification times of a file.
214+
///
215+
/// The file to be changed is determined relative to the directory associated
216+
/// with the file descriptor `dirfd` or the current working directory
217+
/// if `dirfd` is `None`.
218+
///
219+
/// If `flag` is `UtimensatFlags::NoFollowSymlink` and `path` names a symbolic link,
220+
/// then the mode of the symbolic link is changed.
221+
///
222+
/// `utimensat(None, path, times, UtimensatFlags::FollowSymlink)` is identical to
223+
/// `libc::utimes(path, times)`. That's why `utimes` is unimplemented in the `nix` crate.
224+
///
225+
/// # References
226+
///
227+
/// [utimensat(2)](http://pubs.opengroup.org/onlinepubs/9699919799/functions/utimens.html).
228+
pub fn utimensat<P: ?Sized + NixPath>(
229+
dirfd: Option<RawFd>,
230+
path: &P,
231+
atime: &TimeSpec,
232+
mtime: &TimeSpec,
233+
flag: UtimensatFlags
234+
) -> Result<()> {
235+
let atflag =
236+
match flag {
237+
UtimensatFlags::FollowSymlink => AtFlags::empty(),
238+
UtimensatFlags::NoFollowSymlink => AtFlags::AT_SYMLINK_NOFOLLOW,
239+
};
240+
let times: [libc::timespec; 2] = [*atime.as_ref(), *mtime.as_ref()];
241+
let res = path.with_nix_path(|cstr| unsafe {
242+
libc::utimensat(
243+
actual_atfd(dirfd),
244+
cstr.as_ptr(),
245+
&times[0],
246+
atflag.bits() as libc::c_int,
247+
)
248+
})?;
249+
250+
Errno::result(res).map(|_| ())
251+
}

test/test_stat.rs

+50-3
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
1-
use std::fs::File;
1+
use std::fs::{self, File};
22
use std::os::unix::fs::symlink;
33
use std::os::unix::prelude::AsRawFd;
4+
use std::time::{Duration, UNIX_EPOCH};
45

56
use libc::{S_IFMT, S_IFLNK};
67

78
use nix::fcntl;
8-
use nix::sys::stat::{self, fchmod, fchmodat, fstat, lstat, stat};
9-
use nix::sys::stat::{FileStat, Mode, FchmodatFlags};
9+
use nix::sys::stat::{self, fchmod, fchmodat, fstat, futimens, lstat, stat, utimensat};
10+
use nix::sys::stat::{FileStat, Mode, FchmodatFlags, UtimensatFlags};
11+
use nix::sys::time::{TimeSpec, TimeValLike};
1012
use nix::unistd::chdir;
1113
use nix::Result;
1214
use tempfile;
@@ -152,3 +154,48 @@ fn test_fchmodat() {
152154
let file_stat2 = stat(&fullpath).unwrap();
153155
assert_eq!(file_stat2.st_mode & 0o7777, mode2.bits());
154156
}
157+
158+
/// Asserts that the atime and mtime in a file's metadata match expected values.
159+
///
160+
/// The atime and mtime are expressed with a resolution of seconds because some file systems
161+
/// (like macOS's HFS+) do not have higher granularity.
162+
fn assert_times_eq(exp_atime_sec: u64, exp_mtime_sec: u64, attr: &fs::Metadata) {
163+
assert_eq!(
164+
Duration::new(exp_atime_sec, 0),
165+
attr.accessed().unwrap().duration_since(UNIX_EPOCH).unwrap());
166+
assert_eq!(
167+
Duration::new(exp_mtime_sec, 0),
168+
attr.modified().unwrap().duration_since(UNIX_EPOCH).unwrap());
169+
}
170+
171+
#[test]
172+
fn test_futimens() {
173+
let tempdir = tempfile::tempdir().unwrap();
174+
let fullpath = tempdir.path().join("file");
175+
drop(File::create(&fullpath).unwrap());
176+
177+
let fd = fcntl::open(&fullpath, fcntl::OFlag::empty(), stat::Mode::empty()).unwrap();
178+
179+
futimens(fd, &TimeSpec::seconds(10), &TimeSpec::seconds(20)).unwrap();
180+
assert_times_eq(10, 20, &fs::metadata(&fullpath).unwrap());
181+
}
182+
183+
#[test]
184+
fn test_utimensat() {
185+
let tempdir = tempfile::tempdir().unwrap();
186+
let filename = "foo.txt";
187+
let fullpath = tempdir.path().join(filename);
188+
drop(File::create(&fullpath).unwrap());
189+
190+
let dirfd = fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()).unwrap();
191+
192+
utimensat(Some(dirfd), filename, &TimeSpec::seconds(12345), &TimeSpec::seconds(678),
193+
UtimensatFlags::FollowSymlink).unwrap();
194+
assert_times_eq(12345, 678, &fs::metadata(&fullpath).unwrap());
195+
196+
chdir(tempdir.path()).unwrap();
197+
198+
utimensat(None, filename, &TimeSpec::seconds(500), &TimeSpec::seconds(800),
199+
UtimensatFlags::FollowSymlink).unwrap();
200+
assert_times_eq(500, 800, &fs::metadata(&fullpath).unwrap());
201+
}

0 commit comments

Comments
 (0)