// This doesn't work reliably because // sometimes it doesn't attempt to // restore the tty settings until the // shell has wrested back control over // the tty, so its pgrp is no longer the // tty pgrp. // Safe cbreak, if you don't use threads. // A problem with tty modes, in // particular disabling echo, is that // restoring them depends on your code // not crashing. This is a solution; // it uses a "subvisor" process, a // very lightweight spawned child which // waits silently in the background // until the parent exits, as detected // by the closing of a pipe from the // parent. Then it restores the terminal // settings before exiting. // You may want to implement SIGCONT // handling too if you use this, // setting cbreak mode again after being // resumed from a job control shell. #include #include #include #include #include #include // Returns null on success, string // indicating the problem location // on failure. Disables ECHO and ICANON. const char *enable_cbreak(int fd) { struct termios ios; if (ioctl(fd, TCGETS, &ios)) return "TCGETS"; int pipefds[2]; if (pipe(pipefds)) return "pipe"; pid_t pid = fork(); if (pid < 0) return "fork"; if (pid == 0) { // Child restores tty settings // after parent dies, closing // the pipe. close(pipefds[1]); // Try not to die on ^C or ^\.. // If we try to avoid tty signals // by setting our pgid instead, // we get EIO later when we try // to TCSETS (because not only // are we no longer in the // foreground pgrp, we are // orphaned.) signal(SIGINT, SIG_IGN); signal(SIGQUIT, SIG_IGN); char b; read(pipefds[0], &b, 1); // Too late to report any error. int err = ioctl(fd, TCSETS, &ios); if (err) { int e2 = errno; fprintf(stderr, "TCSETS said %d: ", err); errno = e2; perror("ioctl"); } else { fprintf(stderr, "TCSETS OK\n"); } fflush(stdout); _exit(0); } // Note we wait until after the fork // to modify the termios. That way // the copy in the child is pristine. ios.c_lflag &= ~ECHO & ~ICANON; if (ioctl(fd, TCSETS, &ios)) return "TCSETS"; // We succeeded, so return a null // pointer. Note that we don't return // pipefds[1] so the only way to // close the pipe is to exit, and // there's no way to write to it. return 0; } // Demo of how to call the above // function. __attribute__((weak)) int main() { const char *err = enable_cbreak(0); if (err) { perror(err); return 1; } for (int i = 0; i != 5; i++) { char b; read(0, &b, 1); printf("%d ", b); fflush(stdout); } printf("\n"); return 0; }