0

I'm having a strange issue using the dup2 system call to redirect STDOUT to a file.

I'm using 2 functions which i found here: In C how do you redirect stdin/stdout/stderr to files when making an execvp() or similar call?

Below is a simple program i wrote to test the functions after i had errors. The program works as expected and writes the input to the file.

int fd;
fpos_t pos;

int main(){
    while(1){
        char input[100];
        printf("Please enter text: ");
        gets(input);
        printf("\nString = %s\n", input);

        switchStdout("test.txt");

        puts("THIS TEXT SHOULD REDIRECT\n");
        printf("String(file) = %s\n", input);

        revertStdout();

        puts("This should come before the gets() ??\n");
    }
    return 0;
}

void switchStdout(const char *newStream)
{
  fflush(stdout);
  fgetpos(stdout, &pos);
  fd = dup(fileno(stdout));
  freopen(newStream, "w", stdout);
  return;
}

void revertStdout()
{
  fflush(stdout);
  dup2(fd, fileno(stdout));
  close(fd);
  clearerr(stdout);
  fsetpos(stdout, &pos);
}

After the revertStdout() function is called, the program appears to hang.

I realized that, in fact, the program has called gets() before printing "This should come before the gets() ??"

After I enter text, the program prints the skipped lines.

Here is the terminal output with what I enter in bold:

Please enter text: Hello!!!!

String = Hello!!!!

Why am i able to type here ??
This should come before the gets() ??

Please enter text:

String = Why am i able to type here ??

Sorry about the long post. The program writes as expected to the file.

Thanks for any help anyone can provide.

Community
  • 1
  • 1
  • http://stackoverflow.com/questions/1694036/why-is-the-gets-function-so-dangerous-that-it-should-not-be-used – William Pursell Apr 04 '17 at 01:28
  • If you want to reopen a stream to use a different descriptor or handle, you might want `fdopen()`. It’s not in the standard library, but Unix, Windows and OSX all support it. – Davislor Apr 04 '17 at 02:47

1 Answers1

0

Basically, that's what happens when you sneak around behind the standard library's back.

Quoting man setbuf:

Normally all files are block buffered. When the first I/O operation occurs on a file, malloc(3) is called, and a buffer is obtained. If a stream refers to a terminal (as stdout normally does) it is line buffered.

So, your stdout starts out referring to your terminal, and is line buffered. Then you freopen it to refer to a file, so (as part of reopening the stream), it becomes block-buffered. Then you dup2 the fd, so now it is referring to the terminal again, but it is still the same stream; the standard library cannot known that you have reached into the guts of stdout and changed what it refers to. So it stays block-buffered, leading to the unexpected behaviour.

Changing buffering of a stream after the first output leads to undefined behaviour, although with some C library implementations it is possible if the buffer is empty (as it will be after the call to fflush). You should not count on this behaviour. You could, however, freopen the stdout stream again immediately after the dup2, passing a NULL first argument to freopen, thereby giving the standard library a chance to reinitialise.

For what it's worth, the fflush call in switchStdout is unnecessary since freopen effectively closes the stream. If you add the freopen to revertStdout, it will reset the error and end-of-file indicators, so you can remove the call to clearerr.

rici
  • 234,347
  • 28
  • 237
  • 341