Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions crates/uv-build-frontend/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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?;

Expand Down
66 changes: 66 additions & 0 deletions crates/uv-fs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
}
5 changes: 3 additions & 2 deletions crates/uv-python/src/interpreter.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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
Expand Down
10 changes: 6 additions & 4 deletions crates/uv/src/commands/project/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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())
)),
Expand Down
Loading