From 7f09c8ff952e6acbbef3747e84e5e8ae289f96a7 Mon Sep 17 00:00:00 2001 From: Jonas 'Sortie' Termansen Date: Sat, 22 Oct 2016 23:53:44 +0200 Subject: [PATCH] Add pty(1). --- utils/.gitignore | 1 + utils/Makefile | 1 + utils/pty.c | 349 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 351 insertions(+) create mode 100644 utils/pty.c diff --git a/utils/.gitignore b/utils/.gitignore index 96c61ba7..3004a910 100644 --- a/utils/.gitignore +++ b/utils/.gitignore @@ -41,6 +41,7 @@ passwd poweroff ps pstree +pty pwd readlink realpath diff --git a/utils/Makefile b/utils/Makefile index 21bb8b86..631b579f 100644 --- a/utils/Makefile +++ b/utils/Makefile @@ -51,6 +51,7 @@ pager \ passwd \ ps \ pstree \ +pty \ pwd \ readlink \ realpath \ diff --git a/utils/pty.c b/utils/pty.c new file mode 100644 index 00000000..91e210a3 --- /dev/null +++ b/utils/pty.c @@ -0,0 +1,349 @@ +// TODO: Rename to getty(8). + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if !defined(TTY_NAME_MAX) +#include +#endif + +static pid_t main_pid; +static const char* serial = NULL; +static struct termios old_tio; +static bool has_old_tio; +static int incoming_fd; +static int outgoing_fd; +static int master_fd; + +static void exit_handler(void) +{ + if ( main_pid != getpid() ) + return; + + if ( has_old_tio ) + { + if ( tcsetattr(0, TCSAFLUSH, &old_tio) ) + warn("tcsetattr"); + } +} + +static void* incoming_thread(void* ctx) +{ + (void) ctx; + char c; + ssize_t amount = 0; + while ( 0 < (amount = read(incoming_fd, &c, 1)) ) + { + if ( write(master_fd, &c, 1) <= 0 ) + { + warn("incoming write"); + break; + } + } + if ( amount < 0 ) + warn("incoming read"); + exit(0); + return NULL; +} + +static void* outgoing_thread(void* ctx) +{ + (void) ctx; + char c; + ssize_t amount = 0; + struct wincurpos wcp; + bool emulate_getcursor = !serial && tcgetwincurpos(incoming_fd, &wcp) == 0; + if ( emulate_getcursor ) + { + const char* getcursor = "\e[6n"; + size_t i = 0; + while ( 0 < (amount = read(master_fd, &c, 1)) ) + { + if ( c == getcursor[i] ) + { + i++; + if ( !getcursor[i] ) + { + i = 0; + struct wincurpos wcp; + tcgetwincurpos(incoming_fd, &wcp); + char buf[64]; + snprintf(buf, sizeof(buf), "\e[%zu;%zuR", + wcp.wcp_row + 1, wcp.wcp_col + 1); + for ( size_t n = 0; buf[n]; n++ ) + { + if ( write(master_fd, &buf[n], 1) <= 0 ) + { + warn("incoming write"); + break; // TODO: This break is incorrect. + } + } + } + continue; + } + for ( size_t j = 0; j < i; j++ ) + { + if ( write(outgoing_fd, &getcursor[j], 1) <= 0 ) + { + warn("outgoing write"); + break; // TODO: This break is incorrect. + } + } + i = 0; + if ( write(outgoing_fd, &c, 1) <= 0 ) + { + warn("outgoing write"); + break; + } + } + } + else + { + while ( 0 < (amount = read(master_fd, &c, 1)) ) + { + if ( write(outgoing_fd, &c, 1) <= 0 ) + { + warn("outgoing write"); + break; + } + } + } + if ( amount < 0 ) + warn("outgoing read"); + exit(0); + return NULL; +} + +static char serial_getchar(int fd, const char* path) +{ + char c; + ssize_t amount = read(fd, &c, 1); + if ( amount < 0 ) + err(1, "read: %s", path); + if ( amount == 0 ) + errx(1, "unexpected end of file: %s", path); + return c; +} + +static struct winsize serial_winsize(int fd, const char* path) +{ + dprintf(fd, "\e[18t"); + while ( true ) + { + while ( serial_getchar(fd, path) != '\e' ) + continue; + if ( serial_getchar(fd, path) != '[' ) + continue; + unsigned int params[16]; + memset(¶ms, 0, sizeof(params)); + size_t current_param = 0; + while ( true ) + { + char c = serial_getchar(fd, path); + if ( '0' <= c && c <= '9' ) + { + params[current_param] = params[current_param] * 10 + c - '0'; + continue; + } + else if ( c == ';' ) + { + if ( current_param < 16 ) + current_param++; + } + else if ( c == 't' ) + { + if ( params[0] == 8 ) + { + struct winsize ws; + memset(&ws, 0, sizeof(ws)); + ws.ws_row = params[1]; + ws.ws_col = params[2]; + return ws; + } + } + else + break; + } + } +} + +static void compact_arguments(int* argc, char*** argv) +{ + for ( int i = 0; i < *argc; i++ ) + { + while ( i < *argc && !(*argv)[i] ) + { + for ( int n = i; n < *argc; n++ ) + (*argv)[n] = (*argv)[n+1]; + (*argc)--; + } + } +} + +int main(int argc, char* argv[]) +{ + for ( int i = 1; i < argc; i++ ) + { + const char* arg = argv[i]; + if ( arg[0] != '-' || !arg[1] ) + break; + argv[i] = NULL; + if ( !strcmp(arg, "--") ) + break; + if ( arg[1] != '-' ) + { + char c; + while ( (c = *++arg) ) switch ( c ) + { + case 's': + if ( !*(serial = arg + 1) ) + { + if ( i + 1 == argc ) + errx(125, "option requires an argument -- 's'"); + serial = argv[i+1]; + argv[++i] = NULL; + } + arg = "s"; + break; + default: + errx(1, "unknown option -- '%c'", c); + } + } + else + errx(1, "unknown option: %s", arg); + } + + compact_arguments(&argc, &argv); + + main_pid = getpid(); + if ( atexit(exit_handler) ) + err(1, "atexit"); + + struct winsize ws; + memset(&ws, 0, sizeof(ws)); + if ( serial ) + { + incoming_fd = open(serial, O_RDWR); + if ( incoming_fd < 0 ) + err(1, "%s", serial); + outgoing_fd = incoming_fd; +#if 0 + if ( 4 <= argc ) + { + ws.ws_row = atoi(argv[2]); + ws.ws_col = atoi(argv[3]); + } + else +#endif + ws = serial_winsize(incoming_fd, serial); + } + else + { + if ( tcgetattr(0, &old_tio) ) + err(1, "tcgetattr"); + + struct termios tio; + memcpy(&tio, &old_tio, sizeof(struct termios)); + tio.c_iflag &= ~(BRKINT | ICRNL | IGNBRK | IGNCR | INLCR | IXANY | IXOFF | IXON); + tio.c_oflag &= ~(OPOST); // TODO: Others. + tio.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL | ICANON | IEXTEN | ISIG | NOFLSH); + + if ( tcsetattr(0, TCSAFLUSH, &tio) ) + err(1, "tcsetattr"); + has_old_tio = true; + incoming_fd = 0; + outgoing_fd = 1; + + if ( ioctl(incoming_fd, TIOCGWINSZ, &ws) < 0 ) + warn("ioctl: TIOCGWINSZ"); + } + + char path[TTY_NAME_MAX + 1]; + int slave_fd; + if ( openpty(&master_fd, &slave_fd, path, NULL, + ws.ws_row && ws.ws_col ? &ws : NULL) < 0 ) + err(1, "openpty"); + + pid_t child_pid = fork(); + if ( child_pid < 0 ) + err(1, "fork"); + + if ( !child_pid ) + { + if ( setsid() < 0 ) + { + warn("setsid"); + _exit(1); + } + if ( ioctl(slave_fd, TIOCSCTTY) < 0 ) + { + warn("ioctl: TIOCSCTTY"); + _exit(1); + } + if ( close(0) < 0 || close(1) < 0 || close(2) < 0 ) + { + warn("close"); + _exit(1); + } + if ( dup2(slave_fd, 0) != 0 || + dup2(slave_fd, 1) != 1 || + dup2(slave_fd, 2) != 2 ) + { + warn("dup"); + _exit(1); + } + if ( closefrom(3) < 0 ) + { + warn("closefrom"); + _exit(1); + } + if ( argc <= 1 ) + { + const char* program = "sh"; + execlp(program, program, (const char*) NULL); + } + else + { + execvp(argv[1], argv + 1); + } + _exit(127); + } + + close(slave_fd); + + int errnum; + pthread_t inthread; + pthread_t outthread; + if ( (errnum = pthread_create(&inthread, NULL, incoming_thread, NULL)) ) + { + errno = errnum; + err(1, "pthread_create"); + } + if ( (errnum = pthread_create(&outthread, NULL, outgoing_thread, NULL)) ) + { + errno = errnum; + err(1, "pthread_create"); + } + + int status; + waitpid(child_pid, &status, 0); + exit(0); + + pthread_join(inthread, NULL); + pthread_join(outthread, NULL); + + return 0; +}