Dynamically load utimensat if exists on the host (#535)

* Dynamically load utimensat if exists on the host

This commit introduces a change to file time management for *nix based
hosts in that it firstly tries to load `utimensat` symbol, and if it
doesn't exist, then falls back to `utimes` instead. This change is
borrowing very heavily from [filetime] crate, however, it introduces a
couple of helpers and methods specific to WASI use case (or more
generally, to a use case which requires modifying times of entities
specified by a pair `(DirFD, RelativePath)` rather than the typical
file time specification based only absolute path or raw file descriptor
as is the case with [filetime] crate. The trick here is, that on kernels
which do not have `utimensat` symbol, this implementation emulates this
behaviour by a combination of `openat` and `utimes`.

This commit also is meant to address #516.

[filetime]: https://github.com/alexcrichton/filetime

* Fix symlink NOFOLLOW flag setting

* Add docs and specify UTIME_NOW/OMIT on Linux

Previously, we relied on [libc] crate for `UTIME_NOW` and `UTIME_OMIT`
constants on Linux. However, following the convention assumed in
[filetime] crate, this is now changed to directly specified by us
in our crate.

[libc]: https://github.com/rust-lang/libc
[filetime]: https://github.com/alexcrichton/filetime

* Refactor UTIME_NOW/OMIT for BSD

* Address final discussion points
This commit is contained in:
Jakub Konka
2019-11-11 18:42:28 +01:00
committed by Alex Crichton
parent 5a1845b4ca
commit 0006a2af95
8 changed files with 335 additions and 103 deletions

View File

@@ -0,0 +1,61 @@
//! This internal module consists of helper types and functions for dealing
//! with setting the file times specific to Linux.
use super::super::filetime::FileTime;
use std::fs::File;
use std::io;
use std::sync::atomic::{AtomicBool, Ordering::Relaxed};
pub(crate) const UTIME_NOW: i64 = 1_073_741_823;
pub(crate) const UTIME_OMIT: i64 = 1_073_741_822;
/// Wrapper for `utimensat` syscall, however, with an added twist such that `utimensat` symbol
/// is firstly resolved (i.e., we check whether it exists on the host), and only used if that is
/// the case. Otherwise, the syscall resorts to a less accurate `utimesat` emulated syscall.
/// The original implementation can be found here: [filetime::unix::linux::set_times]
///
/// [filetime::unix::linux::set_times]: https://github.com/alexcrichton/filetime/blob/master/src/unix/linux.rs#L64
pub(crate) fn utimensat(
dirfd: &File,
path: &str,
atime: FileTime,
mtime: FileTime,
symlink_nofollow: bool,
) -> io::Result<()> {
use super::super::filetime::{to_timespec, utimesat};
use std::ffi::CString;
use std::os::unix::prelude::*;
let flags = if symlink_nofollow {
libc::AT_SYMLINK_NOFOLLOW
} else {
0
};
// Attempt to use the `utimensat` syscall, but if it's not supported by the
// current kernel then fall back to an older syscall.
static INVALID: AtomicBool = AtomicBool::new(false);
if !INVALID.load(Relaxed) {
let p = CString::new(path.as_bytes())?;
let times = [to_timespec(&atime), to_timespec(&mtime)];
let rc = unsafe {
libc::syscall(
libc::SYS_utimensat,
dirfd.as_raw_fd(),
p.as_ptr(),
times.as_ptr(),
flags,
)
};
if rc == 0 {
return Ok(());
}
let err = io::Error::last_os_error();
if err.raw_os_error() == Some(libc::ENOSYS) {
INVALID.store(true, Relaxed);
} else {
return Err(err);
}
}
utimesat(dirfd, path, atime, mtime, symlink_nofollow)
}

View File

@@ -1,3 +1,4 @@
pub(crate) mod filetime;
pub(crate) mod hostcalls_impl;
pub(crate) mod osfile;
@@ -30,13 +31,3 @@ pub(crate) mod fdentry_impl {
pub(crate) mod host_impl {
pub(crate) const O_RSYNC: nix::fcntl::OFlag = nix::fcntl::OFlag::O_RSYNC;
}
pub(crate) mod fs_helpers {
pub(crate) fn utime_now() -> libc::c_long {
libc::UTIME_NOW
}
pub(crate) fn utime_omit() -> libc::c_long {
libc::UTIME_OMIT
}
}