Serial back end for Unix. Due to hardware limitations (no Linux box
authorsimon <simon@cda61777-01e9-0310-a592-d414129be87e>
Mon, 28 Aug 2006 14:29:02 +0000 (14:29 +0000)
committersimon <simon@cda61777-01e9-0310-a592-d414129be87e>
Mon, 28 Aug 2006 14:29:02 +0000 (14:29 +0000)
I own has both an X display and a working serial port) I have been
unable to give this the full testing it deserves; I've managed to
demonstrate the basic functionality of Unix Plink talking to a
serial port, but I haven't been able to test the GTK front end. I
have no reason to think it will fail, but I'll be more comfortable
once somebody has actually tested it.

git-svn-id: svn://svn.tartarus.org/sgt/putty@6822 cda61777-01e9-0310-a592-d414129be87e

Recipe
unix/gtkdlg.c
unix/gtkwin.c
unix/unix.h
unix/uxcfg.c
unix/uxplink.c
unix/uxser.c [new file with mode: 0644]

diff --git a/Recipe b/Recipe
index 31a6b5d..d2fe8d6 100644 (file)
--- a/Recipe
+++ b/Recipe
@@ -223,7 +223,7 @@ GUITERM  = TERMINAL window windlg winctrls sizetip winucs winprint
          + winutils wincfg sercfg
 
 # Same thing on Unix.
-UXTERM   = TERMINAL uxcfg uxucs uxprint timing
+UXTERM   = TERMINAL uxcfg sercfg uxucs uxprint timing
 GTKTERM  = UXTERM gtkwin gtkcfg gtkdlg gtkcols gtkpanel xkeysym
 OSXTERM  = UXTERM osxwin osxdlg osxctrls
 
@@ -266,6 +266,9 @@ BE_NONE  = be_none nocproxy
 # More backend sets, with the additional Windows serial-port module.
 W_BE_ALL = be_all_s winser cproxy
 W_BE_NOSSH = be_nos_s winser nocproxy
+# And with the Unix serial-port module.
+U_BE_ALL = be_all_s uxser cproxy
+U_BE_NOSSH = be_nos_s uxser nocproxy
 
 # ------------------------------------------------------------
 # Definitions of actual programs. The program name, followed by a
@@ -293,12 +296,13 @@ puttygen : [G] winpgen sshrsag sshdssg sshprime sshdes sshbn sshmd5 version
 
 pterm    : [X] GTKTERM uxmisc misc ldisc settings uxpty uxsel BE_NONE uxstore
          + uxsignal CHARSET cmdline uxpterm version time
-putty    : [X] GTKTERM uxmisc misc ldisc settings uxsel BE_ALL uxstore
+putty    : [X] GTKTERM uxmisc misc ldisc settings uxsel U_BE_ALL uxstore
          + uxsignal CHARSET uxputty NONSSH UXSSH UXMISC ux_x11
-puttytel : [X] GTKTERM uxmisc misc ldisc settings uxsel BE_NOSSH
+puttytel : [X] GTKTERM uxmisc misc ldisc settings uxsel U_BE_NOSSH
         + uxstore uxsignal CHARSET uxputty NONSSH UXMISC
 
-plink    : [U] uxplink uxcons NONSSH UXSSH BE_ALL logging UXMISC uxsignal ux_x11
+plink    : [U] uxplink uxcons NONSSH UXSSH U_BE_ALL logging UXMISC uxsignal
+         + ux_x11
 
 puttygen : [U] cmdgen sshrsag sshdssg sshprime sshdes sshbn sshmd5 version
          + sshrand uxnoise sshsha misc sshrsa sshdss uxcons uxstore uxmisc
@@ -318,5 +322,5 @@ PuTTYgen : [M] macpgen sshrsag sshdssg sshprime sshdes sshbn sshmd5 version
          + sshrand macnoise sshsha macstore misc sshrsa sshdss macmisc sshpubk
          + sshaes sshsh512 import macpgen.rsrc macpgkey macabout
 
-PuTTY    : [MX] osxmain OSXTERM OSXMISC CHARSET BE_ALL NONSSH UXSSH
+PuTTY    : [MX] osxmain OSXTERM OSXMISC CHARSET U_BE_ALL NONSSH UXSSH
          + ux_x11 uxpty uxsignal testback putty.icns info.plist
index 19546d9..b2725e4 100644 (file)
@@ -55,6 +55,7 @@ struct uctrl {
     GtkWidget *menu;         /* for optionmenu (==droplist) */
     GtkWidget *optmenu;              /* also for optionmenu */
     GtkWidget *text;         /* for text */
+    GtkWidget *label;         /* for dlg_label_change */
     GtkAdjustment *adj;       /* for the scrollbar in a list box */
     guint textsig;
 };
@@ -90,6 +91,7 @@ static gboolean widget_focus(GtkWidget *widget, GdkEventFocus *event,
                              gpointer data);
 static void shortcut_add(struct Shortcuts *scs, GtkWidget *labelw,
                         int chr, int action, void *ptr);
+static void shortcut_highlight(GtkWidget *label, int chr);
 static int listitem_single_key(GtkWidget *item, GdkEventKey *event,
                                gpointer data);
 static int listitem_multi_key(GtkWidget *item, GdkEventKey *event,
@@ -594,13 +596,42 @@ void dlg_text_set(union control *ctrl, void *dlg, char const *text)
 
 void dlg_label_change(union control *ctrl, void *dlg, char const *text)
 {
-    /*
-     * This function is currently only used by the config box to
-     * switch the labels on the host and port boxes between serial
-     * and network modes. Since Unix does not (yet) have a serial
-     * back end, this function can safely do nothing for the
-     * moment.
-     */
+    struct dlgparam *dp = (struct dlgparam *)dlg;
+    struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
+
+    switch (uc->ctrl->generic.type) {
+      case CTRL_BUTTON:
+       gtk_label_set_text(GTK_LABEL(uc->toplevel), text);
+       shortcut_highlight(uc->toplevel, ctrl->button.shortcut);
+       break;
+      case CTRL_CHECKBOX:
+       gtk_label_set_text(GTK_LABEL(uc->toplevel), text);
+       shortcut_highlight(uc->toplevel, ctrl->checkbox.shortcut);
+       break;
+      case CTRL_RADIO:
+       gtk_label_set_text(GTK_LABEL(uc->label), text);
+       shortcut_highlight(uc->label, ctrl->radio.shortcut);
+       break;
+      case CTRL_EDITBOX:
+       gtk_label_set_text(GTK_LABEL(uc->label), text);
+       shortcut_highlight(uc->label, ctrl->editbox.shortcut);
+       break;
+      case CTRL_FILESELECT:
+       gtk_label_set_text(GTK_LABEL(uc->label), text);
+       shortcut_highlight(uc->label, ctrl->fileselect.shortcut);
+       break;
+      case CTRL_FONTSELECT:
+       gtk_label_set_text(GTK_LABEL(uc->label), text);
+       shortcut_highlight(uc->label, ctrl->fontselect.shortcut);
+       break;
+      case CTRL_LISTBOX:
+       gtk_label_set_text(GTK_LABEL(uc->label), text);
+       shortcut_highlight(uc->label, ctrl->listbox.shortcut);
+       break;
+      default:
+       assert(!"This shouldn't happen");
+       break;
+    }
 }
 
 void dlg_filesel_set(union control *ctrl, void *dlg, Filename fn)
@@ -1336,6 +1367,7 @@ GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs,
        uc->buttons = NULL;
        uc->entry = uc->list = uc->menu = NULL;
        uc->button = uc->optmenu = uc->text = NULL;
+       uc->label = NULL;
 
         switch (ctrl->generic.type) {
           case CTRL_BUTTON:
@@ -1381,6 +1413,7 @@ GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs,
                     gtk_widget_show(label);
                    shortcut_add(scs, label, ctrl->radio.shortcut,
                                 SHORTCUT_UCTRL, uc);
+                   uc->label = label;
                 }
                 percentages = g_new(gint, ctrl->radio.ncolumns);
                 for (i = 0; i < ctrl->radio.ncolumns; i++) {
@@ -1485,6 +1518,7 @@ GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs,
                    gtk_widget_show(w);
 
                    w = container;
+                   uc->label = label;
                }
                gtk_signal_connect(GTK_OBJECT(uc->entry), "focus_out_event",
                                   GTK_SIGNAL_FUNC(editbox_lostfocus), dp);
@@ -1513,6 +1547,7 @@ GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs,
                                  ctrl->fileselect.shortcut :
                                  ctrl->fontselect.shortcut),
                                 SHORTCUT_UCTRL, uc);
+                   uc->label = ww;
                 }
 
                 uc->entry = ww = gtk_entry_new();
@@ -1650,6 +1685,7 @@ GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs,
                shortcut_add(scs, label, ctrl->listbox.shortcut,
                             SHORTCUT_UCTRL, uc);
                 w = container;
+               uc->label = label;
             }
             break;
           case CTRL_TEXT:
@@ -1716,6 +1752,8 @@ static void treeitem_sel(GtkItem *item, gpointer data)
 
     panels_switch_to(sp->panels, sp->panel);
 
+    dlg_refresh(NULL, sp->dp);
+
     sp->dp->shortcuts = &sp->shortcuts;
     sp->dp->currtreeitem = sp->treeitem;
 }
@@ -1935,13 +1973,31 @@ int tree_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data)
     return FALSE;
 }
 
-void shortcut_add(struct Shortcuts *scs, GtkWidget *labelw,
-                 int chr, int action, void *ptr)
+static void shortcut_highlight(GtkWidget *labelw, int chr)
 {
     GtkLabel *label = GTK_LABEL(labelw);
     gchar *currstr, *pattern;
     int i;
 
+    gtk_label_get(label, &currstr);
+    for (i = 0; currstr[i]; i++)
+       if (tolower((unsigned char)currstr[i]) == chr) {
+           GtkRequisition req;
+
+           pattern = dupprintf("%*s_", i, "");
+
+           gtk_widget_size_request(GTK_WIDGET(label), &req);
+           gtk_label_set_pattern(label, pattern);
+           gtk_widget_set_usize(GTK_WIDGET(label), -1, req.height);
+
+           sfree(pattern);
+           break;
+       }
+}
+
+void shortcut_add(struct Shortcuts *scs, GtkWidget *labelw,
+                 int chr, int action, void *ptr)
+{
     if (chr == NO_SHORTCUT)
        return;
 
@@ -1959,20 +2015,7 @@ void shortcut_add(struct Shortcuts *scs, GtkWidget *labelw,
        scs->sc[chr].uc = (struct uctrl *)ptr;
     }
 
-    gtk_label_get(label, &currstr);
-    for (i = 0; currstr[i]; i++)
-       if (tolower((unsigned char)currstr[i]) == chr) {
-           GtkRequisition req;
-
-           pattern = dupprintf("%*s_", i, "");
-
-           gtk_widget_size_request(GTK_WIDGET(label), &req);
-           gtk_label_set_pattern(label, pattern);
-           gtk_widget_set_usize(GTK_WIDGET(label), -1, req.height);
-
-           sfree(pattern);
-           break;
-       }
+    shortcut_highlight(labelw, chr);
 }
 
 int get_listitemheight(void)
index 76f78f6..920d7fe 100644 (file)
@@ -155,6 +155,8 @@ Filename platform_default_filename(const char *name)
 
 char *platform_default_s(const char *name)
 {
+    if (!strcmp(name, "SerialLine"))
+       return dupstr("/dev/ttyS0");
     return NULL;
 }
 
@@ -3444,7 +3446,7 @@ int pt_main(int argc, char **argv)
 
        cmdline_run_saved(&inst->cfg);
 
-       if (!*inst->cfg.host && !cfgbox(&inst->cfg))
+       if (!cfg_launchable(&inst->cfg) && !cfgbox(&inst->cfg))
            exit(0);                   /* config box hit Cancel */
     }
 
index c0b56c7..ff665b9 100644 (file)
@@ -149,4 +149,9 @@ void *sk_getxdmdata(void *sock, int *lenp);
     if (max < fd + 1) max = fd + 1; \
 } while (0)
 
+/*
+ * Exports from winser.c.
+ */
+extern Backend serial_backend;
+
 #endif
index 067fc7e..de8e886 100644 (file)
@@ -69,4 +69,11 @@ void unix_setup_config_box(struct controlbox *b, int midsession)
        }
     }
 
+    /*
+     * Serial back end is available on Unix. However, we have to
+     * mask out a couple of the configuration options: mark and
+     * space parity are not conveniently supported, and neither is
+     * DSR/DTR flow control.
+     */
+    ser_setup_config_box(b, midsession, 0x07, 0x07);
 }
index 1daafe4..38129f9 100644 (file)
@@ -82,6 +82,8 @@ char *platform_default_s(const char *name)
        return dupstr(getenv("TERM"));
     if (!strcmp(name, "UserName"))
        return get_username();
+    if (!strcmp(name, "SerialLine"))
+       return dupstr("/dev/ttyS0");
     return NULL;
 }
 
@@ -627,7 +629,7 @@ int main(int argc, char **argv)
                errors = 1;
            }
        } else if (*p) {
-           if (!*cfg.host) {
+           if (!cfg_launchable(&cfg)) {
                char *q = p;
 
                /*
@@ -701,7 +703,7 @@ int main(int argc, char **argv)
                    {
                        Config cfg2;
                        do_defaults(host, &cfg2);
-                       if (loaded_session || cfg2.host[0] == '\0') {
+                       if (loaded_session || !cfg_launchable(&cfg2)) {
                            /* No settings for this host; use defaults */
                            /* (or session was already loaded with -load) */
                            strncpy(cfg.host, host, sizeof(cfg.host) - 1);
@@ -755,7 +757,7 @@ int main(int argc, char **argv)
     if (errors)
        return 1;
 
-    if (!*cfg.host) {
+    if (!cfg_launchable(&cfg)) {
        usage();
     }
 
diff --git a/unix/uxser.c b/unix/uxser.c
new file mode 100644 (file)
index 0000000..85e9618
--- /dev/null
@@ -0,0 +1,510 @@
+/*
+ * Serial back end (Unix-specific).
+ */
+
+/*
+ * TODO:
+ * 
+ *  - send break.
+ */
+
+#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_backend_data {
+    void *frontend;
+    int fd;
+    int finished;
+    int inbufsize;
+    bufchain output_data;
+} *Serial;
+
+/*
+ * 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 int serial_select_result(int fd, int event);
+static void serial_uxsel_setup(Serial serial);
+static void serial_try_write(Serial serial);
+
+static const char *serial_configure(Serial serial, Config *cfg)
+{
+    struct termios options;
+    int bflag, bval;
+    const char *str;
+    char *msg;
+
+    if (serial->fd < 0)
+       return "Unable to reconfigure already-closed serial connection";
+
+    tcgetattr(serial->fd, &options);
+
+    /*
+     * Find the appropriate baud rate flag.
+     */
+#define SETBAUD(x) (bflag = B ## x, bval = x)
+#define CHECKBAUD(x) do { if (cfg->serspeed >= 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 B230400
+    CHECKBAUD(230400);
+#endif
+#undef CHECKBAUD
+#undef SETBAUD
+    cfsetispeed(&options, bflag);
+    cfsetospeed(&options, bflag);
+    msg = dupprintf("Configuring baud rate %d", bval);
+    logevent(serial->frontend, msg);
+    sfree(msg);
+
+    options.c_cflag &= ~CSIZE;
+    switch (cfg->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 "Invalid number of data bits (need 5, 6, 7 or 8)";
+    }
+    msg = dupprintf("Configuring %d data bits", cfg->serdatabits);
+    logevent(serial->frontend, msg);
+    sfree(msg);
+
+    if (cfg->serstopbits >= 4) {
+       options.c_cflag |= CSTOPB;
+    } else {
+       options.c_cflag &= ~CSTOPB;
+    }
+    msg = dupprintf("Configuring %d stop bits",
+                   (options.c_cflag & CSTOPB ? 2 : 1));
+    logevent(serial->frontend, msg);
+    sfree(msg);
+
+    options.c_cflag &= ~(IXON|IXOFF);
+    if (cfg->serflow == SER_FLOW_XONXOFF) {
+       options.c_cflag |= IXON | IXOFF;
+       str = "XON/XOFF";
+    } else if (cfg->serflow == 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";
+    msg = dupprintf("Configuring %s flow control", str);
+    logevent(serial->frontend, msg);
+    sfree(msg);
+
+    /* Parity */
+    if (cfg->serparity == SER_PAR_ODD) {
+       options.c_cflag |= PARENB;
+       options.c_cflag |= PARODD;
+       str = "odd";
+    } else if (cfg->serparity == SER_PAR_EVEN) {
+       options.c_cflag |= PARENB;
+       options.c_cflag &= ~PARODD;
+       str = "even";
+    } else {
+       options.c_cflag &= ~PARENB;
+       str = "no";
+    }
+    msg = dupprintf("Configuring %s parity", str);
+    logevent(serial->frontend, msg);
+    sfree(msg);
+
+    options.c_cflag |= CLOCAL | CREAD;
+    options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
+    options.c_oflag &= ~OPOST;
+    options.c_cc[VMIN] = 1;
+    options.c_cc[VTIME] = 0;
+
+    if (tcsetattr(serial->fd, TCSANOW, &options) < 0)
+       return "Unable to configure serial port";
+
+    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 const char *serial_init(void *frontend_handle, void **backend_handle,
+                              Config *cfg,
+                              char *host, int port, char **realhost, int nodelay,
+                              int keepalive)
+{
+    Serial serial;
+    const char *err;
+
+    serial = snew(struct serial_backend_data);
+    *backend_handle = serial;
+
+    serial->frontend = frontend_handle;
+    serial->finished = FALSE;
+    serial->inbufsize = 0;
+    bufchain_init(&serial->output_data);
+
+    {
+       char *msg = dupprintf("Opening serial device %s", cfg->serline);
+       logevent(serial->frontend, msg);
+    }
+
+    serial->fd = open(cfg->serline, O_RDWR | O_NOCTTY | O_NDELAY | O_NONBLOCK);
+    if (serial->fd < 0)
+       return "Unable to open serial port";
+
+    err = serial_configure(serial, cfg);
+    if (err)
+       return err;
+
+    *realhost = dupstr(cfg->serline);
+
+    if (!serial_by_fd)
+       serial_by_fd = newtree234(serial_compare_by_fd);
+    add234(serial_by_fd, serial);
+
+    serial_uxsel_setup(serial);
+
+    return NULL;
+}
+
+static void serial_close(Serial serial)
+{
+    if (serial->fd >= 0) {
+       close(serial->fd);
+       serial->fd = -1;
+    }
+}
+
+static void serial_free(void *handle)
+{
+    Serial serial = (Serial) handle;
+
+    serial_close(serial);
+
+    bufchain_clear(&serial->output_data);
+
+    sfree(serial);
+}
+
+static void serial_reconfig(void *handle, Config *cfg)
+{
+    Serial serial = (Serial) handle;
+    const char *err;
+
+    err = serial_configure(serial, cfg);
+
+    /*
+     * FIXME: what should we do if err returns something?
+     */
+}
+
+static int serial_select_result(int fd, int event)
+{
+    Serial serial;
+    char buf[4096];
+    int ret;
+    int finished = FALSE;
+
+    serial = find234(serial_by_fd, &fd, serial_find_by_fd);
+
+    if (!serial)
+       return 1;                      /* 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) {
+           perror("read serial port");
+           exit(1);
+       } else if (ret > 0) {
+           serial->inbufsize = from_backend(serial->frontend, 0, 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;
+
+       notify_remote_exit(serial->frontend);
+    }
+
+    return !finished;
+}
+
+static void serial_uxsel_setup(Serial serial)
+{
+    int rwx = 0;
+
+    if (serial->inbufsize <= SERIAL_MAX_BACKLOG)
+       rwx |= 1;
+    if (bufchain_size(&serial->output_data))
+        rwx |= 2;                      /* might also want to write to it */
+    uxsel_set(serial->fd, rwx, serial_select_result);
+}
+
+static void serial_try_write(Serial serial)
+{
+    void *data;
+    int len, ret;
+
+    assert(serial->fd >= 0);
+
+    while (bufchain_size(&serial->output_data) > 0) {
+        bufchain_prefix(&serial->output_data, &data, &len);
+       ret = write(serial->fd, 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);
+    }
+
+    serial_uxsel_setup(serial);
+}
+
+/*
+ * Called to send data down the serial connection.
+ */
+static int serial_send(void *handle, char *buf, int len)
+{
+    Serial serial = (Serial) handle;
+
+    if (serial->fd < 0)
+       return 0;
+
+    bufchain_add(&serial->output_data, buf, len);
+    serial_try_write(serial);
+
+    return bufchain_size(&serial->output_data);
+}
+
+/*
+ * Called to query the current sendability status.
+ */
+static int serial_sendbuffer(void *handle)
+{
+    Serial serial = (Serial) handle;
+    return bufchain_size(&serial->output_data);
+}
+
+/*
+ * Called to set the size of the window
+ */
+static void serial_size(void *handle, int width, int height)
+{
+    /* Do nothing! */
+    return;
+}
+
+/*
+ * Send serial special codes.
+ */
+static void serial_special(void *handle, Telnet_Special code)
+{
+    /*
+     * FIXME: serial break? XON? XOFF?
+     */
+    return;
+}
+
+/*
+ * Return a list of the special codes that make sense in this
+ * protocol.
+ */
+static const struct telnet_special *serial_get_specials(void *handle)
+{
+    /*
+     * FIXME: serial break? XON? XOFF?
+     */
+    return NULL;
+}
+
+static int serial_connected(void *handle)
+{
+    return 1;                         /* always connected */
+}
+
+static int serial_sendok(void *handle)
+{
+    return 1;
+}
+
+static void serial_unthrottle(void *handle, int backlog)
+{
+    Serial serial = (Serial) handle;
+    serial->inbufsize = backlog;
+    serial_uxsel_setup(serial);
+}
+
+static int serial_ldisc(void *handle, int option)
+{
+    /*
+     * Local editing and local echo are off by default.
+     */
+    return 0;
+}
+
+static void serial_provide_ldisc(void *handle, void *ldisc)
+{
+    /* This is a stub. */
+}
+
+static void serial_provide_logctx(void *handle, void *logctx)
+{
+    /* This is a stub. */
+}
+
+static int serial_exitcode(void *handle)
+{
+    Serial serial = (Serial) handle;
+    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(void *handle)
+{
+    return 0;
+}
+
+Backend serial_backend = {
+    serial_init,
+    serial_free,
+    serial_reconfig,
+    serial_send,
+    serial_sendbuffer,
+    serial_size,
+    serial_special,
+    serial_get_specials,
+    serial_connected,
+    serial_exitcode,
+    serial_sendok,
+    serial_ldisc,
+    serial_provide_ldisc,
+    serial_provide_logctx,
+    serial_unthrottle,
+    serial_cfg_info,
+    1
+};