Christian
Christian

Reputation: 942

How to redirect stderr from inside the process in Rust?

I'm attempting redirect the Stderr file descriptor from inside the process, but there seems to be no implementations of it, and I don't see a clear route to anything similar to dup2 from C/C++.

I've tried:

I also thought about using libc::dup2 directly - though I'm weary of it.

Upvotes: 9

Views: 9187

Answers (2)

Victor Sergienko
Victor Sergienko

Reputation: 13475

On POSIX systems (Linux/BSD/MacOS) there is an unsafe (in more than one way) hack that lets you do it on a lower level, using libc crate.

Namely, yank and substitute the standard file descriptors 0 (stdin), 1 (stdout) and 2 (stderr) before the program opens any more files.

It relies on the fact that libc will assign the first (lowest) descriptor available to a newly open file. This is guaranteed by POSIX.

  • In C, FILE* stdout can be reassigned with fdopen(), and, as printf() and others work with FILE* stdout, this will do the trick.
  • But Rust's standard library works with file descriptors, so we have to rely on OS or libc implementation details.

I substitute stdout descriptor in this example. It worked on MacOS.

use std::ffi::c_char;
use libc;


fn main() {

    unsafe {
        let out_file_path = "console.txt".to_string().as_ptr() as *const c_char;
        // let open_flag = libc::O_WRONLY | libc::O_CREAT | libc::O_APPEND | libc::FD_CLOEXEC;

        libc::close(libc::STDOUT_FILENO);
        let new_fd = libc::creat(out_file_path, libc::S_IRUSR | libc::S_IWUSR);
        if new_fd < 0 {
            libc::perror(std::ptr::null());
        }
        eprintln!("new fd: {}, expected {}", new_fd, libc::STDOUT_FILENO);
    }

    println!("Yohoho!");
}

There also must be a way to do it on Windows, like in this question, but I will leave this as an exercise for the reader.

Upvotes: 2

Shepmaster
Shepmaster

Reputation: 430554

There is no way to do this in the standard library1. The gag crate allows redirecting stderr or stdout either to a file or to nothing, but it only works on *nix systems.

In a different view of the problem, I'd encourage you to not use stdout or stderr directly at all. Instead, use dependency injection to pass down values that can be written to. Instead of using println, use writeln.

See also:


1 This isn't strictly true. Have you ever noticed that stdout and stderr are not output during tests? That's because the compiler (and the test suite) make use of a pair of unstable, hidden functions that allow changing the thread-local instances of stdout and stderr.

See also:

Upvotes: 11

Related Questions