Skip to content

Commit 50a34c8

Browse files
committed
Avoid clobbering existing pack cache files
Signed-off-by: Quanyi Ma <eli@patch.sh>
1 parent f8f77f9 commit 50a34c8

1 file changed

Lines changed: 13 additions & 52 deletions

File tree

src/internal/pack/cache_object.rs

Lines changed: 13 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,7 @@
33
44
use std::{
55
borrow::Cow,
6-
fs,
7-
fs::OpenOptions,
8-
io,
6+
fs, io,
97
io::Write,
108
ops::Deref,
119
path::{Path, PathBuf},
@@ -25,6 +23,7 @@ use rkyv::{
2523
ser::allocator::ArenaHandle,
2624
util::AlignedVec,
2725
};
26+
use tempfile::NamedTempFile;
2827
use threadpool::ThreadPool;
2928

3029
use crate::{
@@ -38,8 +37,6 @@ use crate::{
3837

3938
// static CACHE_OBJS_MEM_SIZE: AtomicUsize = AtomicUsize::new(0);
4039

41-
const MAX_TEMP_ATTEMPTS: u32 = 64;
42-
4340
/// file load&store trait
4441
pub trait FileLoadStore: Sized {
4542
fn f_load(path: &Path) -> Result<Self, io::Error>;
@@ -50,54 +47,18 @@ fn write_bytes_atomically(path: &Path, data: &[u8]) -> Result<(), io::Error> {
5047
if path.exists() {
5148
return Ok(());
5249
}
53-
let mut attempt = 0u32;
54-
55-
loop {
56-
if path.exists() {
57-
return Ok(());
58-
}
50+
let dir = path
51+
.parent()
52+
.filter(|parent| !parent.as_os_str().is_empty())
53+
.unwrap_or_else(|| Path::new("."));
5954

60-
let temp_path = if attempt == 0 {
61-
path.with_extension("temp")
62-
} else {
63-
path.with_extension(format!("temp.{attempt}"))
64-
};
55+
let mut temp_file = NamedTempFile::new_in(dir)?;
56+
temp_file.write_all(data)?;
6557

66-
match OpenOptions::new()
67-
.write(true)
68-
.create_new(true)
69-
.open(&temp_path)
70-
{
71-
Ok(mut file) => {
72-
file.write_all(data)?;
73-
drop(file);
74-
75-
match fs::rename(&temp_path, path) {
76-
Ok(()) => return Ok(()),
77-
Err(err) if err.kind() == io::ErrorKind::AlreadyExists && path.exists() => {
78-
let _ = fs::remove_file(&temp_path);
79-
return Ok(());
80-
}
81-
Err(err) => {
82-
let _ = fs::remove_file(&temp_path);
83-
return Err(err);
84-
}
85-
}
86-
}
87-
Err(err) if err.kind() == io::ErrorKind::AlreadyExists && path.exists() => {
88-
return Ok(());
89-
}
90-
Err(err) if err.kind() == io::ErrorKind::AlreadyExists => {
91-
attempt = attempt.saturating_add(1);
92-
if attempt >= MAX_TEMP_ATTEMPTS {
93-
return Err(io::Error::new(
94-
io::ErrorKind::AlreadyExists,
95-
format!("exhausted temp file attempts for {}", path.display()),
96-
));
97-
}
98-
}
99-
Err(err) => return Err(err),
100-
}
58+
match temp_file.persist_noclobber(path) {
59+
Ok(_persisted) => Ok(()),
60+
Err(err) if err.error.kind() == io::ErrorKind::AlreadyExists && path.exists() => Ok(()),
61+
Err(err) => Err(err.error),
10162
}
10263
}
10364

@@ -699,7 +660,7 @@ mod test {
699660
}
700661

701662
#[test]
702-
fn test_write_bytes_atomically_retries_when_temp_exists() {
663+
fn test_write_bytes_atomically_ignores_stale_temp_file() {
703664
let dir = tempdir().unwrap();
704665
let path = dir.path().join("object");
705666

0 commit comments

Comments
 (0)