Search code examples
cterminalbufferubuntu-14.04putchar

Why I still need to press 'Enter" in order to let input be read and output even in non-canonical mode?


I was testing this code from the GNU libc manual:

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <termios.h>

/* Use this variable to remember original terminal attributes. */

struct termios saved_attributes;

void
reset_input_mode (void)
{
  tcsetattr (STDIN_FILENO, TCSANOW, &saved_attributes);
}

void
set_input_mode (void)
{
  struct termios tattr;
  char *name;

  /* Make sure stdin is a terminal. */
  if (!isatty (STDIN_FILENO))
    {
      fprintf (stderr, "Not a terminal.\n");
      exit (EXIT_FAILURE);
    }

  /* Save the terminal attributes so we can restore them later. */
  tcgetattr (STDIN_FILENO, &saved_attributes);
  atexit (reset_input_mode);

  /* Set the funny terminal modes. */
  tcgetattr (STDIN_FILENO, &tattr);
  tattr.c_lflag &= ~(ICANON|ECHO); /* Clear ICANON and ECHO. */
  tattr.c_cc[VMIN] = 1;
  tattr.c_cc[VTIME] = 0;
  tcsetattr (STDIN_FILENO, TCSAFLUSH, &tattr);
}

int
main (void)
{
  char c;

  set_input_mode ();

  while (1)
    {
      read (STDIN_FILENO, &c, 1);
      if (c == '\004')          /* C-d */
        break;
      else
        putchar (c);
    }

  return EXIT_SUCCESS;
}

Even though the terminal was set to be in non-canonical mode, I still need to press enter for input to be received.

However, if I change: putchar(c) to write(STDOUT_FILENO, &c, sizeof(char)), it works properly as I thought.


Solution

  • You are being trumped by user buffering! putchar(3) is part of libc I/O API, while write(2) is a system call -- well, not quite a system call, but for simplicity's sake, let's consider it is for now.

    There are three types of buffering in libc: unbuffered, block buffered, and line buffered. If a stream is unbuffered, data goes to the underlying file (or terminal) as soon as it is written; if it's block buffered, data is saved in the memory block until it fills up and then it's written all at once; however, if it's line buffered, data is transmitted to the file (or terminal) when a newline character is found.

    If a stream is connected to a terminal, as normally is the case of standard output, it's line buffered. So, this is your case: when you press enter, the newline character \n causes the (line) buffer to be written to standard output. However, when you call write(2), libc user buffering is bypassed and data is written to the corresponding file descriptor (STDOUT_FILENO).

    So, as I said earlier, write(2) is a system call; but in truth, when you call write, you are calling a library wrapper to the system call, which handles the strict protocol the system call follows (e.g., where it expects arguments, etc).

    By the way, everything I said here can be found in the man pages for putchar(3), write(2), setbuf(3). The numbers in parentheses refer to the section in the manual: 2 is for system calls, 3 is for library functions (man man should give you a list of sections and their topic).

    Hope it helps.