mirror of
https://github.com/rust-lang/cargo
synced 2024-09-17 23:01:51 +00:00
Auto merge of #6940 - alexcrichton:readonly-compat, r=ehuss
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:
commit
414c1eb4d5
|
@ -449,15 +449,16 @@ impl<'cfg> RegistrySource<'cfg> {
|
|||
let path = dst.join(PACKAGE_SOURCE_LOCK);
|
||||
let path = self.config.assert_package_cache_locked(&path);
|
||||
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()
|
||||
.create(true)
|
||||
.read(true)
|
||||
.write(true)
|
||||
.open(&path)?;
|
||||
let meta = ok.metadata()?;
|
||||
if meta.len() > 0 {
|
||||
return Ok(unpack_dir.to_path_buf());
|
||||
}
|
||||
|
||||
let gz = GzDecoder::new(tarball);
|
||||
let mut tar = Archive::new(gz);
|
||||
|
|
|
@ -6,7 +6,7 @@ use std::env;
|
|||
use std::fmt;
|
||||
use std::fs::{self, File};
|
||||
use std::io::prelude::*;
|
||||
use std::io::SeekFrom;
|
||||
use std::io::{self, SeekFrom};
|
||||
use std::mem;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str::FromStr;
|
||||
|
@ -860,21 +860,71 @@ impl Config {
|
|||
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>> {
|
||||
let mut slot = self.package_cache_lock.borrow_mut();
|
||||
match *slot {
|
||||
// We've already acquired the lock in this process, so simply bump
|
||||
// the count and continue.
|
||||
Some((_, ref mut cnt)) => {
|
||||
*cnt += 1;
|
||||
}
|
||||
None => {
|
||||
let lock = self
|
||||
.home_path
|
||||
.open_rw(".package-cache", self, "package cache lock")
|
||||
.chain_err(|| "failed to acquire package cache lock")?;
|
||||
let path = ".package-cache";
|
||||
let desc = "package cache lock";
|
||||
|
||||
// First, attempt to open an exclusive lock which is in general
|
||||
// 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));
|
||||
}
|
||||
}
|
||||
Ok(PackageCacheLock(self))
|
||||
|
||||
Err(e).chain_err(|| "failed to acquire package cache lock")?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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) {}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use std::fs::{self, File};
|
||||
use std::io::prelude::*;
|
||||
use std::path::Path;
|
||||
|
||||
use crate::support::cargo_process;
|
||||
use crate::support::git;
|
||||
|
@ -1979,3 +1980,48 @@ fn ignore_invalid_json_lines() {
|
|||
|
||||
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) {
|
||||
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();
|
||||
p.set_readonly(false);
|
||||
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| {
|
||||
panic!("failed to {} {}: {}", desc, path.display(), e);
|
||||
})
|
||||
|
|
Loading…
Reference in a new issue