Communicating with a cygwin program from Rust?

I have a Linux program where a C program calls pipe a couple of times, then fork and execs a Rust program (passing the pipe fds as arguments). The C and Rust programs then communicate via those pipes.

In Windows the C program must be built with Cygwin (it has too many unix-isms). Although there is no Rust for cygwin, the Rust program builds fine as a true Windows program. The only problem then is communication. I wondered if anyone else had ever tried to handle this?

I was interested in whether this could work, myself, so I tried it out. I don’t think Cygwin’s file descriptors can be passed directly to a pure Windows program, so I retrieved the Windows handle using _get_osfhandle and passed that instead. The Rust end is then fairly straightforward, though a little unsafe:

fn main() {
    let args: Vec<String> = env::args().collect();
    let h = i64::from_str_radix(&args[1], 16).unwrap();
    let mut f = unsafe { File::from_raw_handle(std::mem::transmute(h)) };
    f.write("Hello, world!".as_bytes()).unwrap();
}

I used posix_spawn from C, and the handle was inherited properly. The C program is a bit longer, and doesn’t really handle errors properly:

#include <io.h>
#include <spawn.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <windows.h>

int main() {
    int fildes[2];

    if (pipe(fildes) == -1) {
        perror("pipetest: pipe");
        exit(1);
    }

    long w = _get_osfhandle(fildes[1]);
    char wbuf[50];
    sprintf(wbuf, "%lx", w);
    char *client_argv[3] = { "client", wbuf, NULL };

    posix_spawn_file_actions_t file_actions;
    posix_spawn_file_actions_init(&file_actions);
    posix_spawn_file_actions_addclose(&file_actions, fildes[0]);

    pid_t pid;
    int status = posix_spawn(&pid, "client/target/debug/client.exe", &file_actions, NULL, client_argv, environ);
    if (status != 0) {
        fprintf(stderr, "pipetest: posix_spawn: %s", strerror(status));
        exit(1);
    }

    close(fildes[1]);
    posix_spawn_file_actions_destroy(&file_actions);

    char buf[50];
    int n = read(fildes[0], buf, 50);
    if (n == -1) {
        perror("pipetest: read");
        exit(1);
    }

    printf("Received: [%.*s]\n", n, buf);
    exit(0);
}

If this ended up being a bit flaky, I would try using temporary named pipes, much like the Cygwin pipe implementation does internally, and passing the name of the pipe to Rust. This would just be a bit of Windows-specific code at the C end.

1 Like