putty/unix/uxser.c

597 строки
12 KiB
C

/*
* 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_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 void 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, Conf *conf)
{
struct termios options;
int bflag, bval, speed, flow, parity;
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.
*/
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);
msg = dupprintf("Configuring baud rate %d", bval);
logevent(serial->frontend, msg);
sfree(msg);
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 "Invalid number of data bits (need 5, 6, 7 or 8)";
}
msg = dupprintf("Configuring %d data bits",
conf_get_int(conf, CONF_serdatabits));
logevent(serial->frontend, msg);
sfree(msg);
if (conf_get_int(conf, CONF_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_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";
msg = dupprintf("Configuring %s flow control", str);
logevent(serial->frontend, msg);
sfree(msg);
/* 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";
}
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_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 "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,
Conf *conf,
const char *host, int port, char **realhost,
int nodelay, int keepalive)
{
Serial serial;
const char *err;
char *line;
serial = snew(struct serial_backend_data);
*backend_handle = serial;
serial->frontend = frontend_handle;
serial->finished = FALSE;
serial->inbufsize = 0;
bufchain_init(&serial->output_data);
line = conf_get_str(conf, CONF_serline);
{
char *msg = dupprintf("Opening serial device %s", line);
logevent(serial->frontend, msg);
sfree(msg);
}
serial->fd = open(line, O_RDWR | O_NOCTTY | O_NDELAY | O_NONBLOCK);
if (serial->fd < 0)
return "Unable to open serial port";
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.
*/
update_specials_menu(serial->frontend);
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, Conf *conf)
{
Serial serial = (Serial) handle;
/*
* FIXME: what should we do if this returns an error?
*/
serial_configure(serial, conf);
}
static void 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; /* 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 = 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);
}
}
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, const 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)
{
Serial serial = (Serial) handle;
if (serial->fd >= 0 && code == TS_BRK) {
tcsendbreak(serial->fd, 0);
logevent(serial->frontend, "Sending serial break at user request");
}
return;
}
/*
* Return a list of the special codes that make sense in this
* protocol.
*/
static const struct telnet_special *serial_get_specials(void *handle)
{
static const struct telnet_special specials[] = {
{"Break", TS_BRK},
{NULL, TS_EXITMENU}
};
return specials;
}
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,
NULL /* test_for_upstream */,
"serial",
PROT_SERIAL,
0
};