Passing Child Process its own PID in an Env Var

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.)

the rust standard library uses a read/write lock to access the environment block, that might be the reason it hangs. another reason is probably the format!("{}", process::id()) call, which allocates the String from the heap.

try the libc::setenv() directly, bypass rust's std::env. remember to NUL terminate the strings. for literals, use CStr literals instead of the default rust str literal, e.g. c"LISTEN_FDS".as_ptr(). for the pid, use a stack buffer to do the formatting, e.g. using std::io::Cursor to wrap a local array, which can be passed to write!(), replace the format!() call.