I am writing a tokio-based program and would like to run a "sidecar" process alongside it that I will communicate with over a Unix Domain socket. This program supports systemd-style socket activation, so I can pass it an opened fd and not have to put the socket on the filesystem. Unfortunately, I can't quite make it work:
use tokio::net::UnixStream;
use tokio::process::Command;
let (parent, child) = UnixStream::pair();
let mut Command::new(path);
unsafe {
cmd.pre_exec(move || {
// I know this won't work
let _ = nix::env::clearenv();
std::env::set_var("LISTEN_FDS", "1");
std::env::set_var("LISTEN_FDNAMES", "main");
std::env::set_var("LISTEN_PID", format!("{}", std::process::id()));
nix::unistd::dup2_raw(&child, 3)?;
Ok(())
});
}
let mut handle = cmd.spawn().expect("failed to start child");
This, of course, fails. Any line other than the dup2_raw call inside the pre_exec call causes process creation to hang during the spawn() call, never even reaching the expect.
I didn't expect this to work: I know set_var is not safe in this context, but I wrote this to explain what I need to accomplish. The tricky part is providing the process its own PID in an environment variable. That is part of systemd's protocol for passing fds and unfortunately my child validates it.
Is there any way to do this in a relatively simple manner? My next thought was to manually call fork() and construct an envp value, including parsing the PID to a string, and then calling exec(), but then I would also have to reimplement everything around watching a PID for exiting, etc.
(A so-dumb-it-might-just-work solution would be to invoke via a shell: sh -c 'exec env -i LISTEN_FDS=2 LISTEN_FDNAMES=main LISTEN_ID=$$ child', but I would prefer to not have to require a shell and env as dependencies.)