123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597 |
- /*
- * Serial back end (Unix-specific).
- */
- #include <stdio.h>
- #include <stdlib.h>
- #include <assert.h>
- #include <limits.h>
- #include <errno.h>
- #include <unistd.h>
- #include <fcntl.h>
- #include <termios.h>
- #include "putty.h"
- #include "tree234.h"
- #define SERIAL_MAX_BACKLOG 4096
- typedef struct Serial Serial;
- struct Serial {
- Seat *seat;
- LogContext *logctx;
- int fd;
- bool finished;
- size_t inbufsize;
- bufchain output_data;
- Backend backend;
- };
- /*
- * We store our serial backends in a tree sorted by fd, so that
- * when we get an uxsel notification we know which backend instance
- * is the owner of the serial port that caused it.
- */
- static int serial_compare_by_fd(void *av, void *bv)
- {
- Serial *a = (Serial *)av;
- Serial *b = (Serial *)bv;
- if (a->fd < b->fd)
- return -1;
- else if (a->fd > b->fd)
- return +1;
- return 0;
- }
- static int serial_find_by_fd(void *av, void *bv)
- {
- int a = *(int *)av;
- Serial *b = (Serial *)bv;
- if (a < b->fd)
- return -1;
- else if (a > b->fd)
- return +1;
- return 0;
- }
- static tree234 *serial_by_fd = NULL;
- static void serial_select_result(int fd, int event);
- static void serial_uxsel_setup(Serial *serial);
- static void serial_try_write(Serial *serial);
- static char *serial_configure(Serial *serial, Conf *conf)
- {
- struct termios options;
- int bflag, bval, speed, flow, parity;
- const char *str;
- if (serial->fd < 0)
- return dupstr("Unable to reconfigure already-closed "
- "serial connection");
- tcgetattr(serial->fd, &options);
- /*
- * Find the appropriate baud rate flag.
- */
- speed = conf_get_int(conf, CONF_serspeed);
- #define SETBAUD(x) (bflag = B ## x, bval = x)
- #define CHECKBAUD(x) do { if (speed >= x) SETBAUD(x); } while (0)
- SETBAUD(50);
- #ifdef B75
- CHECKBAUD(75);
- #endif
- #ifdef B110
- CHECKBAUD(110);
- #endif
- #ifdef B134
- CHECKBAUD(134);
- #endif
- #ifdef B150
- CHECKBAUD(150);
- #endif
- #ifdef B200
- CHECKBAUD(200);
- #endif
- #ifdef B300
- CHECKBAUD(300);
- #endif
- #ifdef B600
- CHECKBAUD(600);
- #endif
- #ifdef B1200
- CHECKBAUD(1200);
- #endif
- #ifdef B1800
- CHECKBAUD(1800);
- #endif
- #ifdef B2400
- CHECKBAUD(2400);
- #endif
- #ifdef B4800
- CHECKBAUD(4800);
- #endif
- #ifdef B9600
- CHECKBAUD(9600);
- #endif
- #ifdef B19200
- CHECKBAUD(19200);
- #endif
- #ifdef B38400
- CHECKBAUD(38400);
- #endif
- #ifdef B57600
- CHECKBAUD(57600);
- #endif
- #ifdef B76800
- CHECKBAUD(76800);
- #endif
- #ifdef B115200
- CHECKBAUD(115200);
- #endif
- #ifdef B153600
- CHECKBAUD(153600);
- #endif
- #ifdef B230400
- CHECKBAUD(230400);
- #endif
- #ifdef B307200
- CHECKBAUD(307200);
- #endif
- #ifdef B460800
- CHECKBAUD(460800);
- #endif
- #ifdef B500000
- CHECKBAUD(500000);
- #endif
- #ifdef B576000
- CHECKBAUD(576000);
- #endif
- #ifdef B921600
- CHECKBAUD(921600);
- #endif
- #ifdef B1000000
- CHECKBAUD(1000000);
- #endif
- #ifdef B1152000
- CHECKBAUD(1152000);
- #endif
- #ifdef B1500000
- CHECKBAUD(1500000);
- #endif
- #ifdef B2000000
- CHECKBAUD(2000000);
- #endif
- #ifdef B2500000
- CHECKBAUD(2500000);
- #endif
- #ifdef B3000000
- CHECKBAUD(3000000);
- #endif
- #ifdef B3500000
- CHECKBAUD(3500000);
- #endif
- #ifdef B4000000
- CHECKBAUD(4000000);
- #endif
- #undef CHECKBAUD
- #undef SETBAUD
- cfsetispeed(&options, bflag);
- cfsetospeed(&options, bflag);
- logeventf(serial->logctx, "Configuring baud rate %d", bval);
- options.c_cflag &= ~CSIZE;
- switch (conf_get_int(conf, CONF_serdatabits)) {
- case 5: options.c_cflag |= CS5; break;
- case 6: options.c_cflag |= CS6; break;
- case 7: options.c_cflag |= CS7; break;
- case 8: options.c_cflag |= CS8; break;
- default: return dupstr("Invalid number of data bits "
- "(need 5, 6, 7 or 8)");
- }
- logeventf(serial->logctx, "Configuring %d data bits",
- conf_get_int(conf, CONF_serdatabits));
- if (conf_get_int(conf, CONF_serstopbits) >= 4) {
- options.c_cflag |= CSTOPB;
- } else {
- options.c_cflag &= ~CSTOPB;
- }
- logeventf(serial->logctx, "Configuring %s",
- (options.c_cflag & CSTOPB ? "2 stop bits" : "1 stop bit"));
- options.c_iflag &= ~(IXON|IXOFF);
- #ifdef CRTSCTS
- options.c_cflag &= ~CRTSCTS;
- #endif
- #ifdef CNEW_RTSCTS
- options.c_cflag &= ~CNEW_RTSCTS;
- #endif
- flow = conf_get_int(conf, CONF_serflow);
- if (flow == SER_FLOW_XONXOFF) {
- options.c_iflag |= IXON | IXOFF;
- str = "XON/XOFF";
- } else if (flow == SER_FLOW_RTSCTS) {
- #ifdef CRTSCTS
- options.c_cflag |= CRTSCTS;
- #endif
- #ifdef CNEW_RTSCTS
- options.c_cflag |= CNEW_RTSCTS;
- #endif
- str = "RTS/CTS";
- } else
- str = "no";
- logeventf(serial->logctx, "Configuring %s flow control", str);
- /* Parity */
- parity = conf_get_int(conf, CONF_serparity);
- if (parity == SER_PAR_ODD) {
- options.c_cflag |= PARENB;
- options.c_cflag |= PARODD;
- str = "odd";
- } else if (parity == SER_PAR_EVEN) {
- options.c_cflag |= PARENB;
- options.c_cflag &= ~PARODD;
- str = "even";
- } else {
- options.c_cflag &= ~PARENB;
- str = "no";
- }
- logeventf(serial->logctx, "Configuring %s parity", str);
- options.c_cflag |= CLOCAL | CREAD;
- options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
- options.c_iflag &= ~(ISTRIP | IGNCR | INLCR | ICRNL
- #ifdef IUCLC
- | IUCLC
- #endif
- );
- options.c_oflag &= ~(OPOST
- #ifdef ONLCR
- | ONLCR
- #endif
- #ifdef OCRNL
- | OCRNL
- #endif
- #ifdef ONOCR
- | ONOCR
- #endif
- #ifdef ONLRET
- | ONLRET
- #endif
- );
- options.c_cc[VMIN] = 1;
- options.c_cc[VTIME] = 0;
- if (tcsetattr(serial->fd, TCSANOW, &options) < 0)
- return dupprintf("Configuring serial port: %s", strerror(errno));
- return NULL;
- }
- /*
- * Called to set up the serial connection.
- *
- * Returns an error message, or NULL on success.
- *
- * Also places the canonical host name into `realhost'. It must be
- * freed by the caller.
- */
- static char *serial_init(const BackendVtable *vt, Seat *seat,
- Backend **backend_handle, LogContext *logctx,
- Conf *conf, const char *host, int port,
- char **realhost, bool nodelay, bool keepalive)
- {
- Serial *serial;
- char *err;
- char *line;
- /* No local authentication phase in this protocol */
- seat_set_trust_status(seat, false);
- serial = snew(Serial);
- memset(serial, 0, sizeof(Serial));
- serial->backend.vt = vt;
- *backend_handle = &serial->backend;
- serial->seat = seat;
- serial->logctx = logctx;
- serial->finished = false;
- serial->inbufsize = 0;
- bufchain_init(&serial->output_data);
- line = conf_get_str(conf, CONF_serline);
- logeventf(serial->logctx, "Opening serial device %s", line);
- serial->fd = open(line, O_RDWR | O_NOCTTY | O_NDELAY | O_NONBLOCK);
- if (serial->fd < 0)
- return dupprintf("Opening serial port '%s': %s",
- line, strerror(errno));
- cloexec(serial->fd);
- err = serial_configure(serial, conf);
- if (err)
- return err;
- *realhost = dupstr(line);
- if (!serial_by_fd)
- serial_by_fd = newtree234(serial_compare_by_fd);
- add234(serial_by_fd, serial);
- serial_uxsel_setup(serial);
- /*
- * Specials are always available.
- */
- seat_update_specials_menu(serial->seat);
- return NULL;
- }
- static void serial_close(Serial *serial)
- {
- if (serial->fd >= 0) {
- uxsel_del(serial->fd);
- close(serial->fd);
- serial->fd = -1;
- }
- }
- static void serial_free(Backend *be)
- {
- Serial *serial = container_of(be, Serial, backend);
- serial_close(serial);
- bufchain_clear(&serial->output_data);
- sfree(serial);
- }
- static void serial_reconfig(Backend *be, Conf *conf)
- {
- Serial *serial = container_of(be, Serial, backend);
- char *err = serial_configure(serial, conf);
- if (err) {
- /*
- * FIXME: apart from freeing the dynamically allocated
- * message, what should we do if this returns an error?
- */
- sfree(err);
- }
- }
- static void serial_select_result(int fd, int event)
- {
- Serial *serial;
- char buf[4096];
- int ret;
- bool finished = false;
- serial = find234(serial_by_fd, &fd, serial_find_by_fd);
- if (!serial)
- return; /* spurious event; keep going */
- if (event == 1) {
- ret = read(serial->fd, buf, sizeof(buf));
- if (ret == 0) {
- /*
- * Shouldn't happen on a real serial port, but I'm open
- * to the idea that there might be two-way devices we
- * can treat _like_ serial ports which can return EOF.
- */
- finished = true;
- } else if (ret < 0) {
- #ifdef EAGAIN
- if (errno == EAGAIN)
- return; /* spurious */
- #endif
- #ifdef EWOULDBLOCK
- if (errno == EWOULDBLOCK)
- return; /* spurious */
- #endif
- perror("read serial port");
- exit(1);
- } else if (ret > 0) {
- serial->inbufsize = seat_stdout(serial->seat, buf, ret);
- serial_uxsel_setup(serial); /* might acquire backlog and freeze */
- }
- } else if (event == 2) {
- /*
- * Attempt to send data down the pty.
- */
- serial_try_write(serial);
- }
- if (finished) {
- serial_close(serial);
- serial->finished = true;
- seat_notify_remote_exit(serial->seat);
- }
- }
- static void serial_uxsel_setup(Serial *serial)
- {
- int rwx = 0;
- if (serial->inbufsize <= SERIAL_MAX_BACKLOG)
- rwx |= SELECT_R;
- if (bufchain_size(&serial->output_data))
- rwx |= SELECT_W; /* might also want to write to it */
- uxsel_set(serial->fd, rwx, serial_select_result);
- }
- static void serial_try_write(Serial *serial)
- {
- ssize_t ret;
- assert(serial->fd >= 0);
- while (bufchain_size(&serial->output_data) > 0) {
- ptrlen data = bufchain_prefix(&serial->output_data);
- ret = write(serial->fd, data.ptr, data.len);
- if (ret < 0 && (errno == EWOULDBLOCK)) {
- /*
- * We've sent all we can for the moment.
- */
- break;
- }
- if (ret < 0) {
- perror("write serial port");
- exit(1);
- }
- bufchain_consume(&serial->output_data, ret);
- }
- seat_sent(serial->seat, bufchain_size(&serial->output_data));
- serial_uxsel_setup(serial);
- }
- /*
- * Called to send data down the serial connection.
- */
- static void serial_send(Backend *be, const char *buf, size_t len)
- {
- Serial *serial = container_of(be, Serial, backend);
- if (serial->fd < 0)
- return;
- bufchain_add(&serial->output_data, buf, len);
- serial_try_write(serial);
- }
- /*
- * Called to query the current sendability status.
- */
- static size_t serial_sendbuffer(Backend *be)
- {
- Serial *serial = container_of(be, Serial, backend);
- return bufchain_size(&serial->output_data);
- }
- /*
- * Called to set the size of the window
- */
- static void serial_size(Backend *be, int width, int height)
- {
- /* Do nothing! */
- return;
- }
- /*
- * Send serial special codes.
- */
- static void serial_special(Backend *be, SessionSpecialCode code, int arg)
- {
- Serial *serial = container_of(be, Serial, backend);
- if (serial->fd >= 0 && code == SS_BRK) {
- tcsendbreak(serial->fd, 0);
- logevent(serial->logctx, "Sending serial break at user request");
- }
- return;
- }
- /*
- * Return a list of the special codes that make sense in this
- * protocol.
- */
- static const SessionSpecial *serial_get_specials(Backend *be)
- {
- static const struct SessionSpecial specials[] = {
- {"Break", SS_BRK},
- {NULL, SS_EXITMENU}
- };
- return specials;
- }
- static bool serial_connected(Backend *be)
- {
- return true; /* always connected */
- }
- static bool serial_sendok(Backend *be)
- {
- return true;
- }
- static void serial_unthrottle(Backend *be, size_t backlog)
- {
- Serial *serial = container_of(be, Serial, backend);
- serial->inbufsize = backlog;
- serial_uxsel_setup(serial);
- }
- static bool serial_ldisc(Backend *be, int option)
- {
- /*
- * Local editing and local echo are off by default.
- */
- return false;
- }
- static void serial_provide_ldisc(Backend *be, Ldisc *ldisc)
- {
- /* This is a stub. */
- }
- static int serial_exitcode(Backend *be)
- {
- Serial *serial = container_of(be, Serial, backend);
- if (serial->fd >= 0)
- return -1; /* still connected */
- else
- /* Exit codes are a meaningless concept with serial ports */
- return INT_MAX;
- }
- /*
- * cfg_info for Serial does nothing at all.
- */
- static int serial_cfg_info(Backend *be)
- {
- return 0;
- }
- const BackendVtable serial_backend = {
- .init = serial_init,
- .free = serial_free,
- .reconfig = serial_reconfig,
- .send = serial_send,
- .sendbuffer = serial_sendbuffer,
- .size = serial_size,
- .special = serial_special,
- .get_specials = serial_get_specials,
- .connected = serial_connected,
- .exitcode = serial_exitcode,
- .sendok = serial_sendok,
- .ldisc_option_state = serial_ldisc,
- .provide_ldisc = serial_provide_ldisc,
- .unthrottle = serial_unthrottle,
- .cfg_info = serial_cfg_info,
- .id = "serial",
- .displayname_tc = "Serial",
- .displayname_lc = "serial",
- .protocol = PROT_SERIAL,
- .serial_parity_mask = ((1 << SER_PAR_NONE) |
- (1 << SER_PAR_ODD) |
- (1 << SER_PAR_EVEN)),
- .serial_flow_mask = ((1 << SER_FLOW_NONE) |
- (1 << SER_FLOW_XONXOFF) |
- (1 << SER_FLOW_RTSCTS)),
- };
|