1

Let's say I have a C program that has the following loop.

while ((c = getchar()) != EOF) {
    ...
}

This loop seems as if it reads the characters one by one as they are typed on the tty. But then I should not be able to change the characters once they have been inputted, clearly, this does not happen.
That must mean the tty must keep some buffer to keep the values that it has received and not yet pushed to stdin. Once in stdin the characters are read one by one.

Is this right? If so, how can I obtain the chars in this buffer, without them having to go to stdin? I tried using ioctl with FIONREAD, but it doesn't seem to work(the size of the buffer is always zero, even though there are chars in the terminal), and since this data is not in stdin that means methods that read from stdin wouldn't work(These were given as answers to similar questions)

kannavkm
  • 79
  • 2
  • 9
  • [This question](https://stackoverflow.com/questions/34806490/how-is-line-buffering-implemented-for-c-stdio-input-streams) might have some useful information for you. Note that when you're using a tty, the stdin of your program is a virtual file being written to by the tty and read from by your program. If the tty hasn't flushed something to that virtual file, your code can't read it. – lawruble13 Oct 02 '20 at 06:26
  • So in the case, my program gets interrupted during a read(handling a signal), and I want to get that data(to print it). I should flush the 'received but not yet read' data into a file and start reading it. Is that right? – kannavkm Oct 02 '20 at 06:33
  • See also [this answer](https://stackoverflow.com/questions/9180001#51173273). – Steve Summit Nov 23 '22 at 12:52

2 Answers2

1

Short Answer :

termios.h, a POSIX-standard header file, allows you to get the tty into raw mode whereas it is generally in cooked mode .

Reading it's documentation/man pages is suggested.

A webpage that deals step-by-step with setting up raw mode with termios.h is linked here.

Explanation :

By default , I/O of the teminal is line-buffered, i.e, input is guaranteed to be flushed/sent to your program once a line is terminated.

In output this is via the \n or an fflush(stdout); , whereas in input this is via the user's pressing of the [ENTER] key.

Getting the terminal into raw mode allows it to be such that at soon as a key is pressed, the input signal is sent to your program, along with a lot of other default features like echoing being disabled.

This is very commonly done for any slightly complicated CLI program, especially things like vim, htop, etc.

A P Jo
  • 446
  • 1
  • 4
  • 15
  • `termios.h`. No `n`. – rici Oct 02 '20 at 08:31
  • Thanks for the answer. I have set the tty into raw mode, But now I have to set up a readline like alternative for what I want to accomplish. – kannavkm Oct 02 '20 at 10:08
  • @abakfja Ah, the classical problem of removing the entire structure of abstratctions just to get the benefit of removing 1 % of it. Welcome to low-level programming. Do upvote my answer if you feel I deserve it :) – A P Jo Oct 02 '20 at 15:32
  • @rici My life is a lie. – A P Jo Oct 02 '20 at 15:32
  • @abakfja: perhaps you would be better off just using the readline library directly, rather than trying to reimplement it (which will be a massive task). – rici Oct 02 '20 at 15:38
0

As stated above, the quandry of losing vital layers of abstraction when taking a low-level approach to user input programming is very real. Another way to avoid re-implementing the functionality of readline, etc. is to read bytes into your program directly from the controlling tty, as raw or as cooked by termios.h as you like, and spawn subprocesses on pseudo-terminals or pipes as required to further process those bytes.

For instance, I will run a parent process in C which in turn forks children running bash, perl, python, GNU readline, etc. As a side note, a shell subprocess such as bash requires a pseudo-terminal to run fully interactive with job control, whereas others like perl and readline do well on a simple bi-directional pipe. I have found that under Linux kernel the epoll system call mechanism, level-triggered, works well for handling the traffic between processes, rather than the antiquated select call. Which brings us back to your original question, the answer being that file descriptor 0, your stdin, is ultimately added to a struct epoll_event, as are the output descriptors of your subprocesses, for monitoring via epoll_wait(). Your main process becomes a high-volume capable interprocess bytestream router. Keyboard input becomes just another stream you are handling. With this technique, for example, you can seamlessly incorporate internet and other types of sockets. They are all essentially files controlled by file descriptors under unix.

A look into Linux Input Subsystem Userspace API may also be helpful if you are working with physically attached input devices like mouse, touchpad, joystick, etc.