fix: prevent tmp-overwriting
CI / All (ubuntu-latest) (push) Failing after 29s
CI / All (macos-latest) (push) Canceled after 0s
CI / All (windows-latest) (push) Canceled after 0s

This commit is contained in:
2026-08-15 19:35:12 -06:00
parent ede87df960
commit 374e8c0bf7
+40 -2
View File
@@ -32,7 +32,7 @@ use skill::SKILL_FUNCTION_PREFIX;
use std::ffi::OsStr; use std::ffi::OsStr;
use std::fs::File; use std::fs::File;
use std::io::{Read, Write}; use std::io::{Read, Write};
use std::sync::atomic::Ordering; use std::sync::atomic::{AtomicU64, Ordering};
use std::{collections::VecDeque, thread}; use std::{collections::VecDeque, thread};
use std::{ use std::{
collections::{HashMap, HashSet}, collections::{HashMap, HashSet},
@@ -159,7 +159,12 @@ pub(crate) fn write_file_atomic(
.file_name() .file_name()
.and_then(OsStr::to_str) .and_then(OsStr::to_str)
.ok_or_else(|| anyhow!("Unable to extract file name from path: {}", path.display()))?; .ok_or_else(|| anyhow!("Unable to extract file name from path: {}", path.display()))?;
let tmp = path.with_file_name(format!(".{file_name}.tmp.{}", std::process::id())); static TMP_COUNTER: AtomicU64 = AtomicU64::new(0);
let tmp = path.with_file_name(format!(
".{file_name}.tmp.{}.{}",
std::process::id(),
TMP_COUNTER.fetch_add(1, Ordering::Relaxed)
));
fs::write(&tmp, content)?; fs::write(&tmp, content)?;
#[cfg(unix)] #[cfg(unix)]
@@ -2385,6 +2390,39 @@ mod tests {
fs::remove_dir_all(&dir).unwrap(); fs::remove_dir_all(&dir).unwrap();
} }
#[test]
fn write_file_atomic_concurrent_writers_to_same_target() {
let dir = temp_file("-atomic-concurrent-", "");
fs::create_dir_all(&dir).unwrap();
let path = dir.join("shim");
let contents: Vec<String> = (0..8)
.map(|i| format!("#!/bin/sh\necho writer-{i}\n"))
.collect();
thread::scope(|scope| {
for content in &contents {
scope.spawn(|| {
for _ in 0..50 {
write_file_atomic(&path, content, Some(0o755)).unwrap();
}
});
}
});
let final_content = fs::read_to_string(&path).unwrap();
assert!(
contents.contains(&final_content),
"final content must be one writer's complete content, got: {final_content:?}"
);
assert_eq!(
fs::read_dir(&dir).unwrap().count(),
1,
"no tmp files left behind"
);
fs::remove_dir_all(&dir).unwrap();
}
#[test] #[test]
fn bin_entry_stem_strips_run_prefix_and_extension() { fn bin_entry_stem_strips_run_prefix_and_extension() {
assert_eq!(bin_entry_stem("fs_grep"), "fs_grep"); assert_eq!(bin_entry_stem("fs_grep"), "fs_grep");