Reputation:
I want to open some files, redirect the output of them and then go back to previous situation, so I wrote:
int fd = open("test.txt", O_WRONLY | O_CREAT, 0666);
dup(1);
dup2(3, 1);
close(3);
//call my func() to print in std::cout which will write to test.txt
dup2(4, 1);//Close file
I know that the default input channel is in index 0, output channel (screen) in index 1 and stderr
in index 2 so fd
will get 3, dup
1 will create another pointer to output channel in index 4, then I replace screen with my file and close that isn't needed.
My code is correct, but did I forget anything else or can it be made shorter/ more clear?
Most importantly, in case one line fails I want to return false but this will be a lot of checks and in every advance step we need to close more. How can I solve this problem?
What if I wanted in case of failure to make everything go back to default situation?
Please Note, I only know and want to use open,close,dup,dup2
Upvotes: 0
Views: 2479
Reputation: 2745
Most importantly it is the duty of a programmer to write reliable, faultless code, and this includes thorough error checking. Error checking often makes for large parts of the code, especially when user interaction is part of the program. But, there is no way around it, if you want to be/become a good programmer.
That said, it is easy (in a first step) to change your code so that it does not depend on specific file descriptor numbers (not tested).
int fileFd;
int redirectFd1;
int redirectFd2;
fileFd = open( "test.txt", O_WRONLY | O_CREAT, 0666 );
redirectFd1 = dup( 1 );
redirectFd2 = dup2( fileFd, 1 );
close( fileFd );
//call my func() to print in std::cout which will write to test.txt
dup2( redirectFd1, 1 );//Close file
Next step is to add error checking.
int fileFd;
int redirectFd1;
int redirectFd2;
if ( ( fileFd = open( "test.txt", O_WRONLY | O_CREAT, 0666 ) ) == -1 ) {
// open() failed. Do appropriate error handling here...
exit( 1 );
}
if ( ( redirectFd1 = dup( 1 ) ) == -1 ) {
// dup() failed. Do appropriate error handling here...
// Since we arrvied here, fileFd *is* open. So we need to close it.
// But redirectFd1 is *not* open
close( fileFd );
exit(1);
}
if ( ( redirectFd2 = dup2( fileFD, 1 ) ) == -1 ) {
// dup() failed. Do appropriate error handling here...
// Since we arrvied here, fileFd *and* redirectFd1 *are* open.
// So we need to close them.
// But redirectFd2 is *not* open
close( fileFd );
close( redirectFd1 );
exit(1);
}
close( fileFd );
//call my func() to print in std::cout which will write to test.txt
if ( dup2( redirectFD1, 1 ) == -1 ) {
// dup2() failed. Do appropriate error handling here...
close( redirectFd1 );
}
close( redirectFd1 );
One can argue whether checking for errors on the close( fileFd )
statement is needed or nor. Fact is that open()
succeeded, so it would be very unsusual for close()
to fail here. One can also argue whether checking for errors on the last dup2()
is needed or not.
In general, I keep track of open files, and take care of closing in case of errros in a clean-up routine. The code might look like this:
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
int fileFd;
bool fileFdIsOpen = false;
int redirectFd1;
bool redirectFd1IsOpen = false;
int redirectFd2;
bool redirectFd2IsOpen = false;
void cleanup();
int main( int argc, char* argv[] ) {
int stdoutFd = fileno( stdout );
if ( ( fileFd = open( "test.txt", O_WRONLY | O_CREAT, 0666 ) ) == -1 ) {
// open() failed. Do appropriate error handling here...
exit( 1 );
}
fileFdIsOpen = true;
if ( ( redirectFd1 = dup( stdoutFd ) ) == -1 ) {
// dup() failed. Do appropriate error handling here...
cleanup();
exit(1);
}
redirectFd1IsOpen = true;
if ( ( redirectFd2 = dup2( fileFd, stdoutFd ) ) == -1 ) {
// dup() failed. Do appropriate error handling here...
cleanup();
exit(1);
}
redirectFd2IsOpen = true;
close( fileFd );
fileFdIsOpen = false;
//call my func() to print in std::cout which will write to test.txt
if ( dup2( redirectFd1, stdoutFd ) == -1 ) {
// dup2() failed. Do appropriate error handling here...
cleanup();
}
cleanup();
exit (0);
}
void cleanup() {
if ( fileFdIsOpen ) {
close( fileFd );
fileFdIsOpen = false;
}
if ( redirectFd1IsOpen ) {
close( redirectFd1 );
redirectFd1IsOpen = false;
}
if ( redirectFd2IsOpen ) {
close( redirectFd2 );
redirectFd2IsOpen = false;
}
}
Upvotes: 2
Reputation: 14589
I know that default input channel is in index 0, output channel (screen) in index 1 and stderr in index 2 so fd will get 3
That's undetermined, process can be run in away those ids would be altered and starting descriptor also might be different from stderr+1. Theremore, on Linux there are situation where process can be created with none open, but that's different.
In C header <stdio.h>
defines those variables that are supposed to be initialized by runtime library.
extern FILE *stdin;
extern FILE *stdout;
extern FILE *stderr;
Code you had shown is pretty much C code, not C++, but you can use <cstdio>
and fileno
function to obtain descriptor
int fileno(FILE *stream);
You have to check returned results of fileno
, dup
and open
to be sure that you program didn't run into some limitations imposed on it, and errno
variable would tell you a reason. On some other platforms, like Windows, more appropriate error reporting functions do exist.
Upvotes: 0