blob: a722d2488a36c3452989a4755b17f961bc3822ed [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
Jeremy Kerrbc1e8932016-04-28 12:27:30 +080017#include <assert.h>
18#include <err.h>
Jeremy Kerrf733c852017-02-07 18:40:10 +080019#include <errno.h>
Jeremy Kerrbc1e8932016-04-28 12:27:30 +080020#include <fcntl.h>
21#include <stdio.h>
22#include <stdlib.h>
Xo Wangc5ef8ea2017-04-17 16:20:43 -070023#include <string.h>
Jeremy Kerrbc1e8932016-04-28 12:27:30 +080024#include <unistd.h>
Xo Wangc5ef8ea2017-04-17 16:20:43 -070025#include <termios.h>
Jeremy Kerrbc1e8932016-04-28 12:27:30 +080026
27#include "console-server.h"
Alexander Hansen1e04f442024-06-12 16:35:58 +020028#include "config.h"
Jeremy Kerrbc1e8932016-04-28 12:27:30 +080029
30struct tty_handler {
Andrew Jefferya72711a2023-04-18 18:19:41 +093031 struct handler handler;
32 struct console *console;
33 struct ringbuffer_consumer *rbc;
34 struct poller *poller;
35 int fd;
36 int fd_flags;
37 bool blocked;
Jeremy Kerrbc1e8932016-04-28 12:27:30 +080038};
39
40static struct tty_handler *to_tty_handler(struct handler *handler)
41{
42 return container_of(handler, struct tty_handler, handler);
43}
44
Jeremy Kerr6b1fed22017-02-07 21:40:38 +080045static void tty_set_fd_blocking(struct tty_handler *th, bool fd_blocking)
46{
47 int flags;
48
49 flags = th->fd_flags & ~O_NONBLOCK;
Andrew Jeffery2834c5b2023-04-19 12:47:09 +093050 if (!fd_blocking) {
Jeremy Kerr6b1fed22017-02-07 21:40:38 +080051 flags |= O_NONBLOCK;
Andrew Jeffery2834c5b2023-04-19 12:47:09 +093052 }
Jeremy Kerr6b1fed22017-02-07 21:40:38 +080053
54 if (flags != th->fd_flags) {
55 fcntl(th->fd, F_SETFL, flags);
56 th->fd_flags = flags;
57 }
58}
59
60/*
61 * A "blocked" handler indicates that the last write returned EAGAIN
62 * (==EWOULDBLOCK), so we know not to continue writing (for non-forced output),
63 * as it'll just return EAGAIN again.
64 *
65 * Once we detect this, we watch for POLLOUT in the poller events. A
66 * POLLOUT indicates that the fd is no longer blocking, so we clear
67 * blocked mode and can continue writing.
68 */
69static void tty_set_blocked(struct tty_handler *th, bool blocked)
70{
71 int events;
72
Andrew Jeffery2834c5b2023-04-19 12:47:09 +093073 if (blocked == th->blocked) {
Jeremy Kerr6b1fed22017-02-07 21:40:38 +080074 return;
Andrew Jeffery2834c5b2023-04-19 12:47:09 +093075 }
Jeremy Kerr6b1fed22017-02-07 21:40:38 +080076
77 th->blocked = blocked;
78 events = POLLIN;
79
Andrew Jeffery2834c5b2023-04-19 12:47:09 +093080 if (th->blocked) {
Jeremy Kerr6b1fed22017-02-07 21:40:38 +080081 events |= POLLOUT;
Andrew Jeffery2834c5b2023-04-19 12:47:09 +093082 }
Jeremy Kerr6b1fed22017-02-07 21:40:38 +080083
84 console_poller_set_events(th->console, th->poller, events);
85}
86
Jeremy Kerrf733c852017-02-07 18:40:10 +080087static int tty_drain_queue(struct tty_handler *th, size_t force_len)
88{
Andrew Jefferyb70f8712023-04-19 12:53:34 +093089 size_t len;
90 size_t total_len;
Jeremy Kerrf733c852017-02-07 18:40:10 +080091 ssize_t wlen;
92 uint8_t *buf;
Jeremy Kerrf733c852017-02-07 18:40:10 +080093
94 /* if we're forcing data, we need to clear non-blocking mode */
Andrew Jeffery2834c5b2023-04-19 12:47:09 +093095 if (force_len) {
Jeremy Kerr6b1fed22017-02-07 21:40:38 +080096 tty_set_fd_blocking(th, true);
97
Andrew Jeffery2834c5b2023-04-19 12:47:09 +093098 /* no point writing, we'll just see -EAGAIN */
99 } else if (th->blocked) {
Jeremy Kerr6b1fed22017-02-07 21:40:38 +0800100 return 0;
Andrew Jeffery2834c5b2023-04-19 12:47:09 +0930101 }
Jeremy Kerrf733c852017-02-07 18:40:10 +0800102
103 total_len = 0;
104
105 for (;;) {
106 len = ringbuffer_dequeue_peek(th->rbc, total_len, &buf);
Andrew Jeffery2834c5b2023-04-19 12:47:09 +0930107 if (!len) {
Jeremy Kerrf733c852017-02-07 18:40:10 +0800108 break;
Andrew Jeffery2834c5b2023-04-19 12:47:09 +0930109 }
Jeremy Kerrf733c852017-02-07 18:40:10 +0800110
111 /* write as little as possible while blocking */
Andrew Jeffery2834c5b2023-04-19 12:47:09 +0930112 if (force_len && force_len < total_len + len) {
Jeremy Kerrf733c852017-02-07 18:40:10 +0800113 len = force_len - total_len;
Andrew Jeffery2834c5b2023-04-19 12:47:09 +0930114 }
Jeremy Kerrf733c852017-02-07 18:40:10 +0800115
116 wlen = write(th->fd, buf, len);
117 if (wlen < 0) {
Andrew Jeffery2834c5b2023-04-19 12:47:09 +0930118 if (errno == EINTR) {
Jeremy Kerrf733c852017-02-07 18:40:10 +0800119 continue;
Andrew Jeffery2834c5b2023-04-19 12:47:09 +0930120 }
Andrew Jefferya72711a2023-04-18 18:19:41 +0930121 if ((errno == EAGAIN || errno == EWOULDBLOCK) &&
122 !force_len) {
Jeremy Kerr6b1fed22017-02-07 21:40:38 +0800123 tty_set_blocked(th, true);
Jeremy Kerrf733c852017-02-07 18:40:10 +0800124 break;
Jeremy Kerr6b1fed22017-02-07 21:40:38 +0800125 }
Jeremy Kerrf733c852017-02-07 18:40:10 +0800126 warn("failed writing to local tty; disabling");
127 return -1;
128 }
129
130 total_len += wlen;
131
Andrew Jeffery2834c5b2023-04-19 12:47:09 +0930132 if (force_len && total_len >= force_len) {
Jeremy Kerrf733c852017-02-07 18:40:10 +0800133 break;
Andrew Jeffery2834c5b2023-04-19 12:47:09 +0930134 }
Jeremy Kerrf733c852017-02-07 18:40:10 +0800135 }
136
137 ringbuffer_dequeue_commit(th->rbc, total_len);
138
Andrew Jeffery2834c5b2023-04-19 12:47:09 +0930139 if (force_len) {
Jeremy Kerr6b1fed22017-02-07 21:40:38 +0800140 tty_set_fd_blocking(th, false);
Andrew Jeffery2834c5b2023-04-19 12:47:09 +0930141 }
Jeremy Kerrf733c852017-02-07 18:40:10 +0800142
143 return 0;
144}
145
146static enum ringbuffer_poll_ret tty_ringbuffer_poll(void *arg, size_t force_len)
147{
148 struct tty_handler *th = arg;
149 int rc;
150
151 rc = tty_drain_queue(th, force_len);
152 if (rc) {
153 console_poller_unregister(th->console, th->poller);
154 return RINGBUFFER_POLL_REMOVE;
155 }
156
157 return RINGBUFFER_POLL_OK;
158}
159
Andrew Jefferya72711a2023-04-18 18:19:41 +0930160static enum poller_ret tty_poll(struct handler *handler, int events,
161 void __attribute__((unused)) * data)
Jeremy Kerrbc1e8932016-04-28 12:27:30 +0800162{
163 struct tty_handler *th = to_tty_handler(handler);
164 uint8_t buf[4096];
165 ssize_t len;
Jeremy Kerrf733c852017-02-07 18:40:10 +0800166 int rc;
Jeremy Kerrbc1e8932016-04-28 12:27:30 +0800167
Jeremy Kerrf733c852017-02-07 18:40:10 +0800168 if (events & POLLIN) {
169 len = read(th->fd, buf, sizeof(buf));
Andrew Jeffery2834c5b2023-04-19 12:47:09 +0930170 if (len <= 0) {
Jeremy Kerr67eab042017-07-12 12:08:35 +0800171 goto err;
Andrew Jeffery2834c5b2023-04-19 12:47:09 +0930172 }
Jeremy Kerrbc1e8932016-04-28 12:27:30 +0800173
Jeremy Kerrf733c852017-02-07 18:40:10 +0800174 console_data_out(th->console, buf, len);
Jeremy Kerrbc1e8932016-04-28 12:27:30 +0800175 }
176
Jeremy Kerrf733c852017-02-07 18:40:10 +0800177 if (events & POLLOUT) {
Jeremy Kerr6b1fed22017-02-07 21:40:38 +0800178 tty_set_blocked(th, false);
Jeremy Kerrf733c852017-02-07 18:40:10 +0800179 rc = tty_drain_queue(th, 0);
Andrew Jeffery2834c5b2023-04-19 12:47:09 +0930180 if (rc) {
Jeremy Kerr67eab042017-07-12 12:08:35 +0800181 goto err;
Andrew Jeffery2834c5b2023-04-19 12:47:09 +0930182 }
Jeremy Kerrf733c852017-02-07 18:40:10 +0800183 }
Jeremy Kerrbc1e8932016-04-28 12:27:30 +0800184
185 return POLLER_OK;
Jeremy Kerr67eab042017-07-12 12:08:35 +0800186
187err:
188 th->poller = NULL;
189 close(th->fd);
190 ringbuffer_consumer_unregister(th->rbc);
191 return POLLER_REMOVE;
Jeremy Kerrbc1e8932016-04-28 12:27:30 +0800192}
193
Xo Wangc5ef8ea2017-04-17 16:20:43 -0700194static int set_terminal_baud(struct tty_handler *th, const char *tty_name,
Andrew Jefferya72711a2023-04-18 18:19:41 +0930195 speed_t speed)
196{
Xo Wangc5ef8ea2017-04-17 16:20:43 -0700197 struct termios term_options;
Xo Wangc5ef8ea2017-04-17 16:20:43 -0700198
199 if (tcgetattr(th->fd, &term_options) < 0) {
200 warn("Can't get config for %s", tty_name);
201 return -1;
202 }
203
204 if (cfsetspeed(&term_options, speed) < 0) {
205 warn("Couldn't set speeds for %s", tty_name);
206 return -1;
207 }
208
209 if (tcsetattr(th->fd, TCSAFLUSH, &term_options) < 0) {
210 warn("Couldn't commit terminal options for %s", tty_name);
211 return -1;
212 }
Xo Wangc5ef8ea2017-04-17 16:20:43 -0700213
214 return 0;
215}
216
Andrew Jefferya72711a2023-04-18 18:19:41 +0930217static int make_terminal_raw(struct tty_handler *th, const char *tty_name)
218{
Xo Wang81408bd2017-04-18 16:43:07 -0700219 struct termios term_options;
220
221 if (tcgetattr(th->fd, &term_options) < 0) {
222 warn("Can't get config for %s", tty_name);
223 return -1;
224 }
225
226 /* Disable various input and output processing including character
227 * translation, line edit (canonical) mode, flow control, and special signal
228 * generating characters. */
229 cfmakeraw(&term_options);
230
231 if (tcsetattr(th->fd, TCSAFLUSH, &term_options) < 0) {
232 warn("Couldn't commit terminal options for %s", tty_name);
233 return -1;
234 }
235 printf("Set %s for raw byte handling\n", tty_name);
236
237 return 0;
238}
239
Jeremy Kerre2826c72024-07-05 10:54:21 +0800240static struct handler *tty_init(const struct handler_type *type
241 __attribute__((unused)),
242 struct console *console,
243 struct config *config __attribute__((unused)))
Jeremy Kerrbc1e8932016-04-28 12:27:30 +0800244{
Jeremy Kerre2826c72024-07-05 10:54:21 +0800245 struct tty_handler *th;
Cheng C Yangf9c8f6c2019-03-04 18:39:52 +0800246 speed_t desired_speed;
Jeremy Kerrbc1e8932016-04-28 12:27:30 +0800247 const char *tty_name;
Xo Wangc5ef8ea2017-04-17 16:20:43 -0700248 const char *tty_baud;
Jeremy Kerrbc1e8932016-04-28 12:27:30 +0800249 char *tty_path;
Jeremy Kerrbc506fd2017-02-07 09:24:48 +0800250 int rc;
Jeremy Kerrbc1e8932016-04-28 12:27:30 +0800251
252 tty_name = config_get_value(config, "local-tty");
Andrew Jeffery2834c5b2023-04-19 12:47:09 +0930253 if (!tty_name) {
Jeremy Kerre2826c72024-07-05 10:54:21 +0800254 return NULL;
Andrew Jeffery2834c5b2023-04-19 12:47:09 +0930255 }
Jeremy Kerrbc1e8932016-04-28 12:27:30 +0800256
257 rc = asprintf(&tty_path, "/dev/%s", tty_name);
Andrew Jeffery2834c5b2023-04-19 12:47:09 +0930258 if (!rc) {
Jeremy Kerre2826c72024-07-05 10:54:21 +0800259 return NULL;
260 }
261
262 th = malloc(sizeof(*th));
263 if (!th) {
264 return NULL;
Andrew Jeffery2834c5b2023-04-19 12:47:09 +0930265 }
Jeremy Kerrbc1e8932016-04-28 12:27:30 +0800266
Jeremy Kerrbc506fd2017-02-07 09:24:48 +0800267 th->fd = open(tty_path, O_RDWR | O_NONBLOCK);
Jeremy Kerrbc1e8932016-04-28 12:27:30 +0800268 if (th->fd < 0) {
269 warn("Can't open %s; disabling local tty", tty_name);
270 free(tty_path);
Jeremy Kerre2826c72024-07-05 10:54:21 +0800271 free(th);
272 return NULL;
Jeremy Kerrbc1e8932016-04-28 12:27:30 +0800273 }
274
275 free(tty_path);
Jeremy Kerr6b1fed22017-02-07 21:40:38 +0800276 th->fd_flags = fcntl(th->fd, F_GETFL, 0);
Jeremy Kerrbc1e8932016-04-28 12:27:30 +0800277
Xo Wangc5ef8ea2017-04-17 16:20:43 -0700278 tty_baud = config_get_value(config, "local-tty-baud");
Cheng C Yangf9c8f6c2019-03-04 18:39:52 +0800279 if (tty_baud != NULL) {
280 rc = config_parse_baud(&desired_speed, tty_baud);
281 if (rc) {
282 fprintf(stderr, "%s is not a valid baud rate\n",
283 tty_baud);
284 } else {
285 rc = set_terminal_baud(th, tty_name, desired_speed);
Andrew Jeffery2834c5b2023-04-19 12:47:09 +0930286 if (rc) {
Andrew Jefferya72711a2023-04-18 18:19:41 +0930287 fprintf(stderr,
288 "Couldn't set baud rate for %s to %s\n",
Xo Wangc5ef8ea2017-04-17 16:20:43 -0700289 tty_name, tty_baud);
Andrew Jeffery2834c5b2023-04-19 12:47:09 +0930290 }
Cheng C Yangf9c8f6c2019-03-04 18:39:52 +0800291 }
292 }
Xo Wangc5ef8ea2017-04-17 16:20:43 -0700293
Andrew Jeffery2834c5b2023-04-19 12:47:09 +0930294 if (make_terminal_raw(th, tty_name) != 0) {
Xo Wang81408bd2017-04-18 16:43:07 -0700295 fprintf(stderr, "Couldn't make %s a raw terminal\n", tty_name);
Andrew Jeffery2834c5b2023-04-19 12:47:09 +0930296 }
Xo Wang81408bd2017-04-18 16:43:07 -0700297
Jeremy Kerre2826c72024-07-05 10:54:21 +0800298 th->poller = console_poller_register(console, &th->handler, tty_poll,
299 NULL, th->fd, POLLIN, NULL);
Jeremy Kerrbc1e8932016-04-28 12:27:30 +0800300 th->console = console;
Jeremy Kerrf733c852017-02-07 18:40:10 +0800301 th->rbc = console_ringbuffer_consumer_register(console,
Andrew Jefferya72711a2023-04-18 18:19:41 +0930302 tty_ringbuffer_poll, th);
Jeremy Kerrbc1e8932016-04-28 12:27:30 +0800303
Jeremy Kerre2826c72024-07-05 10:54:21 +0800304 return &th->handler;
Jeremy Kerrbc1e8932016-04-28 12:27:30 +0800305}
306
Jeremy Kerrbc1e8932016-04-28 12:27:30 +0800307static void tty_fini(struct handler *handler)
308{
309 struct tty_handler *th = to_tty_handler(handler);
Andrew Jeffery2834c5b2023-04-19 12:47:09 +0930310 if (th->poller) {
Jeremy Kerr55c97122017-02-07 17:06:46 +0800311 console_poller_unregister(th->console, th->poller);
Andrew Jeffery2834c5b2023-04-19 12:47:09 +0930312 }
Jeremy Kerrbc1e8932016-04-28 12:27:30 +0800313 close(th->fd);
Jeremy Kerre2826c72024-07-05 10:54:21 +0800314 free(th);
Jeremy Kerrbc1e8932016-04-28 12:27:30 +0800315}
316
Cheng C Yangf9c8f6c2019-03-04 18:39:52 +0800317static int tty_baudrate(struct handler *handler, speed_t baudrate)
318{
319 const char *tty_name = "local-tty";
320 struct tty_handler *th = to_tty_handler(handler);
321
322 if (baudrate == 0) {
323 return -1;
324 }
325
326 if (set_terminal_baud(th, tty_name, baudrate) != 0) {
327 fprintf(stderr, "Couldn't set baud rate for %s to %d\n",
328 tty_name, baudrate);
329 return -1;
330 }
331 return 0;
332}
333
Jeremy Kerre2826c72024-07-05 10:54:21 +0800334static const struct handler_type tty_handler = {
335 .name = "tty",
336 .init = tty_init,
337 .fini = tty_fini,
338 .baudrate = tty_baudrate,
Jeremy Kerrbc1e8932016-04-28 12:27:30 +0800339};
340
Jeremy Kerre2826c72024-07-05 10:54:21 +0800341console_handler_register(&tty_handler);