0

I am reading this lecture and found this following code sample which I modified to this:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>

int main()
{
    int fd;
    char *s, *t;
    off_t ret;

    fd = open("file6", O_WRONLY | O_CREAT | O_TRUNC, 0666);
   
    if (dup2(fd, 1) < 0) { perror("dup2"); exit(1); }
    
    printf("Standard output now goes to file6\n");
    s = "before close\n";
    write(1, s, strlen(s));
    
    close(fd);

    printf("It goes even after we closed file descriptor %d\n", fd);
    
    printf("%ld\t"
        "%ld\n",
        (long int) lseek(fd,0,SEEK_CUR), 
        (long int) lseek(1,0,SEEK_CUR));

    s = "And fwrite\n";
    
    fwrite(s, sizeof(char), strlen(s), stdout);

    printf("%ld\t"
        "%ld\n",
        (long int) lseek(fd,0,SEEK_CUR), 
        (long int) lseek(STDOUT_FILENO,0,SEEK_CUR));
    
    fflush(stdout); 

    s = "And write\n";
    write(1, s, strlen(s));
    
    printf("after:\tAnd wri...: lseek(fd,0,SEEK_CUR)=%ld\t"
        "lseek(STDOUT_FILENO,0,SEEK_CUR)=%ld\n",
        (long int) lseek(fd,0,SEEK_CUR), 
        (long int) lseek(STDOUT_FILENO,0,SEEK_CUR));
    return 0;
}

I am sharing two different outputs with the only change in the code being that the line fflush(stdout) is commented out in first and present in the second run.

Output (with fflush(stdout) commented):

before close
And write
Standard output now goes to file4
It goes even after we closed file descriptor 3
-1  13
And fwrite
-1  13
after:  And wri...: lseek(fd,0,SEEK_CUR)=-1 lseek(STDOUT_FILENO,0,SEEK_CUR)=23

Output with flush(stdout) uncommented:

before close
Standard output now goes to file4
It goes even after we closed file descriptor 3
-1  13
And fwrite
-1  13
And write
after:  And wri...: lseek(fd,0,SEEK_CUR)=-1 lseek(STDOUT_FILENO,0,SEEK_CUR)=127

I have two questions:

  • Why does "And write appears" first when fflush(stdout) is commented?
  • Why lseek prints -1 which I checked separately is an error message corresponding to errno ESPIPE. I am aware that lseek on terminal results in an error. But my current understanding is that since the standard output is dup2 to file6, then, this error shouldn't arise? Shouldn't it (lseek(STDOUT_FILENO, 0, SEEK_CUR)) simply return the current lseek pointer in file6, if dup2 is successful?
Singh
  • 55
  • 1
  • 6
  • Did you check `errno` when `lseek` fails to confirm if it's actually because it thinks its connected to a terminal? – ShadowRanger May 27 '22 at 14:28

1 Answers1

2

Why does "And write" appear first when fflush(stdout) is commented?

Because the C stdio buffers haven't filled, so nothing written using stdio APIs is actually sent to the output until the buffers fill, the stdio handle is flushed, or the program ends. Your direct write calls (e.g. for "And write") bypass stdio buffers entirely, and get written immediately, all the buffered stuff doesn't appear until the program ends (or at least, not until after "And write" has already been written).

Why lseek prints -1?

The first lseek was called on fd, which you closed shortly after dup2ing it over STDOUT_FILENO/1, so it fails. If you checked the errno properly (zeroing errno before each lseek, calling the two lseeks separately and storing or printing their errors and errnos separately, so one of them doesn't override the errno of the other before you even see it), you'd see it has a value corresponding to EBADF, not ESPIPE. The second lseek on (STDOUT_FILENO) works just fine. A mildly modified version of your code (using stderr so you can see the output for the last couple outputs even when you can't read the actual file, carefully zeroing errno each time, printing it before calling lseek again, and using strerror to show a friendly description of the errno) shows this clearly: Try it online!

ShadowRanger
  • 143,180
  • 12
  • 188
  • 271
  • I don't think this answers the question, though. The `dup2` closes STDOUT_FILENO, so why does the "second `lseek` on (`STDOUT_FILENO`) work just fine"? – William Pursell May 27 '22 at 15:07
  • 1
    @WilliamPursell: `dup2` closes the *original* file handle bound to file descriptor `1` (`STDOUT_FILENO`) and reopens it as a duplicate of `fd`, so both `fd` and `1` refer to the same file through separate descriptors. The OP then closes `fd`, making `fd` invalid, while `1` remains a valid descriptor referencing the same file `fd` used to point to. This has nothing to do with `1` being attached to a terminal (because after the `dup2`, it isn't), it's entirely about the first `lseek` failing because it was called on an `int` that corresponds to no open file handle. – ShadowRanger May 27 '22 at 15:10
  • @WilliamPursell: I doubt the OP actually checked `errno`, because this code would not have set `ESPIPE`; `ESPIPE` is *one* possible reason for `lseek` to return `-1`, and I suspect they skimmed the docs and thought it was the *only* reason `lseek` returns `-1`. Neither `fd`, nor post-`dup2` `1`/`STDOUT_FILENO`, would ever die with `ESPIPE`, but post `close(fd)`, basically all use of `fd` with file APIs would die with `EBADF`. – ShadowRanger May 27 '22 at 15:14
  • @ShadowRanger Of course. I got so wrapped up in the confusion that I forgot the purpose of the dup2! Silly mistake. – William Pursell May 27 '22 at 15:16
  • @ShadowRanger I checked ESPIPE on errno returned from `lseek(STDOUT_FILENO, 0, SEEK_CUR)`. But I acknowledge that I didn't reset errno before making calls to sleek each time. So, my method of checking on errno wasn't very attentive. – Singh May 27 '22 at 15:16
  • 1
    @Singh: Yeah, `errno` is a royal pain. It all boils down to being global state, that's changed by many different things, some of which don't bother to reset it to `0` when they succeed, while others set it unconditionally. In your original code, even if you *had* included `errno` in that final `printf`, the behavior would be unspecified (because the arguments to `printf` are evaluated in unspecified order, so the `errno` could be read before all the `lseek`s, after all the `lseek`s, or in-between, and you'd have no idea what call it corresponds to). – ShadowRanger May 27 '22 at 15:30