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.
Related
I'm trying to understand what is behind this behaviour in my parent process.
Basically, I create a child process and connect its stdout to my pipe. The parent process continuously reads from the pipe and does some stuff.
I noticed that when inserting the while loop in the parent the stdout seems to be lost, nothing appears on the terminal etc I thought that the output of stdout would somehow go to the pipe (maybe an issue with dup2) but that doesn't seem to be the issue. If I don't continuously fflush(stdout) in the parent process, whatever I'm trying to get to the terminal just won't show. Without a while loop in the parent it works fine, but I'm really not sure why it's happening or if the rest of my implementation is problematic somehow.
Nothing past the read system call seems to be going to the stdout in the parent process. Assuming the output of inotifywait in the pipe is small enough ( 30 > bytes ), what exactly is wrong with this program?
What I expect to happen is the stdout of inotifywait to go to the pipe, then for the parent to read the message, run strtok and print the file name (which only appears in stdout when I fflush)
Running the program with inotify installed and creating any file in the current directory of the program should be enough. Removing the while loop does print the created file's name (as expected).
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <errno.h>
int main(void) {
char b[100];
int pipefd;
if (mkfifo("fifo", 0666) == -1) {
if (errno != EEXIST) {
perror("mkfifo");
exit(EXIT_FAILURE);
}
}
pid_t pid = fork();
if (pid < 0) {
perror("fork");
exit(1);
}
if ((pipefd = open("fifo", O_RDWR)) < 0) {
perror("open pipe");
exit(EXIT_FAILURE);
}
if (pid == 0) {
dup2(pipefd, 1);
const char* dir = ".";
const char* args[] = {"inotifywait", dir, "-m", "-e",
"create", "-e", "moved_to", NULL};
execvp("inotifywait", (char**)args);
perror("inotifywait");
} else {
while (1) {
fflush(stdout); // the output only appears in stdout with this here
if (read(pipefd, b, 30) < 0) {
perror("problem # read");
exit(1);
}
char filename[30];
printf("anything");
sscanf(b, "./ CREATE %s", filename);
printf("%s", filename);
}
}
}
The streams used by the C standard library are designed in such a way that they are normally buffered (except for the standard error stream stderr).
The standard output stream is normally line buffered, unless the output device is not an interactive device, in which case it is normally fully buffered. Therefore, in your case, it is probably line buffered.
This means that the buffer will only be flushed
when it is full,
when an \n character is encountered,
when the stream is closed (e.g. during normal program termination),
when reading input from an unbuffered or line-buffered stream (in certain situations), or
when you explicitly call fflush.
This explains why you are not seeing the output, because none of the above are happening in your infinite loop (when you don't call fflush). Although you are reading input, you are not doing this from a C standard library FILE * stream. Instead, you are bypassing the C runtime library (e.g. glibc) by using the read system call directly (i.e. you are using a file descriptor instead of a stream).
The simplest solution to your problem would probably be to replace the line
printf("%s", filename);
with:
printf("%s\n", filename);
If stdout is line-buffered (which should be the case if it is connected to a terminal), then the input should automatically be flushed after every line and an explicit call to fflush should no longer be necessary.
Given the following code, I have: printf("hello\n"); so I am expecting to see hello\n in myfile. but when I run my program I see haha which means \n was ignored, why is that?
Worth Noting: when I replace printf("hello\n"); with printf("hellos"); I don't see the s letter being printed as well. So I think maybe something is writing on top of it but the question is who and why?
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/file.h>
#include <string.h>
int main() {
// creates a new file having full read/write permissions
int fd = open("myfile", O_RDWR | O_CREAT, 0666);
write(fd, "haha\n", 5);
close(fd); // line 6
fd = open("myfile", O_RDWR); // line 7
close(0);
close(1);
dup(fd);
dup(fd);
if (fork() == 0) {
char s[100];
dup(fd);
scanf("%s", s);
printf("hello\n");
write(2, s, strlen(s)); // line 18
return 0; // line 19
}
wait(NULL);
printf("Father finished\n");
close(fd);
return 0;
}
contents of myfile after running:
haha
helloFather finished (new line after this)
First of all, the behavior is undefined. You are starting to use stdout that refers to the same file descriptor as stdin without closing or flushing stdin before doing it. Let's try to take the important stuff from POSIX 2.5.1 Interaction of File Descriptors and Standard I/O Streams:
[...] if two or more handles are used, and any one of them is a stream, the application shall ensure that their actions are coordinated as described below. If this is not done, the result is undefined.
[...]
For a handle to become the active handle, the application shall ensure that the actions below are performed between the last use of the handle (the current active handle) and the first use of the second handle (the future active handle). [...]
[...]
For the first handle, the first applicable condition below applies. [...]
[...]
If the stream is open with a mode that allows reading and the underlying open file description refers to a device that is capable of seeking, the application shall either perform an fflush(), or the stream shall be closed.
Your code does:
scanf("%s", s); // associates stdin with fd
// Ups - no flush(stdin) nor fclose(stdin)
printf("hello\n"); // associates stdout with fd - undefined behavior
The result you are seeing comes from that scanf calls ungetc which increments file position but also "remembers" to un-increment file position once the stream is flushed. Because it is flushed when child terminates, the file position is decremented, and parent overwrites last character of the child.
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.
I am writing a C program on unix which should redirect it's output to the file, and write to it some text every second in infinite loop:
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
int main(void) {
int outDes = open("./output.txt", O_APPEND | O_WRONLY);
dup2(outDes, 1);
while(1) {
printf("output text\n");
sleep(1);
}
}
But it writes nothing to the output file. I tried to change the 'while' loop for 'for' with 10 loops, and I found that it writes all 10 lines to the file at once after the series ends. It is not very good for me, while I need to have an infinite loop.
When I'm not redirecting output, it is all ok, and new line appears every second on terminal.
I also tried to put one
printf("text\n");
before redirecting output to the file. Then the program wrote the lines to the file in real time, which is good, but wrote there the first (non redirected) line too. I don't want this first line in my output file, I don't understand how it could be written into file when output was not redirected yet (maybe redirect remained there since last run?), and how it could cause that the lines are suddenly written in real time.
Can anyone explain me how does it work?
You are not checking the return value of open() and dup2(). If either open() or dup2() failed, it won't write anything in output.txt.
if (outDes < -1) {
perror("open");
return 1;
}
if (dup2(outDes, 1) == -1) {
perror("dup2");
return 1;
}
stdio streams are buffered, and the writes happen in memory before being done on the real file description.
Try adding a fflush(stdout) after printf().
You're running afoul of a poorly documented DWIMmy feature in many Unix C libraries. The first time you write to stdout or stderr, the library probes the underlying file descriptor (with isatty(3)). If it's a (pseudo-)terminal, the library puts the FILE in "line buffered" mode, meaning that it'll buffer input until a newline is written and then flush it all to the OS. But if the file descriptor is not a terminal, it puts the FILE in "fully buffered" mode, where it'll buffer something like BUFSIZ bytes of output before flushing them, and pays no attention to line breaks.
This is normally the behavior you want, but if you don't want it (as in this case), you can change it with setvbuf(3). This function (although not the behavior I described above) is ISO standard C. Here's how to use it in your case.
#include <stdio.h>
#include <unistd.h>
int
main(void)
{
if (freopen("output.txt", "a", stdout)) {
perror("freopen");
return 1;
}
if (setvbuf(stdout, 0, _IOLBF, 0)) {
perror("setvbuf");
return 1;
}
for (;;) {
puts("output text");
sleep(1);
}
/* not reached */
}
My machine is running ubuntu 10.10, and I'm using the standard gnu C library. I was under the impression that printf flushed the buffer if there was a newline described in the format string, however the following code repeatedly seemed to buck that trend. Could someone clarify why the buffer is not being flushed.
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/wait.h>
int main()
{
int rc;
close(1);
close(2);
printf("HI 1\n");
fprintf(stderr, "ERROR\n");
open("newfile.txt", O_WRONLY | O_CREAT | O_TRUNC, 0600);
printf("WHAT?\n");
fprintf(stderr, "I SAID ERROR\n");
rc = fork();
if (rc == 0)
{
printf("SAY AGAIN?\n");
fprintf(stderr, "ERROR ERROR\n");
}
else
{
wait(NULL);
}
printf("BYE\n");
fprintf(stderr, "HI 2\n");
return 0;
}
The contents of newfile.txt after running this program is as follows.
HI 1
WHAT?
SAY AGAIN?
BYE
HI 1
WHAT?
BYE
No, the standard says that stdout is initially fully buffered if the output device can be determined to be a non-interactive one.
It means that, if you redirect stdout to a file, it won't flush on newline. If you want to try and force it to line-buffered, use setbuf or setvbuf.
The relevant part of C99, 7.19.3 Files, paragraph 7, states:
At program startup, three text streams are predefined and need not be opened explicitly - standard input (for reading conventional input), standard output (for writing conventional output), and standard error (for writing diagnostic output). As initially opened, the standard error stream is not fully buffered; the standard input and standard output streams are fully buffered if and only if the stream can be determined not to refer to an interactive device.
Just keep in mind section 5.1.2.3/6:
What constitutes an interactive device is implementation-defined.
It is flushed if the output device is an interactive one e.g., a terminal.
You have to flush the output buffer in case the output device can be determined to be non-interactive e.g., a file. New line does not do that automatically.
For details see paxdiablo's answer.
You've got a strange sense of humor. :)
int main()
{
int rc;
close(1);
close(2);
printf("HI 1\n");
fprintf(stderr, "ERROR\n");
You close the filedescriptors used for stdout and stderr, and then immediately try to use the C stdout and stderr FILE streams. Not a great idea, I'm not sure what the C library will do to report the error to you but crashing would be one acceptable possibility.
That oddity aside, when you're using the standard IO stream functions to write, the buffering depends in part upon the destination. If you're writing to a terminal, then usual behavior is line buffering. If you're writing to a pipe, a file, or a socket, then the usual behavior is block buffering. You can change the buffering behavior with the setvbuf(3) function. Full details of the buffering behavior are in the manpage.