How to prevent Rust's std::process:Command from inserting .exe's relative path into arguments?

0

I'm using Rust's std:process:Command to call robocopy in Windows. Unfortunately, it seems that somewhere along the execution of robocopy, the relative path from the .exe is inserted before each directory.

Additionally, simple calls like net use work with the same method, though I've only tested ones that don't rely on any directories.

Edit: updated the test folders to have spaces in their names.

Edit 2: the code has been updated with the solution. The solution was to call robocopy directly and .arg() in everything individually. Old attempts are still commented in.

Instead of sending

cmd.exe /c robocopy "C:\Test\Test 1" "C:\Test\Test 2" /MIR /copy:DAT /MT:32 /Z /R:2 /W:03 /v /LOG:"C:\Test\Test 3\logfile.log"

and it running successfully, the output has a lot of errors:

ERROR 123 (0x0000007B) Opening Log File C:\relative\path\where\exe\is\located\"C:\Test\Test 3\logfile.log"
The filename, directory name, or volume label syntax is incorrect.

-------------------------------------------------------------------------------
   ROBOCOPY     ::     Robust File Copy for Windows
-------------------------------------------------------------------------------

   Started : Tuesday, January 8, 2019 7:35:41 AM
    Source - C:\relative\path\where\exe\is\located\"C:\Test\Test 1"\
      Dest - C:\relative\path\where\exe\is\located\"C:\Test\Test 2"\

     Files :
   Options : /V /S /E /DCOPY:DA /COPY:DAT /PURGE /MIR /Z /MT:32 /R:2 /W:3

------------------------------------------------------------------------------

ERROR : Invalid Parameter #10 : "/LOG:"C:\Test\Test 3\logfile.log""

This issue occurs using:

  • Rust version 1.3.1

  • Windows 10

Code that causes this for me is shown below. To use it you'll need to put a Test folder in your C:\ (or change it to wherever) and put inside that directory a Test 1 folder with some Testfile.txt to help show copying took place, as well as a Test 3 folder for the log to be in (not sure if the log can make its own folder - so just to be safe, premake one for it!). Basically you want:

C:\Test\Test 1\Testfile.txt (name or type of file doesn't matter)
C:\Test\Test 3\

robocopy should run and make a C:\Test\Test 2 folder with Testfile.txt in it and put a logfile.log in C:\Test\Test 3 that details the transaction. In the end, the directories should look like this:

C:\Test\Test 1\Testfile.txt
C:\Test\Test 2\Testfile.txt
C:\Test\Test 3\logfile.log

The code follows:

//-----Import Built-in Libraries (not called crates)-----
use std::process::Command; //use cmd.exe

fn main()
{
    let commandOpt1 = "/MIR"; //mirror directories
    let commandOpt2 = "/copy:DAT"; //copy attributes
    let commandOpt3 = "/MT:32"; //use 32 I/O threads (low CPU still, but better bandwidth utilization)
    let commandOpt4 = "/Z"; //idr
    let commandOpt5 = "/R:2"; //Retry twice
    let commandOpt6 = "/W:03"; //Wait 3 sec between tries
    let commandOpt7 = "/v"; //verbose logging
    let commandLogStr = "/LOG:\"C:\\Test\\Test 3\\logfile.log\""; //record where the log file goes
    let commandLogNoParenthStr = "/LOG:C:\\Test\\Test 3\\logfile.log"; //record where the log file goes
    let command = format!("robocopy \"C:\\Test\\Test 1\" \"C:\\Test\\Test 2\" {} {} {} {} {} {} {} {}",
    commandOpt1,commandOpt2,commandOpt3,commandOpt4,commandOpt5,commandOpt6,
    commandOpt7,commandLogStr); //build the command
    let commandStr: &str = &*command; //these two types of strings are
    println!("TEST-Command for robocopy:{}",command);

    //let m = Command::new("cmd.exe").arg("/c").arg(commandStr).output().unwrap(); //run cmd.exe net use
    /*let m = Command::new("cmd.exe")
        .arg("/c")
        .arg("robocopy")
        .arg("\"C:\\Test\\Test 1\"")
        .arg("\"C:\\Test\\Test 2\"")
        .arg(commandOpt1)
        .arg(commandOpt2)
        .arg(commandOpt3)
        .arg(commandOpt4)
        .arg(commandOpt5)
        .arg(commandOpt6)
        .arg(commandOpt7)
        .arg(commandLogStr)
        .output().unwrap(); //run cmd.exe net use */
    let m = Command::new("robocopy")
        .arg("C:\\Test\\Test 1")
        .arg("C:\\Test\\Test 2")
        .arg(commandOpt1)
        .arg(commandOpt2)
        .arg(commandOpt3)
        .arg(commandOpt4)
        .arg(commandOpt5)
        .arg(commandOpt6)
        .arg(commandOpt7)
        .arg(commandLogNoParenthStr )
        .output().unwrap(); //run cmd.exe net use */
    let mOutput = String::from_utf8_lossy(&m.stdout); //get the output
    println!("TEST-cmd output: {}",mOutput);
    println!("TEST-cmd status: {}", m.status.success()); //reports success (true or false) of command
    println!("TEST-cmd stderr: {}", String::from_utf8_lossy(&m.stderr)); //reports error words of command
    let _ = Command::new("cmd.exe").arg("/c").arg("pause").status(); // wait for user input
}

The code gives two options, one to call robocopy in one .arg() addition and one with the .arg()s split up. For me, they give the same result - but options are good!

Also, as a note, I send everything into Command as a &str but that seems to not be required - Strings can be .arg()s too.

windows
cmd
rust
asked on Stack Overflow Jan 11, 2019 by user2403531 • edited Jan 12, 2019 by user2403531

1 Answer

1

I do not have a Windows machine nearby but I'm quite sure that the problem is in the quote characters ".

Escape rules in CMD.exe are quite weird: sometimes you need quotes, sometimes you do not, and sometime you must not use them.

In your case, the log file, for example is chosen with the following option (removing Rust escapes): /LOG:"C:\Test\Test3\logfile.log". This is a file name that starts and ends with a quote. And since it does not start with a drive letter or a \, the OS thinks: surely it is a relative path, let's look for C:\...\"C:\Test..."!

The solution is easy: just remove all those quote.

What I don't understand is why are you calling cmd.exe /c instead of just calling robocopy directly. Quotes in the command line are used to guide the command line parser and properly manage filenames with spaces and so on. But if you do Command::new() of robocopy directly, then there is no need for quoting because the arguments are already passed separatedly.

answered on Stack Overflow Jan 11, 2019 by rodrigo

User contributions licensed under CC BY-SA 3.0