How to implement redirection of stdout of a child process to a file?
Asked Answered
P

2

12

I am implementing I/O redirection in a shell written in Rust. I succeeded in piping between two children processes by using unsafe code with raw file descriptors and pipe() from the libc crate.

When I try to redirect stdout of the last child process to a file that I have permission to, it fails:

extern crate libc;
use std::process::{Command, Stdio};
use std::os::unix::io::{FromRawFd, IntoRawFd};
use std::fs::File;
use self::libc::c_int;

fn main() {
    let mut fds = [-1 as c_int, -1 as c_int];
    let fd1 = File::open("test1").unwrap().into_raw_fd();
    let fd2 = File::open("test2").unwrap().into_raw_fd();
    let fd3 = File::open("test3").unwrap().into_raw_fd();
    println!("{:?}, {:?}, {:?}", fd1, fd2, fd3);
    unsafe {
        libc::pipe(&mut fds[0] as *mut c_int);
        let cmd1 = Command::new("ls")
            .arg("/")
            .stdout(Stdio::from_raw_fd(fds[1]))
            .spawn()
            .unwrap();
        let mut cmd2 = Command::new("grep")
            .arg("etc")
            .stdin(Stdio::from_raw_fd(fds[0]))
            .stdout(Stdio::from_raw_fd(fd1))
            .spawn()
            .unwrap();
        let _result = cmd2.wait().unwrap();
    }
}

The result of the above piece:

3, 4, 5
grep: write error: Bad file descriptor

It seems that the file descriptor isn't correctly returned, but if there were no file named test1, test2 and test3, File::open(_).unwrap() should panic instead of pretending to have opened a file.

The code works perfectly fine if redirection to the file is removed, i.e. only piping is used.

Penick answered 10/3, 2017 at 19:15 Comment(0)
R
12

The documentation for File::open states (emphasis mine):

Attempts to open a file in read-only mode.

Switching to File::create appears to create the file and "etc" is written to it.


Additionally, you should:

  1. Not open 2 additional files - nothing ever closes those file descriptors so you have a resource leak.
  2. Check the return value of pipe to handle errors.
  3. Check out the nix crate.
extern crate libc;
extern crate nix;

use std::process::{Command, Stdio};
use std::os::unix::io::{FromRawFd, IntoRawFd};
use std::fs::File;

use nix::unistd::pipe;

fn main() {
    let fds = pipe().unwrap();
    let fd1 = File::create("test1").unwrap().into_raw_fd();

    let (pipe_in, pipe_out, file_out) = unsafe {
        (Stdio::from_raw_fd(fds.0),
         Stdio::from_raw_fd(fds.1),
         Stdio::from_raw_fd(fd1))
    };

    Command::new("ls")
        .arg("/")
        .stdout(pipe_out)
        .spawn()
        .unwrap();

    let mut cmd2 = Command::new("grep")
        .arg("etc")
        .stdin(pipe_in)
        .stdout(file_out)
        .spawn()
        .unwrap();

    cmd2.wait().unwrap();
}
Recliner answered 10/3, 2017 at 19:40 Comment(0)
Z
13

Since Rust 1.20.0 (released on 2017-08-31), you can now directly create Stdio from a File:

let file = File::create("out.txt").unwrap();
let stdio = Stdio::from(file);

let command = Command::new("foo").stdout(stdio);
Zimmermann answered 3/8, 2022 at 16:15 Comment(0)
R
12

The documentation for File::open states (emphasis mine):

Attempts to open a file in read-only mode.

Switching to File::create appears to create the file and "etc" is written to it.


Additionally, you should:

  1. Not open 2 additional files - nothing ever closes those file descriptors so you have a resource leak.
  2. Check the return value of pipe to handle errors.
  3. Check out the nix crate.
extern crate libc;
extern crate nix;

use std::process::{Command, Stdio};
use std::os::unix::io::{FromRawFd, IntoRawFd};
use std::fs::File;

use nix::unistd::pipe;

fn main() {
    let fds = pipe().unwrap();
    let fd1 = File::create("test1").unwrap().into_raw_fd();

    let (pipe_in, pipe_out, file_out) = unsafe {
        (Stdio::from_raw_fd(fds.0),
         Stdio::from_raw_fd(fds.1),
         Stdio::from_raw_fd(fd1))
    };

    Command::new("ls")
        .arg("/")
        .stdout(pipe_out)
        .spawn()
        .unwrap();

    let mut cmd2 = Command::new("grep")
        .arg("etc")
        .stdin(pipe_in)
        .stdout(file_out)
        .spawn()
        .unwrap();

    cmd2.wait().unwrap();
}
Recliner answered 10/3, 2017 at 19:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.