'The parameter is incorrect. (os error 87)' when call `remove_dir_all` on remote disk on Parallels

I encountered a problem that std::fs::remove_dir_all always fails when deleting a network shared folder on Windows VM inside Parallels Desktop for Mac.

This is annoying because remove_dir_all is used in build scripts in many crates and also by cargo itself. Each rust build prints many warnings like following:

warning: failed to garbage collect finalized incremental compilation session directory `\\?\UNC\Mac\Home\src\samples\parallels-problem\target\debug\incremental\parallels_problem-3d161pebl2984\s-h5ylcgmdc5-1sl1tye-5kba3ot62n4mzposecklyv93z`: The parameter is incorrect. (os error 87)

warning: failed to garbage collect finalized incremental compilation session directory `\\?\UNC\Mac\Home\src\samples\parallels-problem\target\debug\incremental\parallels_problem-3d161pebl2984\s-h5ypnw5wyg-nhle4-3t64cga38i2hprb2a1y2w1iyj`: The parameter is incorrect. (os error 87)
...

I also can't use anyhow after this commit (Clean up dep-info files from OUT_DIR · dtolnay/anyhow@f130b76 · GitHub).

  • The problem only affects remove_dir_all. For example fs::remove_file and fs::remove_dir work without an issue.
  • It fails, regardless the target directory is empty or has files.

I can reproduce this on the latest stable rust and Parallels Desktop on both Intel and Apple Silicon.

use std::fs;

fn main() {

    println!("cwd: {}", std::env::current_dir().unwrap().display());
    
    // remove_dir works properly
    fs::create_dir("dir1").unwrap();
    fs::remove_dir("dir1").expect("remove_dir works");

    // bug remove_dir_all doesn't
    fs::create_dir("dir1").unwrap();
    fs::remove_dir_all("dir1").expect("remove_dir_all works");
}

Output:

cwd: \\Mac\Home\src\samples\parallels-problem
thread 'main' panicked at src/main.rs:13:32:
remove_dir_all works: Os { code: 87, kind: InvalidInput, message: "The parameter is incorrect." }
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
error: process didn't exit successfully: `target\debug\parallels-problem.exe` (exit code: 101)

I'm not sure if it's a problem in rust or in Parallels.

How can I investigate this problem further?

I would locate the actual platform-specific implementation of remove_dir_all(), then copy'n'paste it into a test project, and go through it step-by-step to try to pin-point exactly what goes wrong.

Are there any open files or directories in the to-be-deleted directory?

The 'The parameter is incorrect.` should not be related to a timing problem, I think.

Anyway, sleep doesn't make any difference.

use std::{fs, thread::sleep, time::Duration};

fn main() {

    println!("cwd: {}", std::env::current_dir().unwrap().display());
    
    // remove_file and remove_dir work properly
    fs::create_dir("dir1").unwrap();
    fs::remove_dir("dir1").expect("remove_dir works");

    // bug remove_dir_all doesn't
    fs::create_dir("dir1").unwrap();
    sleep(Duration::new(5, 0));
    fs::remove_dir_all("dir1").expect("remove_dir_all works");
}
cwd: \\Mac\Home\src\samples\parallels-problem
thread 'main' panicked at src/main.rs:14:32:
remove_dir_all works: Os { code: 87, kind: InvalidInput, message: "The parameter is incorrect." }

I pasted the remove_dir_all implementation (with small modifications) and surprisingly, it doesn't throw an error (I pasted the code below).

I'm confused now. How is this possible? I need to double-check there's no mistake on my side.

It might be related that I use x86_64-pc-windows-msvc toolchain.

use std::{fs, io, path::{Path, PathBuf}};


fn main() {
    println!("cwd: {}", std::env::current_dir().unwrap().display());
    std::fs::create_dir("dir1").unwrap();
    remove_dir_all(&PathBuf::from("dir1")).expect("remove_dir_all works");
}

fn remove_dir_all(path: &Path) -> io::Result<()> {
    let filetype = fs::symlink_metadata(path)?.file_type();
    if filetype.is_symlink() { fs::remove_file(path) } else { remove_dir_all_recursive(path) }
}

// modified by me
fn remove_dir_all_recursive(path: &Path) -> io::Result<()> {
    for child in fs::read_dir(path)? {
        let result: io::Result<()> = /*try*/ {
            let child = child.unwrap();
            if child.file_type().unwrap().is_dir() {
                remove_dir_all_recursive(&child.path()).unwrap();
            } else {
                fs::remove_file(&child.path()).unwrap();
            }
            Ok(())
        };
        /*
        // ignore internal NotFound errors to prevent race conditions
        if let Err(err) = &result
            && err.kind() != io::ErrorKind::NotFound
        {
            return result;
        } */
        return result;
    }
    ignore_notfound(fs::remove_dir(path))
}

fn ignore_notfound<T>(result: crate::io::Result<T>) -> crate::io::Result<()> {
    match result {
        Err(err) if err.kind() == crate::io::ErrorKind::NotFound => Ok(()),
        Ok(_) => Ok(()),
        Err(err) => Err(err),
    }
}

Ah yes, I think I pasted wrong implementation.
The Windows implementation is different.

std/src/sys/fs/windows.rs (rust/library/std/src/sys/fs/windows.rs at master · rust-lang/rust · GitHub) uses a lot of private modules. I think it would be very difficult to copy all them into a test project.

Is there another way to find out what exactly is causing the panic?

Have you tried this to narrow it down?

It doesn't show the place of the original panic but the point of expect call:

cwd: \\Mac\Home\src\samples\parallels-problem

thread 'main' panicked at src\main.rs:8:32:
remove_dir_all works: Os { code: 87, kind: InvalidInput, message: "The parameter is incorrect." }
stack backtrace:
   0: std::panicking::begin_panic_handler
             at /rustc/4eb161250e340c8f48f66e2b929ef4a5bed7c181/library\std\src\panicking.rs:692
   1: core::panicking::panic_fmt
             at /rustc/4eb161250e340c8f48f66e2b929ef4a5bed7c181/library\core\src\panicking.rs:75
   2: core::result::unwrap_failed
             at /rustc/4eb161250e340c8f48f66e2b929ef4a5bed7c181/library\core\src\result.rs:1704
   3: enum2$<core::result::Result<tuple$<>,std::io::error::Error> >::expect<tuple$<>,std::io::error::Error>
             at /rustc/4eb161250e340c8f48f66e2b929ef4a5bed7c181\library\core\src\result.rs:1061
   4: parallels_problem::main
             at \Mac\Home\src\samples\parallels-problem\src\main.rs:8
   5: core::ops::function::FnOnce::call_once<void (*)(),tuple$<> >
             at /rustc/4eb161250e340c8f48f66e2b929ef4a5bed7c181\library\core\src\ops\function.rs:250
   6: core::hint::black_box
             at /rustc/4eb161250e340c8f48f66e2b929ef4a5bed7c181\library\core\src\hint.rs:475
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
error: process didn't exit successfully: `target\debug\parallels-problem.exe` (exit code: 101)

Sorry, I shouldn't have suggested that.

Another way to do it is to run the code in a debugger and step into remove_dir_all().

According to this thread, you're supposed to be able to step into std if you just tell the debugger where it can find the source files.

Not sure how to do this in Windows though.

If you don't have a development environment set up inside the virtual machine where the problems occur, you might want to set up remote debugging using msvsmon and connect to it using the Visual Studio debugger.

Is there a free version of Parallels? I'm curious to see if this is reproducible.

What's on the other side? What sort of server are you running?

I'm running Parallels Desktop for Mac on MBP (M1) and with Windows in VM. This is my default dev machine.

Thank you for the suggestion. Yes, I do you have development environment set up.

I'll try to debug it in VS and step into remove_dir_all.

Is there a free version of Parallels? I'm curious to see if this is reproducible.

There is a 14day trial on their website. If you decide to install it, disable 'fancy' stuff as much as possible and only allow to share 'Mac' files with the VM. Do not share 'Windows' (otherwise it will add a lot of shortcuts/symlinks to the host machine). Personally I just share ~/src Mac folder with the VM.

1 Like

Looks like rust analyzer doesn't work on ARM. This platform (win32-arm64) is not suported (exact quote).

I'm going to try on an Intel machine.

update: I tried to uninstall vscode arm64 and installed x64 version instead. Debugging works now.
update: I managed to step and mapped the std source code. Thank you!

The debugger can not step over let more_data = dir.fill_dir_buff(&mut buffer, restart)? in the remove_dir_all_iterative function.

I suppose it's possible that the panic happens within fill_dir_buff because debug stops right after calling GetFileInformationByHandleEx.


I checked, GetFileInformationByHandleEx may throw error 87 (here's an example winapi - Java JNA OpenFileById fails with error code 87 InvalidParameter - Stack Overflow).

I'm going to try the same on the Intel machine, may be it will give me some different results.

1 Like

On an Intel machine with 1.82-x86_64-pc-windows-msvc toolchain I got similar results. I still can't step into fill_dir_buf, but now the process doest exit and I can see that the error definitely happens inside fill_dir_buf.

fill_dir_buf is an unsafe { ... } wrapper around a call to GetFileInformationByHandleEx, so it's probably inlined, and that's why I can't step into it.

Unfortunately I can't see variable values. p <variable> in debug console always returns use of undeclared identifier error.

Update 1: I decided to try to call GetFileInformationByHandleEx directly.
I added the windows crate, but it is dependent on proc-macro2 which uses remove_dir_all in custom build command which fails with The parameter is incorrect. (os error 87) :(. The loop is complete now.

Try using windows-sys instead. You’ll need to enable the Win32_Storage_FileSystem feature and then call windows_sys::Win32::Storage::FileSystem::GetFileInformationByHandleEx. You can use std::os::windows::io::AsRawHandle to get the HANDLE out of an existing std::fs::File.[1] If you pass 0x9 or 0xA as the fileinformationclass[2] (documented at MSDN), the function will write a sequence of FILE_ID_BOTH_DIR_INFO structures to the byte buffer you pass to the function.


  1. To pass a directory handle, you’ll need to open it with CreateFile (Rust API, MSDN). std::fs::File can’t open a directory. ↩︎

  2. as std uses internally here ↩︎

1 Like

I managed to create a simple sample which calls GetFileInformationByHandleEx with FileIdBothDirectoryRestartInfo request and it also returns the 87 error.

I went further and found official Windows API samples, installed c++ environment and run the extended fileapi sample Windows-classic-samples/Samples/Win7Samples/winbase/io/extendedfileapis/ExtendedFileAPIs.cpp at main · microsoft/Windows-classic-samples · GitHub. It also fails iterating through directory items on the shared disk with the same error.

At this point I tend to think that this is a bug in Parallels VM. I reported it to the support, but I'm not sure if they will respond.

3 Likes