use $TMPDIR/uv-locks instead of putting lockfiles directly in $TMPDIR

This commit is contained in:
Jack O'Connor 2025-06-23 14:18:32 -07:00
parent 0019a4acb7
commit 530f4205d3
4 changed files with 77 additions and 8 deletions

View file

@ -739,8 +739,8 @@ impl SourceBuild {
// lock the output dir, but setuptools always writes to `build/` in the source tree,
// regardless of whether its output dir is set to somewhere else.
let canonical_source_path = self.source_tree.canonicalize()?;
let build_lock_path =
std::env::temp_dir().join(format!("uv-{}.lock", cache_digest(&canonical_source_path)));
let build_lock_path = uv_fs::locks_temp_dir()?
.join(format!("uv-{}.lock", cache_digest(&canonical_source_path)));
let _lock =
LockedFile::acquire(build_lock_path, self.source_tree.to_string_lossy()).await?;

View file

@ -778,3 +778,69 @@ pub fn copy_dir_all(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> std::io::Re
}
Ok(())
}
/// The path to a shared, world-writeable directory for creating locks, to avoid polluting `/tmp`.
/// On Linux this is `/tmp/uv_locks`, if `$TMPDIR` is unset.
pub fn locks_temp_dir() -> std::io::Result<PathBuf> {
let locks_dir_path = std::env::temp_dir().join("uv-locks");
if !locks_dir_path.is_dir() {
create_world_writeable_dir(&locks_dir_path)?;
}
Ok(locks_dir_path)
}
#[cfg(unix)]
fn create_world_writeable_dir(path: &Path) -> std::io::Result<()> {
// We need the new directory to have 0o777 permissions on Unix, but if we create it and then
// set those permissions, there's a small window in between where other processes could race in
// and get IO errors. You'd think we could use `DirBuilderExt::mode` from the standard library
// to set the permissions during creation, but the process-wide "umask" interferes with that,
// and we can't change our own umask without interfering other threads. Spawn a subprocess that
// can set its own umask before creating the dir for us. This is expensive, but we only need to
// do it the first time. The `-p` flag makes it not an error for multiple callers to do this
// concurrently.
let output = std::process::Command::new("sh")
.arg("-c")
// The -p flag makes it not an error for multiple callers to do this concurrently. The 1 at
// the top of 1777 is the "sticky bit", which prevents different users from deleting each
// other's files. It hardly matters for us in practice, but this is exactly the sort of use
// case it's intended for, and we might as well set it.
.arg(r#"umask 0 && mkdir -p -m 1777 -- "$1""#)
.arg("--")
.arg(path)
.output()
.unwrap();
if !output.status.success() {
return Err(std::io::Error::other(format!(
"`mkdir {}` failed: {}",
path.to_string_lossy(),
String::from_utf8_lossy(&output.stderr),
)));
}
Ok(())
}
#[cfg(windows)]
fn create_world_writeable_dir(path: &Path) -> std::io::Result<()> {
// Windows doesn't have Unix-style file permission. Just create the dir.
fs_err::create_dir_all(path)
}
#[test]
fn test_create_world_writeable_dir() -> io::Result<()> {
let parent_dir = tempfile::tempdir()?;
let new_dir_path = parent_dir.path().join("foo");
create_world_writeable_dir(&new_dir_path)?;
assert!(new_dir_path.exists());
assert!(new_dir_path.is_dir());
#[cfg(unix)]
{
// On Unix only, explicitly check the permissions mask of the new directory.
use std::os::unix::fs::PermissionsExt;
let metadata = fs_err::metadata(&new_dir_path)?;
assert_eq!(metadata.permissions().mode() & 0o777, 0o777);
}
// Create a file in the new directory, for good measure.
fs_err::File::create(new_dir_path.join("bar.txt"))?;
Ok(())
}

View file

@ -1,10 +1,10 @@
use std::borrow::Cow;
use std::env::consts::ARCH;
use std::fmt::{Display, Formatter};
use std::io;
use std::path::{Path, PathBuf};
use std::process::{Command, ExitStatus};
use std::sync::OnceLock;
use std::{env, io};
use configparser::ini::Ini;
use fs_err as fs;
@ -625,7 +625,8 @@ impl Interpreter {
} else {
// Otherwise, use a global lockfile.
LockedFile::acquire(
env::temp_dir().join(format!("uv-{}.lock", cache_digest(&self.sys_executable))),
uv_fs::locks_temp_dir()?
.join(format!("uv-{}.lock", cache_digest(&self.sys_executable))),
self.sys_prefix.user_display(),
)
.await

View file

@ -722,21 +722,23 @@ impl ScriptInterpreter {
match script {
Pep723ItemRef::Script(script) => {
LockedFile::acquire(
std::env::temp_dir().join(format!("uv-{}.lock", cache_digest(&script.path))),
uv_fs::locks_temp_dir()?
.join(format!("uv-{}.lock", cache_digest(&script.path))),
script.path.simplified_display(),
)
.await
}
Pep723ItemRef::Remote(.., url) => {
LockedFile::acquire(
std::env::temp_dir().join(format!("uv-{}.lock", cache_digest(url))),
uv_fs::locks_temp_dir()?.join(format!("uv-{}.lock", cache_digest(url))),
url.to_string(),
)
.await
}
Pep723ItemRef::Stdin(metadata) => {
LockedFile::acquire(
std::env::temp_dir().join(format!("uv-{}.lock", cache_digest(&metadata.raw))),
uv_fs::locks_temp_dir()?
.join(format!("uv-{}.lock", cache_digest(&metadata.raw))),
"stdin".to_string(),
)
.await
@ -989,7 +991,7 @@ impl ProjectInterpreter {
/// Grab a file lock for the environment to prevent concurrent writes across processes.
pub(crate) async fn lock(workspace: &Workspace) -> Result<LockedFile, std::io::Error> {
LockedFile::acquire(
std::env::temp_dir().join(format!(
uv_fs::locks_temp_dir()?.join(format!(
"uv-{}.lock",
cache_digest(workspace.install_path())
)),