rust-lang / rust

Empowering everyone to build reliable and efficient software.
https://www.rust-lang.org
Other
97.75k stars 12.65k forks source link

std::process::Command feature request: .launch() or .start() #50679

Closed mark-summerfield closed 6 years ago

mark-summerfield commented 6 years ago

The current API for std::process::Command() is ideal for starting an external process and waiting for it to finish. However, there are use cases where you want to start an external process, get immediate feedback on whether it started or not, and then continue (or finish), without waiting for the external process to finish at all.

For example, you might have --help for showing command line options, and --manual to show a full manual which you want to show by starting a web browser at a given URL but don't want the browser window to close when the rust app finishes straight after opening the browser. Similarly, you might have an app that produces results and has a --show option that tells the app to launch an app to show the results (spreadsheet, photoeditor, whatever); and again, you want the results window to hang around even though the command line app has finished.

This is probably easy on Unix; but I can't find a way to do it on Windows.

sfackler commented 6 years ago

Isn't that what Command::spawn does?

mark-summerfield commented 6 years ago

Not on Windows. If you use spawn() to run cmd.exe /C start "" http://www.site.com/page.html". If the user has a browser already running, this works fine, and the rust app can terminate with no problems. But if they don't have a browser running the browser will be started: but then when the rust app terminates, it will also terminate the browser. And this is the problem I'm trying to solve.

the8472 commented 6 years ago

The docs on Child say

There is no implementation of Drop for child processes, so if you do not ensure the Child has exited then it will continue to run, even after the Child handle to the child process has gone out of scope.

So it should already work. Have you tried usiing start instead of cmd?

mark-summerfield commented 6 years ago

Here's an example:

use std::process::Command;
use std::thread;
use std::time;

fn main() {
    println!("Start");
    let _child = Command::new("cmd.exe")
        .arg("/C").arg("start").arg("").arg("http://www.rust-lang.org")
        .spawn().expect("failed to launch browser");
    thread::sleep(time::Duration::new(10, 0)); // Windows needs time!     
    println!("End");
}

If no web browser is running: If you do cargo run this will compile & output 'Start', then after a second or two will start the browser at the correct page. Then, after ~10 sec the browser will close and 'End' is printed. However if a web browser is already running, the browser is left to continue after 'End' is printed (which is the behavior I'm after in either case).

It is not possible to just use start on its own since it is not a .exe. For example:

use std::process::Command;
use std::thread;
use std::time;

fn main() {
    println!("Start");
    let _child = Command::new("start")
        .arg("").arg("http://www.rust-lang.org")
        .spawn().expect("failed to launch browser");
    thread::sleep(time::Duration::new(10, 0)); // Windows needs time!     
    println!("End");
}

Doesn't work:

V:\tmp\myapp>cargo run --release --
   Compiling myapp v0.1.0 (file:///V:/tmp/myapp)
    Finished release [optimized] target(s) in 1.67 secs
     Running `target\release\myapp.exe`
Start
thread 'main' panicked at 'failed to launch browser: Os { code: 2, kind: NotFound, message: "The system cannot find the file specified." }', libcore\result.rs:945:5 note: Run with `RUST_BACKTRACE=1` for a backtrace. error: process didn't exit successfully: `target\release\myapp.exe` (exit code: 101)
abonander commented 6 years ago

What happens if you start the command without piped I/O? The Child may not be killed when it's dropped but it also contains handles for the child's stdio pipes which are closed on-drop; cmd or the browser might be quitting because those pipes are being closed but it may stay alive if no pipes were opened to begin with.

Addendum: spawn() actually causes the child to inherit the current process' stdio which is closed when the process exits. If you set all three streams to Stdio::null() does the browser still quit with your program?

retep998 commented 6 years ago

cargo run

cargo run creates a job object which terminates all processes when cargo terminates. This means that when your 10 second sleep timer is up and your program exits, any processes that were spawned by your program will be terminated. This is not the fault of Command but of the way cargo chooses to use job objects.

Related issue: https://github.com/rust-lang/cargo/issues/4575

mark-summerfield commented 6 years ago

retep998: you are quite right! When I run the .exe directly it works as expected: the browser is launched at the right page or a new tab is opened if it is already running, and it keeps running even when the rust .exe finishes.

Sorry for my mistake!

mbodm commented 2 years ago

I would give you 10+ likes , if i could. Your „mistake“ saved my day! 😁

Zymlex commented 4 months ago
let mut cmd = Command::new("C:/Windows/System32/rundll32.exe");
cmd.args(
    ["url.dll,FileProtocolHandler", url]
);
cmd.spawn()?;

Not the best solution, but better than nothing...

lucioreyli commented 2 months ago

Only Command::new("rundll32.exe") or omitting Command::new("rundll32") looks great, if case C:/ disk doesn't exists

Zymlex commented 2 months ago

Only Command::new("rundll32.exe") or omitting Command::new("rundll32") looks great, if case C:/ disk doesn't exists

hm, as in the case of a windows disk installer that use X:

in the case of just calling the command by name, there are search priorities and through this the binaries can be spoofed + if you don't specify an extension, the com program will be called first, which can also be spoofed.