blob: 734f81e86f12ba82c330037c101aeb6caae4edd6 [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"
28
29struct tty_handler {
Andrew Jefferya72711a2023-04-18 18:19:41 +093030 struct handler handler;
31 struct console *console;
32 struct ringbuffer_consumer *rbc;
33 struct poller *poller;
34 int fd;
35 int fd_flags;
36 bool blocked;
Jeremy Kerrbc1e8932016-04-28 12:27:30 +080037};
38
39static struct tty_handler *to_tty_handler(struct handler *handler)
40{
41 return container_of(handler, struct tty_handler, handler);
42}
43
Jeremy Kerr6b1fed22017-02-07 21:40:38 +080044static void tty_set_fd_blocking(struct tty_handler *th, bool fd_blocking)
45{
46 int flags;
47
48 flags = th->fd_flags & ~O_NONBLOCK;
Andrew Jeffery2834c5b2023-04-19 12:47:09 +093049 if (!fd_blocking) {
Jeremy Kerr6b1fed22017-02-07 21:40:38 +080050 flags |= O_NONBLOCK;
Andrew Jeffery2834c5b2023-04-19 12:47:09 +093051 }
Jeremy Kerr6b1fed22017-02-07 21:40:38 +080052
53 if (flags != th->fd_flags) {
54 fcntl(th->fd, F_SETFL, flags);
55 th->fd_flags = flags;
56 }
57}
58
59/*
60 * A "blocked" handler indicates that the last write returned EAGAIN
61 * (==EWOULDBLOCK), so we know not to continue writing (for non-forced output),
62 * as it'll just return EAGAIN again.
63 *
64 * Once we detect this, we watch for POLLOUT in the poller events. A
65 * POLLOUT indicates that the fd is no longer blocking, so we clear
66 * blocked mode and can continue writing.
67 */
68static void tty_set_blocked(struct tty_handler *th, bool blocked)
69{
70 int events;
71
Andrew Jeffery2834c5b2023-04-19 12:47:09 +093072 if (blocked == th->blocked) {
Jeremy Kerr6b1fed22017-02-07 21:40:38 +080073 return;
Andrew Jeffery2834c5b2023-04-19 12:47:09 +093074 }
Jeremy Kerr6b1fed22017-02-07 21:40:38 +080075
76 th->blocked = blocked;
77 events = POLLIN;
78
Andrew Jeffery2834c5b2023-04-19 12:47:09 +093079 if (th->blocked) {
Jeremy Kerr6b1fed22017-02-07 21:40:38 +080080 events |= POLLOUT;
Andrew Jeffery2834c5b2023-04-19 12:47:09 +093081 }
Jeremy Kerr6b1fed22017-02-07 21:40:38 +080082
83 console_poller_set_events(th->console, th->poller, events);
84}
85
Jeremy Kerrf733c852017-02-07 18:40:10 +080086static int tty_drain_queue(struct tty_handler *th, size_t force_len)
87{
Andrew Jefferyb70f8712023-04-19 12:53:34 +093088 size_t len;
89 size_t total_len;
Jeremy Kerrf733c852017-02-07 18:40:10 +080090 ssize_t wlen;
91 uint8_t *buf;
Jeremy Kerrf733c852017-02-07 18:40:10 +080092
93 /* if we're forcing data, we need to clear non-blocking mode */
Andrew Jeffery2834c5b2023-04-19 12:47:09 +093094 if (force_len) {
Jeremy Kerr6b1fed22017-02-07 21:40:38 +080095 tty_set_fd_blocking(th, true);
96
Andrew Jeffery2834c5b2023-04-19 12:47:09 +093097 /* no point writing, we'll just see -EAGAIN */
98 } else if (th->blocked) {
Jeremy Kerr6b1fed22017-02-07 21:40:38 +080099 return 0;
Andrew Jeffery2834c5b2023-04-19 12:47:09 +0930100 }
Jeremy Kerrf733c852017-02-07 18:40:10 +0800101
102 total_len = 0;
103
104 for (;;) {
105 len = ringbuffer_dequeue_peek(th->rbc, total_len, &buf);
Andrew Jeffery2834c5b2023-04-19 12:47:09 +0930106 if (!len) {
Jeremy Kerrf733c852017-02-07 18:40:10 +0800107 break;
Andrew Jeffery2834c5b2023-04-19 12:47:09 +0930108 }
Jeremy Kerrf733c852017-02-07 18:40:10 +0800109
110 /* write as little as possible while blocking */
Andrew Jeffery2834c5b2023-04-19 12:47:09 +0930111 if (force_len && force_len < total_len + len) {
Jeremy Kerrf733c852017-02-07 18:40:10 +0800112 len = force_len - total_len;
Andrew Jeffery2834c5b2023-04-19 12:47:09 +0930113 }
Jeremy Kerrf733c852017-02-07 18:40:10 +0800114
115 wlen = write(th->fd, buf, len);
116 if (wlen < 0) {
Andrew Jeffery2834c5b2023-04-19 12:47:09 +0930117 if (errno == EINTR) {
Jeremy Kerrf733c852017-02-07 18:40:10 +0800118 continue;
Andrew Jeffery2834c5b2023-04-19 12:47:09 +0930119 }
Andrew Jefferya72711a2023-04-18 18:19:41 +0930120 if ((errno == EAGAIN || errno == EWOULDBLOCK) &&
121 !force_len) {
Jeremy Kerr6b1fed22017-02-07 21:40:38 +0800122 tty_set_blocked(th, true);
Jeremy Kerrf733c852017-02-07 18:40:10 +0800123 break;
Jeremy Kerr6b1fed22017-02-07 21:40:38 +0800124 }
Jeremy Kerrf733c852017-02-07 18:40:10 +0800125 warn("failed writing to local tty; disabling");
126 return -1;
127 }
128
129 total_len += wlen;
130
Andrew Jeffery2834c5b2023-04-19 12:47:09 +0930131 if (force_len && total_len >= force_len) {
Jeremy Kerrf733c852017-02-07 18:40:10 +0800132 break;
Andrew Jeffery2834c5b2023-04-19 12:47:09 +0930133 }
Jeremy Kerrf733c852017-02-07 18:40:10 +0800134 }
135
136 ringbuffer_dequeue_commit(th->rbc, total_len);
137
Andrew Jeffery2834c5b2023-04-19 12:47:09 +0930138 if (force_len) {
Jeremy Kerr6b1fed22017-02-07 21:40:38 +0800139 tty_set_fd_blocking(th, false);
Andrew Jeffery2834c5b2023-04-19 12:47:09 +0930140 }
Jeremy Kerrf733c852017-02-07 18:40:10 +0800141
142 return 0;
143}
144
145static enum ringbuffer_poll_ret tty_ringbuffer_poll(void *arg, size_t force_len)
146{
147 struct tty_handler *th = arg;
148 int rc;
149
150 rc = tty_drain_queue(th, force_len);
151 if (rc) {
152 console_poller_unregister(th->console, th->poller);
153 return RINGBUFFER_POLL_REMOVE;
154 }
155
156 return RINGBUFFER_POLL_OK;
157}
158
Andrew Jefferya72711a2023-04-18 18:19:41 +0930159static enum poller_ret tty_poll(struct handler *handler, int events,
160 void __attribute__((unused)) * data)
Jeremy Kerrbc1e8932016-04-28 12:27:30 +0800161{
162 struct tty_handler *th = to_tty_handler(handler);
163 uint8_t buf[4096];
164 ssize_t len;
Jeremy Kerrf733c852017-02-07 18:40:10 +0800165 int rc;
Jeremy Kerrbc1e8932016-04-28 12:27:30 +0800166
Jeremy Kerrf733c852017-02-07 18:40:10 +0800167 if (events & POLLIN) {
168 len = read(th->fd, buf, sizeof(buf));
Andrew Jeffery2834c5b2023-04-19 12:47:09 +0930169 if (len <= 0) {
Jeremy Kerr67eab042017-07-12 12:08:35 +0800170 goto err;
Andrew Jeffery2834c5b2023-04-19 12:47:09 +0930171 }
Jeremy Kerrbc1e8932016-04-28 12:27:30 +0800172
Jeremy Kerrf733c852017-02-07 18:40:10 +0800173 console_data_out(th->console, buf, len);
Jeremy Kerrbc1e8932016-04-28 12:27:30 +0800174 }
175
Jeremy Kerrf733c852017-02-07 18:40:10 +0800176 if (events & POLLOUT) {
Jeremy Kerr6b1fed22017-02-07 21:40:38 +0800177 tty_set_blocked(th, false);
Jeremy Kerrf733c852017-02-07 18:40:10 +0800178 rc = tty_drain_queue(th, 0);
Andrew Jeffery2834c5b2023-04-19 12:47:09 +0930179 if (rc) {
Jeremy Kerr67eab042017-07-12 12:08:35 +0800180 goto err;
Andrew Jeffery2834c5b2023-04-19 12:47:09 +0930181 }
Jeremy Kerrf733c852017-02-07 18:40:10 +0800182 }
Jeremy Kerrbc1e8932016-04-28 12:27:30 +0800183
184 return POLLER_OK;
Jeremy Kerr67eab042017-07-12 12:08:35 +0800185
186err:
187 th->poller = NULL;
188 close(th->fd);
189 ringbuffer_consumer_unregister(th->rbc);
190 return POLLER_REMOVE;
Jeremy Kerrbc1e8932016-04-28 12:27:30 +0800191}
192
Xo Wangc5ef8ea2017-04-17 16:20:43 -0700193static int set_terminal_baud(struct tty_handler *th, const char *tty_name,
Andrew Jefferya72711a2023-04-18 18:19:41 +0930194 speed_t speed)
195{
Xo Wangc5ef8ea2017-04-17 16:20:43 -0700196 struct termios term_options;
Xo Wangc5ef8ea2017-04-17 16:20:43 -0700197
198 if (tcgetattr(th->fd, &term_options) < 0) {
199 warn("Can't get config for %s", tty_name);
200 return -1;
201 }
202
203 if (cfsetspeed(&term_options, speed) < 0) {
204 warn("Couldn't set speeds for %s", tty_name);
205 return -1;
206 }
207
208 if (tcsetattr(th->fd, TCSAFLUSH, &term_options) < 0) {
209 warn("Couldn't commit terminal options for %s", tty_name);
210 return -1;
211 }
Xo Wangc5ef8ea2017-04-17 16:20:43 -0700212
213 return 0;
214}
215
Andrew Jefferya72711a2023-04-18 18:19:41 +0930216static int make_terminal_raw(struct tty_handler *th, const char *tty_name)
217{
Xo Wang81408bd2017-04-18 16:43:07 -0700218 struct termios term_options;
219
220 if (tcgetattr(th->fd, &term_options) < 0) {
221 warn("Can't get config for %s", tty_name);
222 return -1;
223 }
224
225 /* Disable various input and output processing including character
226 * translation, line edit (canonical) mode, flow control, and special signal
227 * generating characters. */
228 cfmakeraw(&term_options);
229
230 if (tcsetattr(th->fd, TCSAFLUSH, &term_options) < 0) {
231 warn("Couldn't commit terminal options for %s", tty_name);
232 return -1;
233 }
234 printf("Set %s for raw byte handling\n", tty_name);
235
236 return 0;
237}
238
Jeremy Kerre2826c72024-07-05 10:54:21 +0800239static struct handler *tty_init(const struct handler_type *type
240 __attribute__((unused)),
241 struct console *console,
242 struct config *config __attribute__((unused)))
Jeremy Kerrbc1e8932016-04-28 12:27:30 +0800243{
Jeremy Kerre2826c72024-07-05 10:54:21 +0800244 struct tty_handler *th;
Cheng C Yangf9c8f6c2019-03-04 18:39:52 +0800245 speed_t desired_speed;
Jeremy Kerrbc1e8932016-04-28 12:27:30 +0800246 const char *tty_name;
Xo Wangc5ef8ea2017-04-17 16:20:43 -0700247 const char *tty_baud;
Jeremy Kerrbc1e8932016-04-28 12:27:30 +0800248 char *tty_path;
Jeremy Kerrbc506fd2017-02-07 09:24:48 +0800249 int rc;
Jeremy Kerrbc1e8932016-04-28 12:27:30 +0800250
251 tty_name = config_get_value(config, "local-tty");
Andrew Jeffery2834c5b2023-04-19 12:47:09 +0930252 if (!tty_name) {
Jeremy Kerre2826c72024-07-05 10:54:21 +0800253 return NULL;
Andrew Jeffery2834c5b2023-04-19 12:47:09 +0930254 }
Jeremy Kerrbc1e8932016-04-28 12:27:30 +0800255
256 rc = asprintf(&tty_path, "/dev/%s", tty_name);
Andrew Jeffery2834c5b2023-04-19 12:47:09 +0930257 if (!rc) {
Jeremy Kerre2826c72024-07-05 10:54:21 +0800258 return NULL;
259 }
260
261 th = malloc(sizeof(*th));
262 if (!th) {
263 return NULL;
Andrew Jeffery2834c5b2023-04-19 12:47:09 +0930264 }
Jeremy Kerrbc1e8932016-04-28 12:27:30 +0800265
Jeremy Kerrbc506fd2017-02-07 09:24:48 +0800266 th->fd = open(tty_path, O_RDWR | O_NONBLOCK);
Jeremy Kerrbc1e8932016-04-28 12:27:30 +0800267 if (th->fd < 0) {
268 warn("Can't open %s; disabling local tty", tty_name);
269 free(tty_path);
Jeremy Kerre2826c72024-07-05 10:54:21 +0800270 free(th);
271 return NULL;
Jeremy Kerrbc1e8932016-04-28 12:27:30 +0800272 }
273
274 free(tty_path);
Jeremy Kerr6b1fed22017-02-07 21:40:38 +0800275 th->fd_flags = fcntl(th->fd, F_GETFL, 0);
Jeremy Kerrbc1e8932016-04-28 12:27:30 +0800276
Xo Wangc5ef8ea2017-04-17 16:20:43 -0700277 tty_baud = config_get_value(config, "local-tty-baud");
Cheng C Yangf9c8f6c2019-03-04 18:39:52 +0800278 if (tty_baud != NULL) {
279 rc = config_parse_baud(&desired_speed, tty_baud);
280 if (rc) {
281 fprintf(stderr, "%s is not a valid baud rate\n",
282 tty_baud);
283 } else {
284 rc = set_terminal_baud(th, tty_name, desired_speed);
Andrew Jeffery2834c5b2023-04-19 12:47:09 +0930285 if (rc) {
Andrew Jefferya72711a2023-04-18 18:19:41 +0930286 fprintf(stderr,
287 "Couldn't set baud rate for %s to %s\n",
Xo Wangc5ef8ea2017-04-17 16:20:43 -0700288 tty_name, tty_baud);
Andrew Jeffery2834c5b2023-04-19 12:47:09 +0930289 }
Cheng C Yangf9c8f6c2019-03-04 18:39:52 +0800290 }
291 }
Xo Wangc5ef8ea2017-04-17 16:20:43 -0700292
Andrew Jeffery2834c5b2023-04-19 12:47:09 +0930293 if (make_terminal_raw(th, tty_name) != 0) {
Xo Wang81408bd2017-04-18 16:43:07 -0700294 fprintf(stderr, "Couldn't make %s a raw terminal\n", tty_name);
Andrew Jeffery2834c5b2023-04-19 12:47:09 +0930295 }
Xo Wang81408bd2017-04-18 16:43:07 -0700296
Jeremy Kerre2826c72024-07-05 10:54:21 +0800297 th->poller = console_poller_register(console, &th->handler, tty_poll,
298 NULL, th->fd, POLLIN, NULL);
Jeremy Kerrbc1e8932016-04-28 12:27:30 +0800299 th->console = console;
Jeremy Kerrf733c852017-02-07 18:40:10 +0800300 th->rbc = console_ringbuffer_consumer_register(console,
Andrew Jefferya72711a2023-04-18 18:19:41 +0930301 tty_ringbuffer_poll, th);
Jeremy Kerrbc1e8932016-04-28 12:27:30 +0800302
Jeremy Kerre2826c72024-07-05 10:54:21 +0800303 return &th->handler;
Jeremy Kerrbc1e8932016-04-28 12:27:30 +0800304}
305
Jeremy Kerrbc1e8932016-04-28 12:27:30 +0800306static void tty_fini(struct handler *handler)
307{
308 struct tty_handler *th = to_tty_handler(handler);
Andrew Jeffery2834c5b2023-04-19 12:47:09 +0930309 if (th->poller) {
Jeremy Kerr55c97122017-02-07 17:06:46 +0800310 console_poller_unregister(th->console, th->poller);
Andrew Jeffery2834c5b2023-04-19 12:47:09 +0930311 }
Jeremy Kerrbc1e8932016-04-28 12:27:30 +0800312 close(th->fd);
Jeremy Kerre2826c72024-07-05 10:54:21 +0800313 free(th);
Jeremy Kerrbc1e8932016-04-28 12:27:30 +0800314}
315
Cheng C Yangf9c8f6c2019-03-04 18:39:52 +0800316static int tty_baudrate(struct handler *handler, speed_t baudrate)
317{
318 const char *tty_name = "local-tty";
319 struct tty_handler *th = to_tty_handler(handler);
320
321 if (baudrate == 0) {
322 return -1;
323 }
324
325 if (set_terminal_baud(th, tty_name, baudrate) != 0) {
326 fprintf(stderr, "Couldn't set baud rate for %s to %d\n",
327 tty_name, baudrate);
328 return -1;
329 }
330 return 0;
331}
332
Jeremy Kerre2826c72024-07-05 10:54:21 +0800333static const struct handler_type tty_handler = {
334 .name = "tty",
335 .init = tty_init,
336 .fini = tty_fini,
337 .baudrate = tty_baudrate,
Jeremy Kerrbc1e8932016-04-28 12:27:30 +0800338};
339
Jeremy Kerre2826c72024-07-05 10:54:21 +0800340console_handler_register(&tty_handler);