mirror of
https://github.com/rust-lang/cargo.git
synced 2025-10-01 11:30:39 +00:00
Re-enable compatibility with readonly CARGO_HOME
Previously Cargo would attempt to work as much as possible with a previously filled out CARGO_HOME, even if it was mounted as read-only. In #6880 this was regressed as a few global locks and files were always attempted to be opened in writable mode. This commit fixes these issues by correcting two locations: * First the global package cache lock has error handling to allow acquiring the lock in read-only mode inaddition to read/write mode. If the read/write mode failed due to an error that looks like a readonly filesystem then we assume everything in the package cache is readonly and we switch to just acquiring any lock, this time a shared readonly one. We in theory aren't actually doing any synchronization at that point since it's all readonly anyway. * Next when unpacking package we're careful to issue a `stat` call before opening a file in writable mode. This way our preexisting guard to return early if a package is unpacked will succeed before we open anything in writable mode. Closes #6928
This commit is contained in:
parent
fd3d06b3c7
commit
5d9383ed76
@ -449,15 +449,16 @@ impl<'cfg> RegistrySource<'cfg> {
|
|||||||
let path = dst.join(PACKAGE_SOURCE_LOCK);
|
let path = dst.join(PACKAGE_SOURCE_LOCK);
|
||||||
let path = self.config.assert_package_cache_locked(&path);
|
let path = self.config.assert_package_cache_locked(&path);
|
||||||
let unpack_dir = path.parent().unwrap();
|
let unpack_dir = path.parent().unwrap();
|
||||||
|
if let Ok(meta) = path.metadata() {
|
||||||
|
if meta.len() > 0 {
|
||||||
|
return Ok(unpack_dir.to_path_buf());
|
||||||
|
}
|
||||||
|
}
|
||||||
let mut ok = OpenOptions::new()
|
let mut ok = OpenOptions::new()
|
||||||
.create(true)
|
.create(true)
|
||||||
.read(true)
|
.read(true)
|
||||||
.write(true)
|
.write(true)
|
||||||
.open(&path)?;
|
.open(&path)?;
|
||||||
let meta = ok.metadata()?;
|
|
||||||
if meta.len() > 0 {
|
|
||||||
return Ok(unpack_dir.to_path_buf());
|
|
||||||
}
|
|
||||||
|
|
||||||
let gz = GzDecoder::new(tarball);
|
let gz = GzDecoder::new(tarball);
|
||||||
let mut tar = Archive::new(gz);
|
let mut tar = Archive::new(gz);
|
||||||
|
@ -6,7 +6,7 @@ use std::env;
|
|||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::fs::{self, File};
|
use std::fs::{self, File};
|
||||||
use std::io::prelude::*;
|
use std::io::prelude::*;
|
||||||
use std::io::SeekFrom;
|
use std::io::{self, SeekFrom};
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
@ -860,21 +860,71 @@ impl Config {
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Acquires an exclusive lock on the global "package cache"
|
||||||
|
///
|
||||||
|
/// This lock is global per-process and can be acquired recursively. An RAII
|
||||||
|
/// structure is returned to release the lock, and if this process
|
||||||
|
/// abnormally terminates the lock is also released.
|
||||||
pub fn acquire_package_cache_lock<'a>(&'a self) -> CargoResult<PackageCacheLock<'a>> {
|
pub fn acquire_package_cache_lock<'a>(&'a self) -> CargoResult<PackageCacheLock<'a>> {
|
||||||
let mut slot = self.package_cache_lock.borrow_mut();
|
let mut slot = self.package_cache_lock.borrow_mut();
|
||||||
match *slot {
|
match *slot {
|
||||||
|
// We've already acquired the lock in this process, so simply bump
|
||||||
|
// the count and continue.
|
||||||
Some((_, ref mut cnt)) => {
|
Some((_, ref mut cnt)) => {
|
||||||
*cnt += 1;
|
*cnt += 1;
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
let lock = self
|
let path = ".package-cache";
|
||||||
.home_path
|
let desc = "package cache lock";
|
||||||
.open_rw(".package-cache", self, "package cache lock")
|
|
||||||
.chain_err(|| "failed to acquire package cache lock")?;
|
// First, attempt to open an exclusive lock which is in general
|
||||||
*slot = Some((lock, 1));
|
// the purpose of this lock!
|
||||||
|
//
|
||||||
|
// If that fails because of a readonly filesystem, though, then
|
||||||
|
// we don't want to fail because it's a readonly filesystem. In
|
||||||
|
// some situations Cargo is prepared to have a readonly
|
||||||
|
// filesystem yet still work since it's all been pre-downloaded
|
||||||
|
// and/or pre-unpacked. In these situations we want to keep
|
||||||
|
// Cargo running if possible, so if it's a readonly filesystem
|
||||||
|
// switch to a shared lock which should hopefully succeed so we
|
||||||
|
// can continue.
|
||||||
|
//
|
||||||
|
// Note that the package cache lock protects files in the same
|
||||||
|
// directory, so if it's a readonly filesystem we assume that
|
||||||
|
// the entire package cache is readonly, so we're just acquiring
|
||||||
|
// something to prove it works, we're not actually doing any
|
||||||
|
// synchronization at that point.
|
||||||
|
match self.home_path.open_rw(path, self, desc) {
|
||||||
|
Ok(lock) => *slot = Some((lock, 1)),
|
||||||
|
Err(e) => {
|
||||||
|
if maybe_readonly(&e) {
|
||||||
|
if let Ok(lock) = self.home_path.open_ro(path, self, desc) {
|
||||||
|
*slot = Some((lock, 1));
|
||||||
|
return Ok(PackageCacheLock(self));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(e).chain_err(|| "failed to acquire package cache lock")?;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(PackageCacheLock(self))
|
return Ok(PackageCacheLock(self));
|
||||||
|
|
||||||
|
fn maybe_readonly(err: &failure::Error) -> bool {
|
||||||
|
err.iter_chain().any(|err| {
|
||||||
|
if let Some(io) = err.downcast_ref::<io::Error>() {
|
||||||
|
if io.kind() == io::ErrorKind::PermissionDenied {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
return io.raw_os_error() == Some(libc::EROFS);
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn release_package_cache_lock(&self) {}
|
pub fn release_package_cache_lock(&self) {}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
use std::fs::{self, File};
|
use std::fs::{self, File};
|
||||||
use std::io::prelude::*;
|
use std::io::prelude::*;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
use crate::support::cargo_process;
|
use crate::support::cargo_process;
|
||||||
use crate::support::git;
|
use crate::support::git;
|
||||||
@ -1979,3 +1980,48 @@ fn ignore_invalid_json_lines() {
|
|||||||
|
|
||||||
p.cargo("build").run();
|
p.cargo("build").run();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn readonly_registry_still_works() {
|
||||||
|
Package::new("foo", "0.1.0").publish();
|
||||||
|
|
||||||
|
let p = project()
|
||||||
|
.file(
|
||||||
|
"Cargo.toml",
|
||||||
|
r#"
|
||||||
|
[project]
|
||||||
|
name = "a"
|
||||||
|
version = "0.5.0"
|
||||||
|
authors = []
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
foo = '0.1.0'
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.file("src/lib.rs", "")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
p.cargo("generate-lockfile").run();
|
||||||
|
p.cargo("fetch --locked").run();
|
||||||
|
chmod_readonly(&paths::home());
|
||||||
|
p.cargo("build").run();
|
||||||
|
|
||||||
|
fn chmod_readonly(path: &Path) {
|
||||||
|
for entry in t!(path.read_dir()) {
|
||||||
|
let entry = t!(entry);
|
||||||
|
let path = entry.path();
|
||||||
|
if t!(entry.file_type()).is_dir() {
|
||||||
|
chmod_readonly(&path);
|
||||||
|
} else {
|
||||||
|
set_readonly(&path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set_readonly(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_readonly(path: &Path) {
|
||||||
|
let mut perms = t!(path.metadata()).permissions();
|
||||||
|
perms.set_readonly(true);
|
||||||
|
t!(fs::set_permissions(path, perms));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -158,10 +158,18 @@ where
|
|||||||
{
|
{
|
||||||
match f(path) {
|
match f(path) {
|
||||||
Ok(()) => {}
|
Ok(()) => {}
|
||||||
Err(ref e) if cfg!(windows) && e.kind() == ErrorKind::PermissionDenied => {
|
Err(ref e) if e.kind() == ErrorKind::PermissionDenied => {
|
||||||
let mut p = t!(path.metadata()).permissions();
|
let mut p = t!(path.metadata()).permissions();
|
||||||
p.set_readonly(false);
|
p.set_readonly(false);
|
||||||
t!(fs::set_permissions(path, p));
|
t!(fs::set_permissions(path, p));
|
||||||
|
|
||||||
|
// Unix also requires the parent to not be readonly for example when
|
||||||
|
// removing files
|
||||||
|
let parent = path.parent().unwrap();
|
||||||
|
let mut p = t!(parent.metadata()).permissions();
|
||||||
|
p.set_readonly(false);
|
||||||
|
t!(fs::set_permissions(parent, p));
|
||||||
|
|
||||||
f(path).unwrap_or_else(|e| {
|
f(path).unwrap_or_else(|e| {
|
||||||
panic!("failed to {} {}: {}", desc, path.display(), e);
|
panic!("failed to {} {}: {}", desc, path.display(), e);
|
||||||
})
|
})
|
||||||
|
Loading…
x
Reference in New Issue
Block a user