atomicwrites-0.4.4/.cargo_vcs_info.json0000644000000001360000000000100135530ustar { "git": { "sha1": "bfad85d09eabce6414285024fcdcb4d01830726b" }, "path_in_vcs": "" }atomicwrites-0.4.4/.github/workflows/msrv.yml000064400000000000000000000052031046102023000174520ustar 00000000000000on: [push, pull_request] name: MSRV jobs: check: name: Check runs-on: ubuntu-latest strategy: matrix: rust: - 1.63.0 - stable - beta - nightly steps: - name: Checkout sources uses: actions/checkout@v2 - name: Install toolchain uses: actions-rs/toolchain@v1 with: toolchain: ${{ matrix.rust }} override: true - name: Run cargo check if: matrix.rust != 'nightly' uses: actions-rs/cargo@v1 with: command: check - name: Run cargo check (nightly) if: matrix.rust == 'nightly' continue-on-error: true uses: actions-rs/cargo@v1 with: command: check test: needs: [check] name: Test Suite runs-on: ubuntu-latest strategy: matrix: rust: - 1.63.0 - stable - beta - nightly steps: - name: Checkout sources uses: actions/checkout@v2 - name: Install toolchain uses: actions-rs/toolchain@v1 with: toolchain: ${{ matrix.rust }} override: true - name: Run cargo test if: matrix.rust != 'nightly' uses: actions-rs/cargo@v1 with: command: test - name: Run cargo test (nightly) if: matrix.rust == 'nightly' continue-on-error: true uses: actions-rs/cargo@v1 with: command: test fmt: needs: [check] name: Rustfmt runs-on: ubuntu-latest strategy: matrix: rust: - stable - beta steps: - name: Checkout sources uses: actions/checkout@v2 - name: Install toolchain uses: actions-rs/toolchain@v1 with: toolchain: ${{ matrix.rust }} override: true - name: Install rustfmt run: rustup component add rustfmt - name: Run cargo fmt uses: actions-rs/cargo@v1 with: command: fmt args: --all -- --check clippy: needs: [check] name: Clippy runs-on: ubuntu-latest strategy: matrix: rust: - stable - beta - nightly steps: - name: Checkout sources uses: actions/checkout@v2 - name: Install toolchain uses: actions-rs/toolchain@v1 with: toolchain: ${{ matrix.rust }} override: true - name: Install clippy run: rustup component add clippy - name: Run cargo clippy uses: actions-rs/cargo@v1 with: command: clippy args: -- -D warnings atomicwrites-0.4.4/.gitignore000064400000000000000000000000501046102023000143260ustar 00000000000000/target /Cargo.lock atomicwrites-test.* atomicwrites-0.4.4/Cargo.toml0000644000000025660000000000100115620ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] name = "atomicwrites" version = "0.4.4" authors = ["Markus Unterwaditzer "] build = false exclude = [ "/.travis.yml", "/Makefile", "/appveyor.yml", ] autobins = false autoexamples = false autotests = false autobenches = false description = "Atomic file-writes." homepage = "https://github.com/untitaker/rust-atomicwrites" documentation = "https://docs.rs/crate/atomicwrites" readme = "README.md" keywords = [ "filesystem", "posix", ] license = "MIT" repository = "https://github.com/untitaker/rust-atomicwrites" [lib] name = "atomicwrites" path = "src/lib.rs" [[test]] name = "lib" path = "tests/lib.rs" [dependencies.tempfile] version = "3.1" [target."cfg(unix)".dependencies.rustix] version = "0.38.0" features = ["fs"] [target."cfg(windows)".dependencies.windows-sys] version = "0.52.0" features = [ "Win32_Foundation", "Win32_Storage_FileSystem", ] atomicwrites-0.4.4/Cargo.toml.orig000064400000000000000000000013231046102023000152310ustar 00000000000000[package] name = "atomicwrites" version = "0.4.4" authors = ["Markus Unterwaditzer "] license = "MIT" keywords = ["filesystem", "posix"] readme = "README.md" description = "Atomic file-writes." documentation = "https://docs.rs/crate/atomicwrites" homepage = "https://github.com/untitaker/rust-atomicwrites" repository = "https://github.com/untitaker/rust-atomicwrites" exclude = ["/.travis.yml", "/Makefile", "/appveyor.yml"] [dependencies] tempfile = "3.1" [target.'cfg(unix)'.dependencies] rustix = { version = "0.38.0", features = ["fs"] } [target.'cfg(windows)'.dependencies.windows-sys] version = "0.52.0" features = [ "Win32_Foundation", "Win32_Storage_FileSystem", ] atomicwrites-0.4.4/LICENSE000064400000000000000000000020501046102023000133450ustar 00000000000000Copyright (c) 2015 Markus Unterwaditzer Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. atomicwrites-0.4.4/README.md000064400000000000000000000024411046102023000136230ustar 00000000000000# rust-atomicwrites [![Build Status](https://travis-ci.org/untitaker/rust-atomicwrites.svg?branch=master)](https://travis-ci.org/untitaker/rust-atomicwrites) [![Windows build status](https://ci.appveyor.com/api/projects/status/h6642x2d54xl0sev?svg=true)](https://ci.appveyor.com/project/untitaker/rust-atomicwrites) - [Documentation](https://docs.rs/crate/atomicwrites) - [Repository](https://github.com/untitaker/rust-atomicwrites) - [Crates.io](https://crates.io/crates/atomicwrites) Atomic file-writes. Works on both POSIX and Windows. The basic idea is to write to temporary files (in the same file system), and move them when done writing. This avoids the problem of two programs writing to the same file. For `AllowOverwrite`, `rename` is used. For `DisallowOverwrite`, `link + unlink` is used instead to raise errors when the target path already exists. This is mostly a port of the same-named [Python package](https://github.com/untitaker/python-atomicwrites). ## Example ```rust use atomicwrites::{AtomicFile,DisallowOverwrite}; let af = AtomicFile::new("foo", DisallowOverwrite); af.write(|f| { f.write_all(b"HELLO") })?; ``` ## Alternatives - [tempfile](https://github.com/Stebalien/tempfile) has a `persist` method doing the same thing. ## License Licensed under MIT, see ``LICENSE``. atomicwrites-0.4.4/src/lib.rs000064400000000000000000000273251046102023000142570ustar 00000000000000// INSERT_README_VIA_MAKE #[cfg(unix)] extern crate rustix; extern crate tempfile; use std::convert::AsRef; use std::error::Error as ErrorTrait; use std::fmt; use std::fs; use std::io; use std::path; pub use OverwriteBehavior::{AllowOverwrite, DisallowOverwrite}; /// Whether to allow overwriting if the target file exists. #[derive(Clone, Copy)] pub enum OverwriteBehavior { /// Overwrite files silently. AllowOverwrite, /// Don't overwrite files. `AtomicFile.write` will raise errors for such conditions only after /// you've already written your data. DisallowOverwrite, } /// Represents an error raised by `AtomicFile.write`. #[derive(Debug)] pub enum Error { /// The error originated in the library itself, while it was either creating a temporary file /// or moving the file into place. Internal(io::Error), /// The error originated in the user-supplied callback. User(E), } /// If your callback returns a `std::io::Error`, you can unwrap this type to `std::io::Error`. impl From> for io::Error { fn from(e: Error) -> Self { match e { Error::Internal(x) => x, Error::User(x) => x, } } } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { Error::Internal(ref e) => e.fmt(f), Error::User(ref e) => e.fmt(f), } } } impl ErrorTrait for Error { fn cause(&self) -> Option<&dyn ErrorTrait> { match *self { Error::Internal(ref e) => Some(e), Error::User(ref e) => Some(e), } } } fn safe_parent(p: &path::Path) -> Option<&path::Path> { match p.parent() { None => None, Some(x) if x.as_os_str().is_empty() => Some(path::Path::new(".")), x => x, } } /// Create a file and write to it atomically, in a callback. pub struct AtomicFile { /// Path to the final file that is atomically written. path: path::PathBuf, overwrite: OverwriteBehavior, /// Directory to which to write the temporary subdirectories. tmpdir: path::PathBuf, } impl AtomicFile { /// Helper for writing to the file at `path` atomically, in write-only mode. /// /// If `OverwriteBehaviour::DisallowOverwrite` is given, /// an `Error::Internal` containing an `std::io::ErrorKind::AlreadyExists` /// will be returned from `self.write(...)` if the file exists. /// /// The temporary file is written to a temporary subdirectory in `.`, to ensure /// it’s on the same filesystem (so that the move is atomic). pub fn new

(path: P, overwrite: OverwriteBehavior) -> Self where P: AsRef, { let p = path.as_ref(); AtomicFile::new_with_tmpdir( p, overwrite, safe_parent(p).unwrap_or_else(|| path::Path::new(".")), ) } /// Like `AtomicFile::new`, but the temporary file is written to a temporary subdirectory in `tmpdir`. /// /// TODO: does `tmpdir` have to exist? pub fn new_with_tmpdir(path: P, overwrite: OverwriteBehavior, tmpdir: Q) -> Self where P: AsRef, Q: AsRef, { AtomicFile { path: path.as_ref().to_path_buf(), overwrite, tmpdir: tmpdir.as_ref().to_path_buf(), } } /// Move the file to `self.path()`. Only call once! Not exposed! fn commit(&self, tmppath: &path::Path) -> io::Result<()> { match self.overwrite { AllowOverwrite => replace_atomic(tmppath, self.path()), DisallowOverwrite => move_atomic(tmppath, self.path()), } } /// Get the target filepath. pub fn path(&self) -> &path::Path { &self.path } /// Open a temporary file, call `f` on it (which is supposed to write to it), then move the /// file atomically to `self.path`. /// /// The temporary file is written to a randomized temporary subdirectory with prefix `.atomicwrite`. pub fn write(&self, f: F) -> Result> where F: FnOnce(&mut fs::File) -> Result, { let mut options = fs::OpenOptions::new(); // These are the same options as `File::create`. options.write(true).create(true).truncate(true); self.write_with_options(f, options) } /// Open a temporary file with custom [`OpenOptions`], call `f` on it (which is supposed to /// write to it), then move the file atomically to `self.path`. /// /// The temporary file is written to a randomized temporary subdirectory with prefix /// `.atomicwrite`. /// /// [`OpenOptions`]: fs::OpenOptions pub fn write_with_options(&self, f: F, options: fs::OpenOptions) -> Result> where F: FnOnce(&mut fs::File) -> Result, { let tmpdir = tempfile::Builder::new() .prefix(".atomicwrite") .tempdir_in(&self.tmpdir) .map_err(Error::Internal)?; let tmppath = tmpdir.path().join("tmpfile.tmp"); let rv = { let mut tmpfile = options.open(&tmppath).map_err(Error::Internal)?; let r = f(&mut tmpfile).map_err(Error::User)?; tmpfile.sync_all().map_err(Error::Internal)?; r }; self.commit(&tmppath).map_err(Error::Internal)?; Ok(rv) } } #[cfg(unix)] mod imp { use super::safe_parent; use rustix::fs::AtFlags; use std::{fs, io, path}; pub fn replace_atomic(src: &path::Path, dst: &path::Path) -> io::Result<()> { let src_parent_path = safe_parent(src).unwrap(); let dst_parent_path = safe_parent(dst).unwrap(); let src_child_path = src.file_name().unwrap(); let dst_child_path = dst.file_name().unwrap(); // Open the parent directories. If src and dst have the same parent // path, open it once and reuse it. let src_parent = fs::File::open(src_parent_path)?; let dst_parent; let dst_parent = if src_parent_path == dst_parent_path { &src_parent } else { dst_parent = fs::File::open(dst_parent_path)?; &dst_parent }; // Do the `renameat`. rustix::fs::renameat(&src_parent, src_child_path, dst_parent, dst_child_path)?; // Fsync the parent directory (or directories, if they're different). src_parent.sync_all()?; if src_parent_path != dst_parent_path { dst_parent.sync_all()?; } Ok(()) } pub fn move_atomic(src: &path::Path, dst: &path::Path) -> io::Result<()> { let src_parent_path = safe_parent(src).unwrap(); let dst_parent_path = safe_parent(dst).unwrap(); let src_child_path = src.file_name().unwrap(); let dst_child_path = dst.file_name().unwrap(); // Open the parent directories. If src and dst have the same parent // path, open it once and reuse it. let src_parent = fs::File::open(src_parent_path)?; let dst_parent; let dst_parent = if src_parent_path == dst_parent_path { &src_parent } else { dst_parent = fs::File::open(dst_parent_path)?; &dst_parent }; // On Linux, use `renameat2` with `RENAME_NOREPLACE` if we have it, as // that does an atomic rename. #[cfg(any(target_os = "android", target_os = "linux"))] { use rustix::fs::RenameFlags; use std::sync::atomic::AtomicBool; use std::sync::atomic::Ordering::Relaxed; static NO_RENAMEAT2: AtomicBool = AtomicBool::new(false); if !NO_RENAMEAT2.load(Relaxed) { match rustix::fs::renameat_with( &src_parent, src_child_path, dst_parent, dst_child_path, RenameFlags::NOREPLACE, ) { Ok(()) => { // Fsync the parent directory (or directories, if // they're different). src_parent.sync_all()?; if src_parent_path != dst_parent_path { dst_parent.sync_all()?; } return Ok(()); } Err(rustix::io::Errno::INVAL) | Err(rustix::io::Errno::NOSYS) => { // `NOSYS` means the OS doesn't support `renameat2`; // remember this so that we don't bother calling it // again. // // `INVAL` might mean we're on a filesystem that // doesn't support the `NOREPLACE` flag, such as ZFS, // so let's conservatively avoid using `renameat2` // again as well. // // (Or, `INVAL` might mean that the user is trying to // make a directory a subdirectory of itself, in which // case responding by disabling further use of // `renameat2` is unfortunate but what else can we do?) NO_RENAMEAT2.store(true, Relaxed); } Err(e) => return Err(e.into()), } } } // Otherwise, hard-link the src to the dst, and then delete the dst. rustix::fs::linkat( &src_parent, src_child_path, dst_parent, dst_child_path, AtFlags::empty(), )?; rustix::fs::unlinkat(&src_parent, src_child_path, AtFlags::empty())?; // Fsync the parent directory (or directories, if they're different). src_parent.sync_all()?; if src_parent_path != dst_parent_path { dst_parent.sync_all()?; } Ok(()) } } #[cfg(windows)] mod imp { extern crate windows_sys; use std::ffi::OsStr; use std::os::windows::ffi::OsStrExt; use std::{io, path}; macro_rules! call { ($e: expr) => { if $e != 0 { Ok(()) } else { Err(io::Error::last_os_error()) } }; } fn path_to_windows_str>(x: T) -> Vec { x.as_ref().encode_wide().chain(Some(0)).collect() } pub fn replace_atomic(src: &path::Path, dst: &path::Path) -> io::Result<()> { call!(unsafe { windows_sys::Win32::Storage::FileSystem::MoveFileExW( path_to_windows_str(src).as_ptr(), path_to_windows_str(dst).as_ptr(), windows_sys::Win32::Storage::FileSystem::MOVEFILE_WRITE_THROUGH | windows_sys::Win32::Storage::FileSystem::MOVEFILE_REPLACE_EXISTING, ) }) } pub fn move_atomic(src: &path::Path, dst: &path::Path) -> io::Result<()> { call!(unsafe { windows_sys::Win32::Storage::FileSystem::MoveFileExW( path_to_windows_str(src).as_ptr(), path_to_windows_str(dst).as_ptr(), windows_sys::Win32::Storage::FileSystem::MOVEFILE_WRITE_THROUGH, ) }) } } /// Move `src` to `dst`. If `dst` exists, it will be silently overwritten. /// /// Both paths must reside on the same filesystem for the operation to be atomic. pub fn replace_atomic(src: &path::Path, dst: &path::Path) -> io::Result<()> { imp::replace_atomic(src, dst) } /// Move `src` to `dst`. An error will be returned if `dst` exists. /// /// Both paths must reside on the same filesystem for the operation to be atomic. pub fn move_atomic(src: &path::Path, dst: &path::Path) -> io::Result<()> { imp::move_atomic(src, dst) } atomicwrites-0.4.4/tests/lib.rs000064400000000000000000000056611046102023000146310ustar 00000000000000extern crate atomicwrites; extern crate tempfile; use atomicwrites::{AllowOverwrite, AtomicFile, DisallowOverwrite}; use std::io::{self, Read, Write}; use std::{env, fs, path}; use tempfile::TempDir; fn get_tmp() -> path::PathBuf { TempDir::new().unwrap().into_path() } #[test] fn test_simple_allow_override() { let tmpdir = get_tmp(); let path = tmpdir.join("haha"); let af = AtomicFile::new(&path, AllowOverwrite); let res: io::Result<()> = af.write(|f| f.write_all(b"HELLO")).map_err(|x| x.into()); res.unwrap(); af.write(|f| f.write_all(b"HELLO")).unwrap(); let mut rv = String::new(); let mut testfd = fs::File::open(&path).unwrap(); testfd.read_to_string(&mut rv).unwrap(); assert_eq!(&rv[..], "HELLO"); } #[test] fn test_simple_disallow_override() { let tmpdir = get_tmp(); let path = tmpdir.join("haha"); let af = AtomicFile::new(&path, DisallowOverwrite); af.write(|f| f.write_all(b"HELLO")).unwrap(); assert!(af.write(|f| f.write_all(b"HELLO")).is_err()); let mut rv = String::new(); let mut testfd = fs::File::open(&path).unwrap(); testfd.read_to_string(&mut rv).unwrap(); assert_eq!(&rv[..], "HELLO"); } #[test] fn test_allowed_pathtypes() { AtomicFile::new("haha", DisallowOverwrite); AtomicFile::new(&"haha", DisallowOverwrite); AtomicFile::new(&path::Path::new("haha"), DisallowOverwrite); AtomicFile::new(&path::PathBuf::from("haha"), DisallowOverwrite); } #[test] fn test_unicode() { let dmitri = "Дмитрий"; let greeting = format!("HELLO {}", dmitri); let tmpdir = get_tmp(); let path = tmpdir.join(dmitri); let af = AtomicFile::new(&path, DisallowOverwrite); af.write(|f| f.write_all(greeting.as_bytes())).unwrap(); let mut rv = String::new(); let mut testfd = fs::File::open(&path).unwrap(); testfd.read_to_string(&mut rv).unwrap(); assert_eq!(rv, greeting); } #[test] fn test_weird_paths() { let tmpdir = get_tmp(); env::set_current_dir(tmpdir).expect("setup failed"); AtomicFile::new("foo", AllowOverwrite) .write(|f| f.write_all(b"HELLO")) .unwrap(); let mut rv = String::new(); let mut testfd = fs::File::open("foo").unwrap(); testfd.read_to_string(&mut rv).unwrap(); assert_eq!(rv, "HELLO"); } /// Test the error that is returned if the file already exists /// with `OverwriteBehavior::DisallowOverwrite`. #[test] fn disallow_overwrite_error() -> io::Result<()> { let tmp = TempDir::new()?; let file = tmp.path().join("dest"); let af = AtomicFile::new_with_tmpdir(&file, DisallowOverwrite, tmp.path()); // touch file fs::write(&file, "")?; match af.write(|f: &mut fs::File| f.write(b"abc")) { Ok(_) => panic!("should fail!"), Err(e) => { let e = io::Error::from(e); match e.kind() { io::ErrorKind::AlreadyExists => Ok(()), _ => Err(e), } } } }