How should I manage ::std::cout after changing file descriptor 1 to refer to a different file?

随声附和 提交于 2021-02-19 01:22:37

问题


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!


回答1:


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.




回答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.



来源:https://stackoverflow.com/questions/46798924/how-should-i-manage-stdcout-after-changing-file-descriptor-1-to-refer-to-a-d

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!