Fix breakage of `Restart Session' in r6802. When restarting the
[u/mdw/putty] / unix / uxser.c
CommitLineData
aef05b78 1/*
2 * Serial back end (Unix-specific).
3 */
4
aef05b78 5#include <stdio.h>
6#include <stdlib.h>
7#include <assert.h>
8#include <limits.h>
9
10#include <errno.h>
11#include <unistd.h>
12#include <fcntl.h>
13#include <termios.h>
14
15#include "putty.h"
16#include "tree234.h"
17
18#define SERIAL_MAX_BACKLOG 4096
19
20typedef struct serial_backend_data {
21 void *frontend;
22 int fd;
23 int finished;
24 int inbufsize;
25 bufchain output_data;
26} *Serial;
27
28/*
29 * We store our serial backends in a tree sorted by fd, so that
30 * when we get an uxsel notification we know which backend instance
31 * is the owner of the serial port that caused it.
32 */
33static int serial_compare_by_fd(void *av, void *bv)
34{
35 Serial a = (Serial)av;
36 Serial b = (Serial)bv;
37
38 if (a->fd < b->fd)
39 return -1;
40 else if (a->fd > b->fd)
41 return +1;
42 return 0;
43}
44
45static int serial_find_by_fd(void *av, void *bv)
46{
47 int a = *(int *)av;
48 Serial b = (Serial)bv;
49
50 if (a < b->fd)
51 return -1;
52 else if (a > b->fd)
53 return +1;
54 return 0;
55}
56
57static tree234 *serial_by_fd = NULL;
58
59static int serial_select_result(int fd, int event);
60static void serial_uxsel_setup(Serial serial);
61static void serial_try_write(Serial serial);
62
63static const char *serial_configure(Serial serial, Config *cfg)
64{
65 struct termios options;
66 int bflag, bval;
67 const char *str;
68 char *msg;
69
70 if (serial->fd < 0)
71 return "Unable to reconfigure already-closed serial connection";
72
73 tcgetattr(serial->fd, &options);
74
75 /*
76 * Find the appropriate baud rate flag.
77 */
78#define SETBAUD(x) (bflag = B ## x, bval = x)
79#define CHECKBAUD(x) do { if (cfg->serspeed >= x) SETBAUD(x); } while (0)
80 SETBAUD(50);
81#ifdef B75
82 CHECKBAUD(75);
83#endif
84#ifdef B110
85 CHECKBAUD(110);
86#endif
87#ifdef B134
88 CHECKBAUD(134);
89#endif
90#ifdef B150
91 CHECKBAUD(150);
92#endif
93#ifdef B200
94 CHECKBAUD(200);
95#endif
96#ifdef B300
97 CHECKBAUD(300);
98#endif
99#ifdef B600
100 CHECKBAUD(600);
101#endif
102#ifdef B1200
103 CHECKBAUD(1200);
104#endif
105#ifdef B1800
106 CHECKBAUD(1800);
107#endif
108#ifdef B2400
109 CHECKBAUD(2400);
110#endif
111#ifdef B4800
112 CHECKBAUD(4800);
113#endif
114#ifdef B9600
115 CHECKBAUD(9600);
116#endif
117#ifdef B19200
118 CHECKBAUD(19200);
119#endif
120#ifdef B38400
121 CHECKBAUD(38400);
122#endif
123#ifdef B57600
124 CHECKBAUD(57600);
125#endif
126#ifdef B76800
127 CHECKBAUD(76800);
128#endif
129#ifdef B115200
130 CHECKBAUD(115200);
131#endif
132#ifdef B230400
133 CHECKBAUD(230400);
134#endif
135#undef CHECKBAUD
136#undef SETBAUD
137 cfsetispeed(&options, bflag);
138 cfsetospeed(&options, bflag);
139 msg = dupprintf("Configuring baud rate %d", bval);
140 logevent(serial->frontend, msg);
141 sfree(msg);
142
143 options.c_cflag &= ~CSIZE;
144 switch (cfg->serdatabits) {
145 case 5: options.c_cflag |= CS5; break;
146 case 6: options.c_cflag |= CS6; break;
147 case 7: options.c_cflag |= CS7; break;
148 case 8: options.c_cflag |= CS8; break;
149 default: return "Invalid number of data bits (need 5, 6, 7 or 8)";
150 }
151 msg = dupprintf("Configuring %d data bits", cfg->serdatabits);
152 logevent(serial->frontend, msg);
153 sfree(msg);
154
155 if (cfg->serstopbits >= 4) {
156 options.c_cflag |= CSTOPB;
157 } else {
158 options.c_cflag &= ~CSTOPB;
159 }
160 msg = dupprintf("Configuring %d stop bits",
161 (options.c_cflag & CSTOPB ? 2 : 1));
162 logevent(serial->frontend, msg);
163 sfree(msg);
164
165 options.c_cflag &= ~(IXON|IXOFF);
166 if (cfg->serflow == SER_FLOW_XONXOFF) {
167 options.c_cflag |= IXON | IXOFF;
168 str = "XON/XOFF";
169 } else if (cfg->serflow == SER_FLOW_RTSCTS) {
170#ifdef CRTSCTS
171 options.c_cflag |= CRTSCTS;
172#endif
173#ifdef CNEW_RTSCTS
174 options.c_cflag |= CNEW_RTSCTS;
175#endif
176 str = "RTS/CTS";
177 } else
178 str = "no";
179 msg = dupprintf("Configuring %s flow control", str);
180 logevent(serial->frontend, msg);
181 sfree(msg);
182
183 /* Parity */
184 if (cfg->serparity == SER_PAR_ODD) {
185 options.c_cflag |= PARENB;
186 options.c_cflag |= PARODD;
187 str = "odd";
188 } else if (cfg->serparity == SER_PAR_EVEN) {
189 options.c_cflag |= PARENB;
190 options.c_cflag &= ~PARODD;
191 str = "even";
192 } else {
193 options.c_cflag &= ~PARENB;
194 str = "no";
195 }
196 msg = dupprintf("Configuring %s parity", str);
197 logevent(serial->frontend, msg);
198 sfree(msg);
199
200 options.c_cflag |= CLOCAL | CREAD;
201 options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
202 options.c_oflag &= ~OPOST;
203 options.c_cc[VMIN] = 1;
204 options.c_cc[VTIME] = 0;
205
206 if (tcsetattr(serial->fd, TCSANOW, &options) < 0)
207 return "Unable to configure serial port";
208
209 return NULL;
210}
211
212/*
213 * Called to set up the serial connection.
214 *
215 * Returns an error message, or NULL on success.
216 *
217 * Also places the canonical host name into `realhost'. It must be
218 * freed by the caller.
219 */
220static const char *serial_init(void *frontend_handle, void **backend_handle,
221 Config *cfg,
222 char *host, int port, char **realhost, int nodelay,
223 int keepalive)
224{
225 Serial serial;
226 const char *err;
227
228 serial = snew(struct serial_backend_data);
229 *backend_handle = serial;
230
231 serial->frontend = frontend_handle;
232 serial->finished = FALSE;
233 serial->inbufsize = 0;
234 bufchain_init(&serial->output_data);
235
236 {
237 char *msg = dupprintf("Opening serial device %s", cfg->serline);
238 logevent(serial->frontend, msg);
239 }
240
241 serial->fd = open(cfg->serline, O_RDWR | O_NOCTTY | O_NDELAY | O_NONBLOCK);
242 if (serial->fd < 0)
243 return "Unable to open serial port";
244
245 err = serial_configure(serial, cfg);
246 if (err)
247 return err;
248
249 *realhost = dupstr(cfg->serline);
250
251 if (!serial_by_fd)
252 serial_by_fd = newtree234(serial_compare_by_fd);
253 add234(serial_by_fd, serial);
254
255 serial_uxsel_setup(serial);
256
45ef7ce3 257 /*
258 * Specials are always available.
259 */
260 update_specials_menu(serial->frontend);
261
aef05b78 262 return NULL;
263}
264
265static void serial_close(Serial serial)
266{
267 if (serial->fd >= 0) {
268 close(serial->fd);
269 serial->fd = -1;
270 }
271}
272
273static void serial_free(void *handle)
274{
275 Serial serial = (Serial) handle;
276
277 serial_close(serial);
278
279 bufchain_clear(&serial->output_data);
280
281 sfree(serial);
282}
283
284static void serial_reconfig(void *handle, Config *cfg)
285{
286 Serial serial = (Serial) handle;
287 const char *err;
288
289 err = serial_configure(serial, cfg);
290
291 /*
292 * FIXME: what should we do if err returns something?
293 */
294}
295
296static int serial_select_result(int fd, int event)
297{
298 Serial serial;
299 char buf[4096];
300 int ret;
301 int finished = FALSE;
302
303 serial = find234(serial_by_fd, &fd, serial_find_by_fd);
304
305 if (!serial)
306 return 1; /* spurious event; keep going */
307
308 if (event == 1) {
309 ret = read(serial->fd, buf, sizeof(buf));
310
311 if (ret == 0) {
312 /*
313 * Shouldn't happen on a real serial port, but I'm open
314 * to the idea that there might be two-way devices we
315 * can treat _like_ serial ports which can return EOF.
316 */
317 finished = TRUE;
318 } else if (ret < 0) {
319 perror("read serial port");
320 exit(1);
321 } else if (ret > 0) {
322 serial->inbufsize = from_backend(serial->frontend, 0, buf, ret);
323 serial_uxsel_setup(serial); /* might acquire backlog and freeze */
324 }
325 } else if (event == 2) {
326 /*
327 * Attempt to send data down the pty.
328 */
329 serial_try_write(serial);
330 }
331
332 if (finished) {
333 serial_close(serial);
334
335 serial->finished = TRUE;
336
337 notify_remote_exit(serial->frontend);
338 }
339
340 return !finished;
341}
342
343static void serial_uxsel_setup(Serial serial)
344{
345 int rwx = 0;
346
347 if (serial->inbufsize <= SERIAL_MAX_BACKLOG)
348 rwx |= 1;
349 if (bufchain_size(&serial->output_data))
350 rwx |= 2; /* might also want to write to it */
351 uxsel_set(serial->fd, rwx, serial_select_result);
352}
353
354static void serial_try_write(Serial serial)
355{
356 void *data;
357 int len, ret;
358
359 assert(serial->fd >= 0);
360
361 while (bufchain_size(&serial->output_data) > 0) {
362 bufchain_prefix(&serial->output_data, &data, &len);
363 ret = write(serial->fd, data, len);
364
365 if (ret < 0 && (errno == EWOULDBLOCK)) {
366 /*
367 * We've sent all we can for the moment.
368 */
369 break;
370 }
371 if (ret < 0) {
372 perror("write serial port");
373 exit(1);
374 }
375 bufchain_consume(&serial->output_data, ret);
376 }
377
378 serial_uxsel_setup(serial);
379}
380
381/*
382 * Called to send data down the serial connection.
383 */
384static int serial_send(void *handle, char *buf, int len)
385{
386 Serial serial = (Serial) handle;
387
388 if (serial->fd < 0)
389 return 0;
390
391 bufchain_add(&serial->output_data, buf, len);
392 serial_try_write(serial);
393
394 return bufchain_size(&serial->output_data);
395}
396
397/*
398 * Called to query the current sendability status.
399 */
400static int serial_sendbuffer(void *handle)
401{
402 Serial serial = (Serial) handle;
403 return bufchain_size(&serial->output_data);
404}
405
406/*
407 * Called to set the size of the window
408 */
409static void serial_size(void *handle, int width, int height)
410{
411 /* Do nothing! */
412 return;
413}
414
415/*
416 * Send serial special codes.
417 */
418static void serial_special(void *handle, Telnet_Special code)
419{
45ef7ce3 420 Serial serial = (Serial) handle;
421
422 if (serial->fd >= 0 && code == TS_BRK) {
423 tcsendbreak(serial->fd, 0);
424 logevent(serial->frontend, "Sending serial break at user request");
425 }
426
aef05b78 427 return;
428}
429
430/*
431 * Return a list of the special codes that make sense in this
432 * protocol.
433 */
434static const struct telnet_special *serial_get_specials(void *handle)
435{
45ef7ce3 436 static const struct telnet_special specials[] = {
437 {"Break", TS_BRK},
438 {NULL, TS_EXITMENU}
439 };
440 return specials;
aef05b78 441}
442
443static int serial_connected(void *handle)
444{
445 return 1; /* always connected */
446}
447
448static int serial_sendok(void *handle)
449{
450 return 1;
451}
452
453static void serial_unthrottle(void *handle, int backlog)
454{
455 Serial serial = (Serial) handle;
456 serial->inbufsize = backlog;
457 serial_uxsel_setup(serial);
458}
459
460static int serial_ldisc(void *handle, int option)
461{
462 /*
463 * Local editing and local echo are off by default.
464 */
465 return 0;
466}
467
468static void serial_provide_ldisc(void *handle, void *ldisc)
469{
470 /* This is a stub. */
471}
472
473static void serial_provide_logctx(void *handle, void *logctx)
474{
475 /* This is a stub. */
476}
477
478static int serial_exitcode(void *handle)
479{
480 Serial serial = (Serial) handle;
481 if (serial->fd >= 0)
482 return -1; /* still connected */
483 else
484 /* Exit codes are a meaningless concept with serial ports */
485 return INT_MAX;
486}
487
488/*
489 * cfg_info for Serial does nothing at all.
490 */
491static int serial_cfg_info(void *handle)
492{
493 return 0;
494}
495
496Backend serial_backend = {
497 serial_init,
498 serial_free,
499 serial_reconfig,
500 serial_send,
501 serial_sendbuffer,
502 serial_size,
503 serial_special,
504 serial_get_specials,
505 serial_connected,
506 serial_exitcode,
507 serial_sendok,
508 serial_ldisc,
509 serial_provide_ldisc,
510 serial_provide_logctx,
511 serial_unthrottle,
512 serial_cfg_info,
513 1
514};