/* $Id: tty.c,v 1.12 2003/12/11 11:55:06 steve Exp $ */ /*- * Copyright (c) 2001 Steve C. Woodford. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by Steve C. Woodford. * 4. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "context.h" #include "buffer.h" #include "config.h" #include "tty.h" static const char *tty_cf_init(void *, char **, int, int, void *); static int tty_init(struct client_ctx *, const void *); static void tty_destroy(struct client_ctx *); static int tty_event(struct client_ctx *, int); static int tty_output(struct client_ctx *, const char *, size_t, struct client_ctx *); static int tty_read_pending(struct client_ctx *); static int tty_write_pending(struct client_ctx *); static int tty_ioctl(struct client_ctx *, int, void *, struct client_ctx *); static int tty_open_device(struct client_ctx *); static void tty_close_device(struct client_ctx *); struct client_ops tty_ops = { "tty", tty_cf_init, tty_init, tty_destroy, tty_event, tty_output, tty_read_pending, tty_write_pending, tty_ioctl }; struct tty_opt { char *to_device; speed_t to_ispeed; speed_t to_ospeed; tcflag_t to_cflagmask; tcflag_t to_cflag; tcflag_t to_iflagmask; tcflag_t to_iflag; int to_closeidle; }; struct tty_ctx { struct tty_opt tc_to; struct buffer_ctx *tc_buff; }; static struct tty_opt defopt = { NULL, B9600, /* ispeed */ B9600, /* ospeed */ ~0, 0, /* cflag */ ~0, 0, /* iflag */ 0 /* closeidle */ }; static struct { int sp_int; speed_t sp_baud; } tty_speeds[] = { {50, B50}, {75, B75}, {110, B110}, {134, B134}, {150, B150}, {200, B200}, {300, B300}, {600, B600}, {1200, B1200}, {1800, B1800}, {2400, B2400}, {4800, B4800}, {9600, B9600}, {19200, B19200}, {38400, B38400}, #if !defined(_POSIX_C_SOURCE) && !defined(_XOPEN_SOURCE) {7200, B7200}, {14400, B14400}, {28800, B28800}, {57600, B57600}, {76800, B76800}, {115200,B115200}, {230400,B230400}, #endif {0, 0} }; #ifndef MIN #define MIN(a,b) (((a) < (b)) ? (a) : (b)) #endif static const char *cf_tty_device(void *, char **, int, int, void *); static const char *cf_tty_speed(void *, char **, int, int, void *); static const char *cf_tty_clocal(void *, char **, int, int, void *); static const char *cf_tty_crtscts(void *, char **, int, int, void *); static const char *cf_tty_xonxoff(void *, char **, int, int, void *); static const char *cf_tty_closeidle(void *, char **, int, int, void *); static struct config_tokens cf_tty_tokens[] = { {"device", 1, cf_tty_device}, {"speed", 1, cf_tty_speed}, {"baud", 1, cf_tty_speed}, {"ispeed", 1, cf_tty_speed}, {"ospeed", 1, cf_tty_speed}, {"clocal", 1, cf_tty_clocal}, {"crtscts", 1, cf_tty_crtscts}, {"xonxoff", 1, cf_tty_xonxoff}, {"closeidle", 1, cf_tty_closeidle}, {NULL, 0, NULL} }; /* ARGSUSED */ static const char * tty_cf_init(void *cs, char **argv, int argc, int is_compound, void *arg) { static struct tty_opt to; const char *errstr; to = defopt; errstr = config_parse(cs, cf_tty_tokens, (void *)&to); if (errstr) { if (to.to_device) (void) free(to.to_device); return (errstr); } if (to.to_device == NULL) return (config_err(cs, "You must specify a device name")); *((void **) arg) = &to; return (NULL); } static int tty_init(struct client_ctx *cc, const void *arg) { struct tty_ctx *tc; if ((tc = calloc(1, sizeof(*tc))) == NULL) return (-1); cc->cc_data = tc; tc->tc_to = *((const struct tty_opt *) arg); if (tty_open_device(cc) < 0) { (void) free(tc->tc_to.to_device); (void) free(tc); return (-1); } return (0); } static void tty_destroy(struct client_ctx *cc) { struct tty_ctx *tc = cc->cc_data; tty_close_device(cc); (void) free(tc->tc_to.to_device); (void) free(tc); } static int tty_event(struct client_ctx *cc, int events) { struct tty_ctx *tc = cc->cc_data; char buff[128]; ssize_t rv; int nbytes; if (tc->tc_buff == NULL) return (0); if (events & POLLRDNORM) { /* * Read data from the tty */ if (ioctl(cc->cc_fd, FIONREAD, &nbytes) < 0) return (-1); while (nbytes) { rv = read(cc->cc_fd, buff, MIN(nbytes, sizeof(buff))); if (rv == 0) break; else if (rv < 0) return (-1); context_client_output(cc->cc_ctx, buff, (size_t)rv, cc); nbytes -= (int)rv; } } if (events & POLLWRNORM) return ((int) buffer_drain(tc->tc_buff)); return (0); } /* ARGSUSED */ static int tty_output(struct client_ctx *cc, const char *buff, size_t len, struct client_ctx *sender) { struct tty_ctx *tc = cc->cc_data; if (sender->cc_options.co_readonly || tc->tc_buff == NULL) return (0); return buffer_fill(tc->tc_buff, buff, len); } static int tty_read_pending(struct client_ctx *cc) { struct tty_ctx *tc = cc->cc_data; if (tc->tc_buff == NULL) return (0); return (1); } static int tty_write_pending(struct client_ctx *cc) { struct tty_ctx *tc = cc->cc_data; if (tc->tc_buff == NULL) return (0); return (buffer_length(tc->tc_buff) != 0); } /* ARGSUSED */ static int tty_ioctl(struct client_ctx *cc, int cmd, void *arg, struct client_ctx *sender) { struct tty_ctx *tc = cc->cc_data; switch (cmd) { case TTY_IOCTL_SENDBREAK: if (tc->tc_buff != NULL && (sender->cc_options.co_readonly == 0 || sender->cc_options.co_allowbreak)) tcsendbreak(cc->cc_fd, 0); break; case CONTEXT_IOCTL_ADD_CLIENT: if (tc->tc_buff == NULL && tty_open_device(cc) < 0) return (-1); break; case CONTEXT_IOCTL_NEW_SERVER: case CONTEXT_IOCTL_DEL_CLIENT: if (tc->tc_to.to_closeidle && cc->cc_ctx->c_client_count == 0) tty_close_device(cc); break; } return (0); } static int tty_open_device(struct client_ctx *cc) { struct tty_ctx *tc = cc->cc_data; struct termios tent; int flags; if (tc->tc_buff != NULL) return (0); if ((cc->cc_fd = open(tc->tc_to.to_device, O_RDWR | O_NONBLOCK | O_EXCL)) < 0) return (-1); if (tcgetattr(cc->cc_fd, &tent) < 0) { (void) close(cc->cc_fd); return (-1); } cfmakeraw(&tent); if (cfsetispeed(&tent, tc->tc_to.to_ispeed) < 0 || cfsetospeed(&tent, tc->tc_to.to_ospeed) < 0) { (void) close(cc->cc_fd); return (-1); } /* * We don't want to know about BREAK, we don't want to know about * reaching the end of a line, or about parity errors */ tent.c_iflag |= IGNBRK; tent.c_iflag &= ~(IMAXBEL | IGNPAR); tent.c_iflag &= tc->tc_to.to_iflagmask; tent.c_iflag |= tc->tc_to.to_iflag; tent.c_cflag &= tc->tc_to.to_cflagmask; tent.c_cflag |= tc->tc_to.to_cflag; if (tcsetattr(cc->cc_fd, TCSANOW, &tent) < 0) { (void) close(cc->cc_fd); return (-1); } /* * If this is *not* a pseudo tty, ensure DTR is asserted. * Note: This relies on TIOCGFLAGS returning ENOTTY for pty(4)'s. */ if (ioctl(cc->cc_fd, TIOCSDTR, 0) < 0 && errno != ENOTTY) { (void) close(cc->cc_fd); return (-1); } if (buffer_init(&tc->tc_buff, cc->cc_fd) < 0) { (void) close(cc->cc_fd); return (-1); } return (0); } static void tty_close_device(struct client_ctx *cc) { struct tty_ctx *tc = cc->cc_data; if (tc->tc_buff == NULL) return; buffer_destroy(tc->tc_buff); (void) close(cc->cc_fd); tc->tc_buff = NULL; } /* ARGSUSED */ static const char * cf_tty_device(void *cs, char **argv, int argc, int is_compound, void *arg) { struct tty_opt *to = arg; struct stat st; int fd; if (stat(argv[1], &st) < 0) return (config_err(cs, "%s: %s", argv[1], strerror(errno))); if (!S_ISCHR(st.st_mode)) return (config_err(cs, "%s: Not character special", argv[1])); if ((fd = open(argv[1], O_RDWR | O_NONBLOCK | O_EXCL)) < 0) return (config_err(cs, "%s: %s", argv[1], strerror(errno))); if (isatty(fd) == 0) { (void) close(fd); return (config_err(cs, "%s: Not a tty device", argv[1])); } (void) close(fd); if ((to->to_device = strdup(argv[1])) == NULL) return (config_err(cs, "%s", strerror(errno))); return (NULL); } /* ARGSUSED */ static const char * cf_tty_speed(void *cs, char **argv, int argc, int is_compound, void *arg) { struct tty_opt *to = arg; const char *errstr; int speed, i; if (strcasecmp(argv[1], "exta") == 0) speed = EXTA; else if (strcasecmp(argv[1], "extb") == 0) speed = EXTB; else { if ((errstr = config_integer(cs, argv[1], &speed)) != NULL) return (errstr); for (i = 0; tty_speeds[i].sp_int; i++) if (tty_speeds[i].sp_int == speed) break; if (tty_speeds[i].sp_int == 0) return (config_err(cs, "Invalid baudrate: `%s'", argv[1])); } if (strcasecmp(argv[0], "ispeed") == 0) to->to_ispeed = speed; else if (strcasecmp(argv[0], "ospeed") == 0) to->to_ospeed = speed; else { to->to_ispeed = speed; to->to_ospeed = speed; } return (NULL); } /* ARGSUSED */ static const char * cf_tty_clocal(void *cs, char **argv, int argc, int is_compound, void *arg) { struct tty_opt *to = arg; const char *errstr; int clocal; if ((errstr = config_boolean(cs, argv[1], &clocal)) != NULL) return (errstr); to->to_cflagmask &= ~CLOCAL; if (clocal) to->to_cflag |= CLOCAL; else to->to_cflag &= ~CLOCAL; return (0); } /* ARGSUSED */ static const char * cf_tty_crtscts(void *cs, char **argv, int argc, int is_compound, void *arg) { struct tty_opt *to = arg; const char *errstr; int crtscts; if ((errstr = config_boolean(cs, argv[1], &crtscts)) != NULL) return (errstr); to->to_cflagmask &= ~CRTSCTS; if (crtscts) to->to_cflag |= CRTSCTS; else to->to_cflag &= ~CRTSCTS; return (0); } /* ARGSUSED */ static const char * cf_tty_xonxoff(void *cs, char **argv, int argc, int is_compound, void *arg) { struct tty_opt *to = arg; const char *errstr; int xonxoff; if ((errstr = config_boolean(cs, argv[1], &xonxoff)) != NULL) return (errstr); to->to_iflagmask &= ~(IXON | IXOFF | IXANY); if (xonxoff) to->to_iflag |= IXON | IXOFF | IXANY; else to->to_iflag &= ~(IXON | IXOFF | IXANY); return (0); } static const char * cf_tty_closeidle(void *cs, char **argv, int argc, int is_compount, void *arg) { struct tty_opt *to = arg; const char *errstr; if ((errstr = config_boolean(cs, argv[1], &to->to_closeidle)) != NULL) return (errstr); return (0); }