/** * @file kernel/vfs/tty.c * @brief PTY driver. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2013-2021 K. Lange */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define TTY_BUFFER_SIZE 4096 #define MIN(a,b) ((a) < (b) ? (a) : (b)) extern void ptr_validate(void * ptr, const char * syscall); #define validate(o) ptr_validate(o,"ioctl") static int _pty_counter = 0; static hashmap_t * _pty_index = NULL; static fs_node_t * _pty_dir = NULL; static fs_node_t * _dev_tty = NULL; static void pty_write_in(pty_t * pty, uint8_t c) { ring_buffer_write(pty->in, 1, &c); } static void pty_write_out(pty_t * pty, uint8_t c) { ring_buffer_write(pty->out, 1, &c); } #define IN(character) pty->write_in(pty, (uint8_t)character) #define OUT(character) pty->write_out(pty, (uint8_t)character) static void dump_input_buffer(pty_t * pty) { char * c = pty->canon_buffer; while (pty->canon_buflen > 0) { IN(*c); pty->canon_buflen--; c++; } } static void clear_input_buffer(pty_t * pty) { pty->canon_buflen = 0; pty->canon_buffer[0] = '\0'; } #define output_process_slave tty_output_process_slave #define output_process tty_output_process #define input_process tty_input_process void tty_output_process_slave(pty_t * pty, uint8_t c) { if (c == '\n' && (pty->tios.c_oflag & ONLCR)) { c = '\n'; OUT(c); c = '\r'; OUT(c); return; } if (c == '\r' && (pty->tios.c_oflag & ONLRET)) { return; } if (c >= 'a' && c <= 'z' && (pty->tios.c_oflag & OLCUC)) { c = c + 'a' - 'A'; OUT(c); return; } OUT(c); } void tty_output_process(pty_t * pty, uint8_t c) { output_process_slave(pty, c); } static int is_control(int c) { return c < ' ' || c == 0x7F; } static void erase_one(pty_t * pty, int erase) { if (pty->canon_buflen > 0) { /* How many do we backspace? */ int vwidth = 1; pty->canon_buflen--; if (is_control(pty->canon_buffer[pty->canon_buflen])) { /* Erase ^@ */ vwidth = 2; } pty->canon_buffer[pty->canon_buflen] = '\0'; if (pty->tios.c_lflag & ECHO) { if (erase) { for (int i = 0; i < vwidth; ++i) { output_process(pty, '\010'); output_process(pty, ' '); output_process(pty, '\010'); } } } } } void tty_input_process(pty_t * pty, uint8_t c) { if (pty->next_is_verbatim) { pty->next_is_verbatim = 0; if (pty->canon_buflen < pty->canon_bufsize) { pty->canon_buffer[pty->canon_buflen] = c; pty->canon_buflen++; } if (pty->tios.c_lflag & ECHO) { if (is_control(c)) { output_process(pty, '^'); output_process(pty, ('@'+c) % 128); } else { output_process(pty, c); } } return; } if (pty->tios.c_lflag & ISIG) { int sig = -1; if (c == pty->tios.c_cc[VINTR]) { sig = SIGINT; } else if (c == pty->tios.c_cc[VQUIT]) { sig = SIGQUIT; } else if (c == pty->tios.c_cc[VSUSP]) { sig = SIGTSTP; } /* VSUSP */ if (sig != -1) { if (pty->tios.c_lflag & ECHO) { output_process(pty, '^'); output_process(pty, ('@' + c) % 128); output_process(pty, '\n'); } clear_input_buffer(pty); if (pty->fg_proc) { group_send_signal(pty->fg_proc, sig, 1); } return; } } #if 0 if (pty->tios.c_lflag & IXON ) { /* VSTOP, VSTART */ } #endif /* ISTRIP: Strip eighth bit */ if (pty->tios.c_iflag & ISTRIP) { c &= 0x7F; } /* IGNCR: Ignore carriage return. */ if ((pty->tios.c_iflag & IGNCR) && c == '\r') { return; } if ((pty->tios.c_iflag & INLCR) && c == '\n') { /* INLCR: Translate NL to CR. */ c = '\r'; } else if ((pty->tios.c_iflag & ICRNL) && c == '\r') { /* ICRNL: Convert carriage return. */ c = '\n'; } if (pty->tios.c_lflag & ICANON) { if (c == pty->tios.c_cc[VLNEXT] && (pty->tios.c_lflag & IEXTEN)) { pty->next_is_verbatim = 1; output_process(pty, '^'); output_process(pty, '\010'); return; } if (c == pty->tios.c_cc[VKILL]) { while (pty->canon_buflen > 0) { erase_one(pty, pty->tios.c_lflag & ECHOK); } if ((pty->tios.c_lflag & ECHO) && ! (pty->tios.c_lflag & ECHOK)) { output_process(pty, '^'); output_process(pty, ('@' + c) % 128); } return; } if (c == pty->tios.c_cc[VERASE]) { /* Backspace */ erase_one(pty, pty->tios.c_lflag & ECHOE); if ((pty->tios.c_lflag & ECHO) && ! (pty->tios.c_lflag & ECHOE)) { output_process(pty, '^'); output_process(pty, ('@' + c) % 128); } return; } if (c == pty->tios.c_cc[VWERASE] && (pty->tios.c_lflag & IEXTEN)) { while (pty->canon_buflen && pty->canon_buffer[pty->canon_buflen-1] == ' ') { erase_one(pty, pty->tios.c_lflag & ECHOE); } while (pty->canon_buflen && pty->canon_buffer[pty->canon_buflen-1] != ' ') { erase_one(pty, pty->tios.c_lflag & ECHOE); } if ((pty->tios.c_lflag & ECHO) && ! (pty->tios.c_lflag & ECHOE)) { output_process(pty, '^'); output_process(pty, ('@' + c) % 128); } return; } if (c == pty->tios.c_cc[VEOF]) { if (pty->canon_buflen) { dump_input_buffer(pty); } else { ring_buffer_interrupt(pty->in); } return; } if (pty->canon_buflen < pty->canon_bufsize) { pty->canon_buffer[pty->canon_buflen] = c; pty->canon_buflen++; } if (pty->tios.c_lflag & ECHO) { if (is_control(c) && c != '\n') { output_process(pty, '^'); output_process(pty, ('@' + c) % 128); } else { output_process(pty, c); } } if (c == '\n' || (pty->tios.c_cc[VEOL] && c == pty->tios.c_cc[VEOL])) { if (!(pty->tios.c_lflag & ECHO) && (pty->tios.c_lflag & ECHONL)) { output_process(pty, c); } pty->canon_buffer[pty->canon_buflen-1] = c; dump_input_buffer(pty); return; } return; } else if (pty->tios.c_lflag & ECHO) { output_process(pty, c); } IN(c); } static void tty_fill_name(pty_t * pty, char * out) { ((char*)out)[0] = '\0'; snprintf((char*)out, 100, "/dev/pts/%zd", pty->name); } int pty_ioctl(pty_t * pty, unsigned long request, void * argp) { switch (request) { case IOCTLDTYPE: /* * This is a special toaru-specific call to get a simple * integer that describes the kind of device this is. * It's more specific than just "character device" or "file", * but for here we just need to say we're a TTY. */ return IOCTL_DTYPE_TTY; case IOCTLTTYNAME: if (!argp) return -EINVAL; validate(argp); pty->fill_name(pty, argp); return 0; case IOCTLTTYLOGIN: /* Set the user id of the login user */ if (this_core->current_process->user != 0) return -EPERM; if (!argp) return -EINVAL; validate(argp); pty->slave->uid = *(int*)argp; pty->master->uid = *(int*)argp; return 0; case TIOCSWINSZ: if (!argp) return -EINVAL; validate(argp); memcpy(&pty->size, argp, sizeof(struct winsize)); if (pty->fg_proc) { group_send_signal(pty->fg_proc, SIGWINCH, 1); } return 0; case TIOCGWINSZ: if (!argp) return -EINVAL; validate(argp); memcpy(argp, &pty->size, sizeof(struct winsize)); return 0; case TCGETS: if (!argp) return -EINVAL; validate(argp); memcpy(argp, &pty->tios, sizeof(struct termios)); return 0; case TIOCSPGRP: if (!argp) return -EINVAL; validate(argp); pty->fg_proc = *(pid_t *)argp; return 0; case TIOCGPGRP: if (!argp) return -EINVAL; validate(argp); *(pid_t *)argp = pty->fg_proc; return 0; case TCSETS: case TCSETSW: case TCSETSF: if (!argp) return -EINVAL; validate(argp); if (!(((struct termios *)argp)->c_lflag & ICANON) && (pty->tios.c_lflag & ICANON)) { /* Switch out of canonical mode, the dump the input buffer */ dump_input_buffer(pty); } memcpy(&pty->tios, argp, sizeof(struct termios)); return 0; default: return -EINVAL; } } ssize_t read_pty_master(fs_node_t * node, off_t offset, size_t size, uint8_t *buffer) { pty_t * pty = (pty_t *)node->device; /* Standard pipe read */ return ring_buffer_read(pty->out, size, buffer); } ssize_t write_pty_master(fs_node_t * node, off_t offset, size_t size, uint8_t *buffer) { pty_t * pty = (pty_t *)node->device; size_t l = 0; for (uint8_t * c = buffer; l < size; ++c, ++l) { input_process(pty, *c); } return l; } void open_pty_master(fs_node_t * node, unsigned int flags) { return; } void close_pty_master(fs_node_t * node) { return; } ssize_t read_pty_slave(fs_node_t * node, off_t offset, size_t size, uint8_t *buffer) { pty_t * pty = (pty_t *)node->device; if (pty->tios.c_lflag & ICANON) { return ring_buffer_read(pty->in, size, buffer); } else { if (pty->tios.c_cc[VMIN] == 0) { return ring_buffer_read(pty->in, MIN(size, ring_buffer_unread(pty->in)), buffer); } else { return ring_buffer_read(pty->in, MIN(pty->tios.c_cc[VMIN], size), buffer); } } } ssize_t write_pty_slave(fs_node_t * node, off_t offset, size_t size, uint8_t *buffer) { pty_t * pty = (pty_t *)node->device; size_t l = 0; for (uint8_t * c = buffer; l < size; ++c, ++l) { output_process_slave(pty, *c); } return l; } void open_pty_slave(fs_node_t * node, unsigned int flags) { return; } void close_pty_slave(fs_node_t * node) { pty_t * pty = (pty_t *)node->device; if (pty->name) { hashmap_remove(_pty_index, (void*)pty->name); } return; } /* * These are separate functions just in case I ever feel the need to do * things differently in the slave or master. */ int ioctl_pty_master(fs_node_t * node, unsigned long request, void * argp) { pty_t * pty = (pty_t *)node->device; return pty_ioctl(pty, request, argp); } int ioctl_pty_slave(fs_node_t * node, unsigned long request, void * argp) { pty_t * pty = (pty_t *)node->device; return pty_ioctl(pty, request, argp); } int pty_available_input(fs_node_t * node) { pty_t * pty = (pty_t *)node->device; return ring_buffer_unread(pty->in); } int pty_available_output(fs_node_t * node) { pty_t * pty = (pty_t *)node->device; return ring_buffer_unread(pty->out); } static int check_pty_master(fs_node_t * node) { pty_t * pty = (pty_t *)node->device; if (ring_buffer_unread(pty->out) > 0) { return 0; } return 1; } static int check_pty_slave(fs_node_t * node) { pty_t * pty = (pty_t *)node->device; if (ring_buffer_unread(pty->in) > 0) { return 0; } return 1; } static int wait_pty_master(fs_node_t * node, void * process) { pty_t * pty = (pty_t *)node->device; ring_buffer_select_wait(pty->out, process); return 0; } static int wait_pty_slave(fs_node_t * node, void * process) { pty_t * pty = (pty_t *)node->device; ring_buffer_select_wait(pty->in, process); return 0; } fs_node_t * pty_master_create(pty_t * pty) { fs_node_t * fnode = malloc(sizeof(fs_node_t)); memset(fnode, 0x00, sizeof(fs_node_t)); fnode->name[0] = '\0'; snprintf(fnode->name, 100, "pty master"); fnode->uid = this_core->current_process->user; fnode->gid = this_core->current_process->user_group; fnode->mask = 0666; fnode->flags = FS_PIPE; fnode->read = read_pty_master; fnode->write = write_pty_master; fnode->open = open_pty_master; fnode->close = close_pty_master; fnode->selectcheck = check_pty_master; fnode->selectwait = wait_pty_master; fnode->readdir = NULL; fnode->finddir = NULL; fnode->ioctl = ioctl_pty_master; fnode->get_size = pty_available_output; fnode->ctime = now(); fnode->mtime = now(); fnode->atime = now(); fnode->device = pty; return fnode; } fs_node_t * pty_slave_create(pty_t * pty) { fs_node_t * fnode = malloc(sizeof(fs_node_t)); memset(fnode, 0x00, sizeof(fs_node_t)); fnode->name[0] = '\0'; snprintf(fnode->name, 100, "pty slave"); fnode->uid = this_core->current_process->user; fnode->gid = this_core->current_process->user_group; fnode->mask = 0620; fnode->flags = FS_CHARDEVICE; fnode->read = read_pty_slave; fnode->write = write_pty_slave; fnode->open = open_pty_slave; fnode->close = close_pty_slave; fnode->selectcheck = check_pty_slave; fnode->selectwait = wait_pty_slave; fnode->readdir = NULL; fnode->finddir = NULL; fnode->ioctl = ioctl_pty_slave; fnode->get_size = pty_available_input; fnode->ctime = now(); fnode->mtime = now(); fnode->atime = now(); fnode->device = pty; return fnode; } static int isatty(fs_node_t * node) { if (!node) return 0; if (!node->ioctl) return 0; return ioctl_fs(node, IOCTLDTYPE, NULL) == IOCTL_DTYPE_TTY; } static ssize_t readlink_dev_tty(fs_node_t * node, char * buf, size_t size) { pty_t * pty = NULL; for (unsigned int i = 0; i < ((this_core->current_process->fds->length < 3) ? this_core->current_process->fds->length : 3); ++i) { if (isatty(this_core->current_process->fds->entries[i])) { pty = (pty_t *)this_core->current_process->fds->entries[i]->device; break; } } char tmp[30]; size_t req; if (!pty) { snprintf(tmp, 100, "/dev/null"); } else { pty->fill_name(pty, tmp); } req = strlen(tmp) + 1; if (size < req) { memcpy(buf, tmp, size); buf[size-1] = '\0'; return size-1; } if (size > req) size = req; memcpy(buf, tmp, size); return size-1; } static fs_node_t * create_dev_tty(void) { fs_node_t * fnode = malloc(sizeof(fs_node_t)); memset(fnode, 0x00, sizeof(fs_node_t)); fnode->inode = 0; strcpy(fnode->name, "tty"); fnode->mask = 0777; fnode->uid = 0; fnode->gid = 0; fnode->flags = FS_FILE | FS_SYMLINK; fnode->readlink = readlink_dev_tty; fnode->length = 1; fnode->nlink = 1; fnode->ctime = now(); fnode->mtime = now(); fnode->atime = now(); return fnode; } static struct dirent * readdir_pty(fs_node_t *node, unsigned long index) { if (index == 0) { struct dirent * out = malloc(sizeof(struct dirent)); memset(out, 0x00, sizeof(struct dirent)); out->d_ino = 0; strcpy(out->d_name, "."); return out; } if (index == 1) { struct dirent * out = malloc(sizeof(struct dirent)); memset(out, 0x00, sizeof(struct dirent)); out->d_ino = 0; strcpy(out->d_name, ".."); return out; } index -= 2; pty_t * out_pty = NULL; list_t * values = hashmap_values(_pty_index); foreach(node, values) { if (index == 0) { out_pty = node->value; break; } index--; } list_free(values); if (out_pty) { struct dirent * out = malloc(sizeof(struct dirent)); memset(out, 0x00, sizeof(struct dirent)); out->d_ino = out_pty->name; out->d_name[0] = '\0'; snprintf(out->d_name, 100, "%zd", out_pty->name); return out; } else { return NULL; } } static fs_node_t * finddir_pty(fs_node_t * node, char * name) { if (!name) return NULL; if (strlen(name) < 1) return NULL; intptr_t c = 0; for (intptr_t i = 0; name[i]; ++i) { if (name[i] < '0' || name[i] > '9') { return NULL; } c = c * 10 + name[i] - '0'; } pty_t * _pty = hashmap_get(_pty_index, (void*)c); if (!_pty) { return NULL; } return _pty->slave; } static fs_node_t * create_pty_dir(void) { fs_node_t * fnode = malloc(sizeof(fs_node_t)); memset(fnode, 0x00, sizeof(fs_node_t)); fnode->inode = 0; strcpy(fnode->name, "pty"); fnode->mask = 0555; fnode->uid = 0; fnode->gid = 0; fnode->flags = FS_DIRECTORY; fnode->read = NULL; fnode->write = NULL; fnode->open = NULL; fnode->close = NULL; fnode->readdir = readdir_pty; fnode->finddir = finddir_pty; fnode->nlink = 1; fnode->ctime = now(); fnode->mtime = now(); fnode->atime = now(); return fnode; } void pty_install(void) { _pty_index = hashmap_create_int(10); _pty_dir = create_pty_dir(); _dev_tty = create_dev_tty(); vfs_mount("/dev/pts", _pty_dir); vfs_mount("/dev/tty", _dev_tty); } pty_t * pty_new(struct winsize * size, int index) { if (!_pty_index) { pty_install(); } pty_t * pty = malloc(sizeof(pty_t)); pty->next_is_verbatim = 0; /* stdin linkage; characters from terminal → PTY slave */ pty->in = ring_buffer_create(TTY_BUFFER_SIZE); pty->out = ring_buffer_create(TTY_BUFFER_SIZE); /* Master endpoint - writes go to stdin, reads come from stdout */ pty->master = pty_master_create(pty); /* Slave endpoint, reads come from stdin, writes go to stdout */ pty->slave = pty_slave_create(pty); /* tty name */ pty->name = index; pty->fill_name = tty_fill_name; pty->write_in = pty_write_in; pty->write_out = pty_write_out; if (index) { hashmap_set(_pty_index, (void*)pty->name, pty); } if (size) { memcpy(&pty->size, size, sizeof(struct winsize)); } else { /* Sane defaults */ pty->size.ws_row = 25; pty->size.ws_col = 80; } /* Controlling and foreground processes are set to 0 by default */ pty->ct_proc = 0; pty->fg_proc = 0; pty->tios.c_iflag = ICRNL | BRKINT; pty->tios.c_oflag = ONLCR | OPOST; pty->tios.c_lflag = ECHO | ECHOE | ECHOK | ICANON | ISIG | IEXTEN; pty->tios.c_cflag = CREAD | CS8; pty->tios.c_cc[VEOF] = 4; /* ^D */ pty->tios.c_cc[VEOL] = 0; /* Not set */ pty->tios.c_cc[VERASE] = 0x7f; /* ^? */ pty->tios.c_cc[VINTR] = 3; /* ^C */ pty->tios.c_cc[VKILL] = 21; /* ^U */ pty->tios.c_cc[VMIN] = 1; pty->tios.c_cc[VQUIT] = 28; /* ^\ */ pty->tios.c_cc[VSTART] = 17; /* ^Q */ pty->tios.c_cc[VSTOP] = 19; /* ^S */ pty->tios.c_cc[VSUSP] = 26; /* ^Z */ pty->tios.c_cc[VTIME] = 0; pty->tios.c_cc[VLNEXT] = 22; /* ^V */ pty->tios.c_cc[VWERASE] = 23; /* ^W */ pty->canon_buffer = malloc(TTY_BUFFER_SIZE); pty->canon_bufsize = TTY_BUFFER_SIZE-2; pty->canon_buflen = 0; return pty; } int pty_create(void *size, fs_node_t ** fs_master, fs_node_t ** fs_slave) { pty_t * pty = pty_new(size, ++_pty_counter); *fs_master = pty->master; *fs_slave = pty->slave; return 0; }