зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1854025 - Update tempfile to 3.8.0. r=emilio,supply-chain-reviewers,sylvestre
Differential Revision: https://phabricator.services.mozilla.com/D188654
This commit is contained in:
Родитель
390281e651
Коммит
1e09918ec1
|
@ -1626,12 +1626,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "1.9.0"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be"
|
||||
dependencies = [
|
||||
"instant",
|
||||
]
|
||||
checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764"
|
||||
|
||||
[[package]]
|
||||
name = "ffi-support"
|
||||
|
@ -2201,7 +2198,6 @@ dependencies = [
|
|||
"rure",
|
||||
"rusqlite",
|
||||
"rust_minidump_writer_linux",
|
||||
"rustix",
|
||||
"static_prefs",
|
||||
"storage",
|
||||
"suggest",
|
||||
|
@ -2771,15 +2767,6 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "instant"
|
||||
version = "0.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "interrupt-support"
|
||||
version = "0.1.0"
|
||||
|
@ -4151,7 +4138,7 @@ checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447"
|
|||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"libc",
|
||||
"redox_syscall 0.3.999",
|
||||
"redox_syscall",
|
||||
"smallvec",
|
||||
"windows-targets",
|
||||
]
|
||||
|
@ -4553,13 +4540,6 @@ dependencies = [
|
|||
"num_cpus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.2.999"
|
||||
dependencies = [
|
||||
"redox_syscall 0.3.999",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.3.999"
|
||||
|
@ -5409,16 +5389,15 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.3.0"
|
||||
version = "3.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4"
|
||||
checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"fastrand",
|
||||
"libc",
|
||||
"redox_syscall 0.2.999",
|
||||
"remove_dir_all",
|
||||
"winapi",
|
||||
"redox_syscall",
|
||||
"rustix",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
@ -121,7 +121,6 @@ serde_with = { path = "build/rust/serde_with" }
|
|||
redox_users = { path = "build/rust/redox_users" }
|
||||
|
||||
# Patch redox_syscall to an empty crate
|
||||
redox_syscall_0_2 = { package = "redox_syscall", path = "build/rust/redox_syscall_0_2" }
|
||||
redox_syscall = { path = "build/rust/redox_syscall" }
|
||||
|
||||
# Override tinyvec with smallvec
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
[package]
|
||||
name = "redox_syscall"
|
||||
version = "0.2.999"
|
||||
edition = "2018"
|
||||
license = "MPL-2.0"
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
redox_syscall = "0.3"
|
|
@ -1,3 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
@ -1396,6 +1396,11 @@ who = "Mike Hommey <mh+mozilla@glandium.org>"
|
|||
criteria = "safe-to-deploy"
|
||||
delta = "1.8.0 -> 1.9.0"
|
||||
|
||||
[[audits.fastrand]]
|
||||
who = "Mike Hommey <mh+mozilla@glandium.org>"
|
||||
criteria = "safe-to-deploy"
|
||||
delta = "1.9.0 -> 2.0.0"
|
||||
|
||||
[[audits.filetime_win]]
|
||||
who = "Nick Alexander <nalexander@mozilla.com>"
|
||||
criteria = "safe-to-deploy"
|
||||
|
@ -3271,6 +3276,11 @@ who = "Mike Hommey <mh+mozilla@glandium.org>"
|
|||
criteria = "safe-to-deploy"
|
||||
delta = "0.12.6 -> 0.13.0"
|
||||
|
||||
[[audits.tempfile]]
|
||||
who = "Mike Hommey <mh+mozilla@glandium.org>"
|
||||
criteria = "safe-to-deploy"
|
||||
delta = "3.6.0 -> 3.8.0"
|
||||
|
||||
[[audits.termcolor]]
|
||||
who = "Mike Hommey <mh+mozilla@glandium.org>"
|
||||
criteria = "safe-to-deploy"
|
||||
|
|
|
@ -1034,6 +1034,17 @@ criteria = "safe-to-deploy"
|
|||
version = "0.4.6"
|
||||
notes = "provides a datastructure implemented using std's Vec. all uses of unsafe are just delegating to the underlying unsafe Vec methods."
|
||||
|
||||
[[audits.bytecode-alliance.audits.tempfile]]
|
||||
who = "Pat Hickey <phickey@fastly.com>"
|
||||
criteria = "safe-to-deploy"
|
||||
delta = "3.3.0 -> 3.5.0"
|
||||
|
||||
[[audits.bytecode-alliance.audits.tempfile]]
|
||||
who = "Alex Crichton <alex@alexcrichton.com>"
|
||||
criteria = "safe-to-deploy"
|
||||
delta = "3.5.0 -> 3.6.0"
|
||||
notes = "Dependency updates and new optimized trait implementations, but otherwise everything looks normal."
|
||||
|
||||
[[audits.bytecode-alliance.audits.unicase]]
|
||||
who = "Alex Crichton <alex@alexcrichton.com>"
|
||||
criteria = "safe-to-deploy"
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"files":{"CHANGELOG.md":"64192695962b6d2ec5f1f57f3b7909c216611a5de5cd5d0d28d863477eef0a12","Cargo.toml":"9d3bf85fff7d7228a8aae6e0c20a43f19a846412e598ee0b7d1f6f2a30bac880","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3","README.md":"dec6b96d3549746937d7e0e62e35e206e6f5b7e2d1886451281905f4edf096d4","benches/bench.rs":"234b2e5f725102ed0ff7686def089de23e433eeecaf03542af31fc60725e85e3","src/lib.rs":"88bf8a952c723b28b989c7b998b2ec0de6a1351f6891088d6237300c8982f36d","tests/char.rs":"a530b41837f5bf43701d983ef0267d9b44779d455f24cbf30b881cd348de9ee1","tests/smoke.rs":"aac00322cce06f15378aacbf247a37e9602c46cfd2bd588a0887b266cbc4770a"},"package":"e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be"}
|
||||
{"files":{"CHANGELOG.md":"a88b4ec120e965c0219c8d4a95e0868ed9396acb47d171ca864608eacda7efb8","Cargo.toml":"f0bc7071d293be9565d4a960fa914317f00f319901e9578e7a49a3a86959d90a","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3","README.md":"ba09e4125cf5450a26d1bd2236bd079d668b118df639d9055e61eaa4d3c23336","benches/bench.rs":"95df618eeb6f3432e11295d75267c0ececcda35a6d230e9ca504e5d772fa2b62","src/global_rng.rs":"43a74ba2c3c15ebdbbacff65d6da5a90b4c062dedc43c6bf3fcf05499beaeece","src/lib.rs":"67568c53a27b34c5e2eb5e613a9656bcc9da1688a85070c4c36b60c216e3da8b","tests/char.rs":"a530b41837f5bf43701d983ef0267d9b44779d455f24cbf30b881cd348de9ee1","tests/smoke.rs":"8eac48144705364d142882538be43b8d69018959579404c3b1e638827888e62e"},"package":"6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764"}
|
|
@ -1,3 +1,12 @@
|
|||
# Version 2.0.0
|
||||
|
||||
- **Breaking:** Remove interior mutability from `Rng`. (#47)
|
||||
- Add a `fork()` method. (#49)
|
||||
- Add a `no_std` mode. (#50)
|
||||
- Add an iterator selection function. (#51)
|
||||
- Add a `choose_multiple()` function for sampling several elements from an iterator. (#55)
|
||||
- Use the `getrandom` crate for seeding on WebAssembly targets if the `js` feature is enabled. (#60)
|
||||
|
||||
# Version 1.9.0
|
||||
|
||||
- Add `Rng::fill()` (#35, #43)
|
||||
|
|
|
@ -11,9 +11,9 @@
|
|||
|
||||
[package]
|
||||
edition = "2018"
|
||||
rust-version = "1.34"
|
||||
rust-version = "1.36"
|
||||
name = "fastrand"
|
||||
version = "1.9.0"
|
||||
version = "2.0.0"
|
||||
authors = ["Stjepan Glavina <stjepang@gmail.com>"]
|
||||
exclude = ["/.*"]
|
||||
description = "A simple and fast random number generator"
|
||||
|
@ -29,6 +29,13 @@ categories = ["algorithms"]
|
|||
license = "Apache-2.0 OR MIT"
|
||||
repository = "https://github.com/smol-rs/fastrand"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
rustdoc-args = [
|
||||
"--cfg",
|
||||
"docsrs",
|
||||
]
|
||||
|
||||
[dev-dependencies.getrandom]
|
||||
version = "0.2"
|
||||
|
||||
|
@ -38,16 +45,23 @@ version = "0.8"
|
|||
[dev-dependencies.wyhash]
|
||||
version = "0.5"
|
||||
|
||||
[target."cfg(all(target_arch = \"wasm32\", not(target_os = \"wasi\")))".dependencies.instant]
|
||||
version = "0.1"
|
||||
[features]
|
||||
alloc = []
|
||||
default = ["std"]
|
||||
js = [
|
||||
"std",
|
||||
"getrandom",
|
||||
]
|
||||
std = ["alloc"]
|
||||
|
||||
[target."cfg(all(target_arch = \"wasm32\", not(target_os = \"wasi\")))".dev-dependencies.getrandom]
|
||||
[target."cfg(all(any(target_arch = \"wasm32\", target_arch = \"wasm64\"), target_os = \"unknown\"))".dependencies.getrandom]
|
||||
version = "0.2"
|
||||
features = ["js"]
|
||||
optional = true
|
||||
|
||||
[target."cfg(all(any(target_arch = \"wasm32\", target_arch = \"wasm64\"), target_os = \"unknown\"))".dev-dependencies.getrandom]
|
||||
version = "0.2"
|
||||
features = ["js"]
|
||||
|
||||
[target."cfg(all(target_arch = \"wasm32\", not(target_os = \"wasi\")))".dev-dependencies.instant]
|
||||
version = "0.1"
|
||||
features = ["wasm-bindgen"]
|
||||
|
||||
[target."cfg(all(target_arch = \"wasm32\", not(target_os = \"wasi\")))".dev-dependencies.wasm-bindgen-test]
|
||||
[target."cfg(all(any(target_arch = \"wasm32\", target_arch = \"wasm64\"), target_os = \"unknown\"))".dev-dependencies.wasm-bindgen-test]
|
||||
version = "0.3"
|
||||
|
|
|
@ -40,6 +40,13 @@ let i = fastrand::usize(..v.len());
|
|||
let elem = v[i];
|
||||
```
|
||||
|
||||
Sample values from an array with `O(n)` complexity (`n` is the length of array):
|
||||
|
||||
```rust
|
||||
fastrand::choose_multiple(vec![1, 4, 5].iter(), 2);
|
||||
fastrand::choose_multiple(0..20, 12);
|
||||
```
|
||||
|
||||
Shuffle an array:
|
||||
|
||||
```rust
|
||||
|
@ -76,6 +83,12 @@ let rng = fastrand::Rng::new();
|
|||
let mut bytes: Vec<u8> = repeat_with(|| rng.u8(..)).take(10_000).collect();
|
||||
```
|
||||
|
||||
# Features
|
||||
|
||||
- `std` (enabled by default): Enables the `std` library. This is required for the global
|
||||
generator and global entropy. Without this feature, [`Rng`] can only be instantiated using
|
||||
the [`with_seed`](Rng::with_seed) method.
|
||||
|
||||
## License
|
||||
|
||||
Licensed under either of
|
||||
|
|
|
@ -18,7 +18,7 @@ fn shuffle_wyhash(b: &mut Bencher) {
|
|||
|
||||
#[bench]
|
||||
fn shuffle_fastrand(b: &mut Bencher) {
|
||||
let rng = fastrand::Rng::new();
|
||||
let mut rng = fastrand::Rng::new();
|
||||
let mut x = (0..100).collect::<Vec<usize>>();
|
||||
b.iter(|| {
|
||||
rng.shuffle(&mut x);
|
||||
|
@ -40,7 +40,7 @@ fn u8_wyhash(b: &mut Bencher) {
|
|||
|
||||
#[bench]
|
||||
fn u8_fastrand(b: &mut Bencher) {
|
||||
let rng = fastrand::Rng::new();
|
||||
let mut rng = fastrand::Rng::new();
|
||||
b.iter(|| {
|
||||
let mut sum = 0u8;
|
||||
for _ in 0..10_000 {
|
||||
|
@ -64,7 +64,7 @@ fn u32_wyhash(b: &mut Bencher) {
|
|||
|
||||
#[bench]
|
||||
fn u32_fastrand(b: &mut Bencher) {
|
||||
let rng = fastrand::Rng::new();
|
||||
let mut rng = fastrand::Rng::new();
|
||||
b.iter(|| {
|
||||
let mut sum = 0u32;
|
||||
for _ in 0..10_000 {
|
||||
|
@ -76,7 +76,7 @@ fn u32_fastrand(b: &mut Bencher) {
|
|||
|
||||
#[bench]
|
||||
fn fill(b: &mut Bencher) {
|
||||
let rng = fastrand::Rng::new();
|
||||
let mut rng = fastrand::Rng::new();
|
||||
b.iter(|| {
|
||||
// Pick a size that isn't divisble by 8.
|
||||
let mut bytes = [0u8; 367];
|
||||
|
@ -87,7 +87,7 @@ fn fill(b: &mut Bencher) {
|
|||
|
||||
#[bench]
|
||||
fn fill_naive(b: &mut Bencher) {
|
||||
let rng = fastrand::Rng::new();
|
||||
let mut rng = fastrand::Rng::new();
|
||||
b.iter(|| {
|
||||
let mut bytes = [0u8; 367];
|
||||
for item in &mut bytes {
|
||||
|
|
|
@ -0,0 +1,218 @@
|
|||
//! A global, thread-local random number generator.
|
||||
|
||||
use crate::Rng;
|
||||
|
||||
use std::cell::Cell;
|
||||
use std::ops::RangeBounds;
|
||||
|
||||
// Chosen by fair roll of the dice.
|
||||
const DEFAULT_RNG_SEED: u64 = 0xef6f79ed30ba75a;
|
||||
|
||||
impl Default for Rng {
|
||||
/// Initialize the `Rng` from the system's random number generator.
|
||||
///
|
||||
/// This is equivalent to [`Rng::new()`].
|
||||
#[inline]
|
||||
fn default() -> Rng {
|
||||
Rng::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Rng {
|
||||
/// Creates a new random number generator.
|
||||
#[inline]
|
||||
pub fn new() -> Rng {
|
||||
try_with_rng(Rng::fork).unwrap_or_else(|_| Rng::with_seed(0x4d595df4d0f33173))
|
||||
}
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
static RNG: Cell<Rng> = Cell::new(Rng(random_seed().unwrap_or(DEFAULT_RNG_SEED)));
|
||||
}
|
||||
|
||||
/// Run an operation with the current thread-local generator.
|
||||
#[inline]
|
||||
fn with_rng<R>(f: impl FnOnce(&mut Rng) -> R) -> R {
|
||||
RNG.with(|rng| {
|
||||
let current = rng.replace(Rng(0));
|
||||
|
||||
let mut restore = RestoreOnDrop { rng, current };
|
||||
|
||||
f(&mut restore.current)
|
||||
})
|
||||
}
|
||||
|
||||
/// Try to run an operation with the current thread-local generator.
|
||||
#[inline]
|
||||
fn try_with_rng<R>(f: impl FnOnce(&mut Rng) -> R) -> Result<R, std::thread::AccessError> {
|
||||
RNG.try_with(|rng| {
|
||||
let current = rng.replace(Rng(0));
|
||||
|
||||
let mut restore = RestoreOnDrop { rng, current };
|
||||
|
||||
f(&mut restore.current)
|
||||
})
|
||||
}
|
||||
|
||||
/// Make sure the original RNG is restored even on panic.
|
||||
struct RestoreOnDrop<'a> {
|
||||
rng: &'a Cell<Rng>,
|
||||
current: Rng,
|
||||
}
|
||||
|
||||
impl Drop for RestoreOnDrop<'_> {
|
||||
fn drop(&mut self) {
|
||||
self.rng.set(Rng(self.current.0));
|
||||
}
|
||||
}
|
||||
|
||||
/// Initializes the thread-local generator with the given seed.
|
||||
#[inline]
|
||||
pub fn seed(seed: u64) {
|
||||
with_rng(|r| r.seed(seed));
|
||||
}
|
||||
|
||||
/// Gives back **current** seed that is being held by the thread-local generator.
|
||||
#[inline]
|
||||
pub fn get_seed() -> u64 {
|
||||
with_rng(|r| r.get_seed())
|
||||
}
|
||||
|
||||
/// Generates a random `bool`.
|
||||
#[inline]
|
||||
pub fn bool() -> bool {
|
||||
with_rng(|r| r.bool())
|
||||
}
|
||||
|
||||
/// Generates a random `char` in ranges a-z and A-Z.
|
||||
#[inline]
|
||||
pub fn alphabetic() -> char {
|
||||
with_rng(|r| r.alphabetic())
|
||||
}
|
||||
|
||||
/// Generates a random `char` in ranges a-z, A-Z and 0-9.
|
||||
#[inline]
|
||||
pub fn alphanumeric() -> char {
|
||||
with_rng(|r| r.alphanumeric())
|
||||
}
|
||||
|
||||
/// Generates a random `char` in range a-z.
|
||||
#[inline]
|
||||
pub fn lowercase() -> char {
|
||||
with_rng(|r| r.lowercase())
|
||||
}
|
||||
|
||||
/// Generates a random `char` in range A-Z.
|
||||
#[inline]
|
||||
pub fn uppercase() -> char {
|
||||
with_rng(|r| r.uppercase())
|
||||
}
|
||||
|
||||
/// Choose an item from an iterator at random.
|
||||
///
|
||||
/// This function may have an unexpected result if the `len()` property of the
|
||||
/// iterator does not match the actual number of items in the iterator. If
|
||||
/// the iterator is empty, this returns `None`.
|
||||
#[inline]
|
||||
pub fn choice<I>(iter: I) -> Option<I::Item>
|
||||
where
|
||||
I: IntoIterator,
|
||||
I::IntoIter: ExactSizeIterator,
|
||||
{
|
||||
with_rng(|r| r.choice(iter))
|
||||
}
|
||||
|
||||
/// Generates a random digit in the given `base`.
|
||||
///
|
||||
/// Digits are represented by `char`s in ranges 0-9 and a-z.
|
||||
///
|
||||
/// Panics if the base is zero or greater than 36.
|
||||
#[inline]
|
||||
pub fn digit(base: u32) -> char {
|
||||
with_rng(|r| r.digit(base))
|
||||
}
|
||||
|
||||
/// Shuffles a slice randomly.
|
||||
#[inline]
|
||||
pub fn shuffle<T>(slice: &mut [T]) {
|
||||
with_rng(|r| r.shuffle(slice))
|
||||
}
|
||||
|
||||
macro_rules! integer {
|
||||
($t:tt, $doc:tt) => {
|
||||
#[doc = $doc]
|
||||
///
|
||||
/// Panics if the range is empty.
|
||||
#[inline]
|
||||
pub fn $t(range: impl RangeBounds<$t>) -> $t {
|
||||
with_rng(|r| r.$t(range))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
integer!(u8, "Generates a random `u8` in the given range.");
|
||||
integer!(i8, "Generates a random `i8` in the given range.");
|
||||
integer!(u16, "Generates a random `u16` in the given range.");
|
||||
integer!(i16, "Generates a random `i16` in the given range.");
|
||||
integer!(u32, "Generates a random `u32` in the given range.");
|
||||
integer!(i32, "Generates a random `i32` in the given range.");
|
||||
integer!(u64, "Generates a random `u64` in the given range.");
|
||||
integer!(i64, "Generates a random `i64` in the given range.");
|
||||
integer!(u128, "Generates a random `u128` in the given range.");
|
||||
integer!(i128, "Generates a random `i128` in the given range.");
|
||||
integer!(usize, "Generates a random `usize` in the given range.");
|
||||
integer!(isize, "Generates a random `isize` in the given range.");
|
||||
integer!(char, "Generates a random `char` in the given range.");
|
||||
|
||||
/// Generates a random `f32` in range `0..1`.
|
||||
pub fn f32() -> f32 {
|
||||
with_rng(|r| r.f32())
|
||||
}
|
||||
|
||||
/// Generates a random `f64` in range `0..1`.
|
||||
pub fn f64() -> f64 {
|
||||
with_rng(|r| r.f64())
|
||||
}
|
||||
|
||||
/// Collects `amount` values at random from the iterator into a vector.
|
||||
pub fn choose_multiple<T: Iterator>(source: T, amount: usize) -> Vec<T::Item> {
|
||||
with_rng(|rng| rng.choose_multiple(source, amount))
|
||||
}
|
||||
|
||||
#[cfg(not(all(
|
||||
any(target_arch = "wasm32", target_arch = "wasm64"),
|
||||
target_os = "unknown"
|
||||
)))]
|
||||
fn random_seed() -> Option<u64> {
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::thread;
|
||||
use std::time::Instant;
|
||||
|
||||
let mut hasher = DefaultHasher::new();
|
||||
Instant::now().hash(&mut hasher);
|
||||
thread::current().id().hash(&mut hasher);
|
||||
let hash = hasher.finish();
|
||||
Some((hash << 1) | 1)
|
||||
}
|
||||
|
||||
#[cfg(all(
|
||||
any(target_arch = "wasm32", target_arch = "wasm64"),
|
||||
target_os = "unknown",
|
||||
feature = "js"
|
||||
))]
|
||||
fn random_seed() -> Option<u64> {
|
||||
// TODO(notgull): Failures should be logged somewhere.
|
||||
let mut seed = [0u8; 8];
|
||||
getrandom::getrandom(&mut seed).ok()?;
|
||||
Some(u64::from_ne_bytes(seed))
|
||||
}
|
||||
|
||||
#[cfg(all(
|
||||
any(target_arch = "wasm32", target_arch = "wasm64"),
|
||||
target_os = "unknown",
|
||||
not(feature = "js")
|
||||
))]
|
||||
fn random_seed() -> Option<u64> {
|
||||
None
|
||||
}
|
|
@ -29,6 +29,14 @@
|
|||
//! let elem = v[i];
|
||||
//! ```
|
||||
//!
|
||||
//! Sample values from an array with `O(n)` complexity (`n` is the length of array):
|
||||
//!
|
||||
//! ```
|
||||
//! fastrand::choose_multiple(vec![1, 4, 5].iter(), 2);
|
||||
//! fastrand::choose_multiple(0..20, 12);
|
||||
//! ```
|
||||
//!
|
||||
//!
|
||||
//! Shuffle an array:
|
||||
//!
|
||||
//! ```
|
||||
|
@ -61,87 +69,88 @@
|
|||
//! ```
|
||||
//! use std::iter::repeat_with;
|
||||
//!
|
||||
//! let rng = fastrand::Rng::new();
|
||||
//! let mut rng = fastrand::Rng::new();
|
||||
//! let mut bytes: Vec<u8> = repeat_with(|| rng.u8(..)).take(10_000).collect();
|
||||
//! ```
|
||||
//!
|
||||
//! # Features
|
||||
//!
|
||||
//! - `std` (enabled by default): Enables the `std` library. This is required for the global
|
||||
//! generator and global entropy. Without this feature, [`Rng`] can only be instantiated using
|
||||
//! the [`with_seed`](Rng::with_seed) method.
|
||||
//! - `js`: Assumes that WebAssembly targets are being run in a JavaScript environment. See the
|
||||
//! [WebAssembly Notes](#webassembly-notes) section for more information.
|
||||
//!
|
||||
//! # WebAssembly Notes
|
||||
//!
|
||||
//! For non-WASI WASM targets, there is additional sublety to consider when utilizing the global RNG.
|
||||
//! By default, `std` targets will use entropy sources in the standard library to seed the global RNG.
|
||||
//! However, these sources are not available by default on WASM targets outside of WASI.
|
||||
//!
|
||||
//! If the `js` feature is enabled, this crate will assume that it is running in a JavaScript
|
||||
//! environment. At this point, the [`getrandom`] crate will be used in order to access the available
|
||||
//! entropy sources and seed the global RNG. If the `js` feature is not enabled, the global RNG will
|
||||
//! use a predefined seed.
|
||||
//!
|
||||
//! [`getrandom`]: https://crates.io/crates/getrandom
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
#![forbid(unsafe_code)]
|
||||
#![warn(missing_docs, missing_debug_implementations, rust_2018_idioms)]
|
||||
|
||||
use std::cell::Cell;
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::convert::TryInto;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::ops::{Bound, RangeBounds};
|
||||
use std::thread;
|
||||
#[cfg(feature = "alloc")]
|
||||
extern crate alloc;
|
||||
|
||||
#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))]
|
||||
use instant::Instant;
|
||||
#[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"))))]
|
||||
use std::time::Instant;
|
||||
use core::convert::{TryFrom, TryInto};
|
||||
use core::ops::{Bound, RangeBounds};
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
use alloc::vec::Vec;
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
|
||||
mod global_rng;
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
pub use global_rng::*;
|
||||
|
||||
/// A random number generator.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct Rng(Cell<u64>);
|
||||
|
||||
impl Default for Rng {
|
||||
#[inline]
|
||||
fn default() -> Rng {
|
||||
Rng::new()
|
||||
}
|
||||
}
|
||||
pub struct Rng(u64);
|
||||
|
||||
impl Clone for Rng {
|
||||
/// Clones the generator by deterministically deriving a new generator based on the initial
|
||||
/// seed.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// // Seed two generators equally, and clone both of them.
|
||||
/// let base1 = fastrand::Rng::new();
|
||||
/// base1.seed(0x4d595df4d0f33173);
|
||||
/// base1.bool(); // Use the generator once.
|
||||
///
|
||||
/// let base2 = fastrand::Rng::new();
|
||||
/// base2.seed(0x4d595df4d0f33173);
|
||||
/// base2.bool(); // Use the generator once.
|
||||
///
|
||||
/// let rng1 = base1.clone();
|
||||
/// let rng2 = base2.clone();
|
||||
///
|
||||
/// assert_eq!(rng1.u64(..), rng2.u64(..), "the cloned generators are identical");
|
||||
/// ```
|
||||
/// Clones the generator by creating a new generator with the same seed.
|
||||
fn clone(&self) -> Rng {
|
||||
Rng::with_seed(self.gen_u64())
|
||||
Rng::with_seed(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Rng {
|
||||
/// Generates a random `u32`.
|
||||
#[inline]
|
||||
fn gen_u32(&self) -> u32 {
|
||||
fn gen_u32(&mut self) -> u32 {
|
||||
self.gen_u64() as u32
|
||||
}
|
||||
|
||||
/// Generates a random `u64`.
|
||||
#[inline]
|
||||
fn gen_u64(&self) -> u64 {
|
||||
let s = self.0.get().wrapping_add(0xA0761D6478BD642F);
|
||||
self.0.set(s);
|
||||
fn gen_u64(&mut self) -> u64 {
|
||||
let s = self.0.wrapping_add(0xA0761D6478BD642F);
|
||||
self.0 = s;
|
||||
let t = u128::from(s) * u128::from(s ^ 0xE7037ED1A0B428DB);
|
||||
(t as u64) ^ (t >> 64) as u64
|
||||
}
|
||||
|
||||
/// Generates a random `u128`.
|
||||
#[inline]
|
||||
fn gen_u128(&self) -> u128 {
|
||||
fn gen_u128(&mut self) -> u128 {
|
||||
(u128::from(self.gen_u64()) << 64) | u128::from(self.gen_u64())
|
||||
}
|
||||
|
||||
/// Generates a random `u32` in `0..n`.
|
||||
#[inline]
|
||||
fn gen_mod_u32(&self, n: u32) -> u32 {
|
||||
fn gen_mod_u32(&mut self, n: u32) -> u32 {
|
||||
// Adapted from: https://lemire.me/blog/2016/06/30/fast-random-shuffling/
|
||||
let mut r = self.gen_u32();
|
||||
let mut hi = mul_high_u32(r, n);
|
||||
|
@ -159,7 +168,7 @@ impl Rng {
|
|||
|
||||
/// Generates a random `u64` in `0..n`.
|
||||
#[inline]
|
||||
fn gen_mod_u64(&self, n: u64) -> u64 {
|
||||
fn gen_mod_u64(&mut self, n: u64) -> u64 {
|
||||
// Adapted from: https://lemire.me/blog/2016/06/30/fast-random-shuffling/
|
||||
let mut r = self.gen_u64();
|
||||
let mut hi = mul_high_u64(r, n);
|
||||
|
@ -177,7 +186,7 @@ impl Rng {
|
|||
|
||||
/// Generates a random `u128` in `0..n`.
|
||||
#[inline]
|
||||
fn gen_mod_u128(&self, n: u128) -> u128 {
|
||||
fn gen_mod_u128(&mut self, n: u128) -> u128 {
|
||||
// Adapted from: https://lemire.me/blog/2016/06/30/fast-random-shuffling/
|
||||
let mut r = self.gen_u128();
|
||||
let mut hi = mul_high_u128(r, n);
|
||||
|
@ -194,16 +203,6 @@ impl Rng {
|
|||
}
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
static RNG: Rng = Rng(Cell::new({
|
||||
let mut hasher = DefaultHasher::new();
|
||||
Instant::now().hash(&mut hasher);
|
||||
thread::current().id().hash(&mut hasher);
|
||||
let hash = hasher.finish();
|
||||
(hash << 1) | 1
|
||||
}));
|
||||
}
|
||||
|
||||
/// Computes `(a * b) >> 32`.
|
||||
#[inline]
|
||||
fn mul_high_u32(a: u32, b: u32) -> u32 {
|
||||
|
@ -235,7 +234,7 @@ macro_rules! rng_integer {
|
|||
///
|
||||
/// Panics if the range is empty.
|
||||
#[inline]
|
||||
pub fn $t(&self, range: impl RangeBounds<$t>) -> $t {
|
||||
pub fn $t(&mut self, range: impl RangeBounds<$t>) -> $t {
|
||||
let panic_empty_range = || {
|
||||
panic!(
|
||||
"empty range: {:?}..{:?}",
|
||||
|
@ -245,13 +244,13 @@ macro_rules! rng_integer {
|
|||
};
|
||||
|
||||
let low = match range.start_bound() {
|
||||
Bound::Unbounded => std::$t::MIN,
|
||||
Bound::Unbounded => core::$t::MIN,
|
||||
Bound::Included(&x) => x,
|
||||
Bound::Excluded(&x) => x.checked_add(1).unwrap_or_else(panic_empty_range),
|
||||
};
|
||||
|
||||
let high = match range.end_bound() {
|
||||
Bound::Unbounded => std::$t::MAX,
|
||||
Bound::Unbounded => core::$t::MAX,
|
||||
Bound::Included(&x) => x,
|
||||
Bound::Excluded(&x) => x.checked_sub(1).unwrap_or_else(panic_empty_range),
|
||||
};
|
||||
|
@ -260,7 +259,7 @@ macro_rules! rng_integer {
|
|||
panic_empty_range();
|
||||
}
|
||||
|
||||
if low == std::$t::MIN && high == std::$t::MAX {
|
||||
if low == core::$t::MIN && high == core::$t::MAX {
|
||||
self.$gen() as $t
|
||||
} else {
|
||||
let len = high.wrapping_sub(low).wrapping_add(1);
|
||||
|
@ -271,46 +270,59 @@ macro_rules! rng_integer {
|
|||
}
|
||||
|
||||
impl Rng {
|
||||
/// Creates a new random number generator.
|
||||
#[inline]
|
||||
pub fn new() -> Rng {
|
||||
Rng::with_seed(
|
||||
RNG.try_with(|rng| rng.u64(..))
|
||||
.unwrap_or(0x4d595df4d0f33173),
|
||||
)
|
||||
}
|
||||
|
||||
/// Creates a new random number generator with the initial seed.
|
||||
#[inline]
|
||||
#[must_use = "this creates a new instance of `Rng`; if you want to initialize the thread-local generator, use `fastrand::seed()` instead"]
|
||||
pub fn with_seed(seed: u64) -> Self {
|
||||
let rng = Rng(Cell::new(0));
|
||||
let mut rng = Rng(0);
|
||||
|
||||
rng.seed(seed);
|
||||
rng
|
||||
}
|
||||
|
||||
/// Clones the generator by deterministically deriving a new generator based on the initial
|
||||
/// seed.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// // Seed two generators equally, and clone both of them.
|
||||
/// let mut base1 = fastrand::Rng::new();
|
||||
/// base1.seed(0x4d595df4d0f33173);
|
||||
/// base1.bool(); // Use the generator once.
|
||||
///
|
||||
/// let mut base2 = fastrand::Rng::new();
|
||||
/// base2.seed(0x4d595df4d0f33173);
|
||||
/// base2.bool(); // Use the generator once.
|
||||
///
|
||||
/// let mut rng1 = base1.clone();
|
||||
/// let mut rng2 = base2.clone();
|
||||
///
|
||||
/// assert_eq!(rng1.u64(..), rng2.u64(..), "the cloned generators are identical");
|
||||
/// ```
|
||||
#[inline]
|
||||
#[must_use = "this creates a new instance of `Rng`"]
|
||||
pub fn fork(&mut self) -> Self {
|
||||
Rng::with_seed(self.gen_u64())
|
||||
}
|
||||
|
||||
/// Generates a random `char` in ranges a-z and A-Z.
|
||||
#[inline]
|
||||
pub fn alphabetic(&self) -> char {
|
||||
pub fn alphabetic(&mut self) -> char {
|
||||
const CHARS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
||||
let len = CHARS.len() as u8;
|
||||
let i = self.u8(..len);
|
||||
CHARS[i as usize] as char
|
||||
*self.choice(CHARS).unwrap() as char
|
||||
}
|
||||
|
||||
/// Generates a random `char` in ranges a-z, A-Z and 0-9.
|
||||
#[inline]
|
||||
pub fn alphanumeric(&self) -> char {
|
||||
pub fn alphanumeric(&mut self) -> char {
|
||||
const CHARS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
let len = CHARS.len() as u8;
|
||||
let i = self.u8(..len);
|
||||
CHARS[i as usize] as char
|
||||
*self.choice(CHARS).unwrap() as char
|
||||
}
|
||||
|
||||
/// Generates a random `bool`.
|
||||
#[inline]
|
||||
pub fn bool(&self) -> bool {
|
||||
pub fn bool(&mut self) -> bool {
|
||||
self.u8(..) % 2 == 0
|
||||
}
|
||||
|
||||
|
@ -320,7 +332,7 @@ impl Rng {
|
|||
///
|
||||
/// Panics if the base is zero or greater than 36.
|
||||
#[inline]
|
||||
pub fn digit(&self, base: u32) -> char {
|
||||
pub fn digit(&mut self, base: u32) -> char {
|
||||
if base == 0 {
|
||||
panic!("base cannot be zero");
|
||||
}
|
||||
|
@ -336,19 +348,57 @@ impl Rng {
|
|||
}
|
||||
|
||||
/// Generates a random `f32` in range `0..1`.
|
||||
pub fn f32(&self) -> f32 {
|
||||
pub fn f32(&mut self) -> f32 {
|
||||
let b = 32;
|
||||
let f = std::f32::MANTISSA_DIGITS - 1;
|
||||
let f = core::f32::MANTISSA_DIGITS - 1;
|
||||
f32::from_bits((1 << (b - 2)) - (1 << f) + (self.u32(..) >> (b - f))) - 1.0
|
||||
}
|
||||
|
||||
/// Generates a random `f64` in range `0..1`.
|
||||
pub fn f64(&self) -> f64 {
|
||||
pub fn f64(&mut self) -> f64 {
|
||||
let b = 64;
|
||||
let f = std::f64::MANTISSA_DIGITS - 1;
|
||||
let f = core::f64::MANTISSA_DIGITS - 1;
|
||||
f64::from_bits((1 << (b - 2)) - (1 << f) + (self.u64(..) >> (b - f))) - 1.0
|
||||
}
|
||||
|
||||
/// Collects `amount` values at random from the iterator into a vector.
|
||||
///
|
||||
/// The length of the returned vector equals `amount` unless the iterator
|
||||
/// contains insufficient elements, in which case it equals the number of
|
||||
/// elements available.
|
||||
///
|
||||
/// Complexity is `O(n)` where `n` is the length of the iterator.
|
||||
#[cfg(feature = "alloc")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
|
||||
pub fn choose_multiple<T: Iterator>(&mut self, mut source: T, amount: usize) -> Vec<T::Item> {
|
||||
// Adapted from: https://docs.rs/rand/latest/rand/seq/trait.IteratorRandom.html#method.choose_multiple
|
||||
let mut reservoir = Vec::with_capacity(amount);
|
||||
|
||||
reservoir.extend(source.by_ref().take(amount));
|
||||
|
||||
// Continue unless the iterator was exhausted
|
||||
//
|
||||
// note: this prevents iterators that "restart" from causing problems.
|
||||
// If the iterator stops once, then so do we.
|
||||
if reservoir.len() == amount {
|
||||
for (i, elem) in source.enumerate() {
|
||||
let end = i + 1 + amount;
|
||||
let k = self.usize(0..end);
|
||||
if let Some(slot) = reservoir.get_mut(k) {
|
||||
*slot = elem;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If less than one third of the `Vec` was used, reallocate
|
||||
// so that the unused space is not wasted. There is a corner
|
||||
// case where `amount` was much less than `self.len()`.
|
||||
if reservoir.capacity() > 3 * reservoir.len() {
|
||||
reservoir.shrink_to_fit();
|
||||
}
|
||||
}
|
||||
reservoir
|
||||
}
|
||||
|
||||
rng_integer!(
|
||||
i8,
|
||||
u8,
|
||||
|
@ -416,28 +466,49 @@ impl Rng {
|
|||
|
||||
/// Generates a random `char` in range a-z.
|
||||
#[inline]
|
||||
pub fn lowercase(&self) -> char {
|
||||
pub fn lowercase(&mut self) -> char {
|
||||
const CHARS: &[u8] = b"abcdefghijklmnopqrstuvwxyz";
|
||||
let len = CHARS.len() as u8;
|
||||
let i = self.u8(..len);
|
||||
CHARS[i as usize] as char
|
||||
*self.choice(CHARS).unwrap() as char
|
||||
}
|
||||
|
||||
/// Initializes this generator with the given seed.
|
||||
#[inline]
|
||||
pub fn seed(&self, seed: u64) {
|
||||
self.0.set(seed);
|
||||
pub fn seed(&mut self, seed: u64) {
|
||||
self.0 = seed;
|
||||
}
|
||||
|
||||
/// Gives back **current** seed that is being held by this generator.
|
||||
#[inline]
|
||||
pub fn get_seed(&self) -> u64 {
|
||||
self.0.get()
|
||||
self.0
|
||||
}
|
||||
|
||||
/// Choose an item from an iterator at random.
|
||||
///
|
||||
/// This function may have an unexpected result if the `len()` property of the
|
||||
/// iterator does not match the actual number of items in the iterator. If
|
||||
/// the iterator is empty, this returns `None`.
|
||||
#[inline]
|
||||
pub fn choice<I>(&mut self, iter: I) -> Option<I::Item>
|
||||
where
|
||||
I: IntoIterator,
|
||||
I::IntoIter: ExactSizeIterator,
|
||||
{
|
||||
let mut iter = iter.into_iter();
|
||||
|
||||
// Get the item at a random index.
|
||||
let len = iter.len();
|
||||
if len == 0 {
|
||||
return None;
|
||||
}
|
||||
let index = self.usize(0..len);
|
||||
|
||||
iter.nth(index)
|
||||
}
|
||||
|
||||
/// Shuffles a slice randomly.
|
||||
#[inline]
|
||||
pub fn shuffle<T>(&self, slice: &mut [T]) {
|
||||
pub fn shuffle<T>(&mut self, slice: &mut [T]) {
|
||||
for i in 1..slice.len() {
|
||||
slice.swap(i, self.usize(..=i));
|
||||
}
|
||||
|
@ -445,7 +516,7 @@ impl Rng {
|
|||
|
||||
/// Fill a byte slice with random data.
|
||||
#[inline]
|
||||
pub fn fill(&self, slice: &mut [u8]) {
|
||||
pub fn fill(&mut self, slice: &mut [u8]) {
|
||||
// We fill the slice by chunks of 8 bytes, or one block of
|
||||
// WyRand output per new state.
|
||||
let mut chunks = slice.chunks_exact_mut(core::mem::size_of::<u64>());
|
||||
|
@ -542,20 +613,16 @@ impl Rng {
|
|||
|
||||
/// Generates a random `char` in range A-Z.
|
||||
#[inline]
|
||||
pub fn uppercase(&self) -> char {
|
||||
pub fn uppercase(&mut self) -> char {
|
||||
const CHARS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
let len = CHARS.len() as u8;
|
||||
let i = self.u8(..len);
|
||||
CHARS[i as usize] as char
|
||||
*self.choice(CHARS).unwrap() as char
|
||||
}
|
||||
|
||||
/// Generates a random `char` in the given range.
|
||||
///
|
||||
/// Panics if the range is empty.
|
||||
#[inline]
|
||||
pub fn char(&self, range: impl RangeBounds<char>) -> char {
|
||||
use std::convert::TryFrom;
|
||||
|
||||
pub fn char(&mut self, range: impl RangeBounds<char>) -> char {
|
||||
let panic_empty_range = || {
|
||||
panic!(
|
||||
"empty range: {:?}..{:?}",
|
||||
|
@ -581,7 +648,7 @@ impl Rng {
|
|||
};
|
||||
|
||||
let high = match range.end_bound() {
|
||||
Bound::Unbounded => std::char::MAX,
|
||||
Bound::Unbounded => core::char::MAX,
|
||||
Bound::Included(&x) => x,
|
||||
Bound::Excluded(&x) => {
|
||||
let scalar = if x as u32 == surrogate_start + surrogate_len {
|
||||
|
@ -610,97 +677,3 @@ impl Rng {
|
|||
val.try_into().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
/// Initializes the thread-local generator with the given seed.
|
||||
#[inline]
|
||||
pub fn seed(seed: u64) {
|
||||
RNG.with(|rng| rng.seed(seed))
|
||||
}
|
||||
|
||||
/// Gives back **current** seed that is being held by the thread-local generator.
|
||||
#[inline]
|
||||
pub fn get_seed() -> u64 {
|
||||
RNG.with(|rng| rng.get_seed())
|
||||
}
|
||||
|
||||
/// Generates a random `bool`.
|
||||
#[inline]
|
||||
pub fn bool() -> bool {
|
||||
RNG.with(|rng| rng.bool())
|
||||
}
|
||||
|
||||
/// Generates a random `char` in ranges a-z and A-Z.
|
||||
#[inline]
|
||||
pub fn alphabetic() -> char {
|
||||
RNG.with(|rng| rng.alphabetic())
|
||||
}
|
||||
|
||||
/// Generates a random `char` in ranges a-z, A-Z and 0-9.
|
||||
#[inline]
|
||||
pub fn alphanumeric() -> char {
|
||||
RNG.with(|rng| rng.alphanumeric())
|
||||
}
|
||||
|
||||
/// Generates a random `char` in range a-z.
|
||||
#[inline]
|
||||
pub fn lowercase() -> char {
|
||||
RNG.with(|rng| rng.lowercase())
|
||||
}
|
||||
|
||||
/// Generates a random `char` in range A-Z.
|
||||
#[inline]
|
||||
pub fn uppercase() -> char {
|
||||
RNG.with(|rng| rng.uppercase())
|
||||
}
|
||||
|
||||
/// Generates a random digit in the given `base`.
|
||||
///
|
||||
/// Digits are represented by `char`s in ranges 0-9 and a-z.
|
||||
///
|
||||
/// Panics if the base is zero or greater than 36.
|
||||
#[inline]
|
||||
pub fn digit(base: u32) -> char {
|
||||
RNG.with(|rng| rng.digit(base))
|
||||
}
|
||||
|
||||
/// Shuffles a slice randomly.
|
||||
#[inline]
|
||||
pub fn shuffle<T>(slice: &mut [T]) {
|
||||
RNG.with(|rng| rng.shuffle(slice))
|
||||
}
|
||||
|
||||
macro_rules! integer {
|
||||
($t:tt, $doc:tt) => {
|
||||
#[doc = $doc]
|
||||
///
|
||||
/// Panics if the range is empty.
|
||||
#[inline]
|
||||
pub fn $t(range: impl RangeBounds<$t>) -> $t {
|
||||
RNG.with(|rng| rng.$t(range))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
integer!(u8, "Generates a random `u8` in the given range.");
|
||||
integer!(i8, "Generates a random `i8` in the given range.");
|
||||
integer!(u16, "Generates a random `u16` in the given range.");
|
||||
integer!(i16, "Generates a random `i16` in the given range.");
|
||||
integer!(u32, "Generates a random `u32` in the given range.");
|
||||
integer!(i32, "Generates a random `i32` in the given range.");
|
||||
integer!(u64, "Generates a random `u64` in the given range.");
|
||||
integer!(i64, "Generates a random `i64` in the given range.");
|
||||
integer!(u128, "Generates a random `u128` in the given range.");
|
||||
integer!(i128, "Generates a random `i128` in the given range.");
|
||||
integer!(usize, "Generates a random `usize` in the given range.");
|
||||
integer!(isize, "Generates a random `isize` in the given range.");
|
||||
integer!(char, "Generates a random `char` in the given range.");
|
||||
|
||||
/// Generates a random `f32` in range `0..1`.
|
||||
pub fn f32() -> f32 {
|
||||
RNG.with(|rng| rng.f32())
|
||||
}
|
||||
|
||||
/// Generates a random `f64` in range `0..1`.
|
||||
pub fn f64() -> f64 {
|
||||
RNG.with(|rng| rng.f64())
|
||||
}
|
||||
|
|
|
@ -77,7 +77,7 @@ fn u128() {
|
|||
|
||||
#[test]
|
||||
fn fill() {
|
||||
let r = fastrand::Rng::new();
|
||||
let mut r = fastrand::Rng::new();
|
||||
let mut a = [0u8; 64];
|
||||
let mut b = [0u8; 64];
|
||||
|
||||
|
@ -89,7 +89,7 @@ fn fill() {
|
|||
|
||||
#[test]
|
||||
fn rng() {
|
||||
let r = fastrand::Rng::new();
|
||||
let mut r = fastrand::Rng::new();
|
||||
|
||||
assert_ne!(r.u64(..), r.u64(..));
|
||||
|
||||
|
@ -102,8 +102,8 @@ fn rng() {
|
|||
|
||||
#[test]
|
||||
fn rng_init() {
|
||||
let a = fastrand::Rng::new();
|
||||
let b = fastrand::Rng::new();
|
||||
let mut a = fastrand::Rng::new();
|
||||
let mut b = fastrand::Rng::new();
|
||||
assert_ne!(a.u64(..), b.u64(..));
|
||||
|
||||
a.seed(7);
|
||||
|
@ -113,8 +113,31 @@ fn rng_init() {
|
|||
|
||||
#[test]
|
||||
fn with_seed() {
|
||||
let a = fastrand::Rng::with_seed(7);
|
||||
let b = fastrand::Rng::new();
|
||||
let mut a = fastrand::Rng::with_seed(7);
|
||||
let mut b = fastrand::Rng::new();
|
||||
b.seed(7);
|
||||
assert_eq!(a.u64(..), b.u64(..));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn choose_multiple() {
|
||||
let mut a = fastrand::Rng::new();
|
||||
let mut elements = (0..20).collect::<Vec<_>>();
|
||||
|
||||
while !elements.is_empty() {
|
||||
let chosen = a.choose_multiple(0..20, 5);
|
||||
for &x in &chosen {
|
||||
elements.retain(|&y| y != x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn choice() {
|
||||
let items = [1, 4, 9, 5, 2, 3, 6, 7, 8, 0];
|
||||
let mut r = fastrand::Rng::new();
|
||||
|
||||
for item in &items {
|
||||
while r.choice(&items).unwrap() != item {}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
{"files":{"AUTHORS":"451a1be16acafff0cb0eaedc49b46063cc6ce087d0c36f6d64d67aa93d20e485","CHANGELOGS.md":"bb587c08dd9b1f04cba934a241a99655f9d7e38bc70e4d45feca95efa09bd799","Cargo.toml":"a64c7ab3a5906d2e7360fb30cc23e5042bd7c22aa640e8e47bb3dad1a3dbf8eb","LICENSE":"2510d4cde8c4e13d8c54b9fe2a9d144daf567ec8d02832f1454e7ca2d58cdda6","README.md":"22b54b9f16ad18144f29e50d1b1f16791c5e8f1afee81265bad9b72f6efb8e62","src/lib.rs":"cc4822f1c4ac7926859045608d91e3f855914468039bffb75bc9b455c0950ab3","src/native.rs":"bd4cd1f888b83a9eaf07f640bef51c4f16138ceb427a685d3ab07152eec53d1c","src/wasm.rs":"c17c7bf8a28fee134444201212d1cf62bff49650f297e42367ec1b7aeaddc7c9","tests/wasm.rs":"1b81ff541bec36bac824a7ec41cd15f68d9919c3c7522290aa003fa4c253e840"},"package":"7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"}
|
|
@ -1,2 +0,0 @@
|
|||
Main developer:
|
||||
* Sébastien Crozet <developer@crozet.re>
|
|
@ -1,7 +0,0 @@
|
|||
# v0.1.12
|
||||
## Added
|
||||
- Add `SystemTime` which works in both native and WASM environments.
|
||||
|
||||
## Modified
|
||||
- The `now` function is always available now: there is no need to enable the `now` feature any more. The `now` feature
|
||||
still exists (but doesn’t do anything) for backwards compatibility.
|
|
@ -1,81 +0,0 @@
|
|||
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
|
||||
#
|
||||
# When uploading crates to the registry Cargo will automatically
|
||||
# "normalize" Cargo.toml files for maximal compatibility
|
||||
# with all versions of Cargo and also rewrite `path` dependencies
|
||||
# to registry (e.g., crates.io) dependencies.
|
||||
#
|
||||
# If you are reading this file be aware that the original Cargo.toml
|
||||
# will likely look very different (and much more reasonable).
|
||||
# See Cargo.toml.orig for the original contents.
|
||||
|
||||
[package]
|
||||
edition = "2018"
|
||||
name = "instant"
|
||||
version = "0.1.12"
|
||||
authors = ["sebcrozet <developer@crozet.re>"]
|
||||
description = "A partial replacement for std::time::Instant that works on WASM too."
|
||||
readme = "README.md"
|
||||
keywords = ["time", "wasm"]
|
||||
license = "BSD-3-Clause"
|
||||
repository = "https://github.com/sebcrozet/instant"
|
||||
[dependencies.cfg-if]
|
||||
version = "1.0"
|
||||
[dev-dependencies.wasm-bindgen-test]
|
||||
version = "0.3"
|
||||
|
||||
[features]
|
||||
inaccurate = []
|
||||
now = []
|
||||
wasm-bindgen = ["js-sys", "wasm-bindgen_rs", "web-sys"]
|
||||
[target.asmjs-unknown-emscripten.dependencies.js-sys]
|
||||
version = "0.3"
|
||||
optional = true
|
||||
|
||||
[target.asmjs-unknown-emscripten.dependencies.stdweb]
|
||||
version = "0.4"
|
||||
optional = true
|
||||
|
||||
[target.asmjs-unknown-emscripten.dependencies.wasm-bindgen_rs]
|
||||
version = "0.2"
|
||||
optional = true
|
||||
package = "wasm-bindgen"
|
||||
|
||||
[target.asmjs-unknown-emscripten.dependencies.web-sys]
|
||||
version = "0.3"
|
||||
features = ["Window", "Performance", "PerformanceTiming"]
|
||||
optional = true
|
||||
[target.wasm32-unknown-emscripten.dependencies.js-sys]
|
||||
version = "0.3"
|
||||
optional = true
|
||||
|
||||
[target.wasm32-unknown-emscripten.dependencies.stdweb]
|
||||
version = "0.4"
|
||||
optional = true
|
||||
|
||||
[target.wasm32-unknown-emscripten.dependencies.wasm-bindgen_rs]
|
||||
version = "0.2"
|
||||
optional = true
|
||||
package = "wasm-bindgen"
|
||||
|
||||
[target.wasm32-unknown-emscripten.dependencies.web-sys]
|
||||
version = "0.3"
|
||||
features = ["Window", "Performance", "PerformanceTiming"]
|
||||
optional = true
|
||||
[target.wasm32-unknown-unknown.dependencies.js-sys]
|
||||
version = "0.3"
|
||||
optional = true
|
||||
|
||||
[target.wasm32-unknown-unknown.dependencies.stdweb]
|
||||
version = "0.4"
|
||||
optional = true
|
||||
|
||||
[target.wasm32-unknown-unknown.dependencies.wasm-bindgen_rs]
|
||||
version = "0.2"
|
||||
optional = true
|
||||
package = "wasm-bindgen"
|
||||
|
||||
[target.wasm32-unknown-unknown.dependencies.web-sys]
|
||||
version = "0.3"
|
||||
features = ["Window", "Performance", "PerformanceTiming"]
|
||||
optional = true
|
|
@ -1,27 +0,0 @@
|
|||
Copyright (c) 2019, Sébastien Crozet
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the author nor the names of its contributors may be used
|
||||
to endorse or promote products derived from this software without specific
|
||||
prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -1,143 +0,0 @@
|
|||
# Instant
|
||||
|
||||
If you call `std::time::Instant::now()` on a WASM platform, it will panic. This crate provides a partial
|
||||
replacement for `std::time::Instant` that works on WASM too. This defines the type `instant::Instant` which is:
|
||||
|
||||
* A struct emulating the behavior of **std::time::Instant** if you are targeting `wasm32-unknown-unknown` or `wasm32-unknown-asmjs`
|
||||
**and** you enabled either the `stdweb` or the `wasm-bindgen` feature. This emulation is based on the javascript `performance.now()` function.
|
||||
* A type alias for `std::time::Instant` otherwise.
|
||||
|
||||
|
||||
|
||||
Note that even if the **stdweb** or **wasm-bindgen** feature is enabled, this crate will continue to rely on `std::time::Instant`
|
||||
as long as you are not targeting wasm32. This allows for portable code that will work on both native and WASM platforms.
|
||||
|
||||
This crate also exports the function `instant::now()` which returns a representation of the current time as an `f64`, expressed in milliseconds, in a platform-agnostic way. `instant::now()` will either:
|
||||
|
||||
* Call `performance.now()` when compiling for a WASM platform with the features **stdweb** or **wasm-bindgen** enabled, or using a custom javascript function.
|
||||
* Return the time elapsed since the *Unix Epoch* on *native*, *non-WASM* platforms.
|
||||
|
||||
*Note*: The old feature, `now`, has been deprecated. `instant::now()` is always exported and the `now` feature flag no longer has any effect. It remains listed in `Cargo.toml` to avoid introducing breaking changes and may be removed in future versions.
|
||||
|
||||
## Examples
|
||||
### Using `instant` for a native platform.
|
||||
_Cargo.toml_:
|
||||
```toml
|
||||
[dependencies]
|
||||
instant = "0.1"
|
||||
```
|
||||
|
||||
_main.rs_:
|
||||
```rust
|
||||
fn main() {
|
||||
// Will be the same as `std::time::Instant`.
|
||||
let now = instant::Instant::now();
|
||||
}
|
||||
```
|
||||
|
||||
-----
|
||||
|
||||
### Using `instant` for a WASM platform.
|
||||
This example shows the use of the `stdweb` feature. It would be similar with `wasm-bindgen`.
|
||||
|
||||
_Cargo.toml_:
|
||||
```toml
|
||||
[dependencies]
|
||||
instant = { version = "0.1", features = [ "stdweb" ] }
|
||||
```
|
||||
|
||||
_main.rs_:
|
||||
```rust
|
||||
fn main() {
|
||||
// Will emulate `std::time::Instant` based on `performance.now()`.
|
||||
let now = instant::Instant::now();
|
||||
}
|
||||
```
|
||||
|
||||
-----
|
||||
|
||||
### Using `instant` for a WASM platform where `performance.now()` is not available.
|
||||
This example shows the use of the `inaccurate` feature.
|
||||
|
||||
_Cargo.toml_:
|
||||
```toml
|
||||
[dependencies]
|
||||
instant = { version = "0.1", features = [ "wasm-bindgen", "inaccurate" ] }
|
||||
```
|
||||
|
||||
_main.rs_:
|
||||
```rust
|
||||
fn main() {
|
||||
// Will emulate `std::time::Instant` based on `Date.now()`.
|
||||
let now = instant::Instant::now();
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
-----
|
||||
|
||||
### Using `instant` for any platform enabling a feature transitively.
|
||||
_Cargo.toml_:
|
||||
```toml
|
||||
[features]
|
||||
stdweb = [ "instant/stdweb" ]
|
||||
wasm-bindgen = [ "instant/wasm-bindgen" ]
|
||||
|
||||
[dependencies]
|
||||
instant = "0.1"
|
||||
```
|
||||
|
||||
_lib.rs_:
|
||||
```rust
|
||||
fn my_function() {
|
||||
// Will select the proper implementation depending on the
|
||||
// feature selected by the user.
|
||||
let now = instant::Instant::now();
|
||||
}
|
||||
```
|
||||
|
||||
-----
|
||||
|
||||
### Using `instant::now()`
|
||||
_Cargo.toml_:
|
||||
```toml
|
||||
[features]
|
||||
stdweb = [ "instant/stdweb" ]
|
||||
wasm-bindgen = [ "instant/wasm-bindgen" ]
|
||||
|
||||
[dependencies]
|
||||
instant = "0.1"
|
||||
```
|
||||
|
||||
_lib.rs_:
|
||||
```rust
|
||||
fn my_function() {
|
||||
// Will select the proper implementation depending on the
|
||||
// feature selected by the user.
|
||||
let now_instant = instant::Instant::now();
|
||||
let now_milliseconds = instant::now(); // In milliseconds.
|
||||
}
|
||||
```
|
||||
|
||||
### Using the feature `now` without `stdweb` or `wasm-bindgen`.
|
||||
_Cargo.toml_:
|
||||
```toml
|
||||
[dependencies]
|
||||
instant = "0.1"
|
||||
```
|
||||
|
||||
_lib.rs_:
|
||||
```rust
|
||||
fn my_function() {
|
||||
// Will use the 'now' javascript implementation.
|
||||
let now_instant = instant::Instant::now();
|
||||
let now_milliseconds = instant::now(); // In milliseconds.
|
||||
}
|
||||
```
|
||||
|
||||
_javascript WASM bindings file_:
|
||||
```js
|
||||
function now() {
|
||||
return Date.now() / 1000.0;
|
||||
}
|
||||
```
|
|
@ -1,22 +0,0 @@
|
|||
cfg_if::cfg_if! {
|
||||
if #[cfg(any(
|
||||
all(target_arch = "wasm32", not(target_os = "wasi")),
|
||||
target_arch = "asmjs"
|
||||
))] {
|
||||
#[cfg(all(feature = "stdweb", not(feature = "wasm-bindgen")))]
|
||||
#[macro_use]
|
||||
extern crate stdweb;
|
||||
|
||||
mod wasm;
|
||||
pub use wasm::Instant;
|
||||
pub use crate::wasm::now;
|
||||
pub use wasm::SystemTime;
|
||||
} else {
|
||||
mod native;
|
||||
pub use native::Instant;
|
||||
pub use native::now;
|
||||
pub use native::SystemTime;
|
||||
}
|
||||
}
|
||||
|
||||
pub use std::time::Duration;
|
|
@ -1,9 +0,0 @@
|
|||
pub type Instant = std::time::Instant;
|
||||
pub type SystemTime = std::time::SystemTime;
|
||||
|
||||
/// The current time, expressed in milliseconds since the Unix Epoch.
|
||||
pub fn now() -> f64 {
|
||||
std::time::SystemTime::now().duration_since(std::time::SystemTime::UNIX_EPOCH)
|
||||
.expect("System clock was before 1970.")
|
||||
.as_secs_f64() * 1000.0
|
||||
}
|
|
@ -1,240 +0,0 @@
|
|||
use std::cmp::Ordering;
|
||||
use std::ops::{Add, AddAssign, Sub, SubAssign};
|
||||
use std::time::Duration;
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Hash)]
|
||||
pub struct Instant(Duration);
|
||||
|
||||
impl Ord for Instant {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
self.partial_cmp(other)
|
||||
.expect("an instant should never be NaN or Inf.")
|
||||
}
|
||||
}
|
||||
impl Eq for Instant {}
|
||||
|
||||
impl Instant {
|
||||
#[inline]
|
||||
pub fn now() -> Self {
|
||||
Instant(duration_from_f64(now()))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn duration_since(&self, earlier: Instant) -> Duration {
|
||||
assert!(
|
||||
earlier.0 <= self.0,
|
||||
"`earlier` cannot be later than `self`."
|
||||
);
|
||||
self.0 - earlier.0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn elapsed(&self) -> Duration {
|
||||
Self::now().duration_since(*self)
|
||||
}
|
||||
|
||||
/// Returns `Some(t)` where `t` is the time `self + duration` if `t` can be represented as
|
||||
/// `Instant` (which means it's inside the bounds of the underlying data structure), `None`
|
||||
/// otherwise.
|
||||
#[inline]
|
||||
pub fn checked_add(&self, duration: Duration) -> Option<Instant> {
|
||||
self.0.checked_add(duration).map(Instant)
|
||||
}
|
||||
|
||||
/// Returns `Some(t)` where `t` is the time `self - duration` if `t` can be represented as
|
||||
/// `Instant` (which means it's inside the bounds of the underlying data structure), `None`
|
||||
/// otherwise.
|
||||
#[inline]
|
||||
pub fn checked_sub(&self, duration: Duration) -> Option<Instant> {
|
||||
self.0.checked_sub(duration).map(Instant)
|
||||
}
|
||||
|
||||
/// Returns the amount of time elapsed from another instant to this one, or None if that
|
||||
/// instant is later than this one.
|
||||
#[inline]
|
||||
pub fn checked_duration_since(&self, earlier: Instant) -> Option<Duration> {
|
||||
if earlier.0 > self.0 {
|
||||
None
|
||||
} else {
|
||||
Some(self.0 - earlier.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the amount of time elapsed from another instant to this one, or zero duration if
|
||||
/// that instant is later than this one.
|
||||
#[inline]
|
||||
pub fn saturating_duration_since(&self, earlier: Instant) -> Duration {
|
||||
self.checked_duration_since(earlier).unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<Duration> for Instant {
|
||||
type Output = Self;
|
||||
|
||||
#[inline]
|
||||
fn add(self, rhs: Duration) -> Self {
|
||||
Instant(self.0 + rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl AddAssign<Duration> for Instant {
|
||||
#[inline]
|
||||
fn add_assign(&mut self, rhs: Duration) {
|
||||
self.0 += rhs
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub<Duration> for Instant {
|
||||
type Output = Self;
|
||||
|
||||
#[inline]
|
||||
fn sub(self, rhs: Duration) -> Self {
|
||||
Instant(self.0 - rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub<Instant> for Instant {
|
||||
type Output = Duration;
|
||||
|
||||
#[inline]
|
||||
fn sub(self, rhs: Instant) -> Duration {
|
||||
self.duration_since(rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl SubAssign<Duration> for Instant {
|
||||
#[inline]
|
||||
fn sub_assign(&mut self, rhs: Duration) {
|
||||
self.0 -= rhs
|
||||
}
|
||||
}
|
||||
|
||||
fn duration_from_f64(millis: f64) -> Duration {
|
||||
Duration::from_millis(millis.trunc() as u64)
|
||||
+ Duration::from_nanos((millis.fract() * 1.0e6) as u64)
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "stdweb", not(feature = "wasm-bindgen")))]
|
||||
#[allow(unused_results)] // Needed because the js macro triggers it.
|
||||
pub fn now() -> f64 {
|
||||
use stdweb::unstable::TryInto;
|
||||
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Performance/now
|
||||
#[cfg(not(feature = "inaccurate"))]
|
||||
let v = js! { return performance.now(); };
|
||||
#[cfg(feature = "inaccurate")]
|
||||
let v = js! { return Date.now(); };
|
||||
v.try_into().unwrap()
|
||||
}
|
||||
|
||||
#[cfg(feature = "wasm-bindgen")]
|
||||
pub fn now() -> f64 {
|
||||
#[cfg(not(feature = "inaccurate"))]
|
||||
let now = {
|
||||
use wasm_bindgen_rs::prelude::*;
|
||||
use wasm_bindgen_rs::JsCast;
|
||||
js_sys::Reflect::get(&js_sys::global(), &JsValue::from_str("performance"))
|
||||
.expect("failed to get performance from global object")
|
||||
.unchecked_into::<web_sys::Performance>()
|
||||
.now()
|
||||
};
|
||||
#[cfg(feature = "inaccurate")]
|
||||
let now = js_sys::Date::now();
|
||||
now
|
||||
}
|
||||
|
||||
// The JS now function is in a module so it won't have to be renamed
|
||||
#[cfg(not(any(feature = "wasm-bindgen", feature = "stdweb")))]
|
||||
mod js {
|
||||
extern "C" {
|
||||
#[cfg(not(target_os = "emscripten"))]
|
||||
pub fn now() -> f64;
|
||||
#[cfg(target_os = "emscripten")]
|
||||
pub fn _emscripten_get_now() -> f64;
|
||||
}
|
||||
}
|
||||
// Make the unsafe extern function "safe" so it can be called like the other 'now' functions
|
||||
#[cfg(not(any(feature = "wasm-bindgen", feature = "stdweb")))]
|
||||
pub fn now() -> f64 {
|
||||
#[cfg(not(target_os = "emscripten"))]
|
||||
return unsafe { js::now() };
|
||||
#[cfg(target_os = "emscripten")]
|
||||
return unsafe { js::_emscripten_get_now() };
|
||||
}
|
||||
|
||||
/// Returns the number of millisecods elapsed since January 1, 1970 00:00:00 UTC.
|
||||
#[cfg(any(feature = "wasm-bindgen", feature = "stdweb"))]
|
||||
fn get_time() -> f64 {
|
||||
#[cfg(feature = "wasm-bindgen")]
|
||||
return js_sys::Date::now();
|
||||
#[cfg(all(feature = "stdweb", not(feature = "wasm-bindgen")))]
|
||||
{
|
||||
let v = js! { return Date.now(); };
|
||||
return v.try_into().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
|
||||
pub struct SystemTime(f64);
|
||||
|
||||
impl SystemTime {
|
||||
pub const UNIX_EPOCH: SystemTime = SystemTime(0.0);
|
||||
|
||||
pub fn now() -> SystemTime {
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(any(feature = "wasm-bindgen", feature = "stdweb"))] {
|
||||
SystemTime(get_time())
|
||||
} else {
|
||||
SystemTime(now())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn duration_since(&self, earlier: SystemTime) -> Result<Duration, ()> {
|
||||
let dur_ms = self.0 - earlier.0;
|
||||
if dur_ms < 0.0 {
|
||||
return Err(());
|
||||
}
|
||||
Ok(Duration::from_millis(dur_ms as u64))
|
||||
}
|
||||
|
||||
pub fn elapsed(&self) -> Result<Duration, ()> {
|
||||
self.duration_since(SystemTime::now())
|
||||
}
|
||||
|
||||
pub fn checked_add(&self, duration: Duration) -> Option<SystemTime> {
|
||||
Some(*self + duration)
|
||||
}
|
||||
|
||||
pub fn checked_sub(&self, duration: Duration) -> Option<SystemTime> {
|
||||
Some(*self - duration)
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<Duration> for SystemTime {
|
||||
type Output = SystemTime;
|
||||
|
||||
fn add(self, other: Duration) -> SystemTime {
|
||||
SystemTime(self.0 + other.as_millis() as f64)
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub<Duration> for SystemTime {
|
||||
type Output = SystemTime;
|
||||
|
||||
fn sub(self, other: Duration) -> SystemTime {
|
||||
SystemTime(self.0 - other.as_millis() as f64)
|
||||
}
|
||||
}
|
||||
|
||||
impl AddAssign<Duration> for SystemTime {
|
||||
fn add_assign(&mut self, rhs: Duration) {
|
||||
*self = *self + rhs;
|
||||
}
|
||||
}
|
||||
|
||||
impl SubAssign<Duration> for SystemTime {
|
||||
fn sub_assign(&mut self, rhs: Duration) {
|
||||
*self = *self - rhs;
|
||||
}
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
extern crate wasm_bindgen_test;
|
||||
|
||||
use instant::{Instant, SystemTime};
|
||||
use std::time::Duration;
|
||||
use wasm_bindgen_test::*;
|
||||
|
||||
wasm_bindgen_test_configure!(run_in_browser);
|
||||
// run these tests using: wasm-pack test --chrome --headless -- --features wasm-bindgen
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn test_instant_now() {
|
||||
let now = Instant::now();
|
||||
#[cfg(feature = "inaccurate")]
|
||||
while now.elapsed().as_millis() == 0 {}
|
||||
#[cfg(not(feature = "inaccurate"))]
|
||||
assert!(now.elapsed().as_nanos() > 0);
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn test_duration() {
|
||||
let now = Instant::now();
|
||||
let one_sec = Duration::from_secs(1);
|
||||
assert!(now.elapsed() < one_sec);
|
||||
}
|
||||
|
||||
// Duration::new will overflow when you have u64::MAX seconds and one billion nanoseconds.
|
||||
// <https://doc.rust-lang.org/std/time/struct.Duration.html#method.new>
|
||||
const ONE_BILLION: u32 = 1_000_000_000;
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn test_checked_add() {
|
||||
let now = Instant::now();
|
||||
|
||||
assert!(now.checked_add(Duration::from_millis(1)).is_some());
|
||||
assert_eq!(
|
||||
None,
|
||||
now.checked_add(Duration::new(u64::MAX, ONE_BILLION - 1))
|
||||
);
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn test_checked_sub() {
|
||||
let now = Instant::now();
|
||||
|
||||
assert!(now.checked_sub(Duration::from_millis(1)).is_some());
|
||||
assert!(now
|
||||
.checked_sub(Duration::new(u64::MAX, ONE_BILLION - 1))
|
||||
.is_none());
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn test_system_time() {
|
||||
assert!(SystemTime::UNIX_EPOCH
|
||||
.duration_since(SystemTime::now())
|
||||
.is_err());
|
||||
}
|
||||
|
|
@ -1 +1 @@
|
|||
{"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"}
|
||||
{"files":{"CHANGELOG.md":"14cb935001b72d1da431865d0e618b58ca962cc17c8be6bcd9cf4e1699f58b7d","Cargo.toml":"285d4565218bd4a8e5d36cb9b12ece4b8bb9c91d3357e75708acb0ca2f414b49","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"8b427f5bc501764575e52ba4f9d95673cf8f6d80a86d0d06599852e1a9a20a36","README.md":"972f1c35ec653943e067fd2c3d09e78f593b2e9e1eafd5b9668bf3653513de3e","src/dir.rs":"3b515f42feb934ba83ba56d506116e5e932c6b863b764fd61d26994eff28700a","src/error.rs":"cc7d8eace0fff11cb342158d2885d5637bfb14b24ef30755e808554772039c5f","src/file/imp/mod.rs":"f6da9fcd93f11889670a251fdd8231b5f4614e5a971b7b183f52b44af68568d5","src/file/imp/other.rs":"501cd1b444a5821127ea831fc8018706148f2d9f47c478f502b069963a42a2c7","src/file/imp/unix.rs":"0fa63a8b831947fdc7307e889d129adef6f47b19965b963a5e25d70cb3106e62","src/file/imp/windows.rs":"fa4211087c36290064de9a41b5e533e4e8c24a10fb8f8908835a67e00555c06e","src/file/mod.rs":"f417e0e8637116e50de201581b1dfe8feb8dee30f71c5bb9dbcd95603094cb49","src/lib.rs":"6303e7470c680ad785f32eb717de2e512b88c2c5da0e1684e3704471fabd7398","src/spooled.rs":"de848218bb7c0733d9c46e337564137673c95f5a6cf9f6bb28baf218b2503247","src/util.rs":"63737b9180cb769c1fcac56f1fa928221ae41a8917872d3e878d0a915e877710","tests/namedtempfile.rs":"87dd6a8bba2fdd77418ec2b50b8aec5e26d05a2f780182b4e9ff464b3404d47c","tests/spooled.rs":"a97e96404dc5136421ac027b965070c0d5b44c93d06d456e12dc85f81755d064","tests/tempdir.rs":"f5a86f56df6bb60aa5dfa136ce75f8d0f29c2e87546dccfe1fb680d209be525e","tests/tempfile.rs":"9a2f8142151a6aa2fd047aa3749f9982ece4b080a3ace0d3c58d6bdb3f883c81"},"package":"cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef"}
|
|
@ -0,0 +1,256 @@
|
|||
# Changelog
|
||||
|
||||
## 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`.
|
|
@ -11,33 +11,49 @@
|
|||
|
||||
[package]
|
||||
edition = "2018"
|
||||
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.8.0"
|
||||
authors = [
|
||||
"Steven Allen <steven@stebalien.com>",
|
||||
"The Rust Project Developers",
|
||||
"Ashley Mannix <ashleymannix@live.com.au>",
|
||||
"Jason White <me@jasonwhite.io>",
|
||||
]
|
||||
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"
|
||||
|
||||
[dependencies.cfg-if]
|
||||
version = "1"
|
||||
|
||||
[dependencies.fastrand]
|
||||
version = "1.6.0"
|
||||
version = "2.0.0"
|
||||
|
||||
[dependencies.remove_dir_all]
|
||||
version = "0.5"
|
||||
[dev-dependencies.doc-comment]
|
||||
version = "0.3"
|
||||
|
||||
[features]
|
||||
nightly = []
|
||||
[target."cfg(any(unix, target_os = \"wasi\"))".dependencies.libc]
|
||||
version = "0.2.27"
|
||||
|
||||
[target."cfg(any(unix, target_os = \"wasi\"))".dependencies.rustix]
|
||||
version = "0.38"
|
||||
features = ["fs"]
|
||||
|
||||
[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(windows)".dependencies.windows-sys]
|
||||
version = "0.48"
|
||||
features = [
|
||||
"Win32_Storage_FileSystem",
|
||||
"Win32_Foundation",
|
||||
]
|
||||
|
|
|
@ -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`.
|
|
@ -14,9 +14,10 @@ patterns and surprisingly difficult to implement securely).
|
|||
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"
|
||||
|
|
|
@ -8,7 +8,8 @@
|
|||
// 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};
|
||||
|
@ -45,16 +46,16 @@ use crate::Builder;
|
|||
/// # }
|
||||
/// # fn run() -> Result<(), io::Error> {
|
||||
/// // Create a directory inside of `std::env::temp_dir()`
|
||||
/// let dir = tempdir()?;
|
||||
/// 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()?;
|
||||
/// drop(tmp_file);
|
||||
/// tmp_dir.close()?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
|
@ -65,9 +66,9 @@ pub fn tempdir() -> io::Result<TempDir> {
|
|||
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,7 +84,7 @@ pub fn tempdir() -> io::Result<TempDir> {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tempfile::tempdir;
|
||||
/// use tempfile::tempdir_in;
|
||||
/// use std::fs::File;
|
||||
/// use std::io::{self, Write};
|
||||
///
|
||||
|
@ -93,17 +94,17 @@ pub fn tempdir() -> io::Result<TempDir> {
|
|||
/// # }
|
||||
/// # }
|
||||
/// # 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()?;
|
||||
/// drop(tmp_file);
|
||||
/// tmp_dir.close()?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
|
@ -264,6 +265,65 @@ impl 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;
|
||||
///
|
||||
/// # use std::io;
|
||||
/// # fn run() -> Result<(), io::Error> {
|
||||
/// // 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(())
|
||||
/// # }
|
||||
/// ```
|
||||
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;
|
||||
///
|
||||
/// # use std::io;
|
||||
/// # fn run() -> Result<(), io::Error> {
|
||||
/// // 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(())
|
||||
/// # }
|
||||
/// ```
|
||||
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
|
||||
|
@ -292,6 +352,7 @@ impl TempDir {
|
|||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[must_use]
|
||||
pub fn path(&self) -> &path::Path {
|
||||
self.path.as_ref()
|
||||
}
|
||||
|
@ -323,6 +384,7 @@ impl TempDir {
|
|||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[must_use]
|
||||
pub fn into_path(self) -> PathBuf {
|
||||
// Prevent the Drop impl from being called.
|
||||
let mut this = mem::ManuallyDrop::new(self);
|
||||
|
|
|
@ -9,7 +9,7 @@ fn not_supported<T>() -> io::Result<T> {
|
|||
))
|
||||
}
|
||||
|
||||
pub fn create_named(_path: &Path, open_options: &mut OpenOptions) -> io::Result<File> {
|
||||
pub fn create_named(_path: &Path, _open_options: &mut OpenOptions) -> io::Result<File> {
|
||||
not_supported()
|
||||
}
|
||||
|
||||
|
@ -25,6 +25,6 @@ pub fn persist(_old_path: &Path, _new_path: &Path, _overwrite: bool) -> io::Resu
|
|||
not_supported()
|
||||
}
|
||||
|
||||
pub fn keep(path: &Path) -> io::Result<()> {
|
||||
pub fn keep(_path: &Path) -> io::Result<()> {
|
||||
not_supported()
|
||||
}
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
use std::env;
|
||||
use std::ffi::{CString, OsStr};
|
||||
use std::ffi::OsStr;
|
||||
use std::fs::{self, File, OpenOptions};
|
||||
use std::io;
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(not(target_os = "wasi"))] {
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
use std::os::unix::fs::{MetadataExt, OpenOptionsExt};
|
||||
} else {
|
||||
use std::os::wasi::ffi::OsStrExt;
|
||||
#[cfg(feature = "nightly")]
|
||||
use std::os::wasi::fs::MetadataExt;
|
||||
}
|
||||
|
@ -16,29 +14,10 @@ use crate::util;
|
|||
use std::path::Path;
|
||||
|
||||
#[cfg(not(target_os = "redox"))]
|
||||
use libc::{c_char, c_int, link, rename, unlink};
|
||||
|
||||
#[cfg(not(target_os = "redox"))]
|
||||
#[inline(always)]
|
||||
pub fn cvt_err(result: c_int) -> io::Result<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"))
|
||||
}
|
||||
use {
|
||||
rustix::fs::{rename, unlink},
|
||||
std::fs::hard_link,
|
||||
};
|
||||
|
||||
pub fn create_named(path: &Path, open_options: &mut OpenOptions) -> io::Result<File> {
|
||||
open_options.read(true).write(true).create_new(true);
|
||||
|
@ -70,16 +49,18 @@ fn create_unlinked(path: &Path) -> io::Result<File> {
|
|||
|
||||
#[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};
|
||||
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,29 +105,41 @@ pub fn reopen(_file: &File, _path: &Path) -> io::Result<File> {
|
|||
|
||||
#[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))
|
||||
}
|
||||
|
|
|
@ -6,13 +6,12 @@ use std::os::windows::io::{AsRawHandle, FromRawHandle, RawHandle};
|
|||
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;
|
||||
|
||||
|
@ -76,9 +75,6 @@ pub fn keep(path: &Path) -> io::Result<()> {
|
|||
}
|
||||
|
||||
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);
|
||||
|
|
|
@ -6,6 +6,12 @@ use std::fs::{self, File, OpenOptions};
|
|||
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::error::IoResultExt;
|
||||
|
@ -52,7 +58,7 @@ mod imp;
|
|||
///
|
||||
/// [`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.
|
||||
|
@ -467,29 +473,31 @@ impl AsRef<OsStr> for TempPath {
|
|||
/// # 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 +505,46 @@ impl AsRef<Path> for NamedTempFile {
|
|||
}
|
||||
|
||||
/// 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.
|
||||
|
@ -595,6 +608,12 @@ impl NamedTempFile {
|
|||
|
||||
/// Create a new named temporary file in the specified directory.
|
||||
///
|
||||
/// This is equivalent to:
|
||||
///
|
||||
/// ```ignore
|
||||
/// Builder::new().prefix(&prefix).tempfile()
|
||||
/// ```
|
||||
///
|
||||
/// See [`NamedTempFile::new()`] for details.
|
||||
///
|
||||
/// [`NamedTempFile::new()`]: #method.new
|
||||
|
@ -602,6 +621,35 @@ impl NamedTempFile {
|
|||
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
|
||||
|
@ -711,7 +759,7 @@ impl NamedTempFile {
|
|||
/// ```
|
||||
///
|
||||
/// [`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),
|
||||
|
@ -764,7 +812,7 @@ impl NamedTempFile {
|
|||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
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),
|
||||
|
@ -808,7 +856,7 @@ impl NamedTempFile {
|
|||
/// ```
|
||||
///
|
||||
/// [`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 +867,49 @@ impl NamedTempFile {
|
|||
}
|
||||
}
|
||||
|
||||
/// 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
|
||||
|
@ -858,54 +949,67 @@ impl NamedTempFile {
|
|||
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 +1017,27 @@ impl Write for NamedTempFile {
|
|||
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 +1045,61 @@ impl<'a> Write for &'a NamedTempFile {
|
|||
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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
//!
|
||||
|
@ -152,6 +155,7 @@
|
|||
//! [`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",
|
||||
|
@ -161,7 +165,7 @@
|
|||
#![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");
|
||||
|
@ -276,6 +280,15 @@ impl<'a, 'b> Builder<'a, 'b> {
|
|||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// Create a temporary directory with a chosen prefix under a chosen folder:
|
||||
///
|
||||
/// ```ignore
|
||||
/// let dir = Builder::new()
|
||||
/// .prefix("my-temporary-dir")
|
||||
/// .tempdir_in("folder-with-tempdirs")?;
|
||||
/// ```
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
@ -419,7 +432,7 @@ impl<'a, 'b> Builder<'a, 'b> {
|
|||
/// [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.
|
||||
|
@ -493,7 +506,7 @@ impl<'a, 'b> Builder<'a, 'b> {
|
|||
///
|
||||
/// [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`.
|
||||
|
@ -534,4 +547,155 @@ impl<'a, 'b> Builder<'a, 'b> {
|
|||
|
||||
util::create_helper(dir, self.prefix, self.suffix, self.random_len, dir::create)
|
||||
}
|
||||
|
||||
/// 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 [`std::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::io;
|
||||
/// # use std::fs::File;
|
||||
/// # fn main() {
|
||||
/// # if let Err(_) = run() {
|
||||
/// # ::std::process::exit(1);
|
||||
/// # }
|
||||
/// # }
|
||||
/// # fn run() -> Result<(), io::Error> {
|
||||
/// # use tempfile::Builder;
|
||||
/// // This is NOT secure!
|
||||
/// let tempfile = Builder::new().make(|path| {
|
||||
/// if path.is_file() {
|
||||
/// return Err(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(())
|
||||
/// # }
|
||||
/// ```
|
||||
/// Note that simply using [`std::fs::File::create`] alone is not correct
|
||||
/// because it does not fail if the file already exists:
|
||||
/// ```
|
||||
/// # use std::io;
|
||||
/// # use std::fs::File;
|
||||
/// # fn main() {
|
||||
/// # if let Err(_) = run() {
|
||||
/// # ::std::process::exit(1);
|
||||
/// # }
|
||||
/// # }
|
||||
/// # fn run() -> Result<(), io::Error> {
|
||||
/// # use tempfile::Builder;
|
||||
/// // This could overwrite an existing file!
|
||||
/// let tempfile = Builder::new().make(|path| File::create(path))?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
/// 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
|
||||
/// ```
|
||||
/// # use std::io;
|
||||
/// # fn main() {
|
||||
/// # if let Err(_) = run() {
|
||||
/// # ::std::process::exit(1);
|
||||
/// # }
|
||||
/// # }
|
||||
/// # fn run() -> Result<(), io::Error> {
|
||||
/// # use tempfile::Builder;
|
||||
/// # #[cfg(unix)]
|
||||
/// use std::os::unix::net::UnixListener;
|
||||
/// # #[cfg(unix)]
|
||||
/// let tempsock = Builder::new().make(|path| UnixListener::bind(path))?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// [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
|
||||
/// ```
|
||||
/// # use std::io;
|
||||
/// # fn main() {
|
||||
/// # if let Err(_) = run() {
|
||||
/// # ::std::process::exit(1);
|
||||
/// # }
|
||||
/// # }
|
||||
/// # fn run() -> Result<(), io::Error> {
|
||||
/// # use tempfile::Builder;
|
||||
/// # #[cfg(unix)]
|
||||
/// use std::os::unix::net::UnixListener;
|
||||
/// # #[cfg(unix)]
|
||||
/// let tempsock = Builder::new().make_in("./", |path| UnixListener::bind(path))?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
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::from_path(path),
|
||||
))
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -64,14 +64,16 @@ pub fn spooled_tempfile(max_size: usize) -> SpooledTempFile {
|
|||
}
|
||||
|
||||
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 +86,7 @@ impl SpooledTempFile {
|
|||
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()))?;
|
||||
}
|
||||
|
@ -97,16 +99,17 @@ impl SpooledTempFile {
|
|||
if size as usize > self.max_size {
|
||||
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 +117,37 @@ impl SpooledTempFile {
|
|||
|
||||
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 +155,49 @@ impl Read for SpooledTempFile {
|
|||
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() as usize + buf.len() > self.max_size
|
||||
} {
|
||||
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 cursor.position() as usize + bufs.iter()
|
||||
.fold(0usize, |a, b| a.saturating_add(b.len())) > self.max_size
|
||||
} {
|
||||
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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
use fastrand;
|
||||
use std::ffi::{OsStr, OsString};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::{io, iter::repeat_with};
|
||||
|
@ -16,16 +15,13 @@ fn tmpname(prefix: &OsStr, suffix: &OsStr, rand_len: usize) -> OsString {
|
|||
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 +31,10 @@ where
|
|||
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,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -11,6 +11,13 @@ 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();
|
||||
|
@ -87,7 +94,7 @@ fn test_persist_noclobber() {
|
|||
fn test_customnamed() {
|
||||
let tmpfile = Builder::new()
|
||||
.prefix("tmp")
|
||||
.suffix(&".rs".to_string())
|
||||
.suffix(&".rs")
|
||||
.rand_bytes(12)
|
||||
.tempfile()
|
||||
.unwrap();
|
||||
|
@ -100,9 +107,9 @@ fn test_customnamed() {
|
|||
#[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];
|
||||
|
@ -298,6 +305,18 @@ fn test_into_parts() {
|
|||
assert_eq!("abcdefgh", buf);
|
||||
}
|
||||
|
||||
#[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();
|
||||
|
@ -326,3 +345,129 @@ fn test_keep() {
|
|||
}
|
||||
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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ fn test_automatic_rollover() {
|
|||
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 @@ fn test_automatic_rollover() {
|
|||
|
||||
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_automatic_rollover() {
|
|||
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 @@ fn test_explicit_rollover() {
|
|||
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_explicit_rollover() {
|
|||
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 @@ fn test_seek_read(t: &mut SpooledTempFile) {
|
|||
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 @@ fn test_seek_read(t: &mut SpooledTempFile) {
|
|||
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 @@ fn test_overwrite_and_extend_rollover() {
|
|||
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 @@ fn test_set_len(t: &mut SpooledTempFile) {
|
|||
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 @@ fn test_set_len(t: &mut SpooledTempFile) {
|
|||
|
||||
// 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,17 +290,17 @@ fn test_set_len_rollover() {
|
|||
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");
|
||||
|
|
|
@ -18,32 +18,9 @@ use std::thread;
|
|||
|
||||
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 +28,12 @@ fn test_tempdir() {
|
|||
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 +49,8 @@ fn test_customnamed() {
|
|||
|
||||
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 +58,9 @@ fn test_rm_tempdir() {
|
|||
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 +69,7 @@ fn test_rm_tempdir() {
|
|||
|
||||
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 +79,31 @@ fn test_rm_tempdir() {
|
|||
|
||||
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 +111,31 @@ fn test_rm_tempdir_close() {
|
|||
|
||||
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 +148,14 @@ fn in_tmpdir<F>(f: F)
|
|||
where
|
||||
F: FnOnce(),
|
||||
{
|
||||
let tmpdir = t!(TempDir::new());
|
||||
let tmpdir = TempDir::new().unwrap();
|
||||
assert!(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) {
|
||||
|
@ -250,12 +167,10 @@ pub fn pass_as_asref_path() {
|
|||
#[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);
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -1895,7 +1895,6 @@ into source code and to files in the following directories:
|
|||
<ul>
|
||||
<li><code>browser/components/newtab/vendor/react-transition-group.js</code></li>
|
||||
<li><code>third_party/rust/bindgen/</code></li>
|
||||
<li><code>third_party/rust/instant/</code></li>
|
||||
<li><code>third_party/rust/subtle/</code></li>
|
||||
#ifdef MOZ_JXL
|
||||
<li><code>third_party/jpeg-xl/</code></li>
|
||||
|
|
|
@ -104,9 +104,6 @@ url = "=2.1.0"
|
|||
# Since we're building with at least rustc 1.63, enable rust 1.57 features (use of try_reserve methods).
|
||||
fallible_collections = { version = "0.4", features = ["rust_1_57"] }
|
||||
|
||||
# Temporarily add an explicit dependency on rustix to vendor it.
|
||||
rustix = "0.38"
|
||||
|
||||
[target.'cfg(not(target_os = "android"))'.dependencies]
|
||||
viaduct = "0.1"
|
||||
webext_storage_bridge = { path = "../../../components/extensions/storage/webext_storage_bridge" }
|
||||
|
|
Загрузка…
Ссылка в новой задаче