Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Start another program then quit

Tags:

process

rust

From a program A written in rust, I want to start a program B, have A end, and have B normally run just like if it was manually launched from the same shell just after termination of A.

My current program:

use std::process::Command;

pub fn execute(exe: &str, args: &[&str]) {
    Command::new(exe)
        .args(args)
        .spawn()
        .expect("failed to start external executable");
}

fn main() {
    execute("/usr/bin/nvim", &["/home/dys/todo.txt"]);
}

This fails. nvim is launched as a child and is non-working as soon as the calling program stops.

How can I write execute so the caller program immediately stops and lets nvim (or another program) properly run (even without any windowing system) ?

like image 474
Denys Séguret Avatar asked Nov 26 '18 09:11

Denys Séguret


1 Answers

After further discussion, we identified the actual problem: The program you are launching is supposed to stay in the foreground, so it can read from the terminal (which background processes can't do on Unix).

There are two ways to achieve this. The first, and easiest, is to wait for the child process before the parent process exits:

use std::process::{Command, ExitStatus};
use std::io::Result;

pub fn execute(exe: &str, args: &[&str]) -> Result<ExitStatus> {
    Command::new(exe).args(args).spawn()?.wait()
}

This ensures the processes (parent and child) stay in the foreground, since the shell is waiting for the parent process, so the child process can read from the terminal.

If for some reason you can't afford the parent process to linger on while the child process is running, you need platform-dependent code. On Unix, you can use some syscall from the exec() familiy to replace the image of the parent process with the image of the child process:

use std::process::Command;
use std::os::unix::process::CommandExt;
use std::io::Error;

pub fn execute(exe: &str, args: &[&str]) -> Error {
    Command::new(exe).args(args).exec()
}

The function only returns if there is an error. Otherwise, the process image is replaced by the new image. From the viewpoint of the shell, it's still the same process, so the shell will wait for the command you launched to finish.

The advantages of the second approach seem slim. It does not work on Windows, since Windows does not support exec() and friends. You will have one less process around while running the command, but the resource usage of that process should be small in practice – it does not use any CPU, and the memory pages can be swapped out if necessary.

Original Answer

From a program A written in rust, I want to start a program B, have A end, and have B normally run just like if it was manually launched from the same shell just after termination of A.

This is more or less what your code is already doing. There are a few differences to a process launched directly from the shell on Unix systems, though:

  • The new process will not be included in the shell's job list, so you can't use the shell's job control commands like bg and fg.
  • The new process will run in the background, and the shell will immediately show a prompt after the Rust programs exits.

This fails because nvim is launched as a child and is killed as soon as the calling program stops.

This is not true, neither for Unix nor for Windows.

How can I write execute so the caller program immediately stops and lets nvim (or another program) properly run (even without any windowing system)?

This should be exactly what your Rust code is doing (and what it does when run on my Linux machine). The code in your answer, on the other hand, does something else: It uses execv() to replace the Rust process with nvim. In effect, the process does not immediately stop, and the shell remaind blocked until nvim exits.

like image 109
Sven Marnach Avatar answered Oct 14 '22 18:10

Sven Marnach