1
0
mirror of https://git.tartarus.org/simon/putty.git synced 2025-01-09 17:38:00 +00:00
putty-source/unix/serial.c
Simon Tatham 76dc28552c Add memsets after allocation of all Backend implementors.
Now every struct that implements the Backend trait is completely
cleared before we start initialising any of its fields. This will mean
I can add new fields that default to 0 or NULL, without having to mess
around initialising them explicitly everywhere.
2021-10-30 17:28:28 +01:00

597 lines
14 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 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)),
};