Revert^2 "Upgrade tempfile to 3.12.0"
This reverts commit e7131122ebd3be8c6326af7309d9a1ca0b664f6b.
Reason for revert: Relanding once the broken manifest is updated
Bug: 313489216
Bug: 366171449
Change-Id: I341e9e815351d8a7680637a34e97cb4f9a2a06c3
diff --git a/crates/tempfile/.cargo-checksum.json b/crates/tempfile/.cargo-checksum.json
deleted file mode 100644
index 26f8560..0000000
--- a/crates/tempfile/.cargo-checksum.json
+++ /dev/null
@@ -1 +0,0 @@
-{"files":{"Cargo.toml":"685243e302f6e014de9c8e9b95596e5f63c7bf7fde42e8e66a41a6bc7fd5e803","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"8b427f5bc501764575e52ba4f9d95673cf8f6d80a86d0d06599852e1a9a20a36","NEWS":"4255c86ac140a4d08423cd05cbd0aa42ff796bb4b38579dd19cde289ee3baecd","README.md":"db6717cbd0b3cbbce5f3cdb8a80d8f2d90b1be251b4c1c647557ae0f78ec9748","src/dir.rs":"4499ff439b740f8d2f01458664e2bf72bbfdd1206226780c6a91fb309ef15707","src/error.rs":"cc7d8eace0fff11cb342158d2885d5637bfb14b24ef30755e808554772039c5f","src/file/imp/mod.rs":"f6da9fcd93f11889670a251fdd8231b5f4614e5a971b7b183f52b44af68568d5","src/file/imp/other.rs":"99c8f9f3251199fc31e7b88810134712e5725fb6fa14648696ed5cbea980fc5b","src/file/imp/unix.rs":"cf8eeceecfddc37c9eaf95a1ebe088314dc468f07fe357961d80817eef619ca4","src/file/imp/windows.rs":"03d81d71c404f0d448e1162825d6fbd57a78b4af8d4dc5287ec2e7c5a873d7cc","src/file/mod.rs":"bda4ee3998106089a4c0ccbc8e46dc22b7d3aec427487fd4e414fb132b378736","src/lib.rs":"e2b0df7e17cc6680a5bb0829d0433f069c6bf9eede2007d21e3b01a595df41a8","src/spooled.rs":"51fa1d7639027234e257d343a5d3c95f2e47899ba6a24f0abec8d4d729eba6d6","src/util.rs":"2bd80ee69009e7e36b596d0105bb00184cff04e899e9fcce2e4cc21f23dda073","tests/namedtempfile.rs":"0031cb33ae6faf45be103869b4d98af63bef4040dc489b323212eb7a7ef72a9a","tests/spooled.rs":"29e797d486d867cb6ac46d4cf126eb5868a069a4070c3f50ffa02fbb0b887934","tests/tempdir.rs":"771d555d4eaa410207d212eb3744e016e0b5a22f1f1b7199636a4fac5daaf952","tests/tempfile.rs":"92078a1e20a39af77c1daa9a422345d20c41584dd2010b4829911c8741d1c628"},"package":"5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4"}
\ No newline at end of file
diff --git a/crates/tempfile/.cargo_vcs_info.json b/crates/tempfile/.cargo_vcs_info.json
new file mode 100644
index 0000000..c9553df
--- /dev/null
+++ b/crates/tempfile/.cargo_vcs_info.json
@@ -0,0 +1,6 @@
+{
+ "git": {
+ "sha1": "5b90d964e42e5eeac225b53e10829c2a4b151b0a"
+ },
+ "path_in_vcs": ""
+}
\ No newline at end of file
diff --git a/crates/tempfile/.github/dependabot.yml b/crates/tempfile/.github/dependabot.yml
new file mode 100644
index 0000000..98e44ee
--- /dev/null
+++ b/crates/tempfile/.github/dependabot.yml
@@ -0,0 +1,10 @@
+version: 2
+updates:
+ - package-ecosystem: "cargo"
+ directory: "/"
+ schedule:
+ interval: "weekly"
+ - package-ecosystem: "github-actions"
+ directory: "/"
+ schedule:
+ interval: "weekly"
diff --git a/crates/tempfile/.github/workflows/ci.yml b/crates/tempfile/.github/workflows/ci.yml
new file mode 100644
index 0000000..9b773d1
--- /dev/null
+++ b/crates/tempfile/.github/workflows/ci.yml
@@ -0,0 +1,85 @@
+on:
+ push:
+ branches:
+ - master
+ pull_request:
+ branches:
+ - master
+
+name: CI
+jobs:
+ deny:
+ name: Cargo deny
+ strategy:
+ matrix:
+ os:
+ - ubuntu-latest
+ runs-on: ${{ matrix.os }}
+ steps:
+ - uses: actions/checkout@v4
+ - uses: EmbarkStudios/cargo-deny-action@v2
+ timeout-minutes: 10
+
+ build_and_test:
+ name: OS Test
+ strategy:
+ fail-fast: false
+ matrix:
+ rust-version:
+ - nightly
+ - stable
+ - "1.63"
+ os:
+ - ubuntu-latest
+ - windows-latest
+ - macos-latest
+ runs-on: ${{ matrix.os }}
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ - name: Install Rust
+ uses: dtolnay/rust-toolchain@master
+ with:
+ toolchain: ${{ matrix.rust-version }}
+ - name: Build
+ run: cargo build
+ - name: Test
+ run: cargo test
+ wasi:
+ name: WASI Test Build
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ - name: Install Rust
+ uses: dtolnay/rust-toolchain@nightly
+ - name: Install Cargo WASI
+ run: cargo install cargo-wasi
+ - name: Build
+ run: cargo wasi build --features nightly
+ wasm:
+ name: WASM Test Build
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ - name: Install Rust
+ uses: dtolnay/rust-toolchain@stable
+ with:
+ targets: wasm32-unknown-unknown
+ - name: Build
+ run: cargo build --target wasm32-unknown-unknown
+ lint:
+ name: Clippy and fmt
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ - name: Install Rust
+ uses: dtolnay/rust-toolchain@stable
+ with:
+ components: clippy, rustfmt
+ - name: Check formatting
+ run: cargo fmt --check
+ - name: Check for clippy lints
+ run: cargo clippy
diff --git a/crates/tempfile/.gitignore b/crates/tempfile/.gitignore
new file mode 100644
index 0000000..a9d37c5
--- /dev/null
+++ b/crates/tempfile/.gitignore
@@ -0,0 +1,2 @@
+target
+Cargo.lock
diff --git a/crates/tempfile/Android.bp b/crates/tempfile/Android.bp
index 9f141a9..bd1302b 100644
--- a/crates/tempfile/Android.bp
+++ b/crates/tempfile/Android.bp
@@ -18,14 +18,14 @@
host_supported: true,
crate_name: "tempfile",
cargo_env_compat: true,
- cargo_pkg_version: "3.3.0",
+ cargo_pkg_version: "3.12.0",
crate_root: "src/lib.rs",
- edition: "2018",
+ edition: "2021",
rustlibs: [
"libcfg_if",
"libfastrand",
- "liblibc",
- "libremove_dir_all",
+ "libonce_cell",
+ "librustix",
],
apex_available: [
"//apex_available:anyapex",
@@ -33,5 +33,5 @@
],
product_available: true,
vendor_available: true,
- min_sdk_version: "29",
+ min_sdk_version: "30",
}
diff --git a/crates/tempfile/CHANGELOG.md b/crates/tempfile/CHANGELOG.md
new file mode 100644
index 0000000..7a43eae
--- /dev/null
+++ b/crates/tempfile/CHANGELOG.md
@@ -0,0 +1,290 @@
+# Changelog
+
+## 3.12.0
+
+- Add a `keep(keep: bool)` function to builder that suppresses delete-on-drop behavior (thanks to @RalfJung).
+- Update `windows-sys` from 0.52 to 0.59.
+
+## 3.11.0
+
+- Add the ability to override the default temporary directory. This API shouldn't be used in general, but there are some cases where it's unavoidable.
+
+## 3.10.1
+
+- Handle potential integer overflows in 32-bit systems when seeking/truncating "spooled" temporary files past 4GiB (2³²).
+- Handle a theoretical 32-bit overflow when generating a temporary file name larger than 4GiB. Now it'll panic (on allocation failure) rather than silently succeeding due to wraparound.
+
+Thanks to @stoeckmann for finding and fixing both of these issues.
+
+## 3.10.0
+
+- Drop `redox_syscall` dependency, we now use `rustix` for Redox.
+- Add `Builder::permissions` for setting the permissions on temporary files and directories (thanks to @Byron).
+- Update rustix to 0.38.31.
+- Update fastrand to 2.0.1.
+
+## 3.9.0
+
+- Updates windows-sys to 0.52
+- Updates minimum rustix version to 0.38.25
+
+## 3.8.1
+
+- Update rustix to fix a potential panic on `persist_noclobber` on android.
+- Update redox_syscall to 0.4 (on redox).
+- Fix some docs typos.
+
+## 3.8.0
+
+- Added `with_prefix` and `with_prefix_in` to `TempDir` and `NamedTempFile` to make it easier to create temporary files/directories with nice prefixes.
+- Misc cleanups.
+
+## 3.7.1
+
+- Tempfile builds on haiku again.
+- Under the hood, we've switched from the unlinkat/linkat syscalls to the regular unlink/link syscalls where possible.
+
+## 3.7.0
+
+BREAKING: This release updates the MSRV to 1.63. This isn't an API-breaking change (so no major
+release) but it's still a breaking change for some users.
+
+- Update fastrand from 1.6 to 2.0
+- Update rustix to 0.38
+- Updates the MSRV to 1.63.
+- Provide AsFd/AsRawFd on wasi.
+
+## 3.6.0
+
+- Update windows-sys to 0.48.
+- Update rustix min version to 0.37.11
+- Forward some `NamedTempFile` and `SpooledTempFile` methods to the underlying `File` object for
+ better performance (especially vectorized writes, etc.).
+- Implement `AsFd` and `AsHandle`.
+- Misc documentation fixes and code cleanups.
+
+## 3.5.0
+
+- Update rustix from 0.36 to 0.37.1. This makes wasi work on rust stable
+- Update `windows-sys`, `redox_syscall`
+- BREAKING: Remove the implementation of `Write for &NamedTempFile<F> where &F: Write`. Unfortunately, this can cause compile issues in unrelated code (https://github.com/Stebalien/tempfile/issues/224).
+
+## 3.4.0
+
+SECURITY: Prior `tempfile` releases depended on `remove_dir_all` version 0.5.0 which was vulnerable to a [TOCTOU race](https://github.com/XAMPPRocky/remove_dir_all/security/advisories/GHSA-mc8h-8q98-g5hr). This same race is present in rust versions prior to 1.58.1.
+
+Features:
+
+- Generalized temporary files: `NamedTempFile` can now abstract over different kinds of files (e.g.,
+ unix domain sockets, pipes, etc.):
+ - Add `Builder::make` and `Builder::make_in` for generalized temp file
+ creation.
+ - Add `NamedTempFile::from_parts` to complement `NamedTempFile::into_parts`.
+ - Add generic parameter to `NamedTempFile` to support wrapping non-File types.
+
+Bug Fixes/Improvements:
+
+- Don't try to create a temporary file multiple times if the file path has been fully specified by
+ the user (no random characters).
+- `NamedTempFile::persist_noclobber` is now always atomic on linux when `renameat_with` is
+ supported. Previously, it would first link the new path, then unlink the previous path.
+- Fix compiler warnings on windows.
+
+Trivia:
+
+- Switch from `libc` to `rustix` on wasi/unix. This now makes direct syscalls instead of calling
+ through libc.
+- Remove `remove_dir_all` dependency. The rust standard library has optimized their internal version
+ significantly.
+ - Switch to official windows-sys windows bindings.
+
+Breaking:
+
+ - The minimum rust version is now `1.48.0`.
+ - Mark most functions as `must_use`.
+ - Uses direct syscalls on linux by default, instead of libc.
+ - The new type parameter in `NamedTempFile` may lead to type inference issues in some cases.
+
+## 3.3.0
+
+Features:
+
+- Replace rand with fastrand for a significantly smaller dependency tree. Cryptographic randomness
+ isn't necessary for temporary file names, and isn't all that helpful either.
+- Add limited WASI support.
+- Add a function to extract the inner data from a `SpooledTempFile`.
+
+Bug Fixes:
+
+- Make it possible to persist unnamed temporary files on linux by removing the `O_EXCL` flag.
+- Fix redox minimum crate version.
+
+## 3.2.0
+
+Features:
+
+- Bump rand dependency to `0.8`.
+- Bump cfg-if dependency to `1.0`
+
+Other than that, this release mostly includes small cleanups and simplifications.
+
+Breaking: The minimum rust version is now `1.40.0`.
+
+## 3.1.0
+
+Features:
+
+- Bump rand dependency to `0.7`.
+
+Breaking: The minimum rust version is now `1.32.0`.
+
+## 3.0.9
+
+Documentation:
+
+- Add an example for reopening a named temporary file.
+- Flesh out the security documentation.
+
+Features:
+
+- Introduce an `append` option to the builder.
+- Errors:
+ - No longer implement the soft-deprecated `description`.
+ - Implement `source` instead of `cause`.
+
+Breaking: The minimum rust version is now 1.30.
+
+## 3.0.8
+
+This is a bugfix release.
+
+Fixes:
+
+- Export `PathPersistError`.
+- Fix a bug where flushing a `SpooledTempFile` to disk could fail to write part
+ of the file in some rare, yet-to-reproduced cases.
+
+## 3.0.7
+
+Breaking:
+
+- `Builder::prefix` and `Builder::suffix` now accept a generic `&AsRef<OsStr>`.
+ This could affect type inference.
+- Temporary files (except unnamed temporary files on Windows and Linux >= 3.11)
+ now use absolute path names. This will break programs that create temporary
+ files relative to their current working directory when they don't have the
+ search permission (x) on some ancestor directory. This is only likely to
+ affect programs with strange chroot-less filesystem sandboxes. If you believe
+ you're affected by this issue, please comment on #40.
+
+Features:
+
+- Accept anything implementing `&AsRef<OsStr>` in the builder: &OsStr, &OsString, &Path, etc.
+
+Fixes:
+
+- Fix LFS support.
+- Use absolute paths for named temporary files to guard against changes in the
+ current directory.
+- Use absolute paths when creating unnamed temporary files on platforms that
+ can't create unlinked or auto-deleted temporary files. This fixes a very
+ unlikely race where the current directory could change while the temporary
+ file is being created.
+
+Misc:
+
+- Use modern stdlib features to avoid custom unsafe code. This reduces the
+ number of unsafe blocks from 12 to 4.
+
+## 3.0.6
+
+- Don't hide temporary files on windows, fixing #66 and #69.
+
+## 3.0.5
+
+Features:
+
+- Added a spooled temporary file implementation. This temporary file variant
+ starts out as an in-memory temporary file but "rolls-over" onto disk when it
+ grows over a specified size (#68).
+- Errors are now annotated with paths to make debugging easier (#73).
+
+Misc:
+
+- The rand version has been bumped to 0.6 (#74).
+
+Bugs:
+
+- Tempfile compiles again on Redox (#75).
+
+## 3.0.4
+
+- Now compiles on unsupported platforms.
+
+## 3.0.3
+
+- update rand to 0.5
+
+## 3.0.2
+
+- Actually *delete* temporary files on non-Linux unix systems (thanks to
+@oliverhenshaw for the fix and a test case).
+
+## 3.0.1
+
+- Restore NamedTempFile::new_in
+
+## 3.0.0
+
+- Adds temporary directory support (@KodrAus)
+- Allow closing named temporary files without deleting them (@jasonwhite)
+
+## 2.2.0
+
+- Redox Support
+
+## 2.1.6
+
+- Remove build script and bump minimum rustc version to 1.9.0
+
+## 2.1.5
+
+- Don't build platform-specific dependencies on all platforms.
+- Cleanup some documentation.
+
+## 2.1.4
+
+- Fix crates.io tags. No interesting changes.
+
+## 2.1.3
+
+Export `PersistError`.
+
+## 2.1.2
+
+Add `Read`/`Write`/`Seek` impls on `&NamedTempFile`. This mirrors the
+implementations on `&File`. One can currently just deref to a `&File` but these
+implementations are more discoverable.
+
+## 2.1.1
+
+Add LFS Support.
+
+## 2.1.0
+
+- Implement `AsRef<File>` for `NamedTempFile` allowing named temporary files to
+ be borrowed as `File`s.
+- Add a method to convert a `NamedTempFile` to an unnamed temporary `File`.
+
+## 2.0.1
+
+- Arm bugfix
+
+## 2.0.0
+
+This release replaces `TempFile` with a `tempfile()` function that returns
+`std::fs::File` objects. These are significantly more useful because most rust
+libraries expect normal `File` objects.
+
+To continue supporting shared temporary files, this new version adds a
+`reopen()` method to `NamedTempFile`.
diff --git a/crates/tempfile/Cargo.toml b/crates/tempfile/Cargo.toml
index 253f866..a199caa 100644
--- a/crates/tempfile/Cargo.toml
+++ b/crates/tempfile/Cargo.toml
@@ -10,34 +10,81 @@
# See Cargo.toml.orig for the original contents.
[package]
-edition = "2018"
+edition = "2021"
+rust-version = "1.63"
name = "tempfile"
-version = "3.3.0"
-authors = ["Steven Allen <steven@stebalien.com>", "The Rust Project Developers", "Ashley Mannix <ashleymannix@live.com.au>", "Jason White <jasonaw0@gmail.com>"]
-exclude = ["/.travis.yml", "/appveyor.yml"]
+version = "3.12.0"
+authors = [
+ "Steven Allen <steven@stebalien.com>",
+ "The Rust Project Developers",
+ "Ashley Mannix <ashleymannix@live.com.au>",
+ "Jason White <me@jasonwhite.io>",
+]
+build = false
+autobins = false
+autoexamples = false
+autotests = false
+autobenches = false
description = "A library for managing temporary files and directories."
-homepage = "http://stebalien.com/projects/tempfile-rs"
+homepage = "https://stebalien.com/projects/tempfile-rs/"
documentation = "https://docs.rs/tempfile"
-keywords = ["tempfile", "tmpfile", "filesystem"]
+readme = "README.md"
+keywords = [
+ "tempfile",
+ "tmpfile",
+ "filesystem",
+]
license = "MIT OR Apache-2.0"
repository = "https://github.com/Stebalien/tempfile"
+
+[lib]
+name = "tempfile"
+path = "src/lib.rs"
+
+[[test]]
+name = "env"
+path = "tests/env.rs"
+
+[[test]]
+name = "namedtempfile"
+path = "tests/namedtempfile.rs"
+
+[[test]]
+name = "spooled"
+path = "tests/spooled.rs"
+
+[[test]]
+name = "tempdir"
+path = "tests/tempdir.rs"
+
+[[test]]
+name = "tempfile"
+path = "tests/tempfile.rs"
+
[dependencies.cfg-if]
version = "1"
[dependencies.fastrand]
-version = "1.6.0"
+version = "2.0.1"
-[dependencies.remove_dir_all]
-version = "0.5"
+[dependencies.once_cell]
+version = "1.19.0"
+features = ["std"]
+default-features = false
+
[dev-dependencies.doc-comment]
version = "0.3"
[features]
nightly = []
-[target."cfg(any(unix, target_os = \"wasi\"))".dependencies.libc]
-version = "0.2.27"
-[target."cfg(target_os = \"redox\")".dependencies.redox_syscall]
-version = "0.2.9"
-[target."cfg(windows)".dependencies.winapi]
-version = "0.3"
-features = ["fileapi", "handleapi", "winbase"]
+
+[target.'cfg(any(unix, target_os = "wasi"))'.dependencies.rustix]
+version = "0.38.31"
+features = ["fs"]
+
+[target."cfg(windows)".dependencies.windows-sys]
+version = "0.59"
+features = [
+ "Win32_Storage_FileSystem",
+ "Win32_Foundation",
+]
diff --git a/crates/tempfile/Cargo.toml.orig b/crates/tempfile/Cargo.toml.orig
new file mode 100644
index 0000000..1dee2d9
--- /dev/null
+++ b/crates/tempfile/Cargo.toml.orig
@@ -0,0 +1,39 @@
+[package]
+name = "tempfile"
+version = "3.12.0"
+authors = [
+ "Steven Allen <steven@stebalien.com>",
+ "The Rust Project Developers",
+ "Ashley Mannix <ashleymannix@live.com.au>",
+ "Jason White <me@jasonwhite.io>",
+]
+documentation = "https://docs.rs/tempfile"
+edition = "2021"
+rust-version = "1.63"
+homepage = "https://stebalien.com/projects/tempfile-rs/"
+keywords = ["tempfile", "tmpfile", "filesystem"]
+license = "MIT OR Apache-2.0"
+repository = "https://github.com/Stebalien/tempfile"
+description = "A library for managing temporary files and directories."
+
+[dependencies]
+cfg-if = "1"
+fastrand = "2.0.1"
+# Not available in stdlib until 1.70, but we support 1.63 to support Debian stable.
+once_cell = { version = "1.19.0", default-features = false, features = ["std"] }
+
+[target.'cfg(any(unix, target_os = "wasi"))'.dependencies]
+rustix = { version = "0.38.31", features = ["fs"] }
+
+[target.'cfg(windows)'.dependencies.windows-sys]
+version = "0.59"
+features = [
+ "Win32_Storage_FileSystem",
+ "Win32_Foundation",
+]
+
+[dev-dependencies]
+doc-comment = "0.3"
+
+[features]
+nightly = []
diff --git a/crates/tempfile/METADATA b/crates/tempfile/METADATA
index 6add873..daf2b4c 100644
--- a/crates/tempfile/METADATA
+++ b/crates/tempfile/METADATA
@@ -1,17 +1,17 @@
name: "tempfile"
description: "A library for managing temporary files and directories."
third_party {
- version: "3.3.0"
+ version: "3.12.0"
license_type: NOTICE
last_upgrade_date {
- year: 2023
- month: 2
+ year: 2024
+ month: 9
day: 6
}
homepage: "https://crates.io/crates/tempfile"
identifier {
type: "Archive"
- value: "https://static.crates.io/crates/tempfile/tempfile-3.3.0.crate"
- version: "3.3.0"
+ value: "https://static.crates.io/crates/tempfile/tempfile-3.12.0.crate"
+ version: "3.12.0"
}
}
diff --git a/crates/tempfile/NEWS b/crates/tempfile/NEWS
deleted file mode 100644
index c284424..0000000
--- a/crates/tempfile/NEWS
+++ /dev/null
@@ -1,206 +0,0 @@
-3.3.0
-=====
-
-Features:
-
-* Replace rand with fastrand for a significantly smaller dependency tree. Cryptographic randomness
- isn't necessary for temporary file names, and isn't all that helpful either.
-* Add limited WASI support.
-* Add a function to extract the inner data from a `SpooledTempFile`.
-
-Bug Fixes:
-
-* Make it possible to persist unnamed temporary files on linux by removing the `O_EXCL` flag.
-* Fix redox minimum crate version.
-
-3.2.0
-=====
-
-Features:
-
-* Bump rand dependency to `0.8`.
-* Bump cfg-if dependency to `1.0`
-
-Other than that, this release mostly includes small cleanups and simplifications.
-
-Breaking: The minimum rust version is now `1.40.0`.
-
-3.1.0
-=====
-
-Features:
-
-* Bump rand dependency to `0.7`.
-
-Breaking: The minimum rust version is now `1.32.0`.
-
-3.0.9
-=====
-
-Documentation:
-
-* Add an example for reopening a named temporary file.
-* Flesh out the security documentation.
-
-Features:
-
-* Introduce an `append` option to the builder.
-* Errors:
- * No longer implement the soft-deprecated `description`.
- * Implement `source` instead of `cause`.
-
-Breaking: The minimum rust version is now 1.30.
-
-3.0.8
-=====
-
-This is a bugfix release.
-
-Fixes:
-
-* Export `PathPersistError`.
-* Fix a bug where flushing a `SpooledTempFile` to disk could fail to write part
- of the file in some rare, yet-to-reproduced cases.
-
-3.0.7
-=====
-
-Breaking:
-
-* `Builder::prefix` and `Builder::suffix` now accept a generic `&AsRef<OsStr>`.
- This could affect type inference.
-* Temporary files (except unnamed temporary files on Windows and Linux >= 3.11)
- now use absolute path names. This will break programs that create temporary
- files relative to their current working directory when they don't have the
- search permission (x) on some ancestor directory. This is only likely to
- affect programs with strange chroot-less filesystem sandboxes. If you believe
- you're affected by this issue, please comment on #40.
-
-Features:
-
-* Accept anything implementing `&AsRef<OsStr>` in the builder: &OsStr, &OsString, &Path, etc.
-
-Fixes:
-
-* Fix LFS support.
-* Use absolute paths for named temporary files to guard against changes in the
- current directory.
-* Use absolute paths when creating unnamed temporary files on platforms that
- can't create unlinked or auto-deleted temporary files. This fixes a very
- unlikely race where the current directory could change while the temporary
- file is being created.
-
-Misc:
-
-* Use modern stdlib features to avoid custom unsafe code. This reduces the
- number of unsafe blocks from 12 to 4.
-
-3.0.6
-=====
-
-* Don't hide temporary files on windows, fixing #66 and #69.
-
-3.0.5
-=====
-
-Features:
-
-* Added a spooled temporary file implementation. This temporary file variant
- starts out as an in-memory temporary file but "rolls-over" onto disk when it
- grows over a specified size (#68).
-* Errors are now annotated with paths to make debugging easier (#73).
-
-Misc:
-
-* The rand version has been bumped to 0.6 (#74).
-
-Bugs:
-
-* Tempfile compiles again on Redox (#75).
-
-3.0.4
-=====
-
-* Now compiles on unsupported platforms.
-
-3.0.3
-=====
-
-* update rand to 0.5
-
-3.0.2
-=====
-
-* Actually *delete* temporary files on non-Linux unix systems (thanks to
-@oliverhenshaw for the fix and a test case).
-
-3.0.1
-=====
-
-* Restore NamedTempFile::new_in
-
-3.0.0
-=====
-
-* Adds temporary directory support (@KodrAus)
-* Allow closing named temporary files without deleting them (@jasonwhite)
-
-2.2.0
-=====
-
-* Redox Support
-
-2.1.6
-=====
-
-* Remove build script and bump minimum rustc version to 1.9.0
-
-2.1.5
-=====
-
-* Don't build platform-specific dependencies on all platforms.
-* Cleanup some documentation.
-
-2.1.4
-=====
-
-* Fix crates.io tags. No interesting changes.
-
-2.1.3
-=====
-
-Export `PersistError`.
-
-2.1.2
-=====
-
-Add `Read`/`Write`/`Seek` impls on `&NamedTempFile`. This mirrors the
-implementations on `&File`. One can currently just deref to a `&File` but these
-implementations are more discoverable.
-
-2.1.1
-=====
-
-Add LFS Support.
-
-2.1.0
-=====
-
-* Implement `AsRef<File>` for `NamedTempFile` allowing named temporary files to
- be borrowed as `File`s.
-* Add a method to convert a `NamedTempFile` to an unnamed temporary `File`.
-
-2.0.1
-=====
-
-* Arm bugfix
-
-2.0.0
-=====
-
-This release replaces `TempFile` with a `tempfile()` function that returnes
-`std::fs::File` objects. These are significantly more useful because most rust
-libraries expect normal `File` objects.
-
-To continue supporting shared temporary files, this new version adds a
-`reopen()` method to `NamedTempFile`.
diff --git a/crates/tempfile/README.md b/crates/tempfile/README.md
index 1dba3a0..4d886b1 100644
--- a/crates/tempfile/README.md
+++ b/crates/tempfile/README.md
@@ -14,9 +14,10 @@
Usage
-----
-Minimum required Rust version: 1.40.0
+Minimum required Rust version: 1.63.0
Add this to your `Cargo.toml`:
+
```toml
[dependencies]
tempfile = "3"
diff --git a/crates/tempfile/cargo_embargo.json b/crates/tempfile/cargo_embargo.json
index 3e8023e..cd6c19e 100644
--- a/crates/tempfile/cargo_embargo.json
+++ b/crates/tempfile/cargo_embargo.json
@@ -3,6 +3,6 @@
"//apex_available:anyapex",
"//apex_available:platform"
],
- "min_sdk_version": "29",
+ "min_sdk_version": "30",
"run_cargo": false
}
diff --git a/crates/tempfile/deny.toml b/crates/tempfile/deny.toml
new file mode 100644
index 0000000..a2cf7ed
--- /dev/null
+++ b/crates/tempfile/deny.toml
@@ -0,0 +1,33 @@
+
+[advisories]
+yanked = "deny"
+ignore = []
+
+[licenses]
+allow = [
+ "Apache-2.0",
+ "MIT",
+]
+confidence-threshold = 1.0
+
+[bans]
+allow = []
+deny = []
+multiple-versions = "deny"
+skip = [
+ # Dependency of this crate and and rustix (rustix has older). We only use one at a time.
+ { name = "windows-sys" },
+]
+skip-tree = []
+wildcards = "deny"
+
+[sources]
+allow-git = []
+allow-registry = ["https://github.com/rust-lang/crates.io-index"]
+unknown-git = "deny"
+unknown-registry = "deny"
+
+[sources.allow-org]
+github = []
+gitlab = []
+bitbucket = []
diff --git a/crates/tempfile/src/dir/imp/any.rs b/crates/tempfile/src/dir/imp/any.rs
new file mode 100644
index 0000000..512a54e
--- /dev/null
+++ b/crates/tempfile/src/dir/imp/any.rs
@@ -0,0 +1,24 @@
+use crate::error::IoResultExt;
+use crate::TempDir;
+use std::path::PathBuf;
+use std::{fs, io};
+
+fn not_supported<T>(msg: &str) -> io::Result<T> {
+ Err(io::Error::new(io::ErrorKind::Other, msg))
+}
+
+pub fn create(
+ path: PathBuf,
+ permissions: Option<&std::fs::Permissions>,
+ keep: bool,
+) -> io::Result<TempDir> {
+ if permissions.map_or(false, |p| p.readonly()) {
+ return not_supported("changing permissions is not supported on this platform");
+ }
+ fs::create_dir(&path)
+ .with_err_path(|| &path)
+ .map(|_| TempDir {
+ path: path.into_boxed_path(),
+ keep,
+ })
+}
diff --git a/crates/tempfile/src/dir/imp/mod.rs b/crates/tempfile/src/dir/imp/mod.rs
new file mode 100644
index 0000000..26d0a22
--- /dev/null
+++ b/crates/tempfile/src/dir/imp/mod.rs
@@ -0,0 +1,9 @@
+#[cfg(unix)]
+mod unix;
+#[cfg(unix)]
+pub use unix::*;
+
+#[cfg(not(unix))]
+mod any;
+#[cfg(not(unix))]
+pub use any::*;
diff --git a/crates/tempfile/src/dir/imp/unix.rs b/crates/tempfile/src/dir/imp/unix.rs
new file mode 100644
index 0000000..d69dac3
--- /dev/null
+++ b/crates/tempfile/src/dir/imp/unix.rs
@@ -0,0 +1,26 @@
+use crate::error::IoResultExt;
+use crate::TempDir;
+use std::io;
+use std::path::PathBuf;
+
+pub fn create(
+ path: PathBuf,
+ permissions: Option<&std::fs::Permissions>,
+ keep: bool,
+) -> io::Result<TempDir> {
+ let mut dir_options = std::fs::DirBuilder::new();
+ #[cfg(not(target_os = "wasi"))]
+ {
+ use std::os::unix::fs::{DirBuilderExt, PermissionsExt};
+ if let Some(p) = permissions {
+ dir_options.mode(p.mode());
+ }
+ }
+ dir_options
+ .create(&path)
+ .with_err_path(|| &path)
+ .map(|_| TempDir {
+ path: path.into_boxed_path(),
+ keep,
+ })
+}
diff --git a/crates/tempfile/src/dir.rs b/crates/tempfile/src/dir/mod.rs
similarity index 73%
rename from crates/tempfile/src/dir.rs
rename to crates/tempfile/src/dir/mod.rs
index d5a944b..067c74a 100644
--- a/crates/tempfile/src/dir.rs
+++ b/crates/tempfile/src/dir/mod.rs
@@ -8,14 +8,18 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
-use remove_dir_all::remove_dir_all;
+use std::ffi::OsStr;
+use std::fs::remove_dir_all;
use std::mem;
use std::path::{self, Path, PathBuf};
-use std::{fmt, fs, io};
+use std::{fmt, io};
use crate::error::IoResultExt;
use crate::Builder;
+#[cfg(doc)]
+use crate::env;
+
/// Create a new temporary directory.
///
/// The `tempdir` function creates a directory in the file system
@@ -36,27 +40,20 @@
/// ```
/// use tempfile::tempdir;
/// use std::fs::File;
-/// use std::io::{self, Write};
+/// use std::io::Write;
///
-/// # fn main() {
-/// # if let Err(_) = run() {
-/// # ::std::process::exit(1);
-/// # }
-/// # }
-/// # fn run() -> Result<(), io::Error> {
-/// // Create a directory inside of `std::env::temp_dir()`
-/// let dir = tempdir()?;
+/// // Create a directory inside of `env::temp_dir()`
+/// let tmp_dir = tempdir()?;
///
-/// let file_path = dir.path().join("my-temporary-note.txt");
-/// let mut file = File::create(file_path)?;
-/// writeln!(file, "Brian was here. Briefly.")?;
+/// let file_path = tmp_dir.path().join("my-temporary-note.txt");
+/// let mut tmp_file = File::create(file_path)?;
+/// writeln!(tmp_file, "Brian was here. Briefly.")?;
///
/// // `tmp_dir` goes out of scope, the directory as well as
/// // `tmp_file` will be deleted here.
-/// drop(file);
-/// dir.close()?;
-/// # Ok(())
-/// # }
+/// drop(tmp_file);
+/// tmp_dir.close()?;
+/// # Ok::<(), std::io::Error>(())
/// ```
///
/// [`TempDir`]: struct.TempDir.html
@@ -65,9 +62,9 @@
TempDir::new()
}
-/// Create a new temporary directory.
+/// Create a new temporary directory in a specific directory.
///
-/// The `tempdir` function creates a directory in the file system
+/// The `tempdir_in` function creates a directory in the specified directory
/// and returns a [`TempDir`].
/// The directory will be automatically deleted when the `TempDir`s
/// destructor is run.
@@ -83,29 +80,22 @@
/// # Examples
///
/// ```
-/// use tempfile::tempdir;
+/// use tempfile::tempdir_in;
/// use std::fs::File;
-/// use std::io::{self, Write};
+/// use std::io::Write;
///
-/// # fn main() {
-/// # if let Err(_) = run() {
-/// # ::std::process::exit(1);
-/// # }
-/// # }
-/// # fn run() -> Result<(), io::Error> {
-/// // Create a directory inside of `std::env::temp_dir()`,
-/// let dir = tempdir()?;
+/// // Create a directory inside of the current directory.
+/// let tmp_dir = tempdir_in(".")?;
///
-/// let file_path = dir.path().join("my-temporary-note.txt");
-/// let mut file = File::create(file_path)?;
-/// writeln!(file, "Brian was here. Briefly.")?;
+/// let file_path = tmp_dir.path().join("my-temporary-note.txt");
+/// let mut tmp_file = File::create(file_path)?;
+/// writeln!(tmp_file, "Brian was here. Briefly.")?;
///
/// // `tmp_dir` goes out of scope, the directory as well as
/// // `tmp_file` will be deleted here.
-/// drop(file);
-/// dir.close()?;
-/// # Ok(())
-/// # }
+/// drop(tmp_file);
+/// tmp_dir.close()?;
+/// # Ok::<(), std::io::Error>(())
/// ```
///
/// [`TempDir`]: struct.TempDir.html
@@ -122,7 +112,7 @@
/// `TempDir` creates a new directory with a randomly generated name.
///
/// The default constructor, [`TempDir::new()`], creates directories in
-/// the location returned by [`std::env::temp_dir()`], but `TempDir`
+/// the location returned by [`env::temp_dir()`], but `TempDir`
/// can be configured to manage a temporary directory in any location
/// by constructing with a [`Builder`].
///
@@ -157,12 +147,9 @@
/// use std::io::Write;
/// use tempfile::TempDir;
///
-/// # use std::io;
-/// # fn run() -> Result<(), io::Error> {
-/// // Create a directory inside of `std::env::temp_dir()`
+/// // Create a directory inside of `env::temp_dir()`
/// let tmp_dir = TempDir::new()?;
-/// # Ok(())
-/// # }
+/// # Ok::<(), std::io::Error>(())
/// ```
///
/// Create a temporary directory with a prefix in its name:
@@ -172,13 +159,10 @@
/// use std::io::Write;
/// use tempfile::Builder;
///
-/// # use std::io;
-/// # fn run() -> Result<(), io::Error> {
-/// // Create a directory inside of `std::env::temp_dir()`,
+/// // Create a directory inside of `env::temp_dir()`,
/// // whose name will begin with 'example'.
/// let tmp_dir = Builder::new().prefix("example").tempdir()?;
-/// # Ok(())
-/// # }
+/// # Ok::<(), std::io::Error>(())
/// ```
///
/// [`File`]: http://doc.rust-lang.org/std/fs/struct.File.html
@@ -189,11 +173,11 @@
/// [`TempDir::new()`]: struct.TempDir.html#method.new
/// [`TempDir::path()`]: struct.TempDir.html#method.path
/// [`TempDir`]: struct.TempDir.html
-/// [`std::env::temp_dir()`]: https://doc.rust-lang.org/std/env/fn.temp_dir.html
/// [`std::fs`]: http://doc.rust-lang.org/std/fs/index.html
/// [`std::process::exit()`]: http://doc.rust-lang.org/std/process/fn.exit.html
pub struct TempDir {
path: Box<Path>,
+ keep: bool,
}
impl TempDir {
@@ -215,9 +199,7 @@
/// use std::io::Write;
/// use tempfile::TempDir;
///
- /// # use std::io;
- /// # fn run() -> Result<(), io::Error> {
- /// // Create a directory inside of `std::env::temp_dir()`
+ /// // Create a directory inside of `env::temp_dir()`
/// let tmp_dir = TempDir::new()?;
///
/// let file_path = tmp_dir.path().join("my-temporary-note.txt");
@@ -226,8 +208,7 @@
///
/// // `tmp_dir` goes out of scope, the directory as well as
/// // `tmp_file` will be deleted here.
- /// # Ok(())
- /// # }
+ /// # Ok::<(), std::io::Error>(())
/// ```
///
/// [`Builder`]: struct.Builder.html
@@ -250,20 +231,70 @@
/// use std::io::Write;
/// use tempfile::TempDir;
///
- /// # use std::io;
- /// # fn run() -> Result<(), io::Error> {
/// // Create a directory inside of the current directory
/// let tmp_dir = TempDir::new_in(".")?;
/// let file_path = tmp_dir.path().join("my-temporary-note.txt");
/// let mut tmp_file = File::create(file_path)?;
/// writeln!(tmp_file, "Brian was here. Briefly.")?;
- /// # Ok(())
- /// # }
+ /// # Ok::<(), std::io::Error>(())
/// ```
pub fn new_in<P: AsRef<Path>>(dir: P) -> io::Result<TempDir> {
Builder::new().tempdir_in(dir)
}
+ /// Attempts to make a temporary directory with the specified prefix inside of
+ /// `env::temp_dir()`. The directory and everything inside it will be automatically
+ /// deleted once the returned `TempDir` is destroyed.
+ ///
+ /// # Errors
+ ///
+ /// If the directory can not be created, `Err` is returned.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::fs::{self, File};
+ /// use std::io::Write;
+ /// use tempfile::TempDir;
+ ///
+ /// // Create a directory inside of the current directory
+ /// let tmp_dir = TempDir::with_prefix("foo-")?;
+ /// let tmp_name = tmp_dir.path().file_name().unwrap().to_str().unwrap();
+ /// assert!(tmp_name.starts_with("foo-"));
+ /// # Ok::<(), std::io::Error>(())
+ /// ```
+ pub fn with_prefix<S: AsRef<OsStr>>(prefix: S) -> io::Result<TempDir> {
+ Builder::new().prefix(&prefix).tempdir()
+ }
+
+ /// Attempts to make a temporary directory with the specified prefix inside
+ /// the specified directory. The directory and everything inside it will be
+ /// automatically deleted once the returned `TempDir` is destroyed.
+ ///
+ /// # Errors
+ ///
+ /// If the directory can not be created, `Err` is returned.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::fs::{self, File};
+ /// use std::io::Write;
+ /// use tempfile::TempDir;
+ ///
+ /// // Create a directory inside of the current directory
+ /// let tmp_dir = TempDir::with_prefix_in("foo-", ".")?;
+ /// let tmp_name = tmp_dir.path().file_name().unwrap().to_str().unwrap();
+ /// assert!(tmp_name.starts_with("foo-"));
+ /// # Ok::<(), std::io::Error>(())
+ /// ```
+ pub fn with_prefix_in<S: AsRef<OsStr>, P: AsRef<Path>>(
+ prefix: S,
+ dir: P,
+ ) -> io::Result<TempDir> {
+ Builder::new().prefix(&prefix).tempdir_in(dir)
+ }
+
/// Accesses the [`Path`] to the temporary directory.
///
/// [`Path`]: http://doc.rust-lang.org/std/path/struct.Path.html
@@ -273,8 +304,6 @@
/// ```
/// use tempfile::TempDir;
///
- /// # use std::io;
- /// # fn run() -> Result<(), io::Error> {
/// let tmp_path;
///
/// {
@@ -289,9 +318,9 @@
///
/// // Temp directory should be deleted by now
/// assert_eq!(tmp_path.exists(), false);
- /// # Ok(())
- /// # }
+ /// # Ok::<(), std::io::Error>(())
/// ```
+ #[must_use]
pub fn path(&self) -> &path::Path {
self.path.as_ref()
}
@@ -310,8 +339,6 @@
/// use std::fs;
/// use tempfile::TempDir;
///
- /// # use std::io;
- /// # fn run() -> Result<(), io::Error> {
/// let tmp_dir = TempDir::new()?;
///
/// // Persist the temporary directory to disk,
@@ -320,9 +347,9 @@
///
/// // Delete the temporary directory ourselves.
/// fs::remove_dir_all(tmp_path)?;
- /// # Ok(())
- /// # }
+ /// # Ok::<(), std::io::Error>(())
/// ```
+ #[must_use]
pub fn into_path(self) -> PathBuf {
// Prevent the Drop impl from being called.
let mut this = mem::ManuallyDrop::new(self);
@@ -354,9 +381,7 @@
/// use std::io::Write;
/// use tempfile::TempDir;
///
- /// # use std::io;
- /// # fn run() -> Result<(), io::Error> {
- /// // Create a directory inside of `std::env::temp_dir()`.
+ /// // Create a directory inside of `env::temp_dir()`.
/// let tmp_dir = TempDir::new()?;
/// let file_path = tmp_dir.path().join("my-temporary-note.txt");
/// let mut tmp_file = File::create(file_path)?;
@@ -369,8 +394,7 @@
/// // succeeded.
/// drop(tmp_file);
/// tmp_dir.close()?;
- /// # Ok(())
- /// # }
+ /// # Ok::<(), std::io::Error>(())
/// ```
pub fn close(mut self) -> io::Result<()> {
let result = remove_dir_all(self.path()).with_err_path(|| self.path());
@@ -402,14 +426,18 @@
impl Drop for TempDir {
fn drop(&mut self) {
- let _ = remove_dir_all(self.path());
+ if !self.keep {
+ let _ = remove_dir_all(self.path());
+ }
}
}
-pub(crate) fn create(path: PathBuf) -> io::Result<TempDir> {
- fs::create_dir(&path)
- .with_err_path(|| &path)
- .map(|_| TempDir {
- path: path.into_boxed_path(),
- })
+pub(crate) fn create(
+ path: PathBuf,
+ permissions: Option<&std::fs::Permissions>,
+ keep: bool,
+) -> io::Result<TempDir> {
+ imp::create(path, permissions, keep)
}
+
+mod imp;
diff --git a/crates/tempfile/src/env.rs b/crates/tempfile/src/env.rs
new file mode 100644
index 0000000..86d5199
--- /dev/null
+++ b/crates/tempfile/src/env.rs
@@ -0,0 +1,43 @@
+use std::env;
+use std::path::{Path, PathBuf};
+
+// Once rust 1.70 is wide-spread (Debian stable), we can use OnceLock from stdlib.
+use once_cell::sync::OnceCell as OnceLock;
+
+static DEFAULT_TEMPDIR: OnceLock<PathBuf> = OnceLock::new();
+
+/// Override the default temporary directory (defaults to [`std::env::temp_dir`]). This function
+/// changes the _global_ default temporary directory for the entire program and should not be called
+/// except in exceptional cases where it's not configured correctly by the platform.
+///
+/// Only the first call to this function will succeed. All further calls will fail with `Err(path)`
+/// where `path` is previously set default temporary directory override.
+///
+/// **NOTE:** This function does not check if the specified directory exists and/or is writable.
+pub fn override_temp_dir(path: &Path) -> Result<(), PathBuf> {
+ let mut we_set = false;
+ let val = DEFAULT_TEMPDIR.get_or_init(|| {
+ we_set = true;
+ path.to_path_buf()
+ });
+ if we_set {
+ Ok(())
+ } else {
+ Err(val.to_owned())
+ }
+}
+
+/// Returns the default temporary directory, used for both temporary directories and files if no
+/// directory is explicitly specified.
+///
+/// This function simply delegates to [`std::env::temp_dir`] unless the default temporary directory
+/// has been override by a call to [`override_temp_dir`].
+///
+/// **NOTE:** This function does check if the returned directory exists and/or is writable.
+pub fn temp_dir() -> PathBuf {
+ DEFAULT_TEMPDIR
+ .get()
+ .map(|p| p.to_owned())
+ // Don't cache this in case the user uses std::env::set to change the temporary directory.
+ .unwrap_or_else(env::temp_dir)
+}
diff --git a/crates/tempfile/src/file/imp/other.rs b/crates/tempfile/src/file/imp/other.rs
index d8a55a7..bba3671 100644
--- a/crates/tempfile/src/file/imp/other.rs
+++ b/crates/tempfile/src/file/imp/other.rs
@@ -9,7 +9,11 @@
))
}
-pub fn create_named(_path: &Path, open_options: &mut OpenOptions) -> io::Result<File> {
+pub fn create_named(
+ _path: &Path,
+ _open_options: &mut OpenOptions,
+ _permissions: Option<&std::fs::Permissions>,
+) -> io::Result<File> {
not_supported()
}
@@ -25,6 +29,6 @@
not_supported()
}
-pub fn keep(path: &Path) -> io::Result<()> {
+pub fn keep(_path: &Path) -> io::Result<()> {
not_supported()
}
diff --git a/crates/tempfile/src/file/imp/unix.rs b/crates/tempfile/src/file/imp/unix.rs
index 480743c..117d76a 100644
--- a/crates/tempfile/src/file/imp/unix.rs
+++ b/crates/tempfile/src/file/imp/unix.rs
@@ -1,13 +1,10 @@
-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};
+ use std::os::unix::fs::MetadataExt;
} else {
- use std::os::wasi::ffi::OsStrExt;
#[cfg(feature = "nightly")]
use std::os::wasi::fs::MetadataExt;
}
@@ -16,36 +13,22 @@
use std::path::Path;
#[cfg(not(target_os = "redox"))]
-use libc::{c_char, c_int, link, rename, unlink};
+use {
+ rustix::fs::{rename, unlink},
+ std::fs::hard_link,
+};
-#[cfg(not(target_os = "redox"))]
-#[inline(always)]
-pub fn cvt_err(result: c_int) -> io::Result<c_int> {
- if result == -1 {
- Err(io::Error::last_os_error())
- } else {
- Ok(result)
- }
-}
-
-#[cfg(target_os = "redox")]
-#[inline(always)]
-pub fn cvt_err(result: Result<usize, syscall::Error>) -> io::Result<usize> {
- result.map_err(|err| io::Error::from_raw_os_error(err.errno))
-}
-
-// Stolen from std.
-pub fn cstr(path: &Path) -> io::Result<CString> {
- CString::new(path.as_os_str().as_bytes())
- .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "path contained a null"))
-}
-
-pub fn create_named(path: &Path, open_options: &mut OpenOptions) -> io::Result<File> {
+pub fn create_named(
+ path: &Path,
+ open_options: &mut OpenOptions,
+ #[cfg_attr(target_os = "wasi", allow(unused))] permissions: Option<&std::fs::Permissions>,
+) -> io::Result<File> {
open_options.read(true).write(true).create_new(true);
#[cfg(not(target_os = "wasi"))]
{
- open_options.mode(0o600);
+ use std::os::unix::fs::{OpenOptionsExt, PermissionsExt};
+ open_options.mode(permissions.map(|p| p.mode()).unwrap_or(0o600));
}
open_options.open(path)
@@ -56,12 +39,12 @@
// shadow this to decrease the lifetime. It can't live longer than `tmp`.
let mut path = path;
if !path.is_absolute() {
- let cur_dir = env::current_dir()?;
+ let cur_dir = std::env::current_dir()?;
tmp = cur_dir.join(path);
path = &tmp;
}
- let f = create_named(path, &mut OpenOptions::new())?;
+ let f = create_named(path, &mut OpenOptions::new(), None)?;
// don't care whether the path has already been unlinked,
// but perhaps there are some IO error conditions we should send up?
let _ = fs::remove_file(path);
@@ -70,16 +53,19 @@
#[cfg(target_os = "linux")]
pub fn create(dir: &Path) -> io::Result<File> {
- use libc::{EISDIR, ENOENT, EOPNOTSUPP, O_TMPFILE};
+ use rustix::{fs::OFlags, io::Errno};
+ use std::os::unix::fs::OpenOptionsExt;
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,31 +110,44 @@
#[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 {
+ rename(old_path, 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, CWD};
+ 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 `hard_link` to create the new filesystem name, which
+ // will fail if the name already exists, and then `unlink` to remove
+ // the old name.
+ hard_link(old_path, new_path)?;
+
+ // Ignore unlink errors. Can we do better?
+ let _ = unlink(old_path);
}
+ Ok(())
}
#[cfg(target_os = "redox")]
-pub fn persist(old_path: &Path, new_path: &Path, overwrite: bool) -> io::Result<()> {
+pub fn persist(_old_path: &Path, _new_path: &Path, _overwrite: bool) -> io::Result<()> {
// XXX implement when possible
- Err(io::Error::from_raw_os_error(syscall::ENOSYS))
+ use rustix::io::Errno;
+ Err(Errno::NOSYS.into())
}
pub fn keep(_: &Path) -> io::Result<()> {
diff --git a/crates/tempfile/src/file/imp/windows.rs b/crates/tempfile/src/file/imp/windows.rs
index 71b4748..e7f6038 100644
--- a/crates/tempfile/src/file/imp/windows.rs
+++ b/crates/tempfile/src/file/imp/windows.rs
@@ -6,13 +6,12 @@
use std::path::Path;
use std::{io, iter};
-use winapi::um::fileapi::SetFileAttributesW;
-use winapi::um::handleapi::INVALID_HANDLE_VALUE;
-use winapi::um::winbase::{MoveFileExW, ReOpenFile};
-use winapi::um::winbase::{FILE_FLAG_DELETE_ON_CLOSE, MOVEFILE_REPLACE_EXISTING};
-use winapi::um::winnt::{FILE_ATTRIBUTE_NORMAL, FILE_ATTRIBUTE_TEMPORARY};
-use winapi::um::winnt::{FILE_GENERIC_READ, FILE_GENERIC_WRITE, HANDLE};
-use winapi::um::winnt::{FILE_SHARE_DELETE, FILE_SHARE_READ, FILE_SHARE_WRITE};
+use windows_sys::Win32::Foundation::{HANDLE, INVALID_HANDLE_VALUE};
+use windows_sys::Win32::Storage::FileSystem::{
+ MoveFileExW, ReOpenFile, SetFileAttributesW, FILE_ATTRIBUTE_NORMAL, FILE_ATTRIBUTE_TEMPORARY,
+ FILE_FLAG_DELETE_ON_CLOSE, FILE_GENERIC_READ, FILE_GENERIC_WRITE, FILE_SHARE_DELETE,
+ FILE_SHARE_READ, FILE_SHARE_WRITE, MOVEFILE_REPLACE_EXISTING,
+};
use crate::util;
@@ -20,7 +19,18 @@
s.as_os_str().encode_wide().chain(iter::once(0)).collect()
}
-pub fn create_named(path: &Path, open_options: &mut OpenOptions) -> io::Result<File> {
+fn not_supported<T>(msg: &str) -> io::Result<T> {
+ Err(io::Error::new(io::ErrorKind::Other, msg))
+}
+
+pub fn create_named(
+ path: &Path,
+ open_options: &mut OpenOptions,
+ permissions: Option<&std::fs::Permissions>,
+) -> io::Result<File> {
+ if permissions.map_or(false, |p| p.readonly()) {
+ return not_supported("changing permissions is not supported on this platform");
+ }
open_options
.create_new(true)
.read(true)
@@ -76,9 +86,6 @@
}
pub fn persist(old_path: &Path, new_path: &Path, overwrite: bool) -> io::Result<()> {
- // TODO: We should probably do this in one-shot using SetFileInformationByHandle but the API is
- // really painful.
-
unsafe {
let old_path_w = to_utf16(old_path);
let new_path_w = to_utf16(new_path);
diff --git a/crates/tempfile/src/file/mod.rs b/crates/tempfile/src/file/mod.rs
index b859ced..8930da0 100644
--- a/crates/tempfile/src/file/mod.rs
+++ b/crates/tempfile/src/file/mod.rs
@@ -1,4 +1,3 @@
-use std::env;
use std::error;
use std::ffi::OsStr;
use std::fmt;
@@ -6,8 +5,15 @@
use std::io::{self, Read, Seek, SeekFrom, Write};
use std::mem;
use std::ops::Deref;
+#[cfg(unix)]
+use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd};
+#[cfg(target_os = "wasi")]
+use std::os::wasi::io::{AsFd, AsRawFd, BorrowedFd, RawFd};
+#[cfg(windows)]
+use std::os::windows::io::{AsHandle, AsRawHandle, BorrowedHandle, RawHandle};
use std::path::{Path, PathBuf};
+use crate::env;
use crate::error::IoResultExt;
use crate::Builder;
@@ -15,7 +21,7 @@
/// Create a new temporary file.
///
-/// The file will be created in the location returned by [`std::env::temp_dir()`].
+/// The file will be created in the location returned by [`env::temp_dir()`].
///
/// # Security
///
@@ -34,25 +40,16 @@
///
/// ```
/// use tempfile::tempfile;
-/// use std::io::{self, Write};
+/// use std::io::Write;
///
-/// # fn main() {
-/// # if let Err(_) = run() {
-/// # ::std::process::exit(1);
-/// # }
-/// # }
-/// # fn run() -> Result<(), io::Error> {
-/// // Create a file inside of `std::env::temp_dir()`.
+/// // Create a file inside of `env::temp_dir()`.
/// let mut file = tempfile()?;
///
/// writeln!(file, "Brian was here. Briefly.")?;
-/// # Ok(())
-/// # }
+/// # Ok::<(), std::io::Error>(())
/// ```
-///
-/// [`std::env::temp_dir()`]: https://doc.rust-lang.org/std/env/fn.temp_dir.html
pub fn tempfile() -> io::Result<File> {
- tempfile_in(&env::temp_dir())
+ tempfile_in(env::temp_dir())
}
/// Create a new temporary file in the specified directory.
@@ -60,7 +57,7 @@
/// # Security
///
/// This variant is secure/reliable in the presence of a pathological temporary file cleaner.
-/// If the temporary file isn't created in [`std::env::temp_dir()`] then temporary file cleaners aren't an issue.
+/// If the temporary file isn't created in [`env::temp_dir()`] then temporary file cleaners aren't an issue.
///
/// # Resource Leaking
///
@@ -75,23 +72,14 @@
///
/// ```
/// use tempfile::tempfile_in;
-/// use std::io::{self, Write};
+/// use std::io::Write;
///
-/// # fn main() {
-/// # if let Err(_) = run() {
-/// # ::std::process::exit(1);
-/// # }
-/// # }
-/// # fn run() -> Result<(), io::Error> {
/// // Create a file inside of the current working directory
/// let mut file = tempfile_in("./")?;
///
/// writeln!(file, "Brian was here. Briefly.")?;
-/// # Ok(())
-/// # }
+/// # Ok::<(), std::io::Error>(())
/// ```
-///
-/// [`std::env::temp_dir()`]: https://doc.rust-lang.org/std/env/fn.temp_dir.html
pub fn tempfile_in<P: AsRef<Path>>(dir: P) -> io::Result<File> {
imp::create(dir.as_ref())
}
@@ -136,9 +124,11 @@
/// This is useful when the temporary file needs to be used by a child process,
/// for example.
///
-/// When dropped, the temporary file is deleted.
+/// When dropped, the temporary file is deleted unless `keep(true)` was called
+/// on the builder that constructed this value.
pub struct TempPath {
path: Box<Path>,
+ keep: bool,
}
impl TempPath {
@@ -153,15 +143,8 @@
/// # Examples
///
/// ```no_run
- /// # use std::io;
/// use tempfile::NamedTempFile;
///
- /// # fn main() {
- /// # if let Err(_) = run() {
- /// # ::std::process::exit(1);
- /// # }
- /// # }
- /// # fn run() -> Result<(), io::Error> {
/// let file = NamedTempFile::new()?;
///
/// // Close the file, but keep the path to it around.
@@ -172,8 +155,7 @@
/// // file will still be deleted when `file` goes out of scope, but we
/// // won't know whether deleting the file succeeded.
/// path.close()?;
- /// # Ok(())
- /// # }
+ /// # Ok::<(), std::io::Error>(())
/// ```
pub fn close(mut self) -> io::Result<()> {
let result = fs::remove_file(&self.path).with_err_path(|| &*self.path);
@@ -206,22 +188,15 @@
/// # Examples
///
/// ```no_run
- /// # use std::io::{self, Write};
+ /// use std::io::Write;
/// use tempfile::NamedTempFile;
///
- /// # fn main() {
- /// # if let Err(_) = run() {
- /// # ::std::process::exit(1);
- /// # }
- /// # }
- /// # fn run() -> Result<(), io::Error> {
/// let mut file = NamedTempFile::new()?;
/// writeln!(file, "Brian was here. Briefly.")?;
///
/// let path = file.into_temp_path();
/// path.persist("./saved_file.txt")?;
- /// # Ok(())
- /// # }
+ /// # Ok::<(), std::io::Error>(())
/// ```
///
/// [`PathPersistError`]: struct.PathPersistError.html
@@ -265,22 +240,15 @@
/// # Examples
///
/// ```no_run
- /// # use std::io::{self, Write};
/// use tempfile::NamedTempFile;
+ /// use std::io::Write;
///
- /// # fn main() {
- /// # if let Err(_) = run() {
- /// # ::std::process::exit(1);
- /// # }
- /// # }
- /// # fn run() -> Result<(), io::Error> {
/// let mut file = NamedTempFile::new()?;
/// writeln!(file, "Brian was here. Briefly.")?;
///
/// let path = file.into_temp_path();
/// path.persist_noclobber("./saved_file.txt")?;
- /// # Ok(())
- /// # }
+ /// # Ok::<(), std::io::Error>(())
/// ```
///
/// [`PathPersistError`]: struct.PathPersistError.html
@@ -307,7 +275,6 @@
/// Keep the temporary file from being deleted. This function will turn the
/// temporary file into a non-temporary file without moving it.
///
- ///
/// # Errors
///
/// On some platforms (e.g., Windows), we need to mark the file as
@@ -316,22 +283,15 @@
/// # Examples
///
/// ```no_run
- /// # use std::io::{self, Write};
+ /// use std::io::Write;
/// use tempfile::NamedTempFile;
///
- /// # fn main() {
- /// # if let Err(_) = run() {
- /// # ::std::process::exit(1);
- /// # }
- /// # }
- /// # fn run() -> Result<(), io::Error> {
/// let mut file = NamedTempFile::new()?;
/// writeln!(file, "Brian was here. Briefly.")?;
///
/// let path = file.into_temp_path();
/// let path = path.keep()?;
- /// # Ok(())
- /// # }
+ /// # Ok::<(), std::io::Error>(())
/// ```
///
/// [`PathPersistError`]: struct.PathPersistError.html
@@ -361,6 +321,14 @@
pub fn from_path(path: impl Into<PathBuf>) -> Self {
Self {
path: path.into().into_boxed_path(),
+ keep: false,
+ }
+ }
+
+ pub(crate) fn new(path: PathBuf, keep: bool) -> Self {
+ Self {
+ path: path.into_boxed_path(),
+ keep,
}
}
}
@@ -373,7 +341,9 @@
impl Drop for TempPath {
fn drop(&mut self) {
- let _ = fs::remove_file(&self.path);
+ if !self.keep {
+ let _ = fs::remove_file(&self.path);
+ }
}
}
@@ -400,7 +370,7 @@
/// A named temporary file.
///
/// The default constructor, [`NamedTempFile::new()`], creates files in
-/// the location returned by [`std::env::temp_dir()`], but `NamedTempFile`
+/// the location returned by [`env::temp_dir()`], but `NamedTempFile`
/// can be configured to manage a temporary file in any location
/// by constructing with [`NamedTempFile::new_in()`].
///
@@ -467,29 +437,30 @@
/// # Resource Leaking
///
/// If the program exits before the `NamedTempFile` destructor is
-/// run, such as via [`std::process::exit()`], by segfaulting, or by
-/// receiving a signal like `SIGINT`, then the temporary file
-/// will not be deleted.
+/// run, the temporary file will not be deleted. This can happen
+/// if the process exits using [`std::process::exit()`], a segfault occurs,
+/// receiving an interrupt signal like `SIGINT` that is not handled, or by using
+/// a statically declared `NamedTempFile` instance (like with [`lazy_static`]).
///
-/// Use the [`tempfile()`] function unless you absolutely need a named file.
+/// Use the [`tempfile()`] function unless you need a named file path.
///
/// [`tempfile()`]: fn.tempfile.html
/// [`NamedTempFile::new()`]: #method.new
/// [`NamedTempFile::new_in()`]: #method.new_in
-/// [`std::env::temp_dir()`]: https://doc.rust-lang.org/std/env/fn.temp_dir.html
/// [`std::process::exit()`]: http://doc.rust-lang.org/std/process/fn.exit.html
-pub struct NamedTempFile {
+/// [`lazy_static`]: https://github.com/rust-lang-nursery/lazy-static.rs/issues/62
+pub struct NamedTempFile<F = File> {
path: TempPath,
- file: File,
+ file: F,
}
-impl fmt::Debug for NamedTempFile {
+impl<F> fmt::Debug for NamedTempFile<F> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "NamedTempFile({:?})", self.path)
}
}
-impl AsRef<Path> for NamedTempFile {
+impl<F> AsRef<Path> for NamedTempFile<F> {
#[inline]
fn as_ref(&self) -> &Path {
self.path()
@@ -497,41 +468,46 @@
}
/// Error returned when persisting a temporary file fails.
-#[derive(Debug)]
-pub struct PersistError {
+pub struct PersistError<F = File> {
/// The underlying IO error.
pub error: io::Error,
/// The temporary file that couldn't be persisted.
- pub file: NamedTempFile,
+ pub file: NamedTempFile<F>,
}
-impl From<PersistError> for io::Error {
+impl<F> fmt::Debug for PersistError<F> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "PersistError({:?})", self.error)
+ }
+}
+
+impl<F> From<PersistError<F>> for io::Error {
#[inline]
- fn from(error: PersistError) -> io::Error {
+ fn from(error: PersistError<F>) -> io::Error {
error.error
}
}
-impl From<PersistError> for NamedTempFile {
+impl<F> From<PersistError<F>> for NamedTempFile<F> {
#[inline]
- fn from(error: PersistError) -> NamedTempFile {
+ fn from(error: PersistError<F>) -> NamedTempFile<F> {
error.file
}
}
-impl fmt::Display for PersistError {
+impl<F> fmt::Display for PersistError<F> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "failed to persist temporary file: {}", self.error)
}
}
-impl error::Error for PersistError {
+impl<F> error::Error for PersistError<F> {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
Some(&self.error)
}
}
-impl NamedTempFile {
+impl NamedTempFile<File> {
/// Create a new named temporary file.
///
/// See [`Builder`] for more configuration.
@@ -572,20 +548,13 @@
/// Create a named temporary file and write some data to it:
///
/// ```no_run
- /// # use std::io::{self, Write};
+ /// use std::io::Write;
/// use tempfile::NamedTempFile;
///
- /// # fn main() {
- /// # if let Err(_) = run() {
- /// # ::std::process::exit(1);
- /// # }
- /// # }
- /// # fn run() -> Result<(), ::std::io::Error> {
/// let mut file = NamedTempFile::new()?;
///
/// writeln!(file, "Brian was here. Briefly.")?;
- /// # Ok(())
- /// # }
+ /// # Ok::<(), std::io::Error>(())
/// ```
///
/// [`Builder`]: struct.Builder.html
@@ -595,6 +564,12 @@
/// Create a new named temporary file in the specified directory.
///
+ /// This is equivalent to:
+ ///
+ /// ```ignore
+ /// Builder::new().tempfile_in(dir)
+ /// ```
+ ///
/// See [`NamedTempFile::new()`] for details.
///
/// [`NamedTempFile::new()`]: #method.new
@@ -602,6 +577,35 @@
Builder::new().tempfile_in(dir)
}
+ /// Create a new named temporary file with the specified filename prefix.
+ ///
+ /// See [`NamedTempFile::new()`] for details.
+ ///
+ /// [`NamedTempFile::new()`]: #method.new
+ pub fn with_prefix<S: AsRef<OsStr>>(prefix: S) -> io::Result<NamedTempFile> {
+ Builder::new().prefix(&prefix).tempfile()
+ }
+ /// Create a new named temporary file with the specified filename prefix,
+ /// in the specified directory.
+ ///
+ /// This is equivalent to:
+ ///
+ /// ```ignore
+ /// Builder::new().prefix(&prefix).tempfile_in(directory)
+ /// ```
+ ///
+ /// See [`NamedTempFile::new()`] for details.
+ ///
+ /// [`NamedTempFile::new()`]: #method.new
+ pub fn with_prefix_in<S: AsRef<OsStr>, P: AsRef<Path>>(
+ prefix: S,
+ dir: P,
+ ) -> io::Result<NamedTempFile> {
+ Builder::new().prefix(&prefix).tempfile_in(dir)
+ }
+}
+
+impl<F> NamedTempFile<F> {
/// Get the temporary file's path.
///
/// # Security
@@ -613,20 +617,12 @@
/// # Examples
///
/// ```no_run
- /// # use std::io::{self, Write};
/// use tempfile::NamedTempFile;
///
- /// # fn main() {
- /// # if let Err(_) = run() {
- /// # ::std::process::exit(1);
- /// # }
- /// # }
- /// # fn run() -> Result<(), ::std::io::Error> {
/// let file = NamedTempFile::new()?;
///
/// println!("{:?}", file.path());
- /// # Ok(())
- /// # }
+ /// # Ok::<(), std::io::Error>(())
/// ```
#[inline]
pub fn path(&self) -> &Path {
@@ -644,15 +640,8 @@
/// # Examples
///
/// ```no_run
- /// # use std::io;
/// use tempfile::NamedTempFile;
///
- /// # fn main() {
- /// # if let Err(_) = run() {
- /// # ::std::process::exit(1);
- /// # }
- /// # }
- /// # fn run() -> Result<(), io::Error> {
/// let file = NamedTempFile::new()?;
///
/// // By closing the `NamedTempFile` explicitly, we can check that it has
@@ -661,8 +650,7 @@
/// // of scope, but we won't know whether deleting the file
/// // succeeded.
/// file.close()?;
- /// # Ok(())
- /// # }
+ /// # Ok::<(), std::io::Error>(())
/// ```
pub fn close(self) -> io::Result<()> {
let NamedTempFile { path, .. } = self;
@@ -683,7 +671,7 @@
/// # Security
///
/// This method persists the temporary file using its path and may not be
- /// secure in the in all cases. Please read the security section on the top
+ /// secure in all cases. Please read the security section on the top
/// level documentation of this type for details.
///
/// # Errors
@@ -693,25 +681,18 @@
/// # Examples
///
/// ```no_run
- /// # use std::io::{self, Write};
+ /// use std::io::Write;
/// use tempfile::NamedTempFile;
///
- /// # fn main() {
- /// # if let Err(_) = run() {
- /// # ::std::process::exit(1);
- /// # }
- /// # }
- /// # fn run() -> Result<(), io::Error> {
/// let file = NamedTempFile::new()?;
///
/// let mut persisted_file = file.persist("./saved_file.txt")?;
/// writeln!(persisted_file, "Brian was here. Briefly.")?;
- /// # Ok(())
- /// # }
+ /// # Ok::<(), std::io::Error>(())
/// ```
///
/// [`PersistError`]: struct.PersistError.html
- pub fn persist<P: AsRef<Path>>(self, new_path: P) -> Result<File, PersistError> {
+ pub fn persist<P: AsRef<Path>>(self, new_path: P) -> Result<F, PersistError<F>> {
let NamedTempFile { path, file } = self;
match path.persist(new_path) {
Ok(_) => Ok(file),
@@ -737,7 +718,7 @@
/// # Security
///
/// This method persists the temporary file using its path and may not be
- /// secure in the in all cases. Please read the security section on the top
+ /// secure in all cases. Please read the security section on the top
/// level documentation of this type for details.
///
/// # Errors
@@ -748,23 +729,16 @@
/// # Examples
///
/// ```no_run
- /// # use std::io::{self, Write};
+ /// use std::io::Write;
/// use tempfile::NamedTempFile;
///
- /// # fn main() {
- /// # if let Err(_) = run() {
- /// # ::std::process::exit(1);
- /// # }
- /// # }
- /// # fn run() -> Result<(), io::Error> {
/// let file = NamedTempFile::new()?;
///
/// let mut persisted_file = file.persist_noclobber("./saved_file.txt")?;
/// writeln!(persisted_file, "Brian was here. Briefly.")?;
- /// # Ok(())
- /// # }
+ /// # Ok::<(), std::io::Error>(())
/// ```
- pub fn persist_noclobber<P: AsRef<Path>>(self, new_path: P) -> Result<File, PersistError> {
+ pub fn persist_noclobber<P: AsRef<Path>>(self, new_path: P) -> Result<F, PersistError<F>> {
let NamedTempFile { path, file } = self;
match path.persist_noclobber(new_path) {
Ok(_) => Ok(file),
@@ -790,25 +764,18 @@
/// # Examples
///
/// ```no_run
- /// # use std::io::{self, Write};
+ /// use std::io::Write;
/// use tempfile::NamedTempFile;
///
- /// # fn main() {
- /// # if let Err(_) = run() {
- /// # ::std::process::exit(1);
- /// # }
- /// # }
- /// # fn run() -> Result<(), io::Error> {
/// let mut file = NamedTempFile::new()?;
/// writeln!(file, "Brian was here. Briefly.")?;
///
/// let (file, path) = file.keep()?;
- /// # Ok(())
- /// # }
+ /// # Ok::<(), std::io::Error>(())
/// ```
///
/// [`PathPersistError`]: struct.PathPersistError.html
- pub fn keep(self) -> Result<(File, PathBuf), PersistError> {
+ pub fn keep(self) -> Result<(F, PathBuf), PersistError<F>> {
let (file, path) = (self.file, self.path);
match path.keep() {
Ok(path) => Ok((file, path)),
@@ -819,6 +786,49 @@
}
}
+ /// Get a reference to the underlying file.
+ pub fn as_file(&self) -> &F {
+ &self.file
+ }
+
+ /// Get a mutable reference to the underlying file.
+ pub fn as_file_mut(&mut self) -> &mut F {
+ &mut self.file
+ }
+
+ /// Convert the temporary file into a `std::fs::File`.
+ ///
+ /// The inner file will be deleted.
+ pub fn into_file(self) -> F {
+ self.file
+ }
+
+ /// Closes the file, leaving only the temporary file path.
+ ///
+ /// This is useful when another process must be able to open the temporary
+ /// file.
+ pub fn into_temp_path(self) -> TempPath {
+ self.path
+ }
+
+ /// Converts the named temporary file into its constituent parts.
+ ///
+ /// Note: When the path is dropped, the file is deleted but the file handle
+ /// is still usable.
+ pub fn into_parts(self) -> (F, TempPath) {
+ (self.file, self.path)
+ }
+
+ /// Creates a `NamedTempFile` from its constituent parts.
+ ///
+ /// This can be used with [`NamedTempFile::into_parts`] to reconstruct the
+ /// `NamedTempFile`.
+ pub fn from_parts(file: F, path: TempPath) -> Self {
+ Self { file, path }
+ }
+}
+
+impl NamedTempFile<File> {
/// Securely reopen the temporary file.
///
/// This function is useful when you need multiple independent handles to
@@ -839,73 +849,78 @@
/// # Examples
///
/// ```no_run
- /// # use std::io;
/// use tempfile::NamedTempFile;
///
- /// # fn main() {
- /// # if let Err(_) = run() {
- /// # ::std::process::exit(1);
- /// # }
- /// # }
- /// # fn run() -> Result<(), io::Error> {
/// let file = NamedTempFile::new()?;
///
/// let another_handle = file.reopen()?;
- /// # Ok(())
- /// # }
+ /// # Ok::<(), std::io::Error>(())
/// ```
pub fn reopen(&self) -> io::Result<File> {
imp::reopen(self.as_file(), NamedTempFile::path(self))
.with_err_path(|| NamedTempFile::path(self))
}
-
- /// Get a reference to the underlying file.
- pub fn as_file(&self) -> &File {
- &self.file
- }
-
- /// Get a mutable reference to the underlying file.
- pub fn as_file_mut(&mut self) -> &mut File {
- &mut self.file
- }
-
- /// Convert the temporary file into a `std::fs::File`.
- ///
- /// The inner file will be deleted.
- pub fn into_file(self) -> File {
- self.file
- }
-
- /// Closes the file, leaving only the temporary file path.
- ///
- /// This is useful when another process must be able to open the temporary
- /// file.
- pub fn into_temp_path(self) -> TempPath {
- self.path
- }
-
- /// Converts the named temporary file into its constituent parts.
- ///
- /// Note: When the path is dropped, the file is deleted but the file handle
- /// is still usable.
- pub fn into_parts(self) -> (File, TempPath) {
- (self.file, self.path)
- }
}
-impl Read for NamedTempFile {
+impl<F: Read> Read for NamedTempFile<F> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.as_file_mut().read(buf).with_err_path(|| self.path())
}
-}
-impl<'a> Read for &'a NamedTempFile {
- fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
- self.as_file().read(buf).with_err_path(|| self.path())
+ fn read_vectored(&mut self, bufs: &mut [io::IoSliceMut<'_>]) -> io::Result<usize> {
+ self.as_file_mut()
+ .read_vectored(bufs)
+ .with_err_path(|| self.path())
+ }
+
+ fn read_to_end(&mut self, buf: &mut Vec<u8>) -> io::Result<usize> {
+ self.as_file_mut()
+ .read_to_end(buf)
+ .with_err_path(|| self.path())
+ }
+
+ fn read_to_string(&mut self, buf: &mut String) -> io::Result<usize> {
+ self.as_file_mut()
+ .read_to_string(buf)
+ .with_err_path(|| self.path())
+ }
+
+ fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> {
+ self.as_file_mut()
+ .read_exact(buf)
+ .with_err_path(|| self.path())
}
}
-impl Write for NamedTempFile {
+impl Read for &NamedTempFile<File> {
+ fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+ self.as_file().read(buf).with_err_path(|| self.path())
+ }
+
+ fn read_vectored(&mut self, bufs: &mut [io::IoSliceMut<'_>]) -> io::Result<usize> {
+ self.as_file()
+ .read_vectored(bufs)
+ .with_err_path(|| self.path())
+ }
+
+ fn read_to_end(&mut self, buf: &mut Vec<u8>) -> io::Result<usize> {
+ self.as_file()
+ .read_to_end(buf)
+ .with_err_path(|| self.path())
+ }
+
+ fn read_to_string(&mut self, buf: &mut String) -> io::Result<usize> {
+ self.as_file()
+ .read_to_string(buf)
+ .with_err_path(|| self.path())
+ }
+
+ fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> {
+ self.as_file().read_exact(buf).with_err_path(|| self.path())
+ }
+}
+
+impl<F: Write> Write for NamedTempFile<F> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.as_file_mut().write(buf).with_err_path(|| self.path())
}
@@ -913,9 +928,27 @@
fn flush(&mut self) -> io::Result<()> {
self.as_file_mut().flush().with_err_path(|| self.path())
}
+
+ fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result<usize> {
+ self.as_file_mut()
+ .write_vectored(bufs)
+ .with_err_path(|| self.path())
+ }
+
+ fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
+ self.as_file_mut()
+ .write_all(buf)
+ .with_err_path(|| self.path())
+ }
+
+ fn write_fmt(&mut self, fmt: fmt::Arguments<'_>) -> io::Result<()> {
+ self.as_file_mut()
+ .write_fmt(fmt)
+ .with_err_path(|| self.path())
+ }
}
-impl<'a> Write for &'a NamedTempFile {
+impl Write for &NamedTempFile<File> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.as_file().write(buf).with_err_path(|| self.path())
}
@@ -923,32 +956,61 @@
fn flush(&mut self) -> io::Result<()> {
self.as_file().flush().with_err_path(|| self.path())
}
+
+ fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result<usize> {
+ self.as_file()
+ .write_vectored(bufs)
+ .with_err_path(|| self.path())
+ }
+
+ fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
+ self.as_file().write_all(buf).with_err_path(|| self.path())
+ }
+
+ fn write_fmt(&mut self, fmt: fmt::Arguments<'_>) -> io::Result<()> {
+ self.as_file().write_fmt(fmt).with_err_path(|| self.path())
+ }
}
-impl Seek for NamedTempFile {
+impl<F: Seek> Seek for NamedTempFile<F> {
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
self.as_file_mut().seek(pos).with_err_path(|| self.path())
}
}
-impl<'a> Seek for &'a NamedTempFile {
+impl Seek for &NamedTempFile<File> {
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
self.as_file().seek(pos).with_err_path(|| self.path())
}
}
-#[cfg(unix)]
-impl std::os::unix::io::AsRawFd for NamedTempFile {
+#[cfg(any(unix, target_os = "wasi"))]
+impl<F: AsFd> AsFd for NamedTempFile<F> {
+ fn as_fd(&self) -> BorrowedFd<'_> {
+ self.as_file().as_fd()
+ }
+}
+
+#[cfg(any(unix, target_os = "wasi"))]
+impl<F: AsRawFd> AsRawFd for NamedTempFile<F> {
#[inline]
- fn as_raw_fd(&self) -> std::os::unix::io::RawFd {
+ fn as_raw_fd(&self) -> RawFd {
self.as_file().as_raw_fd()
}
}
#[cfg(windows)]
-impl std::os::windows::io::AsRawHandle for NamedTempFile {
+impl<F: AsHandle> AsHandle for NamedTempFile<F> {
#[inline]
- fn as_raw_handle(&self) -> std::os::windows::io::RawHandle {
+ fn as_handle(&self) -> BorrowedHandle<'_> {
+ self.as_file().as_handle()
+ }
+}
+
+#[cfg(windows)]
+impl<F: AsRawHandle> AsRawHandle for NamedTempFile<F> {
+ #[inline]
+ fn as_raw_handle(&self) -> RawHandle {
self.as_file().as_raw_handle()
}
}
@@ -956,17 +1018,20 @@
pub(crate) fn create_named(
mut path: PathBuf,
open_options: &mut OpenOptions,
+ permissions: Option<&std::fs::Permissions>,
+ keep: bool,
) -> io::Result<NamedTempFile> {
// Make the path absolute. Otherwise, changing directories could cause us to
// delete the wrong file.
if !path.is_absolute() {
- path = env::current_dir()?.join(path)
+ path = std::env::current_dir()?.join(path)
}
- imp::create_named(&path, open_options)
+ imp::create_named(&path, open_options, permissions)
.with_err_path(|| path.clone())
.map(|file| NamedTempFile {
path: TempPath {
path: path.into_boxed_path(),
+ keep,
},
file,
})
diff --git a/crates/tempfile/src/lib.rs b/crates/tempfile/src/lib.rs
index c38ca7b..ca657cb 100644
--- a/crates/tempfile/src/lib.rs
+++ b/crates/tempfile/src/lib.rs
@@ -14,9 +14,12 @@
//!
//! ## Resource Leaking
//!
-//! `tempfile` will (almost) never fail to cleanup temporary resources, but `TempDir` and `NamedTempFile` will if
-//! their destructors don't run. This is because `tempfile` relies on the OS to cleanup the
-//! underlying file, while `TempDir` and `NamedTempFile` rely on their destructors to do so.
+//! `tempfile` will (almost) never fail to cleanup temporary resources. However `TempDir` and `NamedTempFile` will
+//! fail if their destructors don't run. This is because `tempfile` relies on the OS to cleanup the
+//! underlying file, while `TempDir` and `NamedTempFile` rely on rust destructors to do so.
+//! Destructors may fail to run if the process exits through an unhandled signal interrupt (like `SIGINT`),
+//! or if the instance is declared statically (like with [`lazy_static`]), among other possible
+//! reasons.
//!
//! ## Security
//!
@@ -34,16 +37,10 @@
//! generic over `AsRef<Path>`. Consider the following example:
//!
//! ```no_run
-//! # use tempfile::tempdir;
-//! # use std::io;
-//! # use std::process::Command;
-//! # fn main() {
-//! # if let Err(_) = run() {
-//! # ::std::process::exit(1);
-//! # }
-//! # }
-//! # fn run() -> Result<(), io::Error> {
-//! // Create a directory inside of `std::env::temp_dir()`.
+//! use tempfile::tempdir;
+//! use std::process::Command;
+//!
+//! // Create a directory inside of `env::temp_dir()`.
//! let temp_dir = tempdir()?;
//!
//! // Spawn the `touch` command inside the temporary directory and collect the exit status
@@ -51,8 +48,7 @@
//! let exit_status = Command::new("touch").arg("tmp").current_dir(&temp_dir).status()?;
//! assert!(exit_status.success());
//!
-//! # Ok(())
-//! # }
+//! # Ok::<(), std::io::Error>(())
//! ```
//!
//! This works because a reference to `temp_dir` is passed to `current_dir`, resulting in the
@@ -69,37 +65,24 @@
//!
//! ```
//! use tempfile::tempfile;
-//! use std::io::{self, Write};
+//! use std::io::Write;
//!
-//! # fn main() {
-//! # if let Err(_) = run() {
-//! # ::std::process::exit(1);
-//! # }
-//! # }
-//! # fn run() -> Result<(), io::Error> {
-//! // Create a file inside of `std::env::temp_dir()`.
+//! // Create a file inside of `env::temp_dir()`.
//! let mut file = tempfile()?;
//!
//! writeln!(file, "Brian was here. Briefly.")?;
-//! # Ok(())
-//! # }
+//! # Ok::<(), std::io::Error>(())
//! ```
//!
//! Create a named temporary file and open an independent file handle:
//!
//! ```
//! use tempfile::NamedTempFile;
-//! use std::io::{self, Write, Read};
+//! use std::io::{Write, Read};
//!
-//! # fn main() {
-//! # if let Err(_) = run() {
-//! # ::std::process::exit(1);
-//! # }
-//! # }
-//! # fn run() -> Result<(), io::Error> {
//! let text = "Brian was here. Briefly.";
//!
-//! // Create a file inside of `std::env::temp_dir()`.
+//! // Create a file inside of `env::temp_dir()`.
//! let mut file1 = NamedTempFile::new()?;
//!
//! // Re-open it.
@@ -112,8 +95,7 @@
//! let mut buf = String::new();
//! file2.read_to_string(&mut buf)?;
//! assert_eq!(buf, text);
-//! # Ok(())
-//! # }
+//! # Ok::<(), std::io::Error>(())
//! ```
//!
//! Create a temporary directory and add a file to it:
@@ -121,15 +103,9 @@
//! ```
//! use tempfile::tempdir;
//! use std::fs::File;
-//! use std::io::{self, Write};
+//! use std::io::Write;
//!
-//! # fn main() {
-//! # if let Err(_) = run() {
-//! # ::std::process::exit(1);
-//! # }
-//! # }
-//! # fn run() -> Result<(), io::Error> {
-//! // Create a directory inside of `std::env::temp_dir()`.
+//! // Create a directory inside of `env::temp_dir()`.
//! let dir = tempdir()?;
//!
//! let file_path = dir.path().join("my-temporary-note.txt");
@@ -143,25 +119,24 @@
//! // succeeded.
//! drop(file);
//! dir.close()?;
-//! # Ok(())
-//! # }
+//! # Ok::<(), std::io::Error>(())
//! ```
//!
//! [`tempfile()`]: fn.tempfile.html
//! [`tempdir()`]: fn.tempdir.html
//! [`TempDir`]: struct.TempDir.html
//! [`NamedTempFile`]: struct.NamedTempFile.html
-//! [`std::env::temp_dir()`]: https://doc.rust-lang.org/std/env/fn.temp_dir.html
+//! [`lazy_static`]: https://github.com/rust-lang-nursery/lazy-static.rs/issues/62
#![doc(
html_logo_url = "https://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png",
html_favicon_url = "https://www.rust-lang.org/favicon.ico",
- html_root_url = "https://docs.rs/tempfile/3.1.0"
+ html_root_url = "https://docs.rs/tempfile/latest"
)]
#![cfg_attr(test, deny(warnings))]
#![deny(rust_2018_idioms)]
#![allow(clippy::redundant_field_names)]
-#![cfg_attr(feature = "nightly", feature(wasi_ext))]
+#![cfg_attr(all(feature = "nightly", target_os = "wasi"), feature(wasi_ext))]
#[cfg(doctest)]
doc_comment::doctest!("../README.md");
@@ -171,8 +146,8 @@
use std::ffi::OsStr;
use std::fs::OpenOptions;
+use std::io;
use std::path::Path;
-use std::{env, io};
mod dir;
mod error;
@@ -180,6 +155,8 @@
mod spooled;
mod util;
+pub mod env;
+
pub use crate::dir::{tempdir, tempdir_in, TempDir};
pub use crate::file::{
tempfile, tempfile_in, NamedTempFile, PathPersistError, PersistError, TempPath,
@@ -193,6 +170,8 @@
prefix: &'a OsStr,
suffix: &'b OsStr,
append: bool,
+ permissions: Option<std::fs::Permissions>,
+ keep: bool,
}
impl<'a, 'b> Default for Builder<'a, 'b> {
@@ -202,6 +181,8 @@
prefix: OsStr::new(".tmp"),
suffix: OsStr::new(""),
append: false,
+ permissions: None,
+ keep: false,
}
}
}
@@ -214,14 +195,7 @@
/// Create a named temporary file and write some data into it:
///
/// ```
- /// # use std::io;
- /// # use std::ffi::OsStr;
- /// # fn main() {
- /// # if let Err(_) = run() {
- /// # ::std::process::exit(1);
- /// # }
- /// # }
- /// # fn run() -> Result<(), io::Error> {
+ /// use std::ffi::OsStr;
/// use tempfile::Builder;
///
/// let named_tempfile = Builder::new()
@@ -239,22 +213,15 @@
/// assert!(name.ends_with(".txt"));
/// assert_eq!(name.len(), "my-temporary-note.txt".len() + 5);
/// }
- /// # Ok(())
- /// # }
+ /// # Ok::<(), std::io::Error>(())
/// ```
///
/// Create a temporary directory and add a file to it:
///
/// ```
- /// # use std::io::{self, Write};
- /// # use std::fs::File;
- /// # use std::ffi::OsStr;
- /// # fn main() {
- /// # if let Err(_) = run() {
- /// # ::std::process::exit(1);
- /// # }
- /// # }
- /// # fn run() -> Result<(), io::Error> {
+ /// use std::io::Write;
+ /// use std::fs::File;
+ /// use std::ffi::OsStr;
/// use tempfile::Builder;
///
/// let dir = Builder::new()
@@ -273,9 +240,20 @@
/// // succeeded.
/// drop(file);
/// dir.close()?;
- /// # Ok(())
- /// # }
+ /// # Ok::<(), std::io::Error>(())
/// ```
+ ///
+ /// Create a temporary directory with a chosen prefix under a chosen folder:
+ ///
+ /// ```no_run
+ /// use tempfile::Builder;
+ ///
+ /// let dir = Builder::new()
+ /// .prefix("my-temporary-dir")
+ /// .tempdir_in("folder-with-tempdirs")?;
+ /// # Ok::<(), std::io::Error>(())
+ /// ```
+ #[must_use]
pub fn new() -> Self {
Self::default()
}
@@ -288,19 +266,12 @@
/// # Examples
///
/// ```
- /// # use std::io;
- /// # fn main() {
- /// # if let Err(_) = run() {
- /// # ::std::process::exit(1);
- /// # }
- /// # }
- /// # fn run() -> Result<(), io::Error> {
- /// # use tempfile::Builder;
+ /// use tempfile::Builder;
+ ///
/// let named_tempfile = Builder::new()
/// .prefix("my-temporary-note")
/// .tempfile()?;
- /// # Ok(())
- /// # }
+ /// # Ok::<(), std::io::Error>(())
/// ```
pub fn prefix<S: AsRef<OsStr> + ?Sized>(&mut self, prefix: &'a S) -> &mut Self {
self.prefix = prefix.as_ref();
@@ -315,19 +286,12 @@
/// # Examples
///
/// ```
- /// # use std::io;
- /// # fn main() {
- /// # if let Err(_) = run() {
- /// # ::std::process::exit(1);
- /// # }
- /// # }
- /// # fn run() -> Result<(), io::Error> {
- /// # use tempfile::Builder;
+ /// use tempfile::Builder;
+ ///
/// let named_tempfile = Builder::new()
/// .suffix(".txt")
/// .tempfile()?;
- /// # Ok(())
- /// # }
+ /// # Ok::<(), std::io::Error>(())
/// ```
pub fn suffix<S: AsRef<OsStr> + ?Sized>(&mut self, suffix: &'b S) -> &mut Self {
self.suffix = suffix.as_ref();
@@ -341,19 +305,12 @@
/// # Examples
///
/// ```
- /// # use std::io;
- /// # fn main() {
- /// # if let Err(_) = run() {
- /// # ::std::process::exit(1);
- /// # }
- /// # }
- /// # fn run() -> Result<(), io::Error> {
- /// # use tempfile::Builder;
+ /// use tempfile::Builder;
+ ///
/// let named_tempfile = Builder::new()
/// .rand_bytes(5)
/// .tempfile()?;
- /// # Ok(())
- /// # }
+ /// # Ok::<(), std::io::Error>(())
/// ```
pub fn rand_bytes(&mut self, rand: usize) -> &mut Self {
self.random_len = rand;
@@ -367,25 +324,108 @@
/// # Examples
///
/// ```
- /// # use std::io;
- /// # fn main() {
- /// # if let Err(_) = run() {
- /// # ::std::process::exit(1);
- /// # }
- /// # }
- /// # fn run() -> Result<(), io::Error> {
- /// # use tempfile::Builder;
+ /// use tempfile::Builder;
+ ///
/// let named_tempfile = Builder::new()
/// .append(true)
/// .tempfile()?;
- /// # Ok(())
- /// # }
+ /// # Ok::<(), std::io::Error>(())
/// ```
pub fn append(&mut self, append: bool) -> &mut Self {
self.append = append;
self
}
+ /// The permissions to create the tempfile or [tempdir](Self::tempdir) with.
+ /// This allows to them differ from the default mode of `0o600` on Unix.
+ ///
+ /// # Security
+ ///
+ /// By default, the permissions of tempfiles on unix are set for it to be
+ /// readable and writable by the owner only, yielding the greatest amount
+ /// of security.
+ /// As this method allows to widen the permissions, security would be
+ /// reduced in such cases.
+ ///
+ /// # Platform Notes
+ /// ## Unix
+ ///
+ /// The actual permission bits set on the tempfile or tempdir will be affected by the
+ /// `umask` applied by the underlying syscall.
+ ///
+ ///
+ /// ## Windows and others
+ ///
+ /// This setting is unsupported and trying to set a file or directory read-only
+ /// will cause an error to be returned..
+ ///
+ /// # Examples
+ ///
+ /// Create a named temporary file that is world-readable.
+ ///
+ /// ```
+ /// # #[cfg(unix)]
+ /// # {
+ /// use tempfile::Builder;
+ /// use std::os::unix::fs::PermissionsExt;
+ ///
+ /// let all_read_write = std::fs::Permissions::from_mode(0o666);
+ /// let tempfile = Builder::new().permissions(all_read_write).tempfile()?;
+ /// let actual_permissions = tempfile.path().metadata()?.permissions();
+ /// assert_ne!(
+ /// actual_permissions.mode() & !0o170000,
+ /// 0o600,
+ /// "we get broader permissions than the default despite umask"
+ /// );
+ /// # }
+ /// # Ok::<(), std::io::Error>(())
+ /// ```
+ ///
+ /// Create a named temporary directory that is restricted to the owner.
+ ///
+ /// ```
+ /// # #[cfg(unix)]
+ /// # {
+ /// use tempfile::Builder;
+ /// use std::os::unix::fs::PermissionsExt;
+ ///
+ /// let owner_rwx = std::fs::Permissions::from_mode(0o700);
+ /// let tempdir = Builder::new().permissions(owner_rwx).tempdir()?;
+ /// let actual_permissions = tempdir.path().metadata()?.permissions();
+ /// assert_eq!(
+ /// actual_permissions.mode() & !0o170000,
+ /// 0o700,
+ /// "we get the narrow permissions we asked for"
+ /// );
+ /// # }
+ /// # Ok::<(), std::io::Error>(())
+ /// ```
+ pub fn permissions(&mut self, permissions: std::fs::Permissions) -> &mut Self {
+ self.permissions = Some(permissions);
+ self
+ }
+
+ /// Set the file/folder to be kept even when the [`NamedTempFile`]/[`TempDir`] goes out of
+ /// scope.
+ ///
+ /// By default, the file/folder is automatically cleaned up in the destructor of
+ /// [`NamedTempFile`]/[`TempDir`]. When `keep` is set to `true`, this behavior is supressed.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use tempfile::Builder;
+ ///
+ /// let named_tempfile = Builder::new()
+ /// .keep(true)
+ /// .tempfile()?;
+ /// # Ok::<(), std::io::Error>(())
+ /// ```
+ pub fn keep(&mut self, keep: bool) -> &mut Self {
+ self.keep = keep;
+ self
+ }
+
/// Create the named temporary file.
///
/// # Security
@@ -403,23 +443,16 @@
/// # Examples
///
/// ```
- /// # use std::io;
- /// # fn main() {
- /// # if let Err(_) = run() {
- /// # ::std::process::exit(1);
- /// # }
- /// # }
- /// # fn run() -> Result<(), io::Error> {
- /// # use tempfile::Builder;
+ /// use tempfile::Builder;
+ ///
/// let tempfile = Builder::new().tempfile()?;
- /// # Ok(())
- /// # }
+ /// # Ok::<(), std::io::Error>(())
/// ```
///
/// [security]: struct.NamedTempFile.html#security
/// [resource-leaking]: struct.NamedTempFile.html#resource-leaking
pub fn tempfile(&self) -> io::Result<NamedTempFile> {
- self.tempfile_in(&env::temp_dir())
+ self.tempfile_in(env::temp_dir())
}
/// Create the named temporary file in the specified directory.
@@ -439,17 +472,10 @@
/// # Examples
///
/// ```
- /// # use std::io;
- /// # fn main() {
- /// # if let Err(_) = run() {
- /// # ::std::process::exit(1);
- /// # }
- /// # }
- /// # fn run() -> Result<(), io::Error> {
- /// # use tempfile::Builder;
+ /// use tempfile::Builder;
+ ///
/// let tempfile = Builder::new().tempfile_in("./")?;
- /// # Ok(())
- /// # }
+ /// # Ok::<(), std::io::Error>(())
/// ```
///
/// [security]: struct.NamedTempFile.html#security
@@ -460,11 +486,18 @@
self.prefix,
self.suffix,
self.random_len,
- |path| file::create_named(path, OpenOptions::new().append(self.append)),
+ |path| {
+ file::create_named(
+ path,
+ OpenOptions::new().append(self.append),
+ self.permissions.as_ref(),
+ self.keep,
+ )
+ },
)
}
- /// Attempts to make a temporary directory inside of `env::temp_dir()` whose
+ /// Attempts to make a temporary directory inside of [`env::temp_dir()`] whose
/// name will have the prefix, `prefix`. The directory and
/// everything inside it will be automatically deleted once the
/// returned `TempDir` is destroyed.
@@ -480,20 +513,15 @@
/// # Examples
///
/// ```
- /// use std::fs::File;
- /// use std::io::Write;
/// use tempfile::Builder;
///
- /// # use std::io;
- /// # fn run() -> Result<(), io::Error> {
/// let tmp_dir = Builder::new().tempdir()?;
- /// # Ok(())
- /// # }
+ /// # Ok::<(), std::io::Error>(())
/// ```
///
/// [resource-leaking]: struct.TempDir.html#resource-leaking
pub fn tempdir(&self) -> io::Result<TempDir> {
- self.tempdir_in(&env::temp_dir())
+ self.tempdir_in(env::temp_dir())
}
/// Attempts to make a temporary directory inside of `dir`.
@@ -511,15 +539,10 @@
/// # Examples
///
/// ```
- /// use std::fs::{self, File};
- /// use std::io::Write;
/// use tempfile::Builder;
///
- /// # use std::io;
- /// # fn run() -> Result<(), io::Error> {
/// let tmp_dir = Builder::new().tempdir_in("./")?;
- /// # Ok(())
- /// # }
+ /// # Ok::<(), std::io::Error>(())
/// ```
///
/// [resource-leaking]: struct.TempDir.html#resource-leaking
@@ -527,11 +550,140 @@
let storage;
let mut dir = dir.as_ref();
if !dir.is_absolute() {
- let cur_dir = env::current_dir()?;
+ let cur_dir = std::env::current_dir()?;
storage = cur_dir.join(dir);
dir = &storage;
}
- util::create_helper(dir, self.prefix, self.suffix, self.random_len, dir::create)
+ util::create_helper(dir, self.prefix, self.suffix, self.random_len, |path| {
+ dir::create(path, self.permissions.as_ref(), self.keep)
+ })
+ }
+
+ /// Attempts to create a temporary file (or file-like object) using the
+ /// provided closure. The closure is passed a temporary file path and
+ /// returns an [`std::io::Result`]. The path provided to the closure will be
+ /// inside of [`env::temp_dir()`]. Use [`Builder::make_in`] to provide
+ /// a custom temporary directory. If the closure returns one of the
+ /// following errors, then another randomized file path is tried:
+ /// - [`std::io::ErrorKind::AlreadyExists`]
+ /// - [`std::io::ErrorKind::AddrInUse`]
+ ///
+ /// This can be helpful for taking full control over the file creation, but
+ /// leaving the temporary file path construction up to the library. This
+ /// also enables creating a temporary UNIX domain socket, since it is not
+ /// possible to bind to a socket that already exists.
+ ///
+ /// Note that [`Builder::append`] is ignored when using [`Builder::make`].
+ ///
+ /// # Security
+ ///
+ /// This has the same [security implications][security] as
+ /// [`NamedTempFile`], but with additional caveats. Specifically, it is up
+ /// to the closure to ensure that the file does not exist and that such a
+ /// check is *atomic*. Otherwise, a [time-of-check to time-of-use
+ /// bug][TOCTOU] could be introduced.
+ ///
+ /// For example, the following is **not** secure:
+ ///
+ /// ```
+ /// use std::fs::File;
+ /// use tempfile::Builder;
+ ///
+ /// // This is NOT secure!
+ /// let tempfile = Builder::new().make(|path| {
+ /// if path.is_file() {
+ /// return Err(std::io::ErrorKind::AlreadyExists.into());
+ /// }
+ ///
+ /// // Between the check above and the usage below, an attacker could
+ /// // have replaced `path` with another file, which would get truncated
+ /// // by `File::create`.
+ ///
+ /// File::create(path)
+ /// })?;
+ /// # Ok::<(), std::io::Error>(())
+ /// ```
+ ///
+ /// Note that simply using [`std::fs::File::create`] alone is not correct
+ /// because it does not fail if the file already exists:
+ ///
+ /// ```
+ /// use tempfile::Builder;
+ /// use std::fs::File;
+ ///
+ /// // This could overwrite an existing file!
+ /// let tempfile = Builder::new().make(|path| File::create(path))?;
+ /// # Ok::<(), std::io::Error>(())
+ /// ```
+ /// For creating regular temporary files, use [`Builder::tempfile`] instead
+ /// to avoid these problems. This function is meant to enable more exotic
+ /// use-cases.
+ ///
+ /// # Resource leaking
+ ///
+ /// See [the resource leaking][resource-leaking] docs on `NamedTempFile`.
+ ///
+ /// # Errors
+ ///
+ /// If the closure returns any error besides
+ /// [`std::io::ErrorKind::AlreadyExists`] or
+ /// [`std::io::ErrorKind::AddrInUse`], then `Err` is returned.
+ ///
+ /// # Examples
+ /// ```
+ /// # #[cfg(unix)]
+ /// # {
+ /// use std::os::unix::net::UnixListener;
+ /// use tempfile::Builder;
+ ///
+ /// let tempsock = Builder::new().make(|path| UnixListener::bind(path))?;
+ /// # }
+ /// # Ok::<(), std::io::Error>(())
+ /// ```
+ ///
+ /// [TOCTOU]: https://en.wikipedia.org/wiki/Time-of-check_to_time-of-use
+ /// [security]: struct.NamedTempFile.html#security
+ /// [resource-leaking]: struct.NamedTempFile.html#resource-leaking
+ pub fn make<F, R>(&self, f: F) -> io::Result<NamedTempFile<R>>
+ where
+ F: FnMut(&Path) -> io::Result<R>,
+ {
+ self.make_in(env::temp_dir(), f)
+ }
+
+ /// This is the same as [`Builder::make`], except `dir` is used as the base
+ /// directory for the temporary file path.
+ ///
+ /// See [`Builder::make`] for more details and security implications.
+ ///
+ /// # Examples
+ /// ```
+ /// # #[cfg(unix)]
+ /// # {
+ /// use tempfile::Builder;
+ /// use std::os::unix::net::UnixListener;
+ ///
+ /// let tempsock = Builder::new().make_in("./", |path| UnixListener::bind(path))?;
+ /// # }
+ /// # Ok::<(), std::io::Error>(())
+ /// ```
+ pub fn make_in<F, R, P>(&self, dir: P, mut f: F) -> io::Result<NamedTempFile<R>>
+ where
+ F: FnMut(&Path) -> io::Result<R>,
+ P: AsRef<Path>,
+ {
+ util::create_helper(
+ dir.as_ref(),
+ self.prefix,
+ self.suffix,
+ self.random_len,
+ move |path| {
+ Ok(NamedTempFile::from_parts(
+ f(&path)?,
+ TempPath::new(path, self.keep),
+ ))
+ },
+ )
}
}
diff --git a/crates/tempfile/src/spooled.rs b/crates/tempfile/src/spooled.rs
index ed6c16f..9c9846c 100644
--- a/crates/tempfile/src/spooled.rs
+++ b/crates/tempfile/src/spooled.rs
@@ -36,14 +36,8 @@
///
/// ```
/// use tempfile::spooled_tempfile;
-/// use std::io::{self, Write};
+/// use std::io::Write;
///
-/// # fn main() {
-/// # if let Err(_) = run() {
-/// # ::std::process::exit(1);
-/// # }
-/// # }
-/// # fn run() -> Result<(), io::Error> {
/// let mut file = spooled_tempfile(15);
///
/// writeln!(file, "short line")?;
@@ -54,9 +48,7 @@
/// // and the in-memory buffer will be dropped
/// writeln!(file, "marvin gardens")?;
/// assert!(file.is_rolled());
-///
-/// # Ok(())
-/// # }
+/// # Ok::<(), std::io::Error>(())
/// ```
#[inline]
pub fn spooled_tempfile(max_size: usize) -> SpooledTempFile {
@@ -64,14 +56,16 @@
}
impl SpooledTempFile {
+ #[must_use]
pub fn new(max_size: usize) -> SpooledTempFile {
SpooledTempFile {
- max_size: max_size,
+ max_size,
inner: SpooledData::InMemory(Cursor::new(Vec::new())),
}
}
/// Returns true if the file has been rolled over to disk.
+ #[must_use]
pub fn is_rolled(&self) -> bool {
match self.inner {
SpooledData::InMemory(_) => false,
@@ -84,7 +78,7 @@
pub fn roll(&mut self) -> io::Result<()> {
if !self.is_rolled() {
let mut file = tempfile()?;
- if let SpooledData::InMemory(ref mut cursor) = self.inner {
+ if let SpooledData::InMemory(cursor) = &mut self.inner {
file.write_all(cursor.get_ref())?;
file.seek(SeekFrom::Start(cursor.position()))?;
}
@@ -94,19 +88,20 @@
}
pub fn set_len(&mut self, size: u64) -> Result<(), io::Error> {
- if size as usize > self.max_size {
+ if size > self.max_size as u64 {
self.roll()?; // does nothing if already rolled over
}
- match self.inner {
- SpooledData::InMemory(ref mut cursor) => {
+ match &mut self.inner {
+ SpooledData::InMemory(cursor) => {
cursor.get_mut().resize(size as usize, 0);
Ok(())
}
- SpooledData::OnDisk(ref mut file) => file.set_len(size),
+ SpooledData::OnDisk(file) => file.set_len(size),
}
}
/// Consumes and returns the inner `SpooledData` type.
+ #[must_use]
pub fn into_inner(self) -> SpooledData {
self.inner
}
@@ -114,9 +109,37 @@
impl Read for SpooledTempFile {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
- match self.inner {
- SpooledData::InMemory(ref mut cursor) => cursor.read(buf),
- SpooledData::OnDisk(ref mut file) => file.read(buf),
+ match &mut self.inner {
+ SpooledData::InMemory(cursor) => cursor.read(buf),
+ SpooledData::OnDisk(file) => file.read(buf),
+ }
+ }
+
+ fn read_vectored(&mut self, bufs: &mut [io::IoSliceMut<'_>]) -> io::Result<usize> {
+ match &mut self.inner {
+ SpooledData::InMemory(cursor) => cursor.read_vectored(bufs),
+ SpooledData::OnDisk(file) => file.read_vectored(bufs),
+ }
+ }
+
+ fn read_to_end(&mut self, buf: &mut Vec<u8>) -> io::Result<usize> {
+ match &mut self.inner {
+ SpooledData::InMemory(cursor) => cursor.read_to_end(buf),
+ SpooledData::OnDisk(file) => file.read_to_end(buf),
+ }
+ }
+
+ fn read_to_string(&mut self, buf: &mut String) -> io::Result<usize> {
+ match &mut self.inner {
+ SpooledData::InMemory(cursor) => cursor.read_to_string(buf),
+ SpooledData::OnDisk(file) => file.read_to_string(buf),
+ }
+ }
+
+ fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> {
+ match &mut self.inner {
+ SpooledData::InMemory(cursor) => cursor.read_exact(buf),
+ SpooledData::OnDisk(file) => file.read_exact(buf),
}
}
}
@@ -124,35 +147,51 @@
impl Write for SpooledTempFile {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
// roll over to file if necessary
- let mut rolling = false;
- if let SpooledData::InMemory(ref mut cursor) = self.inner {
- rolling = cursor.position() as usize + buf.len() > self.max_size;
- }
- if rolling {
+ if matches! {
+ &self.inner, SpooledData::InMemory(cursor)
+ if cursor.position().saturating_add(buf.len() as u64) > self.max_size as u64
+ } {
self.roll()?;
}
// write the bytes
- match self.inner {
- SpooledData::InMemory(ref mut cursor) => cursor.write(buf),
- SpooledData::OnDisk(ref mut file) => file.write(buf),
+ match &mut self.inner {
+ SpooledData::InMemory(cursor) => cursor.write(buf),
+ SpooledData::OnDisk(file) => file.write(buf),
+ }
+ }
+
+ fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result<usize> {
+ if matches! {
+ &self.inner, SpooledData::InMemory(cursor)
+ // Borrowed from the rust standard library.
+ if bufs
+ .iter()
+ .fold(cursor.position(), |a, b| a.saturating_add(b.len() as u64))
+ > self.max_size as u64
+ } {
+ self.roll()?;
+ }
+ match &mut self.inner {
+ SpooledData::InMemory(cursor) => cursor.write_vectored(bufs),
+ SpooledData::OnDisk(file) => file.write_vectored(bufs),
}
}
#[inline]
fn flush(&mut self) -> io::Result<()> {
- match self.inner {
- SpooledData::InMemory(ref mut cursor) => cursor.flush(),
- SpooledData::OnDisk(ref mut file) => file.flush(),
+ match &mut self.inner {
+ SpooledData::InMemory(cursor) => cursor.flush(),
+ SpooledData::OnDisk(file) => file.flush(),
}
}
}
impl Seek for SpooledTempFile {
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
- match self.inner {
- SpooledData::InMemory(ref mut cursor) => cursor.seek(pos),
- SpooledData::OnDisk(ref mut file) => file.seek(pos),
+ match &mut self.inner {
+ SpooledData::InMemory(cursor) => cursor.seek(pos),
+ SpooledData::OnDisk(file) => file.seek(pos),
}
}
}
diff --git a/crates/tempfile/src/util.rs b/crates/tempfile/src/util.rs
index 8c91b9c..2f6f381 100644
--- a/crates/tempfile/src/util.rs
+++ b/crates/tempfile/src/util.rs
@@ -1,4 +1,3 @@
-use fastrand;
use std::ffi::{OsStr, OsString};
use std::path::{Path, PathBuf};
use std::{io, iter::repeat_with};
@@ -6,7 +5,11 @@
use crate::error::IoResultExt;
fn tmpname(prefix: &OsStr, suffix: &OsStr, rand_len: usize) -> OsString {
- let mut buf = OsString::with_capacity(prefix.len() + suffix.len() + rand_len);
+ let capacity = prefix
+ .len()
+ .saturating_add(suffix.len())
+ .saturating_add(rand_len);
+ let mut buf = OsString::with_capacity(capacity);
buf.push(prefix);
let mut char_buf = [0u8; 4];
for c in repeat_with(fastrand::alphanumeric).take(rand_len) {
@@ -16,16 +19,13 @@
buf
}
-pub fn create_helper<F, R>(
+pub fn create_helper<R>(
base: &Path,
prefix: &OsStr,
suffix: &OsStr,
random_len: usize,
- f: F,
-) -> io::Result<R>
-where
- F: Fn(PathBuf) -> io::Result<R>,
-{
+ mut f: impl FnMut(PathBuf) -> io::Result<R>,
+) -> io::Result<R> {
let num_retries = if random_len != 0 {
crate::NUM_RETRIES
} else {
@@ -35,7 +35,10 @@
for _ in 0..num_retries {
let path = base.join(tmpname(prefix, suffix, random_len));
return match f(path) {
- Err(ref e) if e.kind() == io::ErrorKind::AlreadyExists => continue,
+ Err(ref e) if e.kind() == io::ErrorKind::AlreadyExists && num_retries > 1 => continue,
+ // AddrInUse can happen if we're creating a UNIX domain socket and
+ // the path already exists.
+ Err(ref e) if e.kind() == io::ErrorKind::AddrInUse && num_retries > 1 => continue,
res => res,
};
}
diff --git a/crates/tempfile/tests/env.rs b/crates/tempfile/tests/env.rs
new file mode 100644
index 0000000..558f767
--- /dev/null
+++ b/crates/tempfile/tests/env.rs
@@ -0,0 +1,15 @@
+#![deny(rust_2018_idioms)]
+
+use std::path::Path;
+
+#[test]
+fn test_override_temp_dir() {
+ assert_eq!(tempfile::env::temp_dir(), std::env::temp_dir());
+
+ let new_tmp = Path::new("/tmp/override");
+ tempfile::env::override_temp_dir(&new_tmp).unwrap();
+ assert_eq!(tempfile::env::temp_dir(), new_tmp);
+
+ let new_tmp2 = Path::new("/tmp/override2");
+ tempfile::env::override_temp_dir(new_tmp2).expect_err("override should only be possible once");
+}
diff --git a/crates/tempfile/tests/namedtempfile.rs b/crates/tempfile/tests/namedtempfile.rs
index d2c7da2..06b6e86 100644
--- a/crates/tempfile/tests/namedtempfile.rs
+++ b/crates/tempfile/tests/namedtempfile.rs
@@ -1,17 +1,23 @@
#![deny(rust_2018_idioms)]
-use std::env;
use std::ffi::{OsStr, OsString};
use std::fs::File;
use std::io::{Read, Seek, SeekFrom, Write};
use std::path::{Path, PathBuf};
-use tempfile::{tempdir, Builder, NamedTempFile, TempPath};
+use tempfile::{env, tempdir, Builder, NamedTempFile, TempPath};
fn exists<P: AsRef<Path>>(path: P) -> bool {
std::fs::metadata(path.as_ref()).is_ok()
}
#[test]
+fn test_prefix() {
+ let tmpfile = NamedTempFile::with_prefix("prefix").unwrap();
+ let name = tmpfile.path().file_name().unwrap().to_str().unwrap();
+ assert!(name.starts_with("prefix"));
+}
+
+#[test]
fn test_basic() {
let mut tmpfile = NamedTempFile::new().unwrap();
write!(tmpfile, "abcde").unwrap();
@@ -87,7 +93,7 @@
fn test_customnamed() {
let tmpfile = Builder::new()
.prefix("tmp")
- .suffix(&".rs".to_string())
+ .suffix(&".rs")
.rand_bytes(12)
.tempfile()
.unwrap();
@@ -100,9 +106,9 @@
#[test]
fn test_append() {
let mut tmpfile = Builder::new().append(true).tempfile().unwrap();
- tmpfile.write(b"a").unwrap();
+ tmpfile.write_all(b"a").unwrap();
tmpfile.seek(SeekFrom::Start(0)).unwrap();
- tmpfile.write(b"b").unwrap();
+ tmpfile.write_all(b"b").unwrap();
tmpfile.seek(SeekFrom::Start(0)).unwrap();
let mut buf = vec![0u8; 1];
@@ -269,10 +275,10 @@
#[test]
fn test_change_dir() {
- env::set_current_dir(env::temp_dir()).unwrap();
+ std::env::set_current_dir(env::temp_dir()).unwrap();
let tmpfile = NamedTempFile::new_in(".").unwrap();
- let path = env::current_dir().unwrap().join(tmpfile.path());
- env::set_current_dir("/").unwrap();
+ let path = std::env::current_dir().unwrap().join(tmpfile.path());
+ std::env::set_current_dir("/").unwrap();
drop(tmpfile);
assert!(!exists(path))
}
@@ -299,6 +305,18 @@
}
#[test]
+fn test_from_parts() {
+ let mut file = NamedTempFile::new().unwrap();
+ write!(file, "abcd").expect("write failed");
+
+ let (file, temp_path) = file.into_parts();
+
+ let file = NamedTempFile::from_parts(file, temp_path);
+
+ assert!(file.path().exists());
+}
+
+#[test]
fn test_keep() {
let mut tmpfile = NamedTempFile::new().unwrap();
write!(tmpfile, "abcde").unwrap();
@@ -326,3 +344,146 @@
}
std::fs::remove_file(&path).unwrap();
}
+
+#[test]
+fn test_builder_keep() {
+ let mut tmpfile = Builder::new().keep(true).tempfile().unwrap();
+ write!(tmpfile, "abcde").unwrap();
+ let path = tmpfile.path().to_owned();
+ drop(tmpfile);
+
+ {
+ // Try opening it again.
+ let mut f = File::open(&path).unwrap();
+ let mut buf = String::new();
+ f.read_to_string(&mut buf).unwrap();
+ assert_eq!("abcde", buf);
+ }
+ std::fs::remove_file(&path).unwrap();
+}
+
+#[test]
+fn test_make() {
+ let tmpfile = Builder::new().make(|path| File::create(path)).unwrap();
+
+ assert!(tmpfile.path().is_file());
+}
+
+#[test]
+fn test_make_in() {
+ let tmp_dir = tempdir().unwrap();
+
+ let tmpfile = Builder::new()
+ .make_in(tmp_dir.path(), |path| File::create(path))
+ .unwrap();
+
+ assert!(tmpfile.path().is_file());
+ assert_eq!(tmpfile.path().parent(), Some(tmp_dir.path()));
+}
+
+#[test]
+fn test_make_fnmut() {
+ let mut count = 0;
+
+ // Show that an FnMut can be used.
+ let tmpfile = Builder::new()
+ .make(|path| {
+ count += 1;
+ File::create(path)
+ })
+ .unwrap();
+
+ assert!(tmpfile.path().is_file());
+}
+
+#[cfg(unix)]
+#[test]
+fn test_make_uds() {
+ use std::os::unix::net::UnixListener;
+
+ let temp_sock = Builder::new()
+ .prefix("tmp")
+ .suffix(".sock")
+ .rand_bytes(12)
+ .make(|path| UnixListener::bind(path))
+ .unwrap();
+
+ assert!(temp_sock.path().exists());
+}
+
+#[cfg(unix)]
+#[test]
+fn test_make_uds_conflict() {
+ use std::os::unix::net::UnixListener;
+ use std::sync::atomic::{AtomicUsize, Ordering};
+ use std::sync::Arc;
+
+ // Check that retries happen correctly by racing N different threads.
+
+ const NTHREADS: usize = 20;
+
+ // The number of times our callback was called.
+ let tries = Arc::new(AtomicUsize::new(0));
+
+ let mut threads = Vec::with_capacity(NTHREADS);
+
+ for _ in 0..NTHREADS {
+ let tries = tries.clone();
+ threads.push(std::thread::spawn(move || {
+ // Ensure that every thread uses the same seed so we are guaranteed
+ // to retry. Note that fastrand seeds are thread-local.
+ fastrand::seed(42);
+
+ Builder::new()
+ .prefix("tmp")
+ .suffix(".sock")
+ .rand_bytes(12)
+ .make(|path| {
+ tries.fetch_add(1, Ordering::Relaxed);
+ UnixListener::bind(path)
+ })
+ }));
+ }
+
+ // Join all threads, but don't drop the temp file yet. Otherwise, we won't
+ // get a deterministic number of `tries`.
+ let sockets: Vec<_> = threads
+ .into_iter()
+ .map(|thread| thread.join().unwrap().unwrap())
+ .collect();
+
+ // Number of tries is exactly equal to (n*(n+1))/2.
+ assert_eq!(
+ tries.load(Ordering::Relaxed),
+ (NTHREADS * (NTHREADS + 1)) / 2
+ );
+
+ for socket in sockets {
+ assert!(socket.path().exists());
+ }
+}
+
+// Issue #224.
+#[test]
+fn test_overly_generic_bounds() {
+ pub struct Foo<T>(T);
+
+ impl<T> Foo<T>
+ where
+ T: Sync + Send + 'static,
+ for<'a> &'a T: Write + Read,
+ {
+ pub fn new(foo: T) -> Self {
+ Self(foo)
+ }
+ }
+
+ // Don't really need to run this. Only care if it compiles.
+ if let Ok(file) = File::open("i_do_not_exist") {
+ let mut f;
+ let _x = {
+ f = Foo::new(file);
+ &mut f
+ };
+ }
+}
diff --git a/crates/tempfile/tests/spooled.rs b/crates/tempfile/tests/spooled.rs
index 288d1e6..4060214 100644
--- a/crates/tempfile/tests/spooled.rs
+++ b/crates/tempfile/tests/spooled.rs
@@ -10,7 +10,7 @@
let mut buf = Vec::new();
assert!(!t.is_rolled());
- assert_eq!(t.seek(SeekFrom::Current(0)).unwrap(), 0);
+ assert_eq!(t.stream_position().unwrap(), 0);
assert_eq!(t.read_to_end(&mut buf).unwrap(), 0);
assert_eq!(buf.as_slice(), b"");
buf.clear();
@@ -24,7 +24,7 @@
assert_eq!(t.write(b"fghijklmno").unwrap(), 10);
- assert_eq!(t.seek(SeekFrom::Current(0)).unwrap(), 15);
+ assert_eq!(t.stream_position().unwrap(), 15);
assert!(t.is_rolled());
}
@@ -32,13 +32,13 @@
fn test_explicit_rollover() {
let mut t = SpooledTempFile::new(100);
assert_eq!(t.write(b"abcdefghijklmnopqrstuvwxyz").unwrap(), 26);
- assert_eq!(t.seek(SeekFrom::Current(0)).unwrap(), 26);
+ assert_eq!(t.stream_position().unwrap(), 26);
assert!(!t.is_rolled());
// roll over explicitly
assert!(t.roll().is_ok());
assert!(t.is_rolled());
- assert_eq!(t.seek(SeekFrom::Current(0)).unwrap(), 26);
+ assert_eq!(t.stream_position().unwrap(), 26);
let mut buf = Vec::new();
assert_eq!(t.read_to_end(&mut buf).unwrap(), 0);
@@ -48,7 +48,7 @@
assert_eq!(t.seek(SeekFrom::Start(0)).unwrap(), 0);
assert_eq!(t.read_to_end(&mut buf).unwrap(), 26);
assert_eq!(buf.as_slice(), b"abcdefghijklmnopqrstuvwxyz");
- assert_eq!(t.seek(SeekFrom::Current(0)).unwrap(), 26);
+ assert_eq!(t.stream_position().unwrap(), 26);
}
// called by test_seek_{buffer, file}
@@ -56,7 +56,7 @@
fn test_seek(t: &mut SpooledTempFile) {
assert_eq!(t.write(b"abcdefghijklmnopqrstuvwxyz").unwrap(), 26);
- assert_eq!(t.seek(SeekFrom::Current(0)).unwrap(), 26); // tell()
+ assert_eq!(t.stream_position().unwrap(), 26); // tell()
assert_eq!(t.seek(SeekFrom::Current(-1)).unwrap(), 25);
assert_eq!(t.seek(SeekFrom::Current(1)).unwrap(), 26);
assert_eq!(t.seek(SeekFrom::Current(1)).unwrap(), 27);
@@ -110,7 +110,7 @@
buf.clear();
// now we're at the end again
- assert_eq!(t.seek(SeekFrom::Current(0)).unwrap(), 26); // tell()
+ assert_eq!(t.stream_position().unwrap(), 26); // tell()
assert_eq!(t.read_to_end(&mut buf).unwrap(), 0);
assert_eq!(buf.as_slice(), b"");
buf.clear();
@@ -122,7 +122,7 @@
assert_eq!(buf, *b"fghij");
// read again from current spot
- assert_eq!(t.seek(SeekFrom::Current(0)).unwrap(), 10); // tell()
+ assert_eq!(t.stream_position().unwrap(), 10); // tell()
assert!(t.read_exact(&mut buf).is_ok());
assert_eq!(buf, *b"klmno");
@@ -190,11 +190,11 @@
assert_eq!(t.write(b"abcdefghijklmno").unwrap(), 15);
assert!(!t.is_rolled());
assert_eq!(t.seek(SeekFrom::End(-5)).unwrap(), 10);
- assert_eq!(t.seek(SeekFrom::Current(0)).unwrap(), 10); // tell()
+ assert_eq!(t.stream_position().unwrap(), 10); // tell()
assert!(!t.is_rolled());
assert_eq!(t.write(b"0123456789)!@#$%^&*(").unwrap(), 20);
assert!(t.is_rolled());
- assert_eq!(t.seek(SeekFrom::Current(0)).unwrap(), 30); // tell()
+ assert_eq!(t.stream_position().unwrap(), 30); // tell()
let mut buf = Vec::new();
assert_eq!(t.seek(SeekFrom::Start(0)).unwrap(), 0);
assert_eq!(t.read_to_end(&mut buf).unwrap(), 30);
@@ -247,11 +247,11 @@
assert!(t.set_len(10).is_ok());
// position should not have moved
- assert_eq!(t.seek(SeekFrom::Current(0)).unwrap(), 26); // tell()
+ assert_eq!(t.stream_position().unwrap(), 26); // tell()
assert_eq!(t.read_to_end(&mut buf).unwrap(), 0);
assert_eq!(buf.as_slice(), b"");
- assert_eq!(t.seek(SeekFrom::Current(0)).unwrap(), 26); // tell()
+ assert_eq!(t.stream_position().unwrap(), 26); // tell()
buf.clear();
// read whole thing
@@ -262,7 +262,7 @@
// set_len to expand beyond the end
assert!(t.set_len(40).is_ok());
- assert_eq!(t.seek(SeekFrom::Current(0)).unwrap(), 10); // tell()
+ assert_eq!(t.stream_position().unwrap(), 10); // tell()
assert_eq!(t.seek(SeekFrom::Start(0)).unwrap(), 0);
assert_eq!(t.read_to_end(&mut buf).unwrap(), 40);
assert_eq!(
@@ -290,18 +290,33 @@
let mut t = spooled_tempfile(10);
assert_eq!(t.write(b"abcde").unwrap(), 5);
assert!(!t.is_rolled());
- assert_eq!(t.seek(SeekFrom::Current(0)).unwrap(), 5); // tell()
+ assert_eq!(t.stream_position().unwrap(), 5); // tell()
assert_eq!(t.seek(SeekFrom::Start(0)).unwrap(), 0);
assert_eq!(t.read_to_end(&mut buf).unwrap(), 5);
assert_eq!(buf.as_slice(), b"abcde");
- assert_eq!(t.seek(SeekFrom::Current(0)).unwrap(), 5); // tell()
+ assert_eq!(t.stream_position().unwrap(), 5); // tell()
buf.clear();
assert!(t.set_len(20).is_ok());
assert!(t.is_rolled());
- assert_eq!(t.seek(SeekFrom::Current(0)).unwrap(), 5); // tell()
+ assert_eq!(t.stream_position().unwrap(), 5); // tell()
assert_eq!(t.seek(SeekFrom::Start(0)).unwrap(), 0);
assert_eq!(t.read_to_end(&mut buf).unwrap(), 20);
assert_eq!(buf.as_slice(), b"abcde\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0");
}
+
+#[test]
+fn test_write_overflow() {
+ let mut t = spooled_tempfile(10);
+ t.seek(SeekFrom::Start(u64::MAX)).unwrap();
+ assert!(t.write(b"abcde").is_err());
+}
+
+#[cfg(target_pointer_width = "32")]
+#[test]
+fn test_set_len_truncation() {
+ let mut t = spooled_tempfile(100);
+ assert!(t.set_len(usize::MAX as u64 + 5).is_ok());
+ assert!(t.is_rolled());
+}
diff --git a/crates/tempfile/tests/tempdir.rs b/crates/tempfile/tests/tempdir.rs
index 746fe47..93f86ab 100644
--- a/crates/tempfile/tests/tempdir.rs
+++ b/crates/tempfile/tests/tempdir.rs
@@ -10,7 +10,6 @@
#![deny(rust_2018_idioms)]
-use std::env;
use std::fs;
use std::path::Path;
use std::sync::mpsc::channel;
@@ -18,32 +17,9 @@
use tempfile::{Builder, TempDir};
-macro_rules! t {
- ($e:expr) => {
- match $e {
- Ok(n) => n,
- Err(e) => panic!("error: {}", e),
- }
- };
-}
-
-trait PathExt {
- fn exists(&self) -> bool;
- fn is_dir(&self) -> bool;
-}
-
-impl PathExt for Path {
- fn exists(&self) -> bool {
- fs::metadata(self).is_ok()
- }
- fn is_dir(&self) -> bool {
- fs::metadata(self).map(|m| m.is_dir()).unwrap_or(false)
- }
-}
-
fn test_tempdir() {
let path = {
- let p = t!(Builder::new().prefix("foobar").tempdir_in(&Path::new(".")));
+ let p = Builder::new().prefix("foobar").tempdir_in(".").unwrap();
let p = p.path();
assert!(p.to_str().unwrap().contains("foobar"));
p.to_path_buf()
@@ -51,7 +27,12 @@
assert!(!path.exists());
}
-#[test]
+fn test_prefix() {
+ let tmpfile = TempDir::with_prefix_in("prefix", ".").unwrap();
+ let name = tmpfile.path().file_name().unwrap().to_str().unwrap();
+ assert!(name.starts_with("prefix"));
+}
+
fn test_customnamed() {
let tmpfile = Builder::new()
.prefix("prefix")
@@ -67,8 +48,8 @@
fn test_rm_tempdir() {
let (tx, rx) = channel();
- let f = move || -> () {
- let tmp = t!(TempDir::new());
+ let f = move || {
+ let tmp = TempDir::new().unwrap();
tx.send(tmp.path().to_path_buf()).unwrap();
panic!("panic to unwind past `tmp`");
};
@@ -76,9 +57,9 @@
let path = rx.recv().unwrap();
assert!(!path.exists());
- let tmp = t!(TempDir::new());
+ let tmp = TempDir::new().unwrap();
let path = tmp.path().to_path_buf();
- let f = move || -> () {
+ let f = move || {
let _tmp = tmp;
panic!("panic to unwind past `tmp`");
};
@@ -87,7 +68,7 @@
let path;
{
- let f = move || t!(TempDir::new());
+ let f = move || TempDir::new().unwrap();
let tmp = thread::spawn(f).join().unwrap();
path = tmp.path().to_path_buf();
@@ -97,31 +78,31 @@
let path;
{
- let tmp = t!(TempDir::new());
+ let tmp = TempDir::new().unwrap();
path = tmp.into_path();
}
assert!(path.exists());
- t!(fs::remove_dir_all(&path));
+ fs::remove_dir_all(&path).unwrap();
assert!(!path.exists());
}
fn test_rm_tempdir_close() {
let (tx, rx) = channel();
- let f = move || -> () {
- let tmp = t!(TempDir::new());
+ let f = move || {
+ let tmp = TempDir::new().unwrap();
tx.send(tmp.path().to_path_buf()).unwrap();
- t!(tmp.close());
+ tmp.close().unwrap();
panic!("panic when unwinding past `tmp`");
};
let _ = thread::spawn(f).join();
let path = rx.recv().unwrap();
assert!(!path.exists());
- let tmp = t!(TempDir::new());
+ let tmp = TempDir::new().unwrap();
let path = tmp.path().to_path_buf();
- let f = move || -> () {
+ let f = move || {
let tmp = tmp;
- t!(tmp.close());
+ tmp.close().unwrap();
panic!("panic when unwinding past `tmp`");
};
let _ = thread::spawn(f).join();
@@ -129,96 +110,31 @@
let path;
{
- let f = move || t!(TempDir::new());
+ let f = move || TempDir::new().unwrap();
let tmp = thread::spawn(f).join().unwrap();
path = tmp.path().to_path_buf();
assert!(path.exists());
- t!(tmp.close());
+ tmp.close().unwrap();
}
assert!(!path.exists());
let path;
{
- let tmp = t!(TempDir::new());
+ let tmp = TempDir::new().unwrap();
path = tmp.into_path();
}
assert!(path.exists());
- t!(fs::remove_dir_all(&path));
+ fs::remove_dir_all(&path).unwrap();
assert!(!path.exists());
}
-// Ideally these would be in std::os but then core would need
-// to depend on std
-fn recursive_mkdir_rel() {
- let path = Path::new("frob");
- let cwd = env::current_dir().unwrap();
- println!(
- "recursive_mkdir_rel: Making: {} in cwd {} [{}]",
- path.display(),
- cwd.display(),
- path.exists()
- );
- t!(fs::create_dir(&path));
- assert!(path.is_dir());
- t!(fs::create_dir_all(&path));
- assert!(path.is_dir());
-}
-
-fn recursive_mkdir_dot() {
- let dot = Path::new(".");
- t!(fs::create_dir_all(&dot));
- let dotdot = Path::new("..");
- t!(fs::create_dir_all(&dotdot));
-}
-
-fn recursive_mkdir_rel_2() {
- let path = Path::new("./frob/baz");
- let cwd = env::current_dir().unwrap();
- println!(
- "recursive_mkdir_rel_2: Making: {} in cwd {} [{}]",
- path.display(),
- cwd.display(),
- path.exists()
- );
- t!(fs::create_dir_all(&path));
- assert!(path.is_dir());
- assert!(path.parent().unwrap().is_dir());
- let path2 = Path::new("quux/blat");
- println!(
- "recursive_mkdir_rel_2: Making: {} in cwd {}",
- path2.display(),
- cwd.display()
- );
- t!(fs::create_dir("quux"));
- t!(fs::create_dir_all(&path2));
- assert!(path2.is_dir());
- assert!(path2.parent().unwrap().is_dir());
-}
-
-// Ideally this would be in core, but needs TempFile
-pub fn test_remove_dir_all_ok() {
- let tmpdir = t!(TempDir::new());
- let tmpdir = tmpdir.path();
- let root = tmpdir.join("foo");
-
- println!("making {}", root.display());
- t!(fs::create_dir(&root));
- t!(fs::create_dir(&root.join("foo")));
- t!(fs::create_dir(&root.join("foo").join("bar")));
- t!(fs::create_dir(&root.join("foo").join("bar").join("blat")));
- t!(fs::remove_dir_all(&root));
- assert!(!root.exists());
- assert!(!root.join("bar").exists());
- assert!(!root.join("bar").join("blat").exists());
-}
-
-pub fn dont_double_panic() {
+fn dont_double_panic() {
let r: Result<(), _> = thread::spawn(move || {
let tmpdir = TempDir::new().unwrap();
// Remove the temporary directory so that TempDir sees
// an error on drop
- t!(fs::remove_dir(tmpdir.path()));
+ fs::remove_dir(tmpdir.path()).unwrap();
// Panic. If TempDir panics *again* due to the rmdir
// error then the process will abort.
panic!();
@@ -231,14 +147,14 @@
where
F: FnOnce(),
{
- let tmpdir = t!(TempDir::new());
- assert!(env::set_current_dir(tmpdir.path()).is_ok());
+ let tmpdir = TempDir::new().unwrap();
+ assert!(std::env::set_current_dir(tmpdir.path()).is_ok());
f();
}
-pub fn pass_as_asref_path() {
- let tempdir = t!(TempDir::new());
+fn pass_as_asref_path() {
+ let tempdir = TempDir::new().unwrap();
takes_asref_path(&tempdir);
fn takes_asref_path<T: AsRef<Path>>(path: T) {
@@ -247,15 +163,22 @@
}
}
+fn test_keep() {
+ let tmpdir = Builder::new().keep(true).tempdir().unwrap();
+ let path = tmpdir.path().to_owned();
+ drop(tmpdir);
+ assert!(path.exists());
+ fs::remove_dir(path).unwrap();
+}
+
#[test]
fn main() {
in_tmpdir(test_tempdir);
+ in_tmpdir(test_prefix);
+ in_tmpdir(test_customnamed);
in_tmpdir(test_rm_tempdir);
in_tmpdir(test_rm_tempdir_close);
- in_tmpdir(recursive_mkdir_rel);
- in_tmpdir(recursive_mkdir_dot);
- in_tmpdir(recursive_mkdir_rel_2);
- in_tmpdir(test_remove_dir_all_ok);
in_tmpdir(dont_double_panic);
in_tmpdir(pass_as_asref_path);
+ in_tmpdir(test_keep);
}
diff --git a/crates/tempfile/tests/tempfile.rs b/crates/tempfile/tests/tempfile.rs
index f4dddb2..c2f6844 100644
--- a/crates/tempfile/tests/tempfile.rs
+++ b/crates/tempfile/tests/tempfile.rs
@@ -2,8 +2,11 @@
use std::fs;
use std::io::{Read, Seek, SeekFrom, Write};
-use std::sync::mpsc::{sync_channel, TryRecvError};
-use std::thread;
+#[cfg(target_os = "linux")]
+use std::{
+ sync::mpsc::{sync_channel, TryRecvError},
+ thread,
+};
#[test]
fn test_basic() {