Skip to content

Commit c518f22

Browse files
authored
feat: Added warning when failing to update index cache (#15014)
<!-- Thanks for submitting a pull request 🎉! Here are some tips for you: * If this is your first contribution, read "Cargo Contribution Guide" first: https://doc.crates.io/contrib/ * Run `cargo fmt --all` to format your code changes. * Small commits and pull requests are always preferable and easy to review. * If your idea is large and needs feedback from the community, read how: https://doc.crates.io/contrib/process/#working-on-large-features * Cargo takes care of compatibility. Read our design principles: https://doc.crates.io/contrib/design.html * When changing help text of cargo commands, follow the steps to generate docs: https://github.com/rust-lang/cargo/tree/master/src/doc#building-the-man-pages * If your PR is not finished, set it as "draft" PR or add "WIP" in its title. * It's ok to use the CI resources to test your PR, but please don't abuse them. ### What does this PR try to resolve? Explain the motivation behind this change. A clear overview along with an in-depth explanation are helpful. You can use `Fixes #<issue number>` to associate this PR to an existing issue. ### How should we test and review this PR? Demonstrate how you test this change and guide reviewers through your PR. With a smooth review process, a pull request usually gets reviewed quicker. If you don't know how to write and run your tests, please read the guide: https://doc.crates.io/contrib/tests ### Additional information Other information you want to mention in this PR, such as prior arts, future extensions, an unresolved problem, or a TODO list. --> ### What does this PR try to resolve? Fixes #13712 Adds a warning if there is an error updating the index cache. It also attempts to avoid warning spam as requested in [this comment](#13712 (comment)) Below is an example output ``` Updating crates.io index warning: failed to write cache, path: /home/ross/.cargo/registry/index/index.crates.io-1949cf8c6b5b557f/.cache/ba/se/base64, error: Permission denied (os error 13) Compiling serde v1.0.217 Compiling r-efi v5.2.0 Compiling base64 v0.22.1 Compiling cargo-13712 v0.1.0 (/home/ross/projects/cargo-13712) Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.20s ``` ### How should we test and review this PR? I tested this on my machine by manually changing the owner/permission of the index files ```sh sudo chown root ~/.cargo/registry/index/.../.cache/r- sudo chmod 700 ~/.cargo/registry/index/.../.cache/r- # in a project that uses the `r-efi` crate cargo build ``` I wasn't quiet sure about the best way to add a test to the testsuite for this. 😅 If there is good way to create a test for this lmk
2 parents eb69b44 + 272d07a commit c518f22

File tree

2 files changed

+100
-8
lines changed

2 files changed

+100
-8
lines changed

src/cargo/sources/registry/index/cache.rs

+27-7
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
//! [`IndexSummary::parse`]: super::IndexSummary::parse
6666
//! [`RemoteRegistry`]: crate::sources::registry::remote::RemoteRegistry
6767
68+
use std::cell::RefCell;
6869
use std::fs;
6970
use std::io;
7071
use std::path::PathBuf;
@@ -226,14 +227,21 @@ pub struct CacheManager<'gctx> {
226227
cache_root: Filesystem,
227228
/// [`GlobalContext`] reference for convenience.
228229
gctx: &'gctx GlobalContext,
230+
/// Keeps track of if we have sent a warning message if there was an error updating the cache.
231+
/// The motivation is to avoid warning spam if the cache is not writable.
232+
has_warned: RefCell<bool>,
229233
}
230234

231235
impl<'gctx> CacheManager<'gctx> {
232236
/// Creates a new instance of the on-disk index cache manager.
233237
///
234238
/// `root` --- The root path where caches are located.
235239
pub fn new(cache_root: Filesystem, gctx: &'gctx GlobalContext) -> CacheManager<'gctx> {
236-
CacheManager { cache_root, gctx }
240+
CacheManager {
241+
cache_root,
242+
gctx,
243+
has_warned: Default::default(),
244+
}
237245
}
238246

239247
/// Gets the cache associated with the key.
@@ -251,16 +259,28 @@ impl<'gctx> CacheManager<'gctx> {
251259
/// Associates the value with the key.
252260
pub fn put(&self, key: &str, value: &[u8]) {
253261
let cache_path = &self.cache_path(key);
254-
if fs::create_dir_all(cache_path.parent().unwrap()).is_ok() {
255-
let path = Filesystem::new(cache_path.clone());
256-
self.gctx
257-
.assert_package_cache_locked(CacheLockMode::DownloadExclusive, &path);
258-
if let Err(e) = fs::write(cache_path, value) {
259-
tracing::info!(?cache_path, "failed to write cache: {e}");
262+
if let Err(e) = self.put_inner(cache_path, value) {
263+
tracing::info!(?cache_path, "failed to write cache: {e}");
264+
265+
if !*self.has_warned.borrow() {
266+
let _ = self.gctx.shell().warn(format!(
267+
"failed to write cache, path: {}, error: {e}",
268+
cache_path.to_str().unwrap_or_default()
269+
));
270+
*self.has_warned.borrow_mut() = true;
260271
}
261272
}
262273
}
263274

275+
fn put_inner(&self, cache_path: &PathBuf, value: &[u8]) -> std::io::Result<()> {
276+
fs::create_dir_all(cache_path.parent().unwrap())?;
277+
let path = Filesystem::new(cache_path.clone());
278+
self.gctx
279+
.assert_package_cache_locked(CacheLockMode::DownloadExclusive, &path);
280+
fs::write(cache_path, value)?;
281+
Ok(())
282+
}
283+
264284
/// Invalidates the cache associated with the key.
265285
pub fn invalidate(&self, key: &str) {
266286
let cache_path = &self.cache_path(key);

tests/testsuite/registry.rs

+73-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
33
use std::fmt::Write;
44
use std::fs::{self, File};
5-
use std::path::Path;
5+
use std::path::{Path, PathBuf};
66
use std::sync::Arc;
77
use std::sync::Mutex;
88

@@ -3097,6 +3097,78 @@ fn readonly_registry_still_works() {
30973097
}
30983098
}
30993099

3100+
#[cargo_test(ignore_windows = "On Windows setting file attributes is a bit complicated")]
3101+
fn unaccessible_registry_cache_still_works() {
3102+
Package::new("foo", "0.1.0").publish();
3103+
Package::new("fo2", "0.1.0").publish();
3104+
3105+
let p = project()
3106+
.file(
3107+
"Cargo.toml",
3108+
r#"
3109+
[package]
3110+
name = "a"
3111+
version = "0.5.0"
3112+
edition = "2015"
3113+
authors = []
3114+
3115+
[dependencies]
3116+
foo = '0.1.0'
3117+
fo2 = '0.1.0'
3118+
"#,
3119+
)
3120+
.file("src/lib.rs", "")
3121+
.build();
3122+
3123+
p.cargo("generate-lockfile").run();
3124+
p.cargo("fetch --locked").run();
3125+
3126+
let cache_path = inner_dir(&paths::cargo_home().join("registry/index")).join(".cache");
3127+
let f_cache_path = cache_path.join("3/f");
3128+
3129+
// Remove the permissions from the cache path that contains the "foo" crate
3130+
set_permissions(&f_cache_path, 0o000);
3131+
3132+
// Now run a build and make sure we properly build and warn the user
3133+
p.cargo("build")
3134+
.with_stderr_data(str![[r#"
3135+
[WARNING] failed to write cache, path: [ROOT]/home/.cargo/registry/index/-[HASH]/.cache/3/f/fo[..], [ERROR] Permission denied (os error 13)
3136+
[COMPILING] fo[..] v0.1.0
3137+
[COMPILING] fo[..] v0.1.0
3138+
[COMPILING] a v0.5.0 ([ROOT]/foo)
3139+
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s
3140+
3141+
"#]])
3142+
.run();
3143+
// make sure we add the permissions to the files afterwards so "cargo clean" can remove them (#6934)
3144+
set_permissions(&f_cache_path, 0o777);
3145+
3146+
fn set_permissions(path: &Path, permissions: u32) {
3147+
#[cfg(not(windows))]
3148+
{
3149+
use std::os::unix::fs::PermissionsExt;
3150+
let mut perms = t!(path.metadata()).permissions();
3151+
perms.set_mode(permissions);
3152+
t!(fs::set_permissions(path, perms));
3153+
}
3154+
3155+
#[cfg(windows)]
3156+
panic!("This test is not supported on windows. See the reason in the #[cargo_test] macro");
3157+
}
3158+
3159+
fn inner_dir(path: &Path) -> PathBuf {
3160+
for entry in t!(path.read_dir()) {
3161+
let path = t!(entry).path();
3162+
3163+
if path.is_dir() {
3164+
return path;
3165+
}
3166+
}
3167+
3168+
panic!("could not find inner directory of {path:?}");
3169+
}
3170+
}
3171+
31003172
#[cargo_test]
31013173
fn registry_index_rejected_http() {
31023174
let _server = setup_http();

0 commit comments

Comments
 (0)