Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Re-enable compatibility with readonly CARGO_HOME #6940

Merged
merged 1 commit into from
May 14, 2019
Merged
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
9 changes: 5 additions & 4 deletions src/cargo/sources/registry/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
64 changes: 57 additions & 7 deletions src/cargo/util/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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")?;
*slot = Some((lock, 1));
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));
}
}

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) {}
Expand Down
46 changes: 46 additions & 0 deletions tests/testsuite/registry.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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));
}
}
10 changes: 9 additions & 1 deletion tests/testsuite/support/paths.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
})
Expand Down