6

I would like to do dup2(fd, 1); close(fd); and have ::std::cout write to the new fd 1. How do I can reset the state of ::std::cout so nothing goes funny? For example, is flushing beforehand sufficient? Or is there more to do than that?

I'm also curious about the same thing with ::std::cin.

Is there a standard mechanism for resetting these if you change out the file descriptors they're using underneath them?

To be clear, my goal here is basically to redirect my own input and output someplace else. I want to not have the process inadvertently burping up something on its parent's stdout or attempting to consume anything from its parent's stdin. And I never want to touch my parent's stdin or stdout ever again. I want to forget they ever existed.

And I most especially do not want to inadvertently ever send output to the same device my parent is using on a different file descriptor.

My goal is to have cin and cout lead to completely different places than they did when the process started, and to never ever touch in any way the places where they used to lead. Ever!

Omnifarious
  • 54,333
  • 19
  • 131
  • 194
  • 2
    You can save copies of stdin/stdout, and redup them to the fd's. https://stackoverflow.com/questions/9084099/re-opening-stdout-and-stdin-file-descriptors-after-closing-them – the_storyteller Oct 17 '17 at 20:52
  • @the_storyteller - Yes, but in this case I don't want to. I basically want to do I/O redirection on my own process. I'm forking off a new process after creating a socketpair, and I want to set stdin and stdout in the new process to one end of the socketpair after forking and still be able to use `cin` and `cout` reasonably in the new process. – Omnifarious Oct 17 '17 at 20:54
  • Do you really need a redirected stdout/stdin in the forked process or would be a redirected std::cout/std::cin sufficient? – Jodocus Oct 17 '17 at 20:57
  • @Jodocus - That's a good question. I sort of want to preclude the possibility of this process hiccuping something out on stdout or attempting to capture something on stdin, but I also expect that it will only run code I where I have strict control over whether or not that happens. Simply re-opening cin/cout to new file descriptors might be an option. – Omnifarious Oct 17 '17 at 21:00
  • I think on *nix systems, you need to open `/dev/tty`. `int stdin_fd = open("/dev/tty", O_RDWR);` – the_storyteller Oct 17 '17 at 21:04
  • @the_storyteller - Yep, that'd be what I wanted to do if I wanted my process communicating with the tty at all. But I don't. I want to prevent the process from ever being able to do that while still having a working cin and cout. – Omnifarious Oct 17 '17 at 21:49

2 Answers2

7

Option 1: set stdin & stdout

According to cppreference.com:

By default, all eight standard C++ streams are synchronized with their respective C streams.

And as long as you didn't explicitly called sync_with_stdio(false), they'll stay that way. What does it mean? The following:

In practice, this means that the synchronized C++ streams are unbuffered, and each I/O operation on a C++ stream is immediately applied to the corresponding C stream's buffer. This makes it possible to freely mix C++ and C I/O.

So, flush()-ing your cin & cout before dup()-ing them should be enough, since they should be in a consistent state.

If you wish to work with files for example, you could use:

if (freopen("input.txt", "r", stdin) == NULL) {
    // Handle error, errno is set to indicate error
}

if (freopen("output.txt", "w", stdout) == NULL) {
    // Handle error, errno is set to indicate error
}

Note 1: Setting the global extern FILE * stdin or stdout won't work because it simply changes a single instance of a pointer to the relevant FILE struct of the os. Any module that copied this pointer at any moment prior to this change will continue using the old FILE. A specific example is libc++'s implementation for cout, which copies FILE * stdout to a private member during the object's init. freopen on the other hand changes the internal FILE structure of the OS to use the newly opened file, affecting anyone who has a FILE * to it.

Note 2: When using dup() flavors (rather than freopen()), we are changing the underlying fd, rather than the FILE*. The freopen() method does more than that. From POSIX:

The freopen() function shall first attempt to flush the stream associated with stream as if by a call to fflush(stream). Failure to flush the stream successfully shall be ignored. If pathname is not a null pointer, freopen() shall close any file descriptor associated with stream. Failure to close the file descriptor successfully shall be ignored. The error and end-of-file indicators for the stream shall be cleared.

dup()-ing might work, but, it might be tricky, since it won't affect other properties of the FILE*, including: Character width, Buffering state, The buffer, I/O, Binary/text mode indicator, End-of-file status indicator, Error status indicator, File position indicator & (After C++17) Reentrant lock used to prevent data races.

When possible, I suggest using freopen. Otherwise, you could follow the steps described by yourself (fflush(), clearerr()). Skipping fclose() will be wise, since we won't be able to reopen the same internal FILE by any of the API methods.

Option 2: set cin's & cout's rdbuf()

The other way around, just like some comments proposed, is replacing cin's and cout's underlying buffer using rdbuf().

What are your options here?

  • File streams: Open ifstream & ofstream and use them:

    std::ifstream fin("input.txt");
    if (!fin) {
        // Handle error
    }
    cin.rdbuf(fin.rdbuf());
    
    std::ofstream fout("output.txt");
    if (!fout) {
        // Handle error
    }
    cout.rdbuf(fout.rdbuf());
    
  • Network streams: Use boost's boost::asio::ip::tcp::iostream (It's derived from std::streambuf and thus will work):

    boost::asio::ip::tcp::iostream stream("www.boost.org", "http");
    if (!stream) {
        // Handle error
    }
    
    cin.rdbuf(stream.rdbuf());
    cout.rdbuf(stream.rdbuf());
    
    // GET request example
    
    cout << "GET /LICENSE_1_0.txt HTTP/1.0\r\n";
    cout << "Host: www.boost.org\r\n";
    cout << "Accept: */*\r\n";
    cout << "Connection: close\r\n\r\n";
    cout.flush();
    
    std::string response;
    std::getline(cin, response);
    
  • Custom streams: Use your own custom wrapper for std::streambuf. See an example here.

Daniel Trugman
  • 8,186
  • 20
  • 41
  • 1
    Interesting. So you're saying that basically if I do `dup2(something, 1); close(something);` I can then use `cout` (and C `stdout` if it comes to that) as if nothing happened? And that's guaranteed on any Posix compliant system that also has a fully conformant C++ standard library written to the Posix API? – Omnifarious Oct 21 '17 at 23:56
  • 1
    @Omnifarious, I did some more research and updated my answer to give a more thorough explanation about `dup()` flavors. – Daniel Trugman Oct 22 '17 at 00:35
  • Reading it over carefully, that's a very thorough answer. In this case, freopen isn't an option because I created the file descriptors originally with `socketpair` and I wish any child processes to continue using the connection created by `socketpair` for stdin and stdout, so stdin and stdout have to continue to refer to file descriptor 0 and 1. Basically, I'm sort of setting up my own pipeline, but I would like to be able to use it before `exec`. – Omnifarious Oct 22 '17 at 17:38
  • 1
    @Omnifarious, in my opinion, you could create your socketpair, then imitate `freopen` by using `fflush()` > `fclose()` > `clearerr()` on stdin/out and `dup2()` your socketpair to fds `0` and `1`. This should leave the `FILE* stdin` and `FILE* stdout` in a consistent state and allow you to use `std::cout` and `std::cin` with your socketpair. – Daniel Trugman Oct 22 '17 at 18:59
  • 1
    @Omnifarious, can I help you any further? Did this solution work for you? – Daniel Trugman Oct 25 '17 at 11:19
  • the is an excellent and complete answer. I tend to hold off on bounties until just before they expire. – Omnifarious Oct 25 '17 at 13:33
  • @Omnifarious, I simply wanted to make sure you had your answer, and I'm glad you did :) – Daniel Trugman Oct 25 '17 at 13:34
  • One question I have on careful reading, it seems that I should skip the `fclose` step because that would leave it in a closed state. And there is no `fdreopen`, I think, perhaps, I should just content myself with `fflush` and `clearerr()`. I don't care much about the file seek position because seeks on a character stream type file don't work anyway. But, if course, C FILE * might think that they do since stdin/stdout may have been redirected from/to a file before I intervened. – Omnifarious Oct 26 '17 at 20:22
  • I think bash lets you redirect its own stdout. Maybe I should look at the bash code to see what it does. It sounds like if I figure it out for FILE * that it'll just work for cin/cout. – Omnifarious Oct 26 '17 at 20:23
  • @Omnifarious, you are correct. Added Note #1 under the first option and a comment recommending to skip the `fclose` method. Regarding the `fseek()`, since most implementations actually use the underlying `lseek()`, I would deems it as not required, since the `fd` used with `dup2()` is probably already at the right position and the OS manages the position under the related `fd` – Daniel Trugman Oct 26 '17 at 22:00
2

You may create (or use an existing) library for a socket_streambuf class and associate this to std::cout/std::cin:

socket_streambuf<char> buffer{ "127.0.0.1:8888" }; // will call socket(), connect() or throw on failure
std::cout.rdbuf(&buffer); // re-direct cout to the network connection
std::cout << "Hello, World!\n"; // may call send() on basic_streambuf::overflow()

This way, you wouldn't have to bother about manipulating the state of the (global) C-stream buffers.

Jodocus
  • 7,493
  • 1
  • 29
  • 45
  • Is socket_streambuf part of the standard library now? Also, I'd probably attach a streambuf to an existing file descriptor. I'm using the socketpair system call to create two file descriptors. – Omnifarious Oct 17 '17 at 21:13
  • No, I fear it's not. But you can use asio's implementation for example. Even writing it from scratch isn't actually too hard, check e.g. the links in https://stackoverflow.com/questions/524641/how-do-i-create-my-own-ostream-streambuf – Jodocus Oct 17 '17 at 21:17