diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 79fb3a96e..d0286b2a9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,7 +12,7 @@ jobs: matrix: rust-version: - nightly - - "1.40" + - "1.48" os: - ubuntu-latest - windows-latest diff --git a/Cargo.toml b/Cargo.toml index c8b95d0fd..a738615a3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ fastrand = "1.6.0" remove_dir_all = "0.5" [target.'cfg(any(unix, target_os = "wasi"))'.dependencies] -libc = "0.2.27" +rustix = { version = "0.35.6", features = ["fs"] } [target.'cfg(windows)'.dependencies.windows-sys] version = "0.36" diff --git a/README.md b/README.md index 1dba3a01d..e9bd7a7b7 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ patterns and surprisingly difficult to implement securely). Usage ----- -Minimum required Rust version: 1.40.0 +Minimum required Rust version: 1.48.0 Add this to your `Cargo.toml`: ```toml diff --git a/src/file/imp/unix.rs b/src/file/imp/unix.rs index 480743cf7..6dca72630 100644 --- a/src/file/imp/unix.rs +++ b/src/file/imp/unix.rs @@ -1,13 +1,11 @@ use std::env; -use std::ffi::{CString, OsStr}; +use std::ffi::OsStr; use std::fs::{self, File, OpenOptions}; use std::io; cfg_if::cfg_if! { if #[cfg(not(target_os = "wasi"))] { - use std::os::unix::ffi::OsStrExt; use std::os::unix::fs::{MetadataExt, OpenOptionsExt}; } else { - use std::os::wasi::ffi::OsStrExt; #[cfg(feature = "nightly")] use std::os::wasi::fs::MetadataExt; } @@ -16,29 +14,7 @@ use crate::util; use std::path::Path; #[cfg(not(target_os = "redox"))] -use libc::{c_char, c_int, link, rename, unlink}; - -#[cfg(not(target_os = "redox"))] -#[inline(always)] -pub fn cvt_err(result: c_int) -> io::Result { - if result == -1 { - Err(io::Error::last_os_error()) - } else { - Ok(result) - } -} - -#[cfg(target_os = "redox")] -#[inline(always)] -pub fn cvt_err(result: Result) -> io::Result { - result.map_err(|err| io::Error::from_raw_os_error(err.errno)) -} - -// Stolen from std. -pub fn cstr(path: &Path) -> io::Result { - CString::new(path.as_os_str().as_bytes()) - .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "path contained a null")) -} +use rustix::fs::{cwd, linkat, renameat, unlinkat, AtFlags}; pub fn create_named(path: &Path, open_options: &mut OpenOptions) -> io::Result { open_options.read(true).write(true).create_new(true); @@ -70,16 +46,18 @@ fn create_unlinked(path: &Path) -> io::Result { #[cfg(target_os = "linux")] pub fn create(dir: &Path) -> io::Result { - use libc::{EISDIR, ENOENT, EOPNOTSUPP, O_TMPFILE}; + use rustix::{fs::OFlags, io::Errno}; OpenOptions::new() .read(true) .write(true) - .custom_flags(O_TMPFILE) // do not mix with `create_new(true)` + .custom_flags(OFlags::TMPFILE.bits() as i32) // do not mix with `create_new(true)` .open(dir) .or_else(|e| { - match e.raw_os_error() { + match Errno::from_io_error(&e) { // These are the three "not supported" error codes for O_TMPFILE. - Some(EOPNOTSUPP) | Some(EISDIR) | Some(ENOENT) => create_unix(dir), + Some(Errno::OPNOTSUPP) | Some(Errno::ISDIR) | Some(Errno::NOENT) => { + create_unix(dir) + } _ => Err(e), } }) @@ -124,25 +102,36 @@ pub fn reopen(_file: &File, _path: &Path) -> io::Result { #[cfg(not(target_os = "redox"))] pub fn persist(old_path: &Path, new_path: &Path, overwrite: bool) -> io::Result<()> { - unsafe { - let old_path = cstr(old_path)?; - let new_path = cstr(new_path)?; - if overwrite { - cvt_err(rename( - old_path.as_ptr() as *const c_char, - new_path.as_ptr() as *const c_char, - ))?; - } else { - cvt_err(link( - old_path.as_ptr() as *const c_char, - new_path.as_ptr() as *const c_char, - ))?; - // Ignore unlink errors. Can we do better? - // On recent linux, we can use renameat2 to do this atomically. - let _ = unlink(old_path.as_ptr() as *const c_char); + if overwrite { + renameat(cwd(), old_path, cwd(), new_path)?; + } else { + // On Linux, use `renameat_with` to avoid overwriting an existing name, + // if the kernel and the filesystem support it. + #[cfg(any(target_os = "android", target_os = "linux"))] + { + use rustix::fs::{renameat_with, RenameFlags}; + use rustix::io::Errno; + use std::sync::atomic::{AtomicBool, Ordering::Relaxed}; + + static NOSYS: AtomicBool = AtomicBool::new(false); + if !NOSYS.load(Relaxed) { + match renameat_with(cwd(), old_path, cwd(), new_path, RenameFlags::NOREPLACE) { + Ok(()) => return Ok(()), + Err(Errno::NOSYS) => NOSYS.store(true, Relaxed), + Err(Errno::INVAL) => {} + Err(e) => return Err(e.into()), + } + } } - Ok(()) + + // Otherwise use `linkat` to create the new filesystem name, which + // will fail if the name already exists, and then `unlinkat` to remove + // the old name. + linkat(cwd(), old_path, cwd(), new_path, AtFlags::empty())?; + // Ignore unlink errors. Can we do better? + let _ = unlinkat(cwd(), old_path, AtFlags::empty()); } + Ok(()) } #[cfg(target_os = "redox")]