rust-lang/rust · error

Failed to remove {path}: {err}

Error message

Failed to remove {path}: {err}

What it means

This `panic!` fires inside `ensure_empty_dir` (utils.rs:194-198) while iterating directory entries, when removing a sub-directory via `fs::remove_dir_all(entry.path())` returns an error other than `NotFound`. `NotFound` is tolerated (a concurrent remover already deleted it), but any other I/O failure (permission denied, dir not empty due to concurrent writes, busy handle) halts the cranelift build-system. The entry path and OS error are formatted into the message.

Source

Thrown at compiler/rustc_codegen_cranelift/build_system/utils.rs:197

/// Create the specified directory if it doesn't exist yet and delete all contents.
pub(crate) fn ensure_empty_dir(path: &Path) {
    fs::create_dir_all(path).unwrap();
    let read_dir = match fs::read_dir(path) {
        Ok(read_dir) => read_dir,
        Err(err) if err.kind() == io::ErrorKind::NotFound => {
            return;
        }
        Err(err) => {
            panic!("Failed to read contents of {path}: {err}", path = path.display())
        }
    };
    for entry in read_dir {
        let entry = entry.unwrap();
        if entry.file_type().unwrap().is_dir() {
            match fs::remove_dir_all(entry.path()) {
                Ok(()) => {}
                Err(err) if err.kind() == io::ErrorKind::NotFound => {}
                Err(err) => panic!("Failed to remove {path}: {err}", path = entry.path().display()),
            }
        } else {
            match fs::remove_file(entry.path()) {
                Ok(()) => {}
                Err(err) if err.kind() == io::ErrorKind::NotFound => {}
                Err(err) => panic!("Failed to remove {path}: {err}", path = entry.path().display()),
            }
        }
    }
}

pub(crate) fn copy_dir_recursively(from: &Path, to: &Path) {
    for entry in fs::read_dir(from).unwrap() {
        let entry = entry.unwrap();
        let filename = entry.file_name();
        if filename == "." || filename == ".." {
            continue;
        }

View on GitHub (pinned to 22057b88b0)

Solutions

  1. Inspect the `err` and `path` in the panic message: `PermissionDenied` ⇒ fix ownership (`sudo chown -R $USER:$USER <entry path>`).
  2. Ensure no other process (cargo, IDE, file indexer) is touching the build tree; on Windows close VS Code / disable Defender for the path.
  3. Manually remove the offending sub-directory: `rm -rf <entry path>`, then re-run `./y.sh prepare`.
  4. Re-run `ensure_empty_dir` after a `./y.sh clean` to start from a known-empty state.
  5. Relocate the build dir off network/FUSE filesystems onto local disk.

Example fix

# before — ensure_empty_dir panics removing a sub-dir
./y.sh prepare
# panics: Failed to remove /path/dir/sub: Permission denied

# after — fix ownership of the sub-tree and re-run
sudo chown -R $USER:$USER /path/dir
rm -rf /path/dir/sub
./y.sh prepare
Defensive patterns

Strategy: retry

Validate before calling

// utils.rs:197 remove failure — same class as 56: transient/perms/lock.
// Pre-flight: confirm the path is gone-able before the build removes it.
use std::path::Path;
fn ensure_removable(p: &Path) -> std::io::Result<()> {
    match std::fs::metadata(p) {
        Ok(md) if md.permissions().readonly() =>
            Err(std::io::Error::new(std::io::ErrorKind::PermissionDenied, "readonly")),
        Ok(_) => Ok(()),
        Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(()), // nothing to remove
        Err(e) => Err(e),
    }
}

Try / catch

// Retry remove with backoff; surface final cause if it stays stuck.
use std::{fs, path::Path, thread, time::Duration};
fn remove_retry(p: &Path, n: u32) -> std::io::Result<()> {
    let mut last = None;
    for a in 0..n {
        match fs::remove_file(p) {
            Ok(()) => return Ok(()),
            Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(()),
            Err(e) => { last = Some(e); if a + 1 < n { thread::sleep(Duration::from_millis(100 << a)); } }
        }
    }
    Err(last.unwrap())
}

Prevention

When it happens

Trigger: Triggered when `ensure_empty_dir` walks the directory and `fs::remove_dir_all` at line 194 fails for a directory entry with a non-NotFound error. Reachable when the build tree contains a sub-directory whose contents are held open by another process, owned by another user, or on a read-only mount.

Common situations: Seen in cranelift local/CI builds when: a previous interrupted build left sub-dirs owned by root, an IDE/antivirus/Indexer holds handles on Windows, a process is actively writing into the dir (concurrent cargo), or a NFS/SMB mount returns stale file handles during recursive removal.

Related errors


AI-assisted analysis of rust-lang/rust@22057b88b0 (2026-08-03). Data as JSON: /data/errors/6498f009e320bc0b.json. Report an issue: GitHub.