intricacies/understanding the stdio buffer and dup2 - c

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?

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!

Related

How is second scanf working after closing standard input

How is the second scanf working in the below code
#include <stdio.h>
#include <string.h>
#include <unistd.h>
int main()
{
char buf[256];
int i;
write(1, "Hello World\n", strlen("Hello World\n"));
dup(0);
close(0);
scanf("%s", buf);
printf("Buffer:%s\n", buf);
dup(3);
scanf("%s", buf);
printf("Buffer:%s\n", buf);
return 0;
}
We are closing the stdin (fd:0), Then how does dup(3) have the effect of reopening the stdin?
I think when you check the return value of these two call of dup, you will find the first is 3, and the second is 0. So before the second scanf function is called, file descriptor 0 is related with terminal again.
The documentation for dup says that it uses "the lowest-numbered unused file descriptor for the new descriptor." Since you just closed FD 0, the next time you use dup, 0 will be the lowest-numbered unused file descriptor, so that's where the duplicate FD will end up. And since you put back exactly what you got rid of, scanf is happy to work again after doing so.

what will happen calling printf after close stdout?

I tried below code, and screen showed nothing.
close(STDOUT_FILENO);
printf("Child output something\n");
is it just can not find the stdout,then abort the data?
I want to find wether printf write some data, since I can not print the return value so I output it to some file.
close(STDOUT_FILENO);
int res = printf("output something\n");
open("./log.output", O_CREAT|O_WRONLY|O_TRUNC, S_IRWXU);
printf("%d", res); // return 17
So printf work, but I don't know where it write to.
The reason you're seeing this result has to do with buffering. In general, a file which is attached to a terminal is line buffered and all other files are block buffered. stderr is unbuffered.
When you close stdout, it's no longer attached to a terminal, so it's block buffered, not line buffered. You've attempted to write fewer bytes than the buffer size (which is usually some multiple of 512), so printf happily copied it to the buffer and did nothing else. If you wrote a suitable amount of data using printf, you'd find that it did indeed fail at that point.
You can verify a similar behavior by calling fflush(stdout):
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
int main(void)
{
close(STDOUT_FILENO);
int res = printf("output something\n");
fprintf(stderr, "%d\n", res);
res = fflush(stdout);
fprintf(stderr, "%d %s\n", res, strerror(errno));
}
The last line will output -1 Bad file descriptor, which shows that the attempt to write out to stdout failed with EBADF, as expected. If you need to verify that data has been written, you must call fflush or fsync as appropriate.
Note that in general, you don't want to close any of the three default file descriptors, because any time you open a new file descriptor, it will use the lowest unused number and take the place of one of the standard streams. If a separate part of your program attempts to write to one of those streams without checking, it can write into an unexpected file, corrupting it. The safe thing to do is redirect those streams to /dev/null instead.
Your open call for log.output does exactly the thing I just mentioned in that it opens file descriptor 1 (stdout) again.

stdio to terminal after close(STDOUT_FILENO) behavior

I am wondering why uncommenting that first printf statement in the following program changes its subsequent behavior:
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
int main() {
//printf("hi from C \n");
// Close underlying file descriptor:
close(STDOUT_FILENO);
if (write(STDOUT_FILENO, "Direct write\n", 13) != 13) // immediate error detected.
fprintf(stderr, "Error on write after close(STDOUT_FILENO): %s\n", strerror(errno));
// printf() calls continue fine, ferror(stdout) = 0 (but no write to terminal):
int rtn;
if ((rtn = printf("printf after close(STDOUT_FILENO)\n")) < 0 || ferror(stdout))
fprintf(stderr, "Error on printf after close(STDOUT_FILENO)\n");
fprintf(stderr, "printf returned %d\n", rtn);
// Only on fflush is error detected:
if (fflush(stdout) || ferror(stdout))
fprintf(stderr, "Error on fflush(stdout): %s\n", strerror(errno));
}
Without that first printf, the subsequent printf rtns 34 as if no error occured even though the connection from the stdout user buffer to the underlying fd has been closed. Only on a manual fflush(stdout) does the error get reported back.
But with that first printf turned on, the next printf reports errors as I would expect.
Of course nothing is written to the terminal(by printf) after the STDOUT_FILENO fd has been closed in either case.
I know it's silly to close(STDOUT_FILENO) in the first place here; this is an experiment I stumbled into and thinking someone more knowledgeable in these areas may see something instructive to us in it..
I am on Linux with gcc.
If you strace the both programs, it seems that stdio works so that upon first write, it checks the descriptor with fstat to find out what kind of file is connected to the stdout - if it is a terminal, then stdout shall be line-buffered, if it is something else, then stdout will be made block-buffered. If you call close(1); before the first printf, now the initial fstat will return EBADF and as 1 is not a file descriptor that points to a character device, stdout is made block-buffered.
On my computer the buffer size is 8192 bytes - that many bytes can be buffered to be written to stdout before the first failure would occur.
If you uncomment the first printf, the fstat(1, ...) succeeds and Glibc detects that stdout is connected to a terminal; stdout is set to line-buffered, and thus because printf after close(STDOUT_FILENO)\n ends with newline, the buffer will be flushed right away - which will result in an immediate error.

fread() reading from a descriptor based on a pipe sets error, not EOF where there is no data

I need to read with fread() the stuff from the read end of the pipe.
But while i expect the fread() to set EOF when there is nothing in the pipe, it instead sets the error indicator. I have checked the posix and C standards and found no clue there. Probably i'm doing something unintended (read, silly), right:)
Here's the excerpt:
#include <stdio.h>
#include <fcntl.h>
int main()
{
char buf[128];
FILE *f;
int pipe_fd[2], n;
pipe(pipe_fd);
fcntl(pipe_fd[0], F_SETFL, O_NONBLOCK);
f=fdopen(pipe_fd[0], "r");
n=fread(buf, 1, 1, f);
printf("read: %d, Error: %d, EOF: %d\n", n, ferror(f), feof(f));
return 0;
}
Since you're using a non-blocking pipe, I believe you would get:
errno==EAGAIN when there simply isn't anything there to read (meaning nothing now but maybe something later - try (e)again later).
EOF when the writing side of the pipe is closed (meaning no more data is coming).
See the manpage for read() about how read() behaves when O_NONBLOCK mode is set. fread() behavior should be consistent with read().

How to use read() to read data until the end of the file?

I'm trying to read binary data in a C program with read() but EOF test doesn't work. Instead it keeps running forever reading the last bit of the file.
#include <stdio.h>
#include <fcntl.h>
int main() {
// writing binary numbers to a file
int fd = open("afile", O_WRONLY | O_CREAT, 0644);
int i;
for (i = 0; i < 10; i++) {
write(fd, &i, sizeof(int));
}
close(fd);
//trying to read them until EOF
fd = open("afile", O_RDONLY, 0);
while (read(fd, &i, sizeof(int)) != EOF) {
printf("%d", i);
}
close(fd);
}
read returns the number of characters it read. When it reaches the end of the file, it won't be able to read any more (at all) and it'll return 0, not EOF.
You must check for errors. On some (common) errors you want to call read again!
If read() returns -1 you have to check errno for the error code. If errno equals either EAGAIN or EINTR, you want to restart the read() call, without using its (incomplete) returned values. (On other errors, you maybe want to exit the program with the appropriate error message (from strerror))
Example: a wrapper called xread() from git's source code
POSIX rasys return == 0 for end of file
http://pubs.opengroup.org/onlinepubs/9699919799/functions/read.html
If no process has the pipe open for writing, read() shall return 0 to indicate end-of-file.
This confirms Jerry's answer.
EOF is returned by some ANSI functions, e.g. man getc says:
fgetc(), getc() and getchar() return the character read as an unsigned char cast to an int or EOF on end of file or error.
ungetc() returns c on success, or EOF on error.
so you still can't use it to distinguish error and end of file in that case, feof is needed.
See also: How to use EOF to run through a text file in C?

Resources