I'm currently working on a Erlang-like port system. In Erlang a port is a forked os-process usually written in C which communicates with the Erlang-VM via Stdin/Stdout using a simple binary protocol and serialized datastructures in Erlang Binary Term Format.
http://erlang.org/doc/apps/erts/erl_ext_dist.html
This provides a sandbox-like environment for FFI/RPC.
By default the port program is reading from STDIN and writing to STDOUT. To avoid interference in the port communication and allow writing debug output, it is possible to use filedescriptors 3 and 4 instead of 0 and 1 for communication (option: nouse_stdio
).
http://erlang.org/doc/man/erlang.html#open_port-2
I have a prototype running which communicates via Stdin/Stdout but I'd like to switch to FD 3 and 4. How can I open these filedescriptors on the rust side?
I tried something like that, but I get a SIGSEGV when using it:
let ifd = unsafe {
let no = libc::fileno(libc::fdopen(3, CString::new("r").unwrap().as_ptr()));
Stdio::from_raw_fd(no)};
let ofd = unsafe {
let no = libc::fileno(libc::fdopen(4, CString::new("a").unwrap().as_ptr()));
Stdio::from_raw_fd(no)};
let cmd = Command::new("my_port")
.stdin(ifd)
.stdout(ofd);
From man fdopen
:
The fdopen() function associates a stream with the existing file descriptor, fd
.
fdopen
doesn't create a file descriptor for you.
I think you want to use pipe2
.
Wouldn't this do what you want ?
let cmd = Command::new("my_port")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.expect("my_port failed");
I used Stdio::piped()
when using STDIN and STDOUT and it works. But I want to avoid using these because the binary data communication intereferes with e.g. printf
debug output. So I need another channel for this.
Perhaps I must ask Erlang-People how they open FD 3 and 4 for communication.
On the C (actually C++) side I do it with boost::iostreams
like this:
boost::iostreams::file_descriptor_source ifd(3, boost::iostreams::never_close_handle);
boost::iostreams::stream<boost::iostreams::file_descriptor_source> is(ifd);
Well that's what's going to happen anyway if you use .stdin()
and .stdout()
on Command
. These methods replace the STDIN
and STDOUT
in the command executed with whatever Stdio
you provided. You can see it done in do_exec()
in libstd/sys/unix/process.rs
:
if let Some(fd) = stdio.stdin.fd() {
t!(cvt_r(|| libc::dup2(fd, libc::STDIN_FILENO)));
}
if let Some(fd) = stdio.stdout.fd() {
t!(cvt_r(|| libc::dup2(fd, libc::STDOUT_FILENO)));
}
if let Some(fd) = stdio.stderr.fd() {
t!(cvt_r(|| libc::dup2(fd, libc::STDERR_FILENO)));
}
So really it doesn't matter wether you use Stdio::pipep()
or you open an fd
yourself and feed it to Stdio::from_raw_fd()
. The end result is you're overwriting the STDIN
and STDOUT
of the forked process. (By default, if you use spawn()
, the forked process would inherit the same STDIN|OUT|ERR
as the current process.)
If you wish to use additional fd
s for communication, use pipe2
to create the fd
s of a unidirectional pipe. Do it twice if you want two pipes for bidirectional communication between the processes. Then you fork
the process. In the parent, close the write fd
of the first pipe and the read fd
of the second. In the child do the reverse and then use dup2
on your two open fd
s to duplicate them as 3 and 4 and then close the original ones. You may now call exec
. Since 3 and 4 are not marked as FD_CLOEXEC
, they should still be open and usable for communication in the new program running.
To avoid calling fork
and exec
manually, you probably want to use the before_exec()
method from CommandExt
to close
and dup
the fd
s in a closure.
You'll most likely want to set the FD_CLOEXEC
flag in your pipe2
calls. This way you still need to close two fd
s in the parent process but none in the child, they'll be cleaned up automatically (without affecting the fd
s 3 and 4 created with dup2
).
Well that's the procedure for linux 2.6.27+ anyway. If you want to support older kernels, you'll have to use pipe
and not pipe2
(see libstd/sys/unix/pipe.rs
).
For windows I don't know how to do that. You could check out pipe.rs
and process.rs
in libstd/sys/windows
to get an idea of how Command
does it.
1 Like
Thank you very much for your detailed answer. I'll try my luck and perhaps someday a new crate will appear in crates.io
1 Like