mirror of
https://git.tartarus.org/simon/putty.git
synced 2025-07-06 14:02:47 -05:00
Rename most of the platform source files.
This gets rid of all those annoying 'win', 'ux' and 'gtk' prefixes which made filenames annoying to type and to tab-complete. Also, as with my other recent renaming sprees, I've taken the opportunity to expand and clarify some of the names so that they're not such cryptic abbreviations.
This commit is contained in:
595
unix/serial.c
Normal file
595
unix/serial.c
Normal file
@ -0,0 +1,595 @@
|
||||
/*
|
||||
* 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);
|
||||
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);
|
||||
}
|
||||
|
||||
serial_uxsel_setup(serial);
|
||||
}
|
||||
|
||||
/*
|
||||
* Called to send data down the serial connection.
|
||||
*/
|
||||
static size_t serial_send(Backend *be, const char *buf, size_t len)
|
||||
{
|
||||
Serial *serial = container_of(be, Serial, backend);
|
||||
|
||||
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 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 = "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)),
|
||||
};
|
Reference in New Issue
Block a user