1
#include <stdio.h>
#include <unistd.h>

int main(void)
{
  printf("If I had more time, \n");
  write(STDOUT_FILENO, "I would have written you a shorter letter.\n", 43);

  return 0;
}

I read that

I/O handling functions (stdio library functions) and system calls perform buffered operations for increased performance. The printf(3) function used stdio buffer at user space. The kernel also buffers I/O so that is does not have to write to the disk on every system call. By default, when the output file is a terminal, the writes using the printf(3) function are line-buffered as the stdio uses line buffering for the stdout i.e. when newline-character '\n' is found the buffered is flushed to the Buffer Cache. However when is not a terminal i.e., the standard output is redirected to a disk file, the contents are only flushed when ther is no more space at the buffer (or the file stream is close). If the standard output of the program above is a terminal, then the first call to printf will flush its buffer to the Kernel Buffer (Buffer Cache) when it finds a newline-character '\n', hence, the output would be in the same order as in the above statements. However, if the output is redirected to a disk file, then the stdio buffers would not be flushed and the contents of the write(2) system call would hit the kernel buffers first, causing it to be flushed to the disk before the contents of the printf call.

When stdout is a terminal

If I had more time,
I would have written you a shorter letter.

When stdout is a disk file

I would have written you a shorter letter.
If I had more time,

But my question is that how the stdio library functions knows whether the stdout is directed to a terminal or to a disk file ?

arka
  • 253

1 Answers1

3

printf (of my specific libc) internally does a newfstatat() syscall on the stdout file descriptor (which is 1).

The kernel fills in the st_mode fiels with S_IFREG if it's a regular file you're piping into, and s_IFCHR if it's a character device (like a pseudo-terminal).

How I figured out:

gcc -o foo foo.c # compile your program
strace -o file.strace ./foo > tempfile
strace -o term.strace ./foo
diff *.strace #and look for things towards the end that concern the 1 file descriptor 
  • newfstatat() is probably a Linux-specific name. The standard fstat() should do in most systems – ilkkachu Nov 08 '22 at 19:54
  • @ilkkachu indeed, this was just an observation of what my glibc does – others, on other systems might do the same, but in a different way. – Marcus Müller Nov 08 '22 at 19:55
  • yep. And it's even possible the library calls fstat() (or fstatat()), just that the system call wrapper then actually calls a (newer) kernel implementation with another name. Somewhat similarly, strace shows a call to clone() (with a pile of arguments) even for a simple fork() call. – ilkkachu Nov 08 '22 at 19:59