Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mRemoteNG/PuTTYNG.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'unix/serial.c')
-rw-r--r--unix/serial.c596
1 files changed, 596 insertions, 0 deletions
diff --git a/unix/serial.c b/unix/serial.c
new file mode 100644
index 00000000..1e20dd20
--- /dev/null
+++ b/unix/serial.c
@@ -0,0 +1,596 @@
+/*
+ * 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)),
+};