9

My test application is

#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>

int main(int argc, char *argv[], char *envp[]) {
  int fd[2];

  if(pipe(fd) < 0) { 
    printf("Can\'t create pipe\n");
    exit(-1); 
  }

  pid_t fpid = fork();
  if (fpid == 0) {
    close(0);
    close(fd[1]);
    char *s = (char *) malloc(sizeof(char));
    while(1) if (read(fd[0], s, 1)) printf("%i\n", *s);
  }
  close(fd[0]);
  char *c = (char *) malloc(sizeof(char));
  while (1) {
    if (read(0, c, 1) > 0) write(fd[1], c, 1);
  }
  return 0;
}

I want to see char-code after each entered char. But in fact *s is printed only after '\n' in the console. So seems like stdin (file with desc 0) is buffered. But the read function is buffer-less, isn't it? Where am I wrong.

UPD: I use linux.

So the solution is

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

int main(int argc, char *argv[], char *envp[]) {
  int fd[2];

  if(pipe(fd) < 0) { 
    printf("Can\'t create pipe\n");
    exit(-1); 
  }

  struct termios term, term_orig;

  if(tcgetattr(0, &term_orig)) {
    printf("tcgetattr failed\n");
    exit(-1); 
  }

  term = term_orig;

  term.c_lflag &= ~ICANON;
  term.c_lflag |= ECHO;
  term.c_cc[VMIN] = 0;
  term.c_cc[VTIME] = 0;

  if (tcsetattr(0, TCSANOW, &term)) {
    printf("tcsetattr failed\n");
    exit(-1);
  }

  pid_t fpid = fork();
  if (fpid == 0) {
    close(0);
    close(fd[1]);
    char *s = (char *) malloc(sizeof(char));
    while(1) if (read(fd[0], s, 1)) printf("%i\n", *s);
  }
  close(fd[0]);
  char *c = (char *) malloc(sizeof(char));
  while (1) {
    if (read(0, c, 1) > 0) write(fd[1], c, 1);
  }
  return 0;
} 
Ximik
  • 2,435
  • 3
  • 27
  • 53
  • 1
    Note that this has nothing to do with buffering. – Šimon Tóth Dec 01 '10 at 19:05
  • Shouldn't the code reset the terminal attributes in the parent to `term_orig` before exiting? You should probably also have the child exit at some point — it will continually get 0 from `read()` after the parent shuts up shop. However, the parent is also in an infinite loop; the processes only end when signalled. You really need a signal handler that calls `tcsetattr()` with the original terminal values for the main signals you're likely to get (that can be handled): HUP, INT, QUIT perhaps, PIPE and TERM is a good set. You can't do anything about KILL or STOP, of course. – Jonathan Leffler Jan 19 '21 at 03:27

3 Answers3

15

Unfortunately, the behavior you're looking for is not possible with standard ANSI C, and the default mode for UNIX terminal I/O is line-oriented, which means you will always need an inputted \n character to retrieve the input. You'll need to use terminal I/O facilities that let you program in non-canonical mode, so that each key-press triggers an event. On Linux/UNIX, you can look into the <termios.h> header, or the ncurses library.

Community
  • 1
  • 1
Charles Salvia
  • 52,325
  • 13
  • 128
  • 140
5

It seems to me that your solution is a little bit complicated. Still don't understand why do you need pipe and 2 process.

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

int main(int argc, char *argv[], char *envp[]) {
  struct termios term, term_orig;

  if(tcgetattr(0, &term_orig)) {
    printf("tcgetattr failed\n");
    exit(-1);
  }

  term = term_orig;

  term.c_lflag &= ~ICANON;
  term.c_lflag |= ECHO;
  term.c_cc[VMIN] = 0;
  term.c_cc[VTIME] = 0;

  if (tcsetattr(0, TCSANOW, &term)) {
    printf("tcsetattr failed\n");
    exit(-1);
  }

  char ch;
  while (1) {
    if (read(0, &ch, 1) > 0) 
      printf(" %d\n", ch);
  }
  return 0;
}
Sam Toliman
  • 123
  • 1
  • 5
3

Unix buffers your tty characters inside the kernel in part so that programs don't have to individually handle line editing unless they want to.

You can instruct the tty driver to give you the bytes immediately. There are various libraries that make this a bit easier than using the raw ioctl. You might start with termios(3).

DigitalRoss
  • 143,651
  • 25
  • 248
  • 329