blob: cd1e0e8c07593e41633706f166e0552e163fc42a [file] [log] [blame]
Jeremy Kerrbc1e8932016-04-28 12:27:30 +08001/**
2 * Copyright © 2016 IBM Corporation
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#define _GNU_SOURCE
18
19#include <assert.h>
20#include <err.h>
Jeremy Kerrf733c852017-02-07 18:40:10 +080021#include <errno.h>
Jeremy Kerrbc1e8932016-04-28 12:27:30 +080022#include <fcntl.h>
23#include <stdio.h>
24#include <stdlib.h>
Xo Wangc5ef8ea2017-04-17 16:20:43 -070025#include <string.h>
Jeremy Kerrbc1e8932016-04-28 12:27:30 +080026#include <unistd.h>
Xo Wangc5ef8ea2017-04-17 16:20:43 -070027#include <termios.h>
Jeremy Kerrbc1e8932016-04-28 12:27:30 +080028
29#include "console-server.h"
30
31struct tty_handler {
Jeremy Kerrf733c852017-02-07 18:40:10 +080032 struct handler handler;
33 struct console *console;
34 struct ringbuffer_consumer *rbc;
35 struct poller *poller;
36 int fd;
Jeremy Kerr6b1fed22017-02-07 21:40:38 +080037 int fd_flags;
38 bool blocked;
Jeremy Kerrbc1e8932016-04-28 12:27:30 +080039};
40
Xo Wangc5ef8ea2017-04-17 16:20:43 -070041struct terminal_speed_name {
42 speed_t speed;
43 const char *name;
44};
45
Jeremy Kerrbc1e8932016-04-28 12:27:30 +080046static struct tty_handler *to_tty_handler(struct handler *handler)
47{
48 return container_of(handler, struct tty_handler, handler);
49}
50
Jeremy Kerr6b1fed22017-02-07 21:40:38 +080051static void tty_set_fd_blocking(struct tty_handler *th, bool fd_blocking)
52{
53 int flags;
54
55 flags = th->fd_flags & ~O_NONBLOCK;
56 if (!fd_blocking)
57 flags |= O_NONBLOCK;
58
59 if (flags != th->fd_flags) {
60 fcntl(th->fd, F_SETFL, flags);
61 th->fd_flags = flags;
62 }
63}
64
65/*
66 * A "blocked" handler indicates that the last write returned EAGAIN
67 * (==EWOULDBLOCK), so we know not to continue writing (for non-forced output),
68 * as it'll just return EAGAIN again.
69 *
70 * Once we detect this, we watch for POLLOUT in the poller events. A
71 * POLLOUT indicates that the fd is no longer blocking, so we clear
72 * blocked mode and can continue writing.
73 */
74static void tty_set_blocked(struct tty_handler *th, bool blocked)
75{
76 int events;
77
78 if (blocked == th->blocked)
79 return;
80
81 th->blocked = blocked;
82 events = POLLIN;
83
84 if (th->blocked)
85 events |= POLLOUT;
86
87 console_poller_set_events(th->console, th->poller, events);
88}
89
Jeremy Kerrf733c852017-02-07 18:40:10 +080090static int tty_drain_queue(struct tty_handler *th, size_t force_len)
91{
92 size_t len, total_len;
93 ssize_t wlen;
94 uint8_t *buf;
Jeremy Kerrf733c852017-02-07 18:40:10 +080095
96 /* if we're forcing data, we need to clear non-blocking mode */
Jeremy Kerr6b1fed22017-02-07 21:40:38 +080097 if (force_len)
98 tty_set_fd_blocking(th, true);
99
100 /* no point writing, we'll just see -EAGAIN */
101 else if (th->blocked)
102 return 0;
Jeremy Kerrf733c852017-02-07 18:40:10 +0800103
104 total_len = 0;
105
106 for (;;) {
107 len = ringbuffer_dequeue_peek(th->rbc, total_len, &buf);
108 if (!len)
109 break;
110
111 /* write as little as possible while blocking */
112 if (force_len && force_len < total_len + len)
113 len = force_len - total_len;
114
115 wlen = write(th->fd, buf, len);
116 if (wlen < 0) {
117 if (errno == EINTR)
118 continue;
119 if ((errno == EAGAIN || errno == EWOULDBLOCK)
Jeremy Kerr6b1fed22017-02-07 21:40:38 +0800120 && !force_len) {
121 tty_set_blocked(th, true);
Jeremy Kerrf733c852017-02-07 18:40:10 +0800122 break;
Jeremy Kerr6b1fed22017-02-07 21:40:38 +0800123 }
Jeremy Kerrf733c852017-02-07 18:40:10 +0800124 warn("failed writing to local tty; disabling");
125 return -1;
126 }
127
128 total_len += wlen;
129
130 if (force_len && total_len >= force_len)
131 break;
132 }
133
134 ringbuffer_dequeue_commit(th->rbc, total_len);
135
136 if (force_len)
Jeremy Kerr6b1fed22017-02-07 21:40:38 +0800137 tty_set_fd_blocking(th, false);
Jeremy Kerrf733c852017-02-07 18:40:10 +0800138
139 return 0;
140}
141
142static enum ringbuffer_poll_ret tty_ringbuffer_poll(void *arg, size_t force_len)
143{
144 struct tty_handler *th = arg;
145 int rc;
146
147 rc = tty_drain_queue(th, force_len);
148 if (rc) {
149 console_poller_unregister(th->console, th->poller);
150 return RINGBUFFER_POLL_REMOVE;
151 }
152
153 return RINGBUFFER_POLL_OK;
154}
155
Jeremy Kerrbc1e8932016-04-28 12:27:30 +0800156static enum poller_ret tty_poll(struct handler *handler,
157 int events, void __attribute__((unused)) *data)
158{
159 struct tty_handler *th = to_tty_handler(handler);
160 uint8_t buf[4096];
161 ssize_t len;
Jeremy Kerrf733c852017-02-07 18:40:10 +0800162 int rc;
Jeremy Kerrbc1e8932016-04-28 12:27:30 +0800163
Jeremy Kerrf733c852017-02-07 18:40:10 +0800164 if (events & POLLIN) {
165 len = read(th->fd, buf, sizeof(buf));
166 if (len <= 0) {
167 th->poller = NULL;
168 close(th->fd);
169 return POLLER_REMOVE;
170 }
Jeremy Kerrbc1e8932016-04-28 12:27:30 +0800171
Jeremy Kerrf733c852017-02-07 18:40:10 +0800172 console_data_out(th->console, buf, len);
Jeremy Kerrbc1e8932016-04-28 12:27:30 +0800173 }
174
Jeremy Kerrf733c852017-02-07 18:40:10 +0800175 if (events & POLLOUT) {
Jeremy Kerr6b1fed22017-02-07 21:40:38 +0800176 tty_set_blocked(th, false);
Jeremy Kerrf733c852017-02-07 18:40:10 +0800177 rc = tty_drain_queue(th, 0);
178 if (rc) {
179 ringbuffer_consumer_unregister(th->rbc);
180 return POLLER_REMOVE;
181 }
182 }
Jeremy Kerrbc1e8932016-04-28 12:27:30 +0800183
184 return POLLER_OK;
185}
186
Xo Wangc5ef8ea2017-04-17 16:20:43 -0700187static int baud_string_to_speed(speed_t *speed, const char *baud_string) {
188 const struct terminal_speed_name terminal_speeds[] = {
189 { B50, "50" },
190 { B75, "75" },
191 { B110, "110" },
192 { B134, "134" },
193 { B150, "150" },
194 { B200, "200" },
195 { B300, "300" },
196 { B600, "600" },
197 { B1200, "1200" },
198 { B1800, "1800" },
199 { B2400, "2400" },
200 { B4800, "4800" },
201 { B9600, "9600" },
202 { B19200, "19200" },
203 { B38400, "38400" },
204 { B57600, "57600" },
205 { B115200, "115200" },
206 { B230400, "230400" },
207 { B460800, "460800" },
208 { B500000, "500000" },
209 { B576000, "576000" },
210 { B921600, "921600" },
211 { B1000000, "1000000" },
212 { B1152000, "1152000" },
213 { B1500000, "1500000" },
214 { B2000000, "2000000" },
215 { B2500000, "2500000" },
216 { B3000000, "3000000" },
217 { B3500000, "3500000" },
218 { B4000000, "4000000" },
219 };
220 const size_t num_terminal_speeds = sizeof(terminal_speeds) /
221 sizeof(struct terminal_speed_name);
222 size_t i;
223
224 for (i = 0; i < num_terminal_speeds; i++) {
225 if (strcmp(baud_string, terminal_speeds[i].name) == 0) {
226 *speed = terminal_speeds[i].speed;
227 return 0;
228 }
229 }
230 return -1;
231}
232
233static int set_terminal_baud(struct tty_handler *th, const char *tty_name,
234 const char *desired_baud) {
235 struct termios term_options;
236 speed_t speed;
237
238 if (baud_string_to_speed(&speed, desired_baud) != 0) {
239 fprintf(stderr, "%s is not a valid baud rate for terminal %s\n",
240 desired_baud, tty_name);
241 return -1;
242 }
243
244 if (tcgetattr(th->fd, &term_options) < 0) {
245 warn("Can't get config for %s", tty_name);
246 return -1;
247 }
248
249 if (cfsetspeed(&term_options, speed) < 0) {
250 warn("Couldn't set speeds for %s", tty_name);
251 return -1;
252 }
253
254 if (tcsetattr(th->fd, TCSAFLUSH, &term_options) < 0) {
255 warn("Couldn't commit terminal options for %s", tty_name);
256 return -1;
257 }
258 printf("Set %s terminal baud rate to %s\n", tty_name, desired_baud);
259
260 return 0;
261}
262
Xo Wang81408bd2017-04-18 16:43:07 -0700263static int make_terminal_raw(struct tty_handler *th, const char *tty_name) {
264 struct termios term_options;
265
266 if (tcgetattr(th->fd, &term_options) < 0) {
267 warn("Can't get config for %s", tty_name);
268 return -1;
269 }
270
271 /* Disable various input and output processing including character
272 * translation, line edit (canonical) mode, flow control, and special signal
273 * generating characters. */
274 cfmakeraw(&term_options);
275
276 if (tcsetattr(th->fd, TCSAFLUSH, &term_options) < 0) {
277 warn("Couldn't commit terminal options for %s", tty_name);
278 return -1;
279 }
280 printf("Set %s for raw byte handling\n", tty_name);
281
282 return 0;
283}
284
Jeremy Kerrbc1e8932016-04-28 12:27:30 +0800285static int tty_init(struct handler *handler, struct console *console,
286 struct config *config __attribute__((unused)))
287{
288 struct tty_handler *th = to_tty_handler(handler);
289 const char *tty_name;
Xo Wangc5ef8ea2017-04-17 16:20:43 -0700290 const char *tty_baud;
Jeremy Kerrbc1e8932016-04-28 12:27:30 +0800291 char *tty_path;
Jeremy Kerrbc506fd2017-02-07 09:24:48 +0800292 int rc;
Jeremy Kerrbc1e8932016-04-28 12:27:30 +0800293
294 tty_name = config_get_value(config, "local-tty");
295 if (!tty_name)
296 return -1;
297
298 rc = asprintf(&tty_path, "/dev/%s", tty_name);
299 if (!rc)
300 return -1;
301
Jeremy Kerrbc506fd2017-02-07 09:24:48 +0800302 th->fd = open(tty_path, O_RDWR | O_NONBLOCK);
Jeremy Kerrbc1e8932016-04-28 12:27:30 +0800303 if (th->fd < 0) {
304 warn("Can't open %s; disabling local tty", tty_name);
305 free(tty_path);
306 return -1;
307 }
308
309 free(tty_path);
Jeremy Kerr6b1fed22017-02-07 21:40:38 +0800310 th->fd_flags = fcntl(th->fd, F_GETFL, 0);
Jeremy Kerrbc1e8932016-04-28 12:27:30 +0800311
Xo Wangc5ef8ea2017-04-17 16:20:43 -0700312 tty_baud = config_get_value(config, "local-tty-baud");
313 if (tty_baud != NULL)
314 if (set_terminal_baud(th, tty_name, tty_baud) != 0)
315 fprintf(stderr, "Couldn't set baud rate for %s to %s\n",
316 tty_name, tty_baud);
317
Xo Wang81408bd2017-04-18 16:43:07 -0700318 if (make_terminal_raw(th, tty_name) != 0)
319 fprintf(stderr, "Couldn't make %s a raw terminal\n", tty_name);
320
Jeremy Kerr55c97122017-02-07 17:06:46 +0800321 th->poller = console_poller_register(console, handler, tty_poll,
Jeremy Kerrbc1e8932016-04-28 12:27:30 +0800322 th->fd, POLLIN, NULL);
323 th->console = console;
Jeremy Kerrf733c852017-02-07 18:40:10 +0800324 th->rbc = console_ringbuffer_consumer_register(console,
325 tty_ringbuffer_poll, th);
Jeremy Kerrbc1e8932016-04-28 12:27:30 +0800326
327 return 0;
328}
329
Jeremy Kerrbc1e8932016-04-28 12:27:30 +0800330static void tty_fini(struct handler *handler)
331{
332 struct tty_handler *th = to_tty_handler(handler);
333 if (th->poller)
Jeremy Kerr55c97122017-02-07 17:06:46 +0800334 console_poller_unregister(th->console, th->poller);
Jeremy Kerrbc1e8932016-04-28 12:27:30 +0800335 close(th->fd);
336}
337
338static struct tty_handler tty_handler = {
339 .handler = {
340 .name = "tty",
341 .init = tty_init,
Jeremy Kerrbc1e8932016-04-28 12:27:30 +0800342 .fini = tty_fini,
343 },
344};
345
Jeremy Kerr55c97122017-02-07 17:06:46 +0800346console_handler_register(&tty_handler.handler);
Jeremy Kerrbc1e8932016-04-28 12:27:30 +0800347