]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/basic/terminal-util.c
man/systemd-sysext: list ephemeral/ephemeral-import in the list of options
[thirdparty/systemd.git] / src / basic / terminal-util.c
CommitLineData
db9ecf05 1/* SPDX-License-Identifier: LGPL-2.1-or-later */
288a74cc 2
07630cea 3#include <fcntl.h>
23b27b39
LP
4#include <linux/kd.h>
5#include <linux/tiocl.h>
6#include <linux/vt.h>
7#include <poll.h>
8#include <signal.h>
11c3a366 9#include <stdlib.h>
11c3a366 10#include <sys/inotify.h>
23b27b39 11#include <sys/ioctl.h>
11c3a366 12#include <sys/sysmacros.h>
288a74cc 13#include <termios.h>
0c15577a 14#include <time.h>
07630cea 15#include <unistd.h>
288a74cc 16
b5efdb8a 17#include "alloc-util.h"
b7120388 18#include "ansi-color.h"
dffbe1d1 19#include "chase.h"
7176f06c 20#include "devnum-util.h"
0c15577a 21#include "errno-util.h"
6553db60 22#include "extract-word.h"
3ffd4af2 23#include "fd-util.h"
288a74cc 24#include "fileio.h"
f4f15635 25#include "fs-util.h"
63e9c383 26#include "hexdecoct.h"
9e5fd717 27#include "inotify-util.h"
c004493c 28#include "io-util.h"
93cc7779 29#include "log.h"
ce3a1593 30#include "missing_magic.h"
0cb8e3d1 31#include "namespace-util.h"
6bedfcbb 32#include "parse-util.h"
c2b32159
LP
33#include "path-util.h"
34#include "proc-cmdline.h"
07630cea 35#include "process-util.h"
0bd72176 36#include "signal-util.h"
2583fbea 37#include "socket-util.h"
8fcde012 38#include "stat-util.h"
11f3c130 39#include "stdio-util.h"
07630cea 40#include "string-util.h"
6af62124 41#include "strv.h"
3ffd4af2 42#include "terminal-util.h"
07630cea 43#include "time-util.h"
e4a08721 44#include "utf8.h"
288a74cc 45
b177095b
LP
46#define ANSI_RESET_CURSOR \
47 "\033?25h" /* turn on cursor */ \
48 "\033?12l" /* reset cursor blinking */ \
49 "\033 1q" /* reset cursor style */
50
5321b957
ZJS
51/* How much to wait for a reply to a terminal sequence */
52#define CONSOLE_REPLY_WAIT_USEC (333 * USEC_PER_MSEC)
53
288a74cc
RC
54static volatile unsigned cached_columns = 0;
55static volatile unsigned cached_lines = 0;
56
c6063244 57static volatile int cached_on_tty = -1;
197dd3a9 58static volatile int cached_on_dev_null = -1;
c6063244 59
76270f5c
MY
60bool isatty_safe(int fd) {
61 assert(fd >= 0);
62
63 if (isatty(fd))
64 return true;
65
1b24357c
LP
66 /* Linux/glibc returns EIO for hung up TTY on isatty(). Which is wrong, the thing doesn't stop being
67 * a TTY after all, just because it is temporarily hung up. Let's work around this here, until this
68 * is fixed in glibc. See: https://sourceware.org/bugzilla/show_bug.cgi?id=32103 */
69 if (errno == EIO)
70 return true;
71
76270f5c
MY
72 /* Be resilient if we're working on stdio, since they're set up by parent process. */
73 assert(errno != EBADF || IN_SET(fd, STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO));
74
75 return false;
76}
77
288a74cc 78int chvt(int vt) {
254d1313 79 _cleanup_close_ int fd = -EBADF;
288a74cc 80
0295642d
LP
81 /* Switch to the specified vt number. If the VT is specified <= 0 switch to the VT the kernel log messages go,
82 * if that's configured. */
83
0a8b555c 84 fd = open_terminal("/dev/tty0", O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK);
288a74cc 85 if (fd < 0)
4c8c499e 86 return fd;
288a74cc 87
b9e74c39 88 if (vt <= 0) {
288a74cc
RC
89 int tiocl[2] = {
90 TIOCL_GETKMSGREDIRECT,
91 0
92 };
93
94 if (ioctl(fd, TIOCLINUX, tiocl) < 0)
95 return -errno;
96
97 vt = tiocl[0] <= 0 ? 1 : tiocl[0];
98 }
99
7c248223 100 return RET_NERRNO(ioctl(fd, VT_ACTIVATE, vt));
288a74cc
RC
101}
102
8fcd8576 103int read_one_char(FILE *f, char *ret, usec_t t, bool echo, bool *need_nl) {
715bcf36
LP
104 _cleanup_free_ char *line = NULL;
105 struct termios old_termios;
14f594b9 106 int r, fd;
288a74cc 107
288a74cc
RC
108 assert(ret);
109
8fcd8576
LP
110 if (!f)
111 f = stdin;
112
14f594b9
LP
113 /* If this is a terminal, then switch canonical mode off, so that we can read a single
114 * character. (Note that fmemopen() streams do not have an fd associated with them, let's handle that
8fcd8576
LP
115 * nicely.) If 'echo' is false we'll also disable ECHO mode so that the pressed key is not made
116 * visible to the user. */
14f594b9
LP
117 fd = fileno(f);
118 if (fd >= 0 && tcgetattr(fd, &old_termios) >= 0) {
715bcf36 119 struct termios new_termios = old_termios;
288a74cc 120
8fcd8576 121 new_termios.c_lflag &= ~(ICANON|(echo ? 0 : ECHO));
288a74cc
RC
122 new_termios.c_cc[VMIN] = 1;
123 new_termios.c_cc[VTIME] = 0;
124
f789b17e 125 if (tcsetattr(fd, TCSANOW, &new_termios) >= 0) {
03a7dbea 126 char c;
288a74cc
RC
127
128 if (t != USEC_INFINITY) {
14f594b9 129 if (fd_wait_for_event(fd, POLLIN, t) <= 0) {
f789b17e 130 (void) tcsetattr(fd, TCSANOW, &old_termios);
288a74cc
RC
131 return -ETIMEDOUT;
132 }
133 }
134
03a7dbea 135 r = safe_fgetc(f, &c);
f789b17e 136 (void) tcsetattr(fd, TCSANOW, &old_termios);
d3f9790c
LP
137 if (r < 0)
138 return r;
03a7dbea
LP
139 if (r == 0)
140 return -EIO;
288a74cc
RC
141
142 if (need_nl)
143 *need_nl = c != '\n';
144
145 *ret = c;
146 return 0;
147 }
148 }
149
978e7d16 150 if (t != USEC_INFINITY && fd >= 0) {
14f594b9
LP
151 /* Let's wait the specified amount of time for input. When we have no fd we skip this, under
152 * the assumption that this is an fmemopen() stream or so where waiting doesn't make sense
153 * anyway, as the data is either already in the stream or cannot possible be placed there
154 * while we access the stream */
155
156 if (fd_wait_for_event(fd, POLLIN, t) <= 0)
288a74cc
RC
157 return -ETIMEDOUT;
158 }
159
715bcf36 160 /* If this is not a terminal, then read a full line instead */
288a74cc 161
715bcf36
LP
162 r = read_line(f, 16, &line); /* longer than necessary, to eat up UTF-8 chars/vt100 key sequences */
163 if (r < 0)
164 return r;
165 if (r == 0)
166 return -EIO;
288a74cc
RC
167
168 if (strlen(line) != 1)
169 return -EBADMSG;
170
171 if (need_nl)
172 *need_nl = false;
173
174 *ret = line[0];
175 return 0;
176}
177
3c670f89
FB
178#define DEFAULT_ASK_REFRESH_USEC (2*USEC_PER_SEC)
179
180int ask_char(char *ret, const char *replies, const char *fmt, ...) {
288a74cc
RC
181 int r;
182
183 assert(ret);
184 assert(replies);
3c670f89 185 assert(fmt);
288a74cc
RC
186
187 for (;;) {
188 va_list ap;
189 char c;
190 bool need_nl = true;
191
25e4608b 192 fputs(ansi_highlight(), stdout);
288a74cc 193
3c670f89
FB
194 putchar('\r');
195
196 va_start(ap, fmt);
197 vprintf(fmt, ap);
288a74cc
RC
198 va_end(ap);
199
25e4608b 200 fputs(ansi_normal(), stdout);
288a74cc
RC
201
202 fflush(stdout);
203
8fcd8576 204 r = read_one_char(stdin, &c, DEFAULT_ASK_REFRESH_USEC, /* echo= */ true, &need_nl);
288a74cc
RC
205 if (r < 0) {
206
3c670f89
FB
207 if (r == -ETIMEDOUT)
208 continue;
209
288a74cc
RC
210 if (r == -EBADMSG) {
211 puts("Bad input, please try again.");
212 continue;
213 }
214
215 putchar('\n');
216 return r;
217 }
218
219 if (need_nl)
220 putchar('\n');
221
222 if (strchr(replies, c)) {
223 *ret = c;
224 return 0;
225 }
226
227 puts("Read unexpected character, please try again.");
228 }
229}
230
94a2b1cd
LP
231typedef enum CompletionResult{
232 COMPLETION_ALREADY, /* the input string is already complete */
233 COMPLETION_FULL, /* completed the input string to be complete now */
234 COMPLETION_PARTIAL, /* completed the input string so that is still incomplete */
235 COMPLETION_NONE, /* found no matching completion */
236 _COMPLETION_RESULT_MAX,
237 _COMPLETION_RESULT_INVALID = -EINVAL,
238 _COMPLETION_RESULT_ERRNO_MAX = -ERRNO_MAX,
239} CompletionResult;
240
241static CompletionResult pick_completion(const char *string, char *const*completions, char **ret) {
242 _cleanup_free_ char *found = NULL;
243 bool partial = false;
244
245 string = strempty(string);
246
247 STRV_FOREACH(c, completions) {
248
249 /* Ignore entries that are not actually completions */
250 if (!startswith(*c, string))
251 continue;
252
253 /* Store first completion that matches */
254 if (!found) {
255 found = strdup(*c);
256 if (!found)
257 return -ENOMEM;
258
259 continue;
260 }
261
262 /* If there's another completion that works truncate the one we already found by common
263 * prefix */
264 size_t n = str_common_prefix(found, *c);
265 if (n == SIZE_MAX)
266 continue;
267
268 found[n] = 0;
269 partial = true;
270 }
271
272 *ret = TAKE_PTR(found);
273
274 if (!*ret)
275 return COMPLETION_NONE;
276 if (partial)
277 return COMPLETION_PARTIAL;
278
279 return streq(string, *ret) ? COMPLETION_ALREADY : COMPLETION_FULL;
280}
281
282static void clear_by_backspace(size_t n) {
283 /* Erase the specified number of character cells backwards on the terminal */
284 for (size_t i = 0; i < n; i++)
285 fputs("\b \b", stdout);
286}
287
288int ask_string_full(
289 char **ret,
290 GetCompletionsCallback get_completions,
291 void *userdata,
292 const char *text, ...) {
293
03d94294 294 va_list ap;
715bcf36
LP
295 int r;
296
288a74cc
RC
297 assert(ret);
298 assert(text);
299
94a2b1cd 300 /* Output the prompt */
25e4608b 301 fputs(ansi_highlight(), stdout);
03d94294
ZJS
302 va_start(ap, text);
303 vprintf(text, ap);
304 va_end(ap);
25e4608b 305 fputs(ansi_normal(), stdout);
03d94294 306 fflush(stdout);
288a74cc 307
94a2b1cd
LP
308 _cleanup_free_ char *string = NULL;
309 size_t n = 0;
310
311 /* Do interactive logic only if stdin + stdout are connected to the same place. And yes, we could use
312 * STDIN_FILENO and STDOUT_FILENO here, but let's be overly correct for once, after all libc allows
313 * swapping out stdin/stdout. */
314 int fd_input = fileno(stdin);
315 int fd_output = fileno(stdout);
316 if (fd_input < 0 || fd_output < 0 || same_fd(fd_input, fd_output) <= 0)
317 goto fallback;
318
319 /* Try to disable echo, which also tells us if this even is a terminal */
320 struct termios old_termios;
321 if (tcgetattr(fd_input, &old_termios) < 0)
322 goto fallback;
323
324 struct termios new_termios = old_termios;
325 termios_disable_echo(&new_termios);
f789b17e 326 if (tcsetattr(fd_input, TCSANOW, &new_termios) < 0)
94a2b1cd
LP
327 return -errno;
328
329 for (;;) {
330 int c = fgetc(stdin);
331
332 /* On EOF or NUL, end the request, don't output anything anymore */
333 if (IN_SET(c, EOF, 0))
334 break;
335
336 /* On Return also end the request, but make this visible */
337 if (IN_SET(c, '\n', '\r')) {
338 fputc('\n', stdout);
339 break;
340 }
341
342 if (c == '\t') {
343 /* Tab */
344
345 _cleanup_strv_free_ char **completions = NULL;
346 if (get_completions) {
347 r = get_completions(string, &completions, userdata);
348 if (r < 0)
349 goto fail;
350 }
351
352 _cleanup_free_ char *new_string = NULL;
353 CompletionResult cr = pick_completion(string, completions, &new_string);
354 if (cr < 0) {
355 r = cr;
356 goto fail;
357 }
358 if (IN_SET(cr, COMPLETION_PARTIAL, COMPLETION_FULL)) {
359 /* Output the new suffix we learned */
360 fputs(ASSERT_PTR(startswith(new_string, strempty(string))), stdout);
361
362 /* And update the whole string */
363 free_and_replace(string, new_string);
364 n = strlen(string);
365 }
366 if (cr == COMPLETION_NONE)
367 fputc('\a', stdout); /* BEL */
368
369 if (IN_SET(cr, COMPLETION_PARTIAL, COMPLETION_ALREADY)) {
370 /* If this worked only partially, or if the user hit TAB even though we were
371 * complete already, then show the remaining options (in the latter case just
372 * the one). */
373 fputc('\n', stdout);
374
375 _cleanup_strv_free_ char **filtered = strv_filter_prefix(completions, string);
376 if (!filtered) {
377 r = -ENOMEM;
378 goto fail;
379 }
380
381 r = show_menu(filtered,
382 /* n_columns= */ SIZE_MAX,
383 /* column_width= */ SIZE_MAX,
384 /* ellipsize_percentage= */ 0,
385 /* grey_prefix=*/ string,
386 /* with_numbers= */ false);
387 if (r < 0)
388 goto fail;
389
390 /* Show the prompt again */
391 fputs(ansi_highlight(), stdout);
392 va_start(ap, text);
393 vprintf(text, ap);
394 va_end(ap);
395 fputs(ansi_normal(), stdout);
396 fputs(string, stdout);
397 }
398
399 } else if (IN_SET(c, '\b', 127)) {
400 /* Backspace */
401
402 if (n == 0)
403 fputc('\a', stdout); /* BEL */
404 else {
405 size_t m = utf8_last_length(string, n);
406
407 char *e = string + n - m;
408 clear_by_backspace(utf8_console_width(e));
409
410 *e = 0;
411 n -= m;
412 }
413
414 } else if (c == 21) {
415 /* Ctrl-u → erase all input */
416
417 clear_by_backspace(utf8_console_width(string));
a6eb2296
YW
418 if (string)
419 string[n = 0] = 0;
420 else
421 assert(n == 0);
94a2b1cd
LP
422
423 } else if (c == 4) {
424 /* Ctrl-d → cancel this field input */
425
426 r = -ECANCELED;
427 goto fail;
428
429 } else if (char_is_cc(c) || n >= LINE_MAX)
430 /* refuse control characters and too long strings */
431 fputc('\a', stdout); /* BEL */
432 else {
433 /* Regular char */
434
435 if (!GREEDY_REALLOC(string, n+2)) {
436 r = -ENOMEM;
437 goto fail;
438 }
439
440 string[n++] = (char) c;
441 string[n] = 0;
442
443 fputc(c, stdout);
444 }
445
446 fflush(stdout);
447 }
448
f789b17e 449 if (tcsetattr(fd_input, TCSANOW, &old_termios) < 0)
94a2b1cd
LP
450 return -errno;
451
452 if (!string) {
453 string = strdup("");
454 if (!string)
455 return -ENOMEM;
456 }
457
458 *ret = TAKE_PTR(string);
459 return 0;
460
461fail:
f789b17e 462 (void) tcsetattr(fd_input, TCSANOW, &old_termios);
94a2b1cd
LP
463 return r;
464
465fallback:
466 /* A simple fallback without TTY magic */
467 r = read_line(stdin, LONG_LINE_MAX, &string);
03d94294
ZJS
468 if (r < 0)
469 return r;
470 if (r == 0)
471 return -EIO;
288a74cc 472
94a2b1cd 473 *ret = TAKE_PTR(string);
03d94294 474 return 0;
288a74cc
RC
475}
476
91ea3dcf 477bool any_key_to_proceed(void) {
91ea3dcf 478
8fcd8576
LP
479 /* Insert a new line here as well as to when the user inputs, as this is also used during the boot up
480 * sequence when status messages may be interleaved with the current program output. This ensures
481 * that the status messages aren't appended on the same line as this message. */
91ea3dcf 482
8fcd8576
LP
483 fputc('\n', stdout);
484 fputs(ansi_highlight_magenta(), stdout);
485 fputs("-- Press any key to proceed --", stdout);
486 fputs(ansi_normal(), stdout);
787904d0 487 fputc('\n', stdout);
8fcd8576 488 fflush(stdout);
91ea3dcf 489
8fcd8576
LP
490 char key = 0;
491 (void) read_one_char(stdin, &key, USEC_INFINITY, /* echo= */ false, /* need_nl= */ NULL);
492
8fcd8576
LP
493 fputc('\n', stdout);
494 fflush(stdout);
91ea3dcf
MF
495
496 return key != 'q';
497}
498
b6478aa1
LP
499static size_t widest_list_element(char *const*l) {
500 size_t w = 0;
501
502 /* Returns the largest console width of all elements in 'l' */
503
504 STRV_FOREACH(i, l)
505 w = MAX(w, utf8_console_width(*i));
506
507 return w;
508}
509
510int show_menu(char **x,
511 size_t n_columns,
512 size_t column_width,
513 unsigned ellipsize_percentage,
514 const char *grey_prefix,
515 bool with_numbers) {
ec75a254
DDM
516
517 assert(n_columns > 0);
518
b6478aa1
LP
519 if (n_columns == SIZE_MAX)
520 n_columns = 3;
521
522 if (column_width == SIZE_MAX) {
523 size_t widest = widest_list_element(x);
524
525 /* If not specified, derive column width from screen width */
526 size_t column_max = (columns()-1) / n_columns;
527
528 /* Subtract room for numbers */
529 if (with_numbers && column_max > 6)
530 column_max -= 6;
ec75a254 531
b6478aa1
LP
532 /* If columns would get too tight let's make this a linear list instead. */
533 if (column_max < 10 && widest > 10) {
534 n_columns = 1;
535 column_max = columns()-1;
536
537 if (with_numbers && column_max > 6)
538 column_max -= 6;
539 }
540
541 column_width = CLAMP(widest+1, 10U, column_max);
542 }
543
544 size_t n = strv_length(x);
545 size_t per_column = DIV_ROUND_UP(n, n_columns);
546
547 size_t break_lines = lines();
ec75a254
DDM
548 if (break_lines > 2)
549 break_lines--;
550
551 /* The first page gets two extra lines, since we want to show
552 * a title */
b6478aa1 553 size_t break_modulo = break_lines;
ec75a254
DDM
554 if (break_modulo > 3)
555 break_modulo -= 3;
556
b6478aa1 557 for (size_t i = 0; i < per_column; i++) {
ec75a254 558
b6478aa1 559 for (size_t j = 0; j < n_columns; j++) {
ec75a254
DDM
560 _cleanup_free_ char *e = NULL;
561
562 if (j * per_column + i >= n)
563 break;
564
b6478aa1 565 e = ellipsize(x[j * per_column + i], column_width, ellipsize_percentage);
ec75a254 566 if (!e)
b6478aa1
LP
567 return -ENOMEM;
568
569 if (with_numbers)
570 printf("%s%4zu)%s ",
571 ansi_grey(),
572 j * per_column + i + 1,
573 ansi_normal());
574
575 if (grey_prefix && startswith(e, grey_prefix)) {
576 size_t k = MIN(strlen(grey_prefix), column_width);
577 printf("%s%.*s%s",
578 ansi_grey(),
579 (int) k, e,
580 ansi_normal());
581 printf("%-*s",
582 (int) (column_width - k), e+k);
583 } else
584 printf("%-*s", (int) column_width, e);
ec75a254
DDM
585 }
586
587 putchar('\n');
588
589 /* on the first screen we reserve 2 extra lines for the title */
8fcd8576 590 if (i % break_lines == break_modulo)
ec75a254
DDM
591 if (!any_key_to_proceed())
592 return 0;
ec75a254
DDM
593 }
594
595 return 0;
596}
597
288a74cc 598int open_terminal(const char *name, int mode) {
254d1313 599 _cleanup_close_ int fd = -EBADF;
288a74cc
RC
600
601 /*
4768529f
LP
602 * If a TTY is in the process of being closed opening it might cause EIO. This is horribly awful, but
603 * unlikely to be changed in the kernel. Hence we work around this problem by retrying a couple of
604 * times.
288a74cc
RC
605 *
606 * https://bugs.launchpad.net/ubuntu/+source/linux/+bug/554172/comments/245
607 */
608
fbd2679f 609 assert((mode & (O_CREAT|O_PATH|O_DIRECTORY|O_TMPFILE)) == 0);
288a74cc 610
fbd2679f 611 for (unsigned c = 0;; c++) {
288a74cc
RC
612 fd = open(name, mode, 0);
613 if (fd >= 0)
614 break;
615
616 if (errno != EIO)
617 return -errno;
618
619 /* Max 1s in total */
620 if (c >= 20)
fbd2679f 621 return -EIO;
288a74cc 622
4251512e 623 (void) usleep_safe(50 * USEC_PER_MSEC);
288a74cc
RC
624 }
625
dd9c8da8 626 if (!isatty_safe(fd))
aae47bf7 627 return -ENOTTY;
288a74cc 628
4768529f 629 return TAKE_FD(fd);
288a74cc
RC
630}
631
632int acquire_terminal(
633 const char *name,
8854d795 634 AcquireTerminalFlags flags,
288a74cc
RC
635 usec_t timeout) {
636
254d1313 637 _cleanup_close_ int notify = -EBADF, fd = -EBADF;
8854d795
LP
638 usec_t ts = USEC_INFINITY;
639 int r, wd = -1;
288a74cc
RC
640
641 assert(name);
789f4f7e
LP
642
643 AcquireTerminalFlags mode = flags & _ACQUIRE_TERMINAL_MODE_MASK;
644 assert(IN_SET(mode, ACQUIRE_TERMINAL_TRY, ACQUIRE_TERMINAL_FORCE, ACQUIRE_TERMINAL_WAIT));
645 assert(mode == ACQUIRE_TERMINAL_WAIT || !FLAGS_SET(flags, ACQUIRE_TERMINAL_WATCH_SIGTERM));
288a74cc 646
8854d795
LP
647 /* We use inotify to be notified when the tty is closed. We create the watch before checking if we can actually
648 * acquire it, so that we don't lose any event.
288a74cc 649 *
8854d795
LP
650 * Note: strictly speaking this actually watches for the device being closed, it does *not* really watch
651 * whether a tty loses its controlling process. However, unless some rogue process uses TIOCNOTTY on /dev/tty
f95dbcc2
ZJS
652 * *after* closing its tty otherwise this will not become a problem. As long as the administrator makes sure to
653 * not configure any service on the same tty as an untrusted user this should not be a problem. (Which they
8854d795
LP
654 * probably should not do anyway.) */
655
789f4f7e
LP
656 if (mode == ACQUIRE_TERMINAL_WAIT) {
657 notify = inotify_init1(IN_CLOEXEC | IN_NONBLOCK);
8854d795
LP
658 if (notify < 0)
659 return -errno;
288a74cc
RC
660
661 wd = inotify_add_watch(notify, name, IN_CLOSE);
8854d795
LP
662 if (wd < 0)
663 return -errno;
664
665 if (timeout != USEC_INFINITY)
666 ts = now(CLOCK_MONOTONIC);
288a74cc
RC
667 }
668
789f4f7e
LP
669 /* If we are called with ACQUIRE_TERMINAL_WATCH_SIGTERM we'll unblock SIGTERM during ppoll() temporarily */
670 sigset_t poll_ss;
671 assert_se(sigprocmask(SIG_SETMASK, /* newset= */ NULL, &poll_ss) >= 0);
672 if (flags & ACQUIRE_TERMINAL_WATCH_SIGTERM) {
673 assert_se(sigismember(&poll_ss, SIGTERM) > 0);
674 assert_se(sigdelset(&poll_ss, SIGTERM) >= 0);
675 }
676
288a74cc 677 for (;;) {
288a74cc
RC
678 if (notify >= 0) {
679 r = flush_fd(notify);
680 if (r < 0)
8854d795 681 return r;
288a74cc
RC
682 }
683
8854d795
LP
684 /* We pass here O_NOCTTY only so that we can check the return value TIOCSCTTY and have a reliable way
685 * to figure out if we successfully became the controlling process of the tty */
288a74cc
RC
686 fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC);
687 if (fd < 0)
688 return fd;
689
8854d795 690 /* Temporarily ignore SIGHUP, so that we don't get SIGHUP'ed if we already own the tty. */
0bd72176
LP
691 struct sigaction sa_old;
692 assert_se(sigaction(SIGHUP, &sigaction_ignore, &sa_old) >= 0);
288a74cc
RC
693
694 /* First, try to get the tty */
789f4f7e 695 r = RET_NERRNO(ioctl(fd, TIOCSCTTY, mode == ACQUIRE_TERMINAL_FORCE));
288a74cc 696
8854d795 697 /* Reset signal handler to old value */
0bd72176 698 assert_se(sigaction(SIGHUP, &sa_old, NULL) >= 0);
288a74cc 699
8854d795
LP
700 /* Success? Exit the loop now! */
701 if (r >= 0)
702 break;
288a74cc 703
8854d795
LP
704 /* Any failure besides -EPERM? Fail, regardless of the mode. */
705 if (r != -EPERM)
706 return r;
288a74cc 707
8854d795
LP
708 if (flags & ACQUIRE_TERMINAL_PERMISSIVE) /* If we are in permissive mode, then EPERM is fine, turn this
709 * into a success. Note that EPERM is also returned if we
710 * already are the owner of the TTY. */
288a74cc
RC
711 break;
712
789f4f7e 713 if (mode != ACQUIRE_TERMINAL_WAIT) /* If we are in TRY or FORCE mode, then propagate EPERM as EPERM */
8854d795
LP
714 return r;
715
288a74cc 716 assert(notify >= 0);
8854d795 717 assert(wd >= 0);
288a74cc
RC
718
719 for (;;) {
789f4f7e
LP
720 usec_t left;
721 if (timeout == USEC_INFINITY)
722 left = USEC_INFINITY;
723 else {
8854d795
LP
724 assert(ts != USEC_INFINITY);
725
789f4f7e 726 usec_t n = usec_sub_unsigned(now(CLOCK_MONOTONIC), ts);
496db330 727 if (n >= timeout)
8854d795 728 return -ETIMEDOUT;
288a74cc 729
789f4f7e 730 left = timeout - n;
288a74cc
RC
731 }
732
789f4f7e
LP
733 r = ppoll_usec_full(
734 &(struct pollfd) {
735 .fd = notify,
736 .events = POLLIN,
737 },
fa6f4484 738 /* n_fds = */ 1,
789f4f7e
LP
739 left,
740 &poll_ss);
741 if (r < 0)
742 return r;
743 if (r == 0)
744 return -ETIMEDOUT;
745
746 union inotify_event_buffer buffer;
747 ssize_t l;
288a74cc
RC
748 l = read(notify, &buffer, sizeof(buffer));
749 if (l < 0) {
8add30a0 750 if (ERRNO_IS_TRANSIENT(errno))
288a74cc
RC
751 continue;
752
8854d795 753 return -errno;
288a74cc
RC
754 }
755
756 FOREACH_INOTIFY_EVENT(e, buffer, l) {
8854d795
LP
757 if (e->mask & IN_Q_OVERFLOW) /* If we hit an inotify queue overflow, simply check if the terminal is up for grabs now. */
758 break;
759
760 if (e->wd != wd || !(e->mask & IN_CLOSE)) /* Safety checks */
761 return -EIO;
288a74cc
RC
762 }
763
764 break;
765 }
766
8854d795
LP
767 /* We close the tty fd here since if the old session ended our handle will be dead. It's important that
768 * we do this after sleeping, so that we don't enter an endless loop. */
288a74cc
RC
769 fd = safe_close(fd);
770 }
771
c10d6bdb 772 return TAKE_FD(fd);
288a74cc
RC
773}
774
775int release_terminal(void) {
254d1313 776 _cleanup_close_ int fd = -EBADF;
87964ec7 777 int r;
288a74cc 778
0a8b555c 779 fd = open("/dev/tty", O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK);
288a74cc
RC
780 if (fd < 0)
781 return -errno;
782
783 /* Temporarily ignore SIGHUP, so that we don't get SIGHUP'ed
784 * by our own TIOCNOTTY */
0bd72176
LP
785 struct sigaction sa_old;
786 assert_se(sigaction(SIGHUP, &sigaction_ignore, &sa_old) >= 0);
288a74cc 787
7c248223 788 r = RET_NERRNO(ioctl(fd, TIOCNOTTY));
288a74cc 789
0bd72176 790 assert_se(sigaction(SIGHUP, &sa_old, NULL) >= 0);
288a74cc
RC
791
792 return r;
793}
794
1de12823
MY
795int terminal_new_session(void) {
796
797 /* Make us the new session leader, and set stdin tty to be our controlling terminal.
798 *
799 * Why stdin? Well, the ctty logic is relevant for signal delivery mostly, i.e. if people hit C-c
800 * or the line is hung up. Such events are basically just a form of input, via a side channel
801 * (that side channel being signal delivery, i.e. SIGINT, SIGHUP et al). Hence we focus on input,
802 * not output here. */
803
804 if (!isatty_safe(STDIN_FILENO))
805 return -ENXIO;
806
807 (void) setsid();
808 return RET_NERRNO(ioctl(STDIN_FILENO, TIOCSCTTY, 0));
809}
810
0c15577a
DDM
811void terminal_detach_session(void) {
812 (void) setsid();
813 (void) release_terminal();
814}
815
288a74cc
RC
816int terminal_vhangup_fd(int fd) {
817 assert(fd >= 0);
7c248223 818 return RET_NERRNO(ioctl(fd, TIOCVHANGUP));
288a74cc
RC
819}
820
bc3477fd
DDM
821int terminal_vhangup(const char *tty) {
822 _cleanup_close_ int fd = -EBADF;
823
824 assert(tty);
825
826 fd = open_terminal(tty, O_RDWR|O_NOCTTY|O_CLOEXEC);
827 if (fd < 0)
828 return fd;
829
830 return terminal_vhangup_fd(fd);
831}
832
45d785df
LP
833int vt_disallocate(const char *tty_path) {
834 assert(tty_path);
288a74cc 835
45d785df
LP
836 /* Deallocate the VT if possible. If not possible (i.e. because it is the active one), at least clear
837 * it entirely (including the scrollback buffer). */
288a74cc 838
45d785df
LP
839 int ttynr = vtnr_from_tty(tty_path);
840 if (ttynr > 0) {
841 _cleanup_close_ int fd = open_terminal("/dev/tty0", O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK);
ba5d26cc
ZJS
842 if (fd < 0)
843 return fd;
288a74cc 844
45d785df
LP
845 /* Try to deallocate */
846 if (ioctl(fd, VT_DISALLOCATE, ttynr) >= 0)
ba5d26cc
ZJS
847 return 0;
848 if (errno != EBUSY)
849 return -errno;
850 }
288a74cc 851
45d785df
LP
852 /* So this is not a VT (in which case we cannot deallocate it), or we failed to deallocate. Let's at
853 * least clear the screen. */
288a74cc 854
45d785df 855 _cleanup_close_ int fd2 = open_terminal(tty_path, O_WRONLY|O_NOCTTY|O_CLOEXEC|O_NONBLOCK);
ba5d26cc
ZJS
856 if (fd2 < 0)
857 return fd2;
288a74cc 858
45d785df 859 return loop_write_full(fd2,
b177095b 860 ANSI_RESET_CURSOR
45d785df
LP
861 "\033[r" /* clear scrolling region */
862 "\033[H" /* move home */
863 "\033[3J" /* clear screen including scrollback, requires Linux 2.6.40 */
864 "\033c", /* reset to initial state */
865 SIZE_MAX,
866 100 * USEC_PER_MSEC);
288a74cc
RC
867}
868
b61c015a
LP
869static int vt_default_utf8(void) {
870 _cleanup_free_ char *b = NULL;
871 int r;
872
873 /* Read the default VT UTF8 setting from the kernel */
874
875 r = read_one_line_file("/sys/module/vt/parameters/default_utf8", &b);
876 if (r < 0)
877 return r;
878
879 return parse_boolean(b);
880}
881
882static int vt_reset_keyboard(int fd) {
af1d3a6d 883 int r, kb;
b61c015a 884
af1d3a6d 885 assert(fd >= 0);
b61c015a 886
af1d3a6d
LP
887 /* If we can't read the default, then default to Unicode. It's 2024 after all. */
888 r = vt_default_utf8();
889 if (r < 0)
890 log_debug_errno(r, "Failed to determine kernel VT UTF-8 mode, assuming enabled: %m");
891
892 kb = vt_default_utf8() != 0 ? K_UNICODE : K_XLATE;
b61c015a
LP
893 return RET_NERRNO(ioctl(fd, KDSKBMODE, kb));
894}
895
e2216800
LP
896static int terminal_reset_ioctl(int fd, bool switch_to_text) {
897 struct termios termios;
898 int r;
899
900 /* Set terminal to some sane defaults */
901
902 assert(fd >= 0);
903
904 /* We leave locked terminal attributes untouched, so that Plymouth may set whatever it wants to set,
905 * and we don't interfere with that. */
906
907 /* Disable exclusive mode, just in case */
908 if (ioctl(fd, TIOCNXCL) < 0)
909 log_debug_errno(errno, "TIOCNXCL ioctl failed on TTY, ignoring: %m");
910
911 /* Switch to text mode */
912 if (switch_to_text)
913 if (ioctl(fd, KDSETMODE, KD_TEXT) < 0)
914 log_debug_errno(errno, "KDSETMODE ioctl for switching to text mode failed on TTY, ignoring: %m");
915
916 /* Set default keyboard mode */
917 r = vt_reset_keyboard(fd);
918 if (r < 0)
919 log_debug_errno(r, "Failed to reset VT keyboard, ignoring: %m");
920
921 if (tcgetattr(fd, &termios) < 0) {
922 r = log_debug_errno(errno, "Failed to get terminal parameters: %m");
923 goto finish;
924 }
925
926 /* We only reset the stuff that matters to the software. How
927 * hardware is set up we don't touch assuming that somebody
928 * else will do that for us */
929
930 termios.c_iflag &= ~(IGNBRK | BRKINT | ISTRIP | INLCR | IGNCR | IUCLC);
931 termios.c_iflag |= ICRNL | IMAXBEL | IUTF8;
932 termios.c_oflag |= ONLCR | OPOST;
933 termios.c_cflag |= CREAD;
934 termios.c_lflag = ISIG | ICANON | IEXTEN | ECHO | ECHOE | ECHOK | ECHOCTL | ECHOKE;
935
936 termios.c_cc[VINTR] = 03; /* ^C */
937 termios.c_cc[VQUIT] = 034; /* ^\ */
938 termios.c_cc[VERASE] = 0177;
939 termios.c_cc[VKILL] = 025; /* ^X */
940 termios.c_cc[VEOF] = 04; /* ^D */
941 termios.c_cc[VSTART] = 021; /* ^Q */
942 termios.c_cc[VSTOP] = 023; /* ^S */
943 termios.c_cc[VSUSP] = 032; /* ^Z */
944 termios.c_cc[VLNEXT] = 026; /* ^V */
945 termios.c_cc[VWERASE] = 027; /* ^W */
946 termios.c_cc[VREPRINT] = 022; /* ^R */
947 termios.c_cc[VEOL] = 0;
948 termios.c_cc[VEOL2] = 0;
949
950 termios.c_cc[VTIME] = 0;
951 termios.c_cc[VMIN] = 1;
952
953 r = RET_NERRNO(tcsetattr(fd, TCSANOW, &termios));
954 if (r < 0)
955 log_debug_errno(r, "Failed to set terminal parameters: %m");
956
957finish:
958 /* Just in case, flush all crap out */
959 (void) tcflush(fd, TCIOFLUSH);
960
961 return r;
962}
963
964static int terminal_reset_ansi_seq(int fd) {
965 int r, k;
966
967 assert(fd >= 0);
968
969 if (getenv_terminal_is_dumb())
970 return 0;
971
972 r = fd_nonblock(fd, true);
973 if (r < 0)
974 return log_debug_errno(r, "Failed to set terminal to non-blocking mode: %m");
975
976 k = loop_write_full(fd,
b177095b 977 ANSI_RESET_CURSOR
e2216800
LP
978 "\033[!p" /* soft terminal reset */
979 "\033]104\007" /* reset colors */
4efd46c4
LP
980 "\033[?7h" /* enable line-wrapping */
981 "\033[1G" /* place cursor at beginning of current line */
982 "\033[0J", /* erase till end of screen */
e2216800 983 SIZE_MAX,
524e1240 984 100 * USEC_PER_MSEC);
e2216800 985 if (k < 0)
524e1240 986 log_debug_errno(k, "Failed to reset terminal through ANSI sequences: %m");
e2216800
LP
987
988 if (r > 0) {
989 r = fd_nonblock(fd, false);
990 if (r < 0)
991 log_debug_errno(r, "Failed to set terminal back to blocking mode: %m");
992 }
993
994 return k < 0 ? k : r;
995}
996
2736295d
LP
997void reset_dev_console_fd(int fd, bool switch_to_text) {
998 int r;
999
1000 assert(fd >= 0);
1001
2cd19499
LP
1002 _cleanup_close_ int lock_fd = lock_dev_console();
1003 if (lock_fd < 0)
1004 log_debug_errno(lock_fd, "Failed to lock /dev/console, ignoring: %m");
1005
5a4e5417 1006 r = terminal_reset_ioctl(fd, switch_to_text);
2736295d
LP
1007 if (r < 0)
1008 log_warning_errno(r, "Failed to reset /dev/console, ignoring: %m");
1009
1010 unsigned rows, cols;
1011 r = proc_cmdline_tty_size("/dev/console", &rows, &cols);
1012 if (r < 0)
1013 log_warning_errno(r, "Failed to get /dev/console size, ignoring: %m");
1014 else if (r > 0) {
1015 r = terminal_set_size_fd(fd, NULL, rows, cols);
1016 if (r < 0)
1017 log_warning_errno(r, "Failed to set configured terminal size on /dev/console, ignoring: %m");
1018 } else
1019 (void) terminal_fix_size(fd, fd);
1020
1021 r = terminal_reset_ansi_seq(fd);
1022 if (r < 0)
1023 log_warning_errno(r, "Failed to reset /dev/console using ANSI sequences, ignoring: %m");
1024}
1025
4a24cc85
LP
1026int lock_dev_console(void) {
1027 _cleanup_close_ int fd = -EBADF;
1028 int r;
1029
1030 /* NB: We do not use O_NOFOLLOW here, because some container managers might place a symlink to some
1031 * pty in /dev/console, in which case it should be fine to lock the target TTY. */
1032 fd = open_terminal("/dev/console", O_RDONLY|O_CLOEXEC|O_NOCTTY);
1033 if (fd < 0)
1034 return fd;
1035
1036 r = lock_generic(fd, LOCK_BSD, LOCK_EX);
1037 if (r < 0)
1038 return r;
1039
1040 return TAKE_FD(fd);
1041}
1042
288a74cc
RC
1043int make_console_stdio(void) {
1044 int fd, r;
1045
9281e703
LP
1046 /* Make /dev/console the controlling terminal and stdin/stdout/stderr, if we can. If we can't use
1047 * /dev/null instead. This is particularly useful if /dev/console is turned off, e.g. if console=null
1048 * is specified on the kernel command line. */
288a74cc 1049
8854d795 1050 fd = acquire_terminal("/dev/console", ACQUIRE_TERMINAL_FORCE|ACQUIRE_TERMINAL_PERMISSIVE, USEC_INFINITY);
9281e703
LP
1051 if (fd < 0) {
1052 log_warning_errno(fd, "Failed to acquire terminal, using /dev/null stdin/stdout/stderr instead: %m");
288a74cc 1053
9281e703
LP
1054 r = make_null_stdio();
1055 if (r < 0)
1056 return log_error_errno(r, "Failed to make /dev/null stdin/stdout/stderr: %m");
3d18b167 1057
9281e703 1058 } else {
2736295d 1059 reset_dev_console_fd(fd, /* switch_to_text= */ true);
963e25c2 1060
9281e703
LP
1061 r = rearrange_stdio(fd, fd, fd); /* This invalidates 'fd' both on success and on failure. */
1062 if (r < 0)
1063 return log_error_errno(r, "Failed to make terminal stdin/stdout/stderr: %m");
1064 }
c6063244 1065
9281e703 1066 reset_terminal_feature_caches();
288a74cc
RC
1067 return 0;
1068}
1069
b27f7916 1070static int vtnr_from_tty_raw(const char *tty, unsigned *ret) {
288a74cc
RC
1071 assert(tty);
1072
b27f7916 1073 tty = skip_dev_prefix(tty);
288a74cc 1074
b27f7916
MY
1075 const char *e = startswith(tty, "tty");
1076 if (!e)
1077 return -EINVAL;
288a74cc 1078
b27f7916 1079 return safe_atou(e, ret);
288a74cc
RC
1080}
1081
1082int vtnr_from_tty(const char *tty) {
b27f7916 1083 unsigned u;
57e55f93 1084 int r;
288a74cc
RC
1085
1086 assert(tty);
1087
b27f7916 1088 r = vtnr_from_tty_raw(tty, &u);
288a74cc
RC
1089 if (r < 0)
1090 return r;
57e55f93
LP
1091 if (!vtnr_is_valid(u))
1092 return -ERANGE;
288a74cc 1093
57e55f93 1094 return (int) u;
288a74cc
RC
1095}
1096
b27f7916
MY
1097bool tty_is_vc(const char *tty) {
1098 assert(tty);
1099
1100 /* NB: for >= 0 values no range check is conducted here, on the assumption that the caller will
1101 * either extract vtnr through vtnr_from_tty() later where ERANGE would be reported, or doesn't care
79ef0374 1102 * about whether it's strictly valid, but only asking "does this fall into the vt category?", for which
b27f7916
MY
1103 * "yes" seems to be a better answer. */
1104
1105 return vtnr_from_tty_raw(tty, /* ret = */ NULL) >= 0;
1106}
1107
1108bool tty_is_console(const char *tty) {
1109 assert(tty);
1110
1111 return streq(skip_dev_prefix(tty), "console");
1112}
1113
845be16f 1114int resolve_dev_console(char **ret) {
7b912648 1115 int r;
288a74cc 1116
7b912648
LP
1117 assert(ret);
1118
dffbe1d1
LP
1119 /* Resolve where /dev/console is pointing to. If /dev/console is a symlink (like in container
1120 * managers), we'll just resolve the symlink. If it's a real device node, we'll use if
1121 * /sys/class/tty/tty0/active, but only if /sys/ is actually ours (i.e. not read-only-mounted which
1122 * is a sign for container setups). */
288a74cc 1123
dffbe1d1 1124 _cleanup_free_ char *chased = NULL;
39f46860 1125 r = chase("/dev/console", /* root= */ NULL, /* flags= */ 0, &chased, /* ret_fd= */ NULL);
dffbe1d1
LP
1126 if (r < 0)
1127 return r;
1128 if (!path_equal(chased, "/dev/console")) {
1129 *ret = TAKE_PTR(chased);
1130 return 0;
1131 }
1132
1133 r = path_is_read_only_fs("/sys");
1134 if (r < 0)
1135 return r;
1136 if (r > 0)
7b912648 1137 return -ENOMEDIUM;
288a74cc 1138
dffbe1d1 1139 _cleanup_free_ char *active = NULL;
7b912648
LP
1140 r = read_one_line_file("/sys/class/tty/console/active", &active);
1141 if (r < 0)
1142 return r;
288a74cc 1143
7b912648 1144 /* If multiple log outputs are configured the last one is what /dev/console points to */
dffbe1d1 1145 const char *tty = strrchr(active, ' ');
288a74cc
RC
1146 if (tty)
1147 tty++;
1148 else
7b912648 1149 tty = active;
288a74cc
RC
1150
1151 if (streq(tty, "tty0")) {
7b912648 1152 active = mfree(active);
288a74cc
RC
1153
1154 /* Get the active VC (e.g. tty1) */
7b912648
LP
1155 r = read_one_line_file("/sys/class/tty/tty0/active", &active);
1156 if (r < 0)
1157 return r;
1158
1159 tty = active;
1160 }
1161
c7f7d87d
DDM
1162 _cleanup_free_ char *path = NULL;
1163 path = path_join("/dev", tty);
1164 if (!path)
1165 return -ENOMEM;
288a74cc 1166
c7f7d87d 1167 *ret = TAKE_PTR(path);
7b912648 1168 return 0;
288a74cc
RC
1169}
1170
bef41af2
LP
1171int get_kernel_consoles(char ***ret) {
1172 _cleanup_strv_free_ char **l = NULL;
6af62124 1173 _cleanup_free_ char *line = NULL;
6af62124
WF
1174 int r;
1175
bef41af2
LP
1176 assert(ret);
1177
daf13202
MY
1178 /* If /sys/ is mounted read-only this means we are running in some kind of container environment.
1179 * In that case /sys/ would reflect the host system, not us, hence ignore the data we can read from it. */
bef41af2
LP
1180 if (path_is_read_only_fs("/sys") > 0)
1181 goto fallback;
6af62124
WF
1182
1183 r = read_one_line_file("/sys/class/tty/console/active", &line);
1184 if (r < 0)
1185 return r;
1186
daf13202 1187 for (const char *p = line;;) {
6abdec98 1188 _cleanup_free_ char *tty = NULL, *path = NULL;
6af62124 1189
bef41af2 1190 r = extract_first_word(&p, &tty, NULL, 0);
6af62124
WF
1191 if (r < 0)
1192 return r;
1193 if (r == 0)
1194 break;
1195
1196 if (streq(tty, "tty0")) {
1197 tty = mfree(tty);
1198 r = read_one_line_file("/sys/class/tty/tty0/active", &tty);
1199 if (r < 0)
1200 return r;
1201 }
1202
6abdec98 1203 path = path_join("/dev", tty);
6af62124
WF
1204 if (!path)
1205 return -ENOMEM;
1206
1207 if (access(path, F_OK) < 0) {
1208 log_debug_errno(errno, "Console device %s is not accessible, skipping: %m", path);
6af62124
WF
1209 continue;
1210 }
1211
6abdec98 1212 r = strv_consume(&l, TAKE_PTR(path));
6af62124
WF
1213 if (r < 0)
1214 return r;
1215 }
1216
bef41af2 1217 if (strv_isempty(l)) {
6af62124 1218 log_debug("No devices found for system console");
bef41af2 1219 goto fallback;
6af62124
WF
1220 }
1221
ae2a15bc 1222 *ret = TAKE_PTR(l);
daf13202 1223 return strv_length(*ret);
bef41af2
LP
1224
1225fallback:
1226 r = strv_extend(&l, "/dev/console");
1227 if (r < 0)
1228 return r;
1229
ae2a15bc 1230 *ret = TAKE_PTR(l);
6af62124
WF
1231 return 0;
1232}
1233
288a74cc 1234bool tty_is_vc_resolve(const char *tty) {
7b912648 1235 _cleanup_free_ char *resolved = NULL;
288a74cc
RC
1236
1237 assert(tty);
1238
c7f7d87d 1239 if (streq(skip_dev_prefix(tty), "console")) {
7b912648 1240 if (resolve_dev_console(&resolved) < 0)
288a74cc 1241 return false;
7b912648
LP
1242
1243 tty = resolved;
288a74cc
RC
1244 }
1245
1246 return tty_is_vc(tty);
1247}
1248
288a74cc
RC
1249int fd_columns(int fd) {
1250 struct winsize ws = {};
1251
14f594b9
LP
1252 if (fd < 0)
1253 return -EBADF;
1254
288a74cc
RC
1255 if (ioctl(fd, TIOCGWINSZ, &ws) < 0)
1256 return -errno;
1257
1258 if (ws.ws_col <= 0)
0ea4198f 1259 return -ENODATA; /* some tty types come up with invalid row/column initially, return a recognizable error for that */
288a74cc
RC
1260
1261 return ws.ws_col;
1262}
1263
e8139b15
LP
1264int getenv_columns(void) {
1265 int r;
1266
1267 const char *e = getenv("COLUMNS");
1268 if (!e)
1269 return -ENXIO;
1270
1271 unsigned c;
1272 r = safe_atou_bounded(e, 1, USHRT_MAX, &c);
1273 if (r < 0)
1274 return r;
1275
1276 return (int) c;
1277}
1278
288a74cc 1279unsigned columns(void) {
288a74cc 1280
c6063244 1281 if (cached_columns > 0)
288a74cc
RC
1282 return cached_columns;
1283
e8139b15
LP
1284 int c = getenv_columns();
1285 if (c < 0) {
288a74cc 1286 c = fd_columns(STDOUT_FILENO);
e8139b15 1287 if (c < 0)
d09a7135
LP
1288 c = 80;
1289 }
288a74cc 1290
e8139b15
LP
1291 assert(c > 0);
1292
288a74cc
RC
1293 cached_columns = c;
1294 return cached_columns;
1295}
1296
1297int fd_lines(int fd) {
1298 struct winsize ws = {};
1299
14f594b9
LP
1300 if (fd < 0)
1301 return -EBADF;
1302
288a74cc
RC
1303 if (ioctl(fd, TIOCGWINSZ, &ws) < 0)
1304 return -errno;
1305
1306 if (ws.ws_row <= 0)
0ea4198f 1307 return -ENODATA; /* some tty types come up with invalid row/column initially, return a recognizable error for that */
288a74cc
RC
1308
1309 return ws.ws_row;
1310}
1311
1312unsigned lines(void) {
1313 const char *e;
1314 int l;
1315
c6063244 1316 if (cached_lines > 0)
288a74cc
RC
1317 return cached_lines;
1318
1319 l = 0;
1320 e = getenv("LINES");
1321 if (e)
1322 (void) safe_atoi(e, &l);
1323
d09a7135 1324 if (l <= 0 || l > USHRT_MAX) {
288a74cc 1325 l = fd_lines(STDOUT_FILENO);
d09a7135
LP
1326 if (l <= 0)
1327 l = 24;
1328 }
288a74cc
RC
1329
1330 cached_lines = l;
1331 return cached_lines;
1332}
1333
51462135
DDM
1334int terminal_set_size_fd(int fd, const char *ident, unsigned rows, unsigned cols) {
1335 struct winsize ws;
1336
061b4458
LP
1337 assert(fd >= 0);
1338
1339 if (!ident)
1340 ident = "TTY";
1341
51462135
DDM
1342 if (rows == UINT_MAX && cols == UINT_MAX)
1343 return 0;
1344
1345 if (ioctl(fd, TIOCGWINSZ, &ws) < 0)
1346 return log_debug_errno(errno,
1347 "TIOCGWINSZ ioctl for getting %s size failed, not setting terminal size: %m",
061b4458 1348 ident);
51462135
DDM
1349
1350 if (rows == UINT_MAX)
1351 rows = ws.ws_row;
1352 else if (rows > USHRT_MAX)
1353 rows = USHRT_MAX;
1354
1355 if (cols == UINT_MAX)
1356 cols = ws.ws_col;
1357 else if (cols > USHRT_MAX)
1358 cols = USHRT_MAX;
1359
1360 if (rows == ws.ws_row && cols == ws.ws_col)
1361 return 0;
1362
1363 ws.ws_row = rows;
1364 ws.ws_col = cols;
1365
1366 if (ioctl(fd, TIOCSWINSZ, &ws) < 0)
061b4458 1367 return log_debug_errno(errno, "TIOCSWINSZ ioctl for setting %s size failed: %m", ident);
51462135
DDM
1368
1369 return 0;
1370}
1371
29f5a5ae
DDM
1372int proc_cmdline_tty_size(const char *tty, unsigned *ret_rows, unsigned *ret_cols) {
1373 _cleanup_free_ char *rowskey = NULL, *rowsvalue = NULL, *colskey = NULL, *colsvalue = NULL;
1374 unsigned rows = UINT_MAX, cols = UINT_MAX;
1375 int r;
1376
1377 assert(tty);
1378
1379 if (!ret_rows && !ret_cols)
1380 return 0;
1381
1382 tty = skip_dev_prefix(tty);
7036dd8b
MY
1383 if (path_startswith(tty, "pts/"))
1384 return -EMEDIUMTYPE;
29f5a5ae 1385 if (!in_charset(tty, ALPHANUMERICAL))
7036dd8b
MY
1386 return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
1387 "TTY name '%s' contains non-alphanumeric characters, not searching kernel cmdline for size.", tty);
29f5a5ae
DDM
1388
1389 rowskey = strjoin("systemd.tty.rows.", tty);
1390 if (!rowskey)
1391 return -ENOMEM;
1392
1393 colskey = strjoin("systemd.tty.columns.", tty);
1394 if (!colskey)
1395 return -ENOMEM;
1396
1397 r = proc_cmdline_get_key_many(/* flags = */ 0,
1398 rowskey, &rowsvalue,
1399 colskey, &colsvalue);
1400 if (r < 0)
1401 return log_debug_errno(r, "Failed to read TTY size of %s from kernel cmdline: %m", tty);
1402
1403 if (rowsvalue) {
1404 r = safe_atou(rowsvalue, &rows);
1405 if (r < 0)
1406 return log_debug_errno(r, "Failed to parse %s=%s: %m", rowskey, rowsvalue);
1407 }
1408
1409 if (colsvalue) {
1410 r = safe_atou(colsvalue, &cols);
1411 if (r < 0)
1412 return log_debug_errno(r, "Failed to parse %s=%s: %m", colskey, colsvalue);
1413 }
1414
1415 if (ret_rows)
1416 *ret_rows = rows;
1417 if (ret_cols)
1418 *ret_cols = cols;
1419
f6927e3c 1420 return rows != UINT_MAX || cols != UINT_MAX;
29f5a5ae
DDM
1421}
1422
288a74cc
RC
1423/* intended to be used as a SIGWINCH sighandler */
1424void columns_lines_cache_reset(int signum) {
1425 cached_columns = 0;
1426 cached_lines = 0;
1427}
1428
c6063244
LP
1429void reset_terminal_feature_caches(void) {
1430 cached_columns = 0;
1431 cached_lines = 0;
1432
c6063244 1433 cached_on_tty = -1;
197dd3a9 1434 cached_on_dev_null = -1;
7227d5bf
DDM
1435
1436 reset_ansi_feature_caches();
c6063244 1437}
288a74cc 1438
c6063244 1439bool on_tty(void) {
8cd0356e
LP
1440
1441 /* We check both stdout and stderr, so that situations where pipes on the shell are used are reliably
1442 * recognized, regardless if only the output or the errors are piped to some place. Since on_tty() is generally
1443 * used to default to a safer, non-interactive, non-color mode of operation it's probably good to be defensive
1444 * here, and check for both. Note that we don't check for STDIN_FILENO, because it should fine to use fancy
1445 * terminal functionality when outputting stuff, even if the input is piped to us. */
1446
c6063244 1447 if (cached_on_tty < 0)
8cd0356e 1448 cached_on_tty =
300b7e76
LP
1449 isatty_safe(STDOUT_FILENO) &&
1450 isatty_safe(STDERR_FILENO);
288a74cc
RC
1451
1452 return cached_on_tty;
1453}
1454
288a74cc 1455int getttyname_malloc(int fd, char **ret) {
454318d3 1456 char path[PATH_MAX]; /* PATH_MAX is counted *with* the trailing NUL byte */
288a74cc
RC
1457 int r;
1458
1459 assert(fd >= 0);
1460 assert(ret);
1461
30222f4b
ZJS
1462 r = ttyname_r(fd, path, sizeof path); /* positive error */
1463 assert(r >= 0);
1464 if (r == ERANGE)
1465 return -ENAMETOOLONG;
1466 if (r > 0)
1467 return -r;
288a74cc 1468
454318d3 1469 return strdup_to(ret, skip_dev_prefix(path));
288a74cc
RC
1470}
1471
f171decd
LP
1472int getttyname_harder(int fd, char **ret) {
1473 _cleanup_free_ char *s = NULL;
1474 int r;
288a74cc 1475
f171decd
LP
1476 r = getttyname_malloc(fd, &s);
1477 if (r < 0)
1478 return r;
288a74cc 1479
f171decd
LP
1480 if (streq(s, "tty"))
1481 return get_ctty(0, NULL, ret);
288a74cc 1482
f171decd 1483 *ret = TAKE_PTR(s);
288a74cc
RC
1484 return 0;
1485}
1486
ac508b11 1487int get_ctty_devnr(pid_t pid, dev_t *ret) {
288a74cc 1488 _cleanup_free_ char *line = NULL;
288a74cc 1489 unsigned long ttynr;
ac508b11
LP
1490 const char *p;
1491 int r;
288a74cc
RC
1492
1493 assert(pid >= 0);
1494
1495 p = procfs_file_alloca(pid, "stat");
1496 r = read_one_line_file(p, &line);
1497 if (r < 0)
1498 return r;
1499
1500 p = strrchr(line, ')');
1501 if (!p)
1502 return -EIO;
1503
1504 p++;
1505
1506 if (sscanf(p, " "
1507 "%*c " /* state */
1508 "%*d " /* ppid */
1509 "%*d " /* pgrp */
1510 "%*d " /* session */
1511 "%lu ", /* ttynr */
1512 &ttynr) != 1)
1513 return -EIO;
1514
d80e2a1e 1515 if (devnum_is_zero(ttynr))
cfeaa44a 1516 return -ENXIO;
288a74cc 1517
ac508b11
LP
1518 if (ret)
1519 *ret = (dev_t) ttynr;
288a74cc
RC
1520
1521 return 0;
1522}
1523
54b22b26 1524int get_ctty(pid_t pid, dev_t *ret_devnr, char **ret) {
11f3c130
LP
1525 char pty[STRLEN("/dev/pts/") + DECIMAL_STR_MAX(dev_t) + 1];
1526 _cleanup_free_ char *buf = NULL;
1527 const char *fn = NULL, *w;
288a74cc 1528 dev_t devnr;
54b22b26 1529 int r;
288a74cc 1530
54b22b26
LP
1531 r = get_ctty_devnr(pid, &devnr);
1532 if (r < 0)
1533 return r;
288a74cc 1534
11f3c130 1535 r = device_path_make_canonical(S_IFCHR, devnr, &buf);
54b22b26 1536 if (r < 0) {
11f3c130
LP
1537 struct stat st;
1538
54b22b26
LP
1539 if (r != -ENOENT) /* No symlink for this in /dev/char/? */
1540 return r;
288a74cc 1541
11f3c130
LP
1542 /* Maybe this is PTY? PTY devices are not listed in /dev/char/, as they don't follow the
1543 * Linux device model and hence device_path_make_canonical() doesn't work for them. Let's
1544 * assume this is a PTY for a moment, and check if the device node this would then map to in
1545 * /dev/pts/ matches the one we are looking for. This way we don't have to hardcode the major
1546 * number (which is 136 btw), but we still rely on the fact that PTY numbers map directly to
1547 * the minor number of the pty. */
1548 xsprintf(pty, "/dev/pts/%u", minor(devnr));
1549
1550 if (stat(pty, &st) < 0) {
1551 if (errno != ENOENT)
1552 return -errno;
54b22b26 1553
11f3c130
LP
1554 } else if (S_ISCHR(st.st_mode) && devnr == st.st_rdev) /* Bingo! */
1555 fn = pty;
288a74cc 1556
11f3c130
LP
1557 if (!fn) {
1558 /* Doesn't exist, or not a PTY? Probably something similar to the PTYs which have no
1559 * symlink in /dev/char/. Let's return something vaguely useful. */
1560 r = device_path_make_major_minor(S_IFCHR, devnr, &buf);
54b22b26
LP
1561 if (r < 0)
1562 return r;
11f3c130
LP
1563
1564 fn = buf;
288a74cc 1565 }
11f3c130
LP
1566 } else
1567 fn = buf;
288a74cc 1568
11f3c130
LP
1569 w = path_startswith(fn, "/dev/");
1570 if (!w)
1571 return -EINVAL;
54b22b26 1572
11f3c130 1573 if (ret) {
4f77ddca
ZJS
1574 r = strdup_to(ret, w);
1575 if (r < 0)
1576 return r;
11f3c130 1577 }
54b22b26
LP
1578
1579 if (ret_devnr)
1580 *ret_devnr = devnr;
288a74cc
RC
1581
1582 return 0;
1583}
a07c35c3 1584
66cb2fde 1585int ptsname_malloc(int fd, char **ret) {
66cb2fde
LP
1586 assert(fd >= 0);
1587 assert(ret);
1588
deedd5c2
MY
1589 for (size_t l = 50;;) {
1590 _cleanup_free_ char *c = NULL;
66cb2fde
LP
1591
1592 c = new(char, l);
1593 if (!c)
1594 return -ENOMEM;
1595
deedd5c2
MY
1596 if (ptsname_r(fd, c, l) >= 0) {
1597 *ret = TAKE_PTR(c);
66cb2fde
LP
1598 return 0;
1599 }
deedd5c2 1600 if (errno != ERANGE)
66cb2fde 1601 return -errno;
1fd4c4ed 1602
deedd5c2 1603 if (!MUL_ASSIGN_SAFE(&l, 2))
1fd4c4ed 1604 return -ENOMEM;
66cb2fde
LP
1605 }
1606}
1607
fbd2679f 1608int openpt_allocate(int flags, char **ret_peer_path) {
254d1313 1609 _cleanup_close_ int fd = -EBADF;
ae1d13db
FB
1610 int r;
1611
1612 fd = posix_openpt(flags|O_NOCTTY|O_CLOEXEC);
1613 if (fd < 0)
1614 return -errno;
1615
fbd2679f
LP
1616 _cleanup_free_ char *p = NULL;
1617 if (ret_peer_path) {
ae1d13db
FB
1618 r = ptsname_malloc(fd, &p);
1619 if (r < 0)
1620 return r;
1621
1622 if (!path_startswith(p, "/dev/pts/"))
1623 return -EINVAL;
1624 }
1625
1626 if (unlockpt(fd) < 0)
1627 return -errno;
1628
fbd2679f
LP
1629 if (ret_peer_path)
1630 *ret_peer_path = TAKE_PTR(p);
ae1d13db
FB
1631
1632 return TAKE_FD(fd);
1633}
1634
1635static int ptsname_namespace(int pty, char **ret) {
fbd2679f
LP
1636 int no = -1;
1637
1638 assert(pty >= 0);
1639 assert(ret);
a07c35c3
LP
1640
1641 /* Like ptsname(), but doesn't assume that the path is
1642 * accessible in the local namespace. */
1643
fbd2679f 1644 if (ioctl(pty, TIOCGPTN, &no) < 0)
a07c35c3
LP
1645 return -errno;
1646
1647 if (no < 0)
1648 return -EIO;
1649
1650 if (asprintf(ret, "/dev/pts/%i", no) < 0)
1651 return -ENOMEM;
1652
1653 return 0;
1654}
66cb2fde 1655
b25430de
LP
1656int openpt_allocate_in_namespace(
1657 const PidRef *pidref,
1658 int flags,
1659 char **ret_peer_path) {
1660
254d1313 1661 _cleanup_close_ int pidnsfd = -EBADF, mntnsfd = -EBADF, usernsfd = -EBADF, rootfd = -EBADF, fd = -EBADF;
71136404 1662 _cleanup_close_pair_ int pair[2] = EBADF_PAIR;
66cb2fde
LP
1663 int r;
1664
b25430de 1665 r = pidref_namespace_open(pidref, &pidnsfd, &mntnsfd, /* ret_netns_fd = */ NULL, &usernsfd, &rootfd);
66cb2fde
LP
1666 if (r < 0)
1667 return r;
1668
fbd2679f 1669 if (socketpair(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0, pair) < 0)
66cb2fde
LP
1670 return -errno;
1671
fbd2679f
LP
1672 r = namespace_fork(
1673 "(sd-openptns)",
1674 "(sd-openpt)",
1675 /* except_fds= */ NULL,
1676 /* n_except_fds= */ 0,
1677 FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGKILL|FORK_WAIT,
1678 pidnsfd,
1679 mntnsfd,
1680 /* netns_fd= */ -EBADF,
1681 usernsfd,
1682 rootfd,
1683 /* ret_pid= */ NULL);
4c253ed1
LP
1684 if (r < 0)
1685 return r;
1686 if (r == 0) {
66cb2fde
LP
1687 pair[0] = safe_close(pair[0]);
1688
fbd2679f 1689 fd = openpt_allocate(flags, /* ret_peer_path= */ NULL);
ae1d13db 1690 if (fd < 0)
66cb2fde
LP
1691 _exit(EXIT_FAILURE);
1692
ae1d13db 1693 if (send_one_fd(pair[1], fd, 0) < 0)
66cb2fde
LP
1694 _exit(EXIT_FAILURE);
1695
1696 _exit(EXIT_SUCCESS);
1697 }
1698
1699 pair[1] = safe_close(pair[1]);
1700
ae1d13db
FB
1701 fd = receive_one_fd(pair[0], 0);
1702 if (fd < 0)
1703 return fd;
1704
fbd2679f
LP
1705 if (ret_peer_path) {
1706 r = ptsname_namespace(fd, ret_peer_path);
ae1d13db
FB
1707 if (r < 0)
1708 return r;
1709 }
1710
1711 return TAKE_FD(fd);
66cb2fde 1712}
40e1f4ea 1713
197dd3a9
DDM
1714static bool on_dev_null(void) {
1715 struct stat dst, ost, est;
1716
1717 if (cached_on_dev_null >= 0)
1718 return cached_on_dev_null;
1719
1720 if (stat("/dev/null", &dst) < 0 || fstat(STDOUT_FILENO, &ost) < 0 || fstat(STDERR_FILENO, &est) < 0)
1721 cached_on_dev_null = false;
1722 else
1723 cached_on_dev_null = stat_inode_same(&dst, &ost) && stat_inode_same(&dst, &est);
1724
1725 return cached_on_dev_null;
1726}
1727
1b889631 1728bool getenv_terminal_is_dumb(void) {
ac96418b
LP
1729 const char *e;
1730
ac96418b
LP
1731 e = getenv("TERM");
1732 if (!e)
1733 return true;
1734
1735 return streq(e, "dumb");
1736}
1737
158fbf76 1738bool terminal_is_dumb(void) {
197dd3a9 1739 if (!on_tty() && !on_dev_null())
158fbf76
ZJS
1740 return true;
1741
1742 return getenv_terminal_is_dumb();
1743}
1744
c2b32159
LP
1745bool dev_console_colors_enabled(void) {
1746 _cleanup_free_ char *s = NULL;
c4fea19a 1747 ColorMode m;
c2b32159
LP
1748
1749 /* Returns true if we assume that color is supported on /dev/console.
1750 *
1751 * For that we first check if we explicitly got told to use colors or not, by checking $SYSTEMD_COLORS. If that
f95dbcc2
ZJS
1752 * isn't set we check whether PID 1 has $TERM set, and if not, whether TERM is set on the kernel command
1753 * line. If we find $TERM set we assume color if it's not set to "dumb", similarly to how regular
c2b32159
LP
1754 * colors_enabled() operates. */
1755
c4fea19a
MGR
1756 m = parse_systemd_colors();
1757 if (m >= 0)
1758 return m;
c2b32159 1759
c484315b
ZJS
1760 if (getenv("NO_COLOR"))
1761 return false;
1762
c2b32159
LP
1763 if (getenv_for_pid(1, "TERM", &s) <= 0)
1764 (void) proc_cmdline_get_key("TERM", 0, &s);
1765
1766 return !streq_ptr(s, "dumb");
1767}
1768
6179ede1 1769int vt_restore(int fd) {
d3f818fe 1770
6179ede1
FB
1771 static const struct vt_mode mode = {
1772 .mode = VT_AUTO,
1773 };
d3f818fe
MY
1774
1775 int r, ret = 0;
1776
1777 assert(fd >= 0);
6179ede1 1778
dd9c8da8 1779 if (!isatty_safe(fd))
aae47bf7 1780 return log_debug_errno(SYNTHETIC_ERRNO(ENOTTY), "Asked to restore the VT for an fd that does not refer to a terminal: %m");
e60a4a3c 1781
1802d5f2 1782 if (ioctl(fd, KDSETMODE, KD_TEXT) < 0)
d3f818fe 1783 RET_GATHER(ret, log_debug_errno(errno, "Failed to set VT to text mode, ignoring: %m"));
6179ede1
FB
1784
1785 r = vt_reset_keyboard(fd);
d3f818fe
MY
1786 if (r < 0)
1787 RET_GATHER(ret, log_debug_errno(r, "Failed to reset keyboard mode, ignoring: %m"));
6179ede1 1788
d3f818fe
MY
1789 if (ioctl(fd, VT_SETMODE, &mode) < 0)
1790 RET_GATHER(ret, log_debug_errno(errno, "Failed to set VT_AUTO mode, ignoring: %m"));
6179ede1 1791
f5fbe71d 1792 r = fchmod_and_chown(fd, TTY_MODE, 0, GID_INVALID);
d3f818fe
MY
1793 if (r < 0)
1794 RET_GATHER(ret, log_debug_errno(r, "Failed to chmod()/chown() VT, ignoring: %m"));
6179ede1 1795
d3f818fe 1796 return ret;
6179ede1 1797}
27dafac9
FB
1798
1799int vt_release(int fd, bool restore) {
1800 assert(fd >= 0);
1801
1802 /* This function releases the VT by acknowledging the VT-switch signal
1803 * sent by the kernel and optionally reset the VT in text and auto
1804 * VT-switching modes. */
1805
dd9c8da8 1806 if (!isatty_safe(fd))
aae47bf7 1807 return log_debug_errno(SYNTHETIC_ERRNO(ENOTTY), "Asked to release the VT for an fd that does not refer to a terminal: %m");
e60a4a3c 1808
27dafac9
FB
1809 if (ioctl(fd, VT_RELDISP, 1) < 0)
1810 return -errno;
1811
1812 if (restore)
1813 return vt_restore(fd);
1814
1815 return 0;
1816}
37b8d2f6
ZJS
1817
1818void get_log_colors(int priority, const char **on, const char **off, const char **highlight) {
1819 /* Note that this will initialize output variables only when there's something to output.
5e2b0e1c 1820 * The caller must pre-initialize to "" or NULL as appropriate. */
37b8d2f6
ZJS
1821
1822 if (priority <= LOG_ERR) {
1823 if (on)
25e4608b 1824 *on = ansi_highlight_red();
37b8d2f6 1825 if (off)
bb146d23 1826 *off = ansi_normal();
37b8d2f6 1827 if (highlight)
bb146d23 1828 *highlight = ansi_highlight();
37b8d2f6 1829
0d0464d3
ZJS
1830 } else if (priority <= LOG_WARNING) {
1831 if (on)
25e4608b 1832 *on = ansi_highlight_yellow();
0d0464d3 1833 if (off)
bb146d23 1834 *off = ansi_normal();
0d0464d3 1835 if (highlight)
bb146d23 1836 *highlight = ansi_highlight();
0d0464d3 1837
37b8d2f6
ZJS
1838 } else if (priority <= LOG_NOTICE) {
1839 if (on)
bb146d23 1840 *on = ansi_highlight();
37b8d2f6 1841 if (off)
bb146d23 1842 *off = ansi_normal();
37b8d2f6 1843 if (highlight)
25e4608b 1844 *highlight = ansi_highlight_red();
37b8d2f6
ZJS
1845
1846 } else if (priority >= LOG_DEBUG) {
1847 if (on)
25e4608b 1848 *on = ansi_grey();
37b8d2f6 1849 if (off)
bb146d23 1850 *off = ansi_normal();
37b8d2f6 1851 if (highlight)
25e4608b 1852 *highlight = ansi_highlight_red();
37b8d2f6
ZJS
1853 }
1854}
fc7eb132 1855
53f0ab51 1856int terminal_set_cursor_position(int fd, unsigned row, unsigned column) {
fc7eb132
OJ
1857 assert(fd >= 0);
1858
53f0ab51 1859 char cursor_position[STRLEN("\x1B[" ";" "H") + DECIMAL_STR_MAX(unsigned) * 2 + 1];
fc7eb132
OJ
1860 xsprintf(cursor_position, "\x1B[%u;%uH", row, column);
1861
53f0ab51 1862 return loop_write(fd, cursor_position, SIZE_MAX);
fc7eb132 1863}
d02d4f83 1864
9ab703d8 1865int terminal_reset_defensive(int fd, TerminalResetFlags flags) {
cfac0908
LP
1866 int r = 0;
1867
1868 assert(fd >= 0);
5b3eaf9e 1869 assert(!FLAGS_SET(flags, TERMINAL_RESET_AVOID_ANSI_SEQ|TERMINAL_RESET_FORCE_ANSI_SEQ));
cfac0908 1870
5b3eaf9e
LP
1871 /* Resets the terminal comprehensively, i.e. via both ioctl()s and via ANSI sequences, but do so only
1872 * if $TERM is unset or set to "dumb" */
cfac0908 1873
e2216800
LP
1874 if (!isatty_safe(fd))
1875 return -ENOTTY;
1876
9ab703d8 1877 RET_GATHER(r, terminal_reset_ioctl(fd, FLAGS_SET(flags, TERMINAL_RESET_SWITCH_TO_TEXT)));
cfac0908 1878
5b3eaf9e
LP
1879 if (!FLAGS_SET(flags, TERMINAL_RESET_AVOID_ANSI_SEQ) &&
1880 (FLAGS_SET(flags, TERMINAL_RESET_FORCE_ANSI_SEQ) || !getenv_terminal_is_dumb()))
cfac0908
LP
1881 RET_GATHER(r, terminal_reset_ansi_seq(fd));
1882
1883 return r;
1884}
1885
9ab703d8 1886int terminal_reset_defensive_locked(int fd, TerminalResetFlags flags) {
2cd19499
LP
1887 assert(fd >= 0);
1888
1889 _cleanup_close_ int lock_fd = lock_dev_console();
1890 if (lock_fd < 0)
1891 log_debug_errno(lock_fd, "Failed to acquire lock for /dev/console, ignoring: %m");
1892
9ab703d8 1893 return terminal_reset_defensive(fd, flags);
2cd19499
LP
1894}
1895
d02d4f83
LP
1896void termios_disable_echo(struct termios *termios) {
1897 assert(termios);
1898
1899 termios->c_lflag &= ~(ICANON|ECHO);
1900 termios->c_cc[VMIN] = 1;
1901 termios->c_cc[VTIME] = 0;
1902}
63e9c383 1903
445e5738
LP
1904static int terminal_verify_same(int input_fd, int output_fd) {
1905 assert(input_fd >= 0);
1906 assert(output_fd >= 0);
1907
1908 /* Validates that the specified fds reference the same TTY */
1909
1910 if (input_fd != output_fd) {
1911 struct stat sti;
1912 if (fstat(input_fd, &sti) < 0)
1913 return -errno;
1914
1915 if (!S_ISCHR(sti.st_mode)) /* TTYs are character devices */
1916 return -ENOTTY;
1917
1918 struct stat sto;
1919 if (fstat(output_fd, &sto) < 0)
1920 return -errno;
1921
1922 if (!S_ISCHR(sto.st_mode))
1923 return -ENOTTY;
1924
1925 if (sti.st_rdev != sto.st_rdev)
1926 return -ENOLINK;
1927 }
1928
1929 if (!isatty_safe(input_fd)) /* The check above was just for char device, but now let's ensure it's actually a tty */
1930 return -ENOTTY;
1931
1932 return 0;
1933}
1934
63e9c383
LP
1935typedef enum BackgroundColorState {
1936 BACKGROUND_TEXT,
1937 BACKGROUND_ESCAPE,
1938 BACKGROUND_BRACKET,
1939 BACKGROUND_FIRST_ONE,
1940 BACKGROUND_SECOND_ONE,
1941 BACKGROUND_SEMICOLON,
1942 BACKGROUND_R,
1943 BACKGROUND_G,
1944 BACKGROUND_B,
1945 BACKGROUND_RED,
1946 BACKGROUND_GREEN,
1947 BACKGROUND_BLUE,
73a72e3a 1948 BACKGROUND_STRING_TERMINATOR,
63e9c383
LP
1949} BackgroundColorState;
1950
1951typedef struct BackgroundColorContext {
1952 BackgroundColorState state;
1953 uint32_t red, green, blue;
1954 unsigned red_bits, green_bits, blue_bits;
1955} BackgroundColorContext;
1956
1957static int scan_background_color_response(
1958 BackgroundColorContext *context,
1959 const char *buf,
1df569b2
LP
1960 size_t size,
1961 size_t *ret_processed) {
63e9c383
LP
1962
1963 assert(context);
f4bdf373
ZJS
1964 assert(buf);
1965 assert(ret_processed);
63e9c383
LP
1966
1967 for (size_t i = 0; i < size; i++) {
1968 char c = buf[i];
1969
1970 switch (context->state) {
1971
1972 case BACKGROUND_TEXT:
1973 context->state = c == '\x1B' ? BACKGROUND_ESCAPE : BACKGROUND_TEXT;
1974 break;
1975
1976 case BACKGROUND_ESCAPE:
1977 context->state = c == ']' ? BACKGROUND_BRACKET : BACKGROUND_TEXT;
1978 break;
1979
1980 case BACKGROUND_BRACKET:
1981 context->state = c == '1' ? BACKGROUND_FIRST_ONE : BACKGROUND_TEXT;
1982 break;
1983
1984 case BACKGROUND_FIRST_ONE:
1985 context->state = c == '1' ? BACKGROUND_SECOND_ONE : BACKGROUND_TEXT;
1986 break;
1987
1988 case BACKGROUND_SECOND_ONE:
1989 context->state = c == ';' ? BACKGROUND_SEMICOLON : BACKGROUND_TEXT;
1990 break;
1991
1992 case BACKGROUND_SEMICOLON:
1993 context->state = c == 'r' ? BACKGROUND_R : BACKGROUND_TEXT;
1994 break;
1995
1996 case BACKGROUND_R:
1997 context->state = c == 'g' ? BACKGROUND_G : BACKGROUND_TEXT;
1998 break;
1999
2000 case BACKGROUND_G:
2001 context->state = c == 'b' ? BACKGROUND_B : BACKGROUND_TEXT;
2002 break;
2003
2004 case BACKGROUND_B:
2005 context->state = c == ':' ? BACKGROUND_RED : BACKGROUND_TEXT;
2006 break;
2007
2008 case BACKGROUND_RED:
2009 if (c == '/')
2010 context->state = context->red_bits > 0 ? BACKGROUND_GREEN : BACKGROUND_TEXT;
2011 else {
2012 int d = unhexchar(c);
2013 if (d < 0 || context->red_bits >= sizeof(context->red)*8)
2014 context->state = BACKGROUND_TEXT;
2015 else {
2016 context->red = (context->red << 4) | d;
2017 context->red_bits += 4;
2018 }
2019 }
2020 break;
2021
2022 case BACKGROUND_GREEN:
2023 if (c == '/')
2024 context->state = context->green_bits > 0 ? BACKGROUND_BLUE : BACKGROUND_TEXT;
2025 else {
2026 int d = unhexchar(c);
2027 if (d < 0 || context->green_bits >= sizeof(context->green)*8)
2028 context->state = BACKGROUND_TEXT;
2029 else {
2030 context->green = (context->green << 4) | d;
2031 context->green_bits += 4;
2032 }
2033 }
2034 break;
2035
2036 case BACKGROUND_BLUE:
2037 if (c == '\x07') {
1df569b2 2038 if (context->blue_bits > 0) {
f4bdf373 2039 *ret_processed = i + 1;
63e9c383 2040 return 1; /* success! */
1df569b2 2041 }
63e9c383
LP
2042
2043 context->state = BACKGROUND_TEXT;
73a72e3a
SL
2044 } else if (c == '\x1b')
2045 context->state = context->blue_bits > 0 ? BACKGROUND_STRING_TERMINATOR : BACKGROUND_TEXT;
2046 else {
63e9c383
LP
2047 int d = unhexchar(c);
2048 if (d < 0 || context->blue_bits >= sizeof(context->blue)*8)
2049 context->state = BACKGROUND_TEXT;
2050 else {
2051 context->blue = (context->blue << 4) | d;
2052 context->blue_bits += 4;
2053 }
2054 }
2055 break;
73a72e3a
SL
2056
2057 case BACKGROUND_STRING_TERMINATOR:
1df569b2 2058 if (c == '\\') {
f4bdf373 2059 *ret_processed = i + 1;
73a72e3a 2060 return 1; /* success! */
1df569b2 2061 }
73a72e3a
SL
2062
2063 context->state = c == ']' ? BACKGROUND_ESCAPE : BACKGROUND_TEXT;
2064 break;
2065
63e9c383
LP
2066 }
2067
2068 /* Reset any colors we might have picked up */
73a72e3a 2069 if (IN_SET(context->state, BACKGROUND_TEXT, BACKGROUND_ESCAPE)) {
63e9c383
LP
2070 /* reset color */
2071 context->red = context->green = context->blue = 0;
2072 context->red_bits = context->green_bits = context->blue_bits = 0;
2073 }
2074 }
2075
f4bdf373 2076 *ret_processed = size;
63e9c383
LP
2077 return 0; /* all good, but not enough data yet */
2078}
2079
2080int get_default_background_color(double *ret_red, double *ret_green, double *ret_blue) {
a0c314d6 2081 _cleanup_close_ int nonblock_input_fd = -EBADF;
63e9c383
LP
2082 int r;
2083
2084 assert(ret_red);
2085 assert(ret_green);
2086 assert(ret_blue);
2087
2088 if (!colors_enabled())
2089 return -EOPNOTSUPP;
2090
445e5738
LP
2091 r = terminal_verify_same(STDIN_FILENO, STDOUT_FILENO);
2092 if (r < 0)
2093 return r;
63e9c383
LP
2094
2095 if (streq_ptr(getenv("TERM"), "linux")) {
2096 /* Linux console is black */
2097 *ret_red = *ret_green = *ret_blue = 0.0;
2098 return 0;
2099 }
2100
2101 struct termios old_termios;
2102 if (tcgetattr(STDIN_FILENO, &old_termios) < 0)
2103 return -errno;
2104
2105 struct termios new_termios = old_termios;
2106 termios_disable_echo(&new_termios);
2107
f789b17e 2108 if (tcsetattr(STDIN_FILENO, TCSANOW, &new_termios) < 0)
63e9c383
LP
2109 return -errno;
2110
03674247 2111 r = loop_write(STDOUT_FILENO, ANSI_OSC "11;?" ANSI_ST, SIZE_MAX);
63e9c383
LP
2112 if (r < 0)
2113 goto finish;
2114
a0c314d6
LP
2115 /* Open a 2nd input fd, in non-blocking mode, so that we won't ever hang in read() should someone
2116 * else process the POLLIN. */
2117
13cb6641
MY
2118 nonblock_input_fd = r = fd_reopen(STDIN_FILENO, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
2119 if (r < 0)
2120 goto finish;
a0c314d6 2121
5321b957 2122 usec_t end = usec_add(now(CLOCK_MONOTONIC), CONSOLE_REPLY_WAIT_USEC);
03674247 2123 char buf[STRLEN(ANSI_OSC "11;rgb:0/0/0" ANSI_ST)]; /* shortest possible reply */
63e9c383
LP
2124 size_t buf_full = 0;
2125 BackgroundColorContext context = {};
2126
abe8e99e 2127 for (bool first = true;; first = false) {
1df569b2
LP
2128 if (buf_full == 0) {
2129 usec_t n = now(CLOCK_MONOTONIC);
1df569b2
LP
2130 if (n >= end) {
2131 r = -EOPNOTSUPP;
2132 goto finish;
2133 }
63e9c383 2134
a0c314d6 2135 r = fd_wait_for_event(nonblock_input_fd, POLLIN, usec_sub_unsigned(end, n));
1df569b2
LP
2136 if (r < 0)
2137 goto finish;
2138 if (r == 0) {
2139 r = -EOPNOTSUPP;
2140 goto finish;
2141 }
63e9c383 2142
abe8e99e
LP
2143 /* On the first try, read multiple characters, i.e. the shortest valid
2144 * reply. Afterwards read byte-wise, since we don't want to read too much, and
2145 * unnecessarily drop too many characters from the input queue. */
a0c314d6 2146 ssize_t l = read(nonblock_input_fd, buf, first ? sizeof(buf) : 1);
1df569b2 2147 if (l < 0) {
a0c314d6
LP
2148 if (errno == EAGAIN)
2149 continue;
1df569b2
LP
2150 r = -errno;
2151 goto finish;
2152 }
63e9c383 2153
1df569b2
LP
2154 assert((size_t) l <= sizeof(buf));
2155 buf_full = l;
2156 }
63e9c383 2157
1df569b2
LP
2158 size_t processed;
2159 r = scan_background_color_response(&context, buf, buf_full, &processed);
63e9c383
LP
2160 if (r < 0)
2161 goto finish;
1df569b2
LP
2162
2163 assert(processed <= buf_full);
2164 buf_full -= processed;
2165 memmove(buf, buf + processed, buf_full);
2166
63e9c383
LP
2167 if (r > 0) {
2168 assert(context.red_bits > 0);
2169 *ret_red = (double) context.red / ((UINT64_C(1) << context.red_bits) - 1);
2170 assert(context.green_bits > 0);
2171 *ret_green = (double) context.green / ((UINT64_C(1) << context.green_bits) - 1);
2172 assert(context.blue_bits > 0);
2173 *ret_blue = (double) context.blue / ((UINT64_C(1) << context.blue_bits) - 1);
2174 r = 0;
2175 goto finish;
2176 }
2177 }
2178
2179finish:
f789b17e 2180 RET_GATHER(r, RET_NERRNO(tcsetattr(STDIN_FILENO, TCSANOW, &old_termios)));
63e9c383
LP
2181 return r;
2182}
3390be38
LP
2183
2184typedef enum CursorPositionState {
2185 CURSOR_TEXT,
2186 CURSOR_ESCAPE,
2187 CURSOR_ROW,
2188 CURSOR_COLUMN,
2189} CursorPositionState;
2190
2191typedef struct CursorPositionContext {
2192 CursorPositionState state;
2193 unsigned row, column;
2194} CursorPositionContext;
2195
2196static int scan_cursor_position_response(
2197 CursorPositionContext *context,
2198 const char *buf,
2199 size_t size,
2200 size_t *ret_processed) {
2201
2202 assert(context);
f4bdf373
ZJS
2203 assert(buf);
2204 assert(ret_processed);
3390be38
LP
2205
2206 for (size_t i = 0; i < size; i++) {
2207 char c = buf[i];
2208
2209 switch (context->state) {
2210
2211 case CURSOR_TEXT:
2212 context->state = c == '\x1B' ? CURSOR_ESCAPE : CURSOR_TEXT;
2213 break;
2214
2215 case CURSOR_ESCAPE:
2216 context->state = c == '[' ? CURSOR_ROW : CURSOR_TEXT;
2217 break;
2218
2219 case CURSOR_ROW:
2220 if (c == ';')
2221 context->state = context->row > 0 ? CURSOR_COLUMN : CURSOR_TEXT;
2222 else {
2223 int d = undecchar(c);
2224
2225 /* We read a decimal character, let's suffix it to the number we so far read,
2226 * but let's do an overflow check first. */
2227 if (d < 0 || context->row > (UINT_MAX-d)/10)
2228 context->state = CURSOR_TEXT;
2229 else
2230 context->row = context->row * 10 + d;
2231 }
2232 break;
2233
2234 case CURSOR_COLUMN:
2235 if (c == 'R') {
2236 if (context->column > 0) {
f4bdf373 2237 *ret_processed = i + 1;
3390be38
LP
2238 return 1; /* success! */
2239 }
2240
2241 context->state = CURSOR_TEXT;
2242 } else {
2243 int d = undecchar(c);
2244
77c796d4 2245 /* As above, add the decimal character to our column number */
3390be38
LP
2246 if (d < 0 || context->column > (UINT_MAX-d)/10)
2247 context->state = CURSOR_TEXT;
2248 else
2249 context->column = context->column * 10 + d;
2250 }
2251
2252 break;
2253 }
2254
2255 /* Reset any positions we might have picked up */
2256 if (IN_SET(context->state, CURSOR_TEXT, CURSOR_ESCAPE))
2257 context->row = context->column = 0;
2258 }
2259
f4bdf373 2260 *ret_processed = size;
3390be38
LP
2261 return 0; /* all good, but not enough data yet */
2262}
2263
2264int terminal_get_size_by_dsr(
2265 int input_fd,
2266 int output_fd,
2267 unsigned *ret_rows,
2268 unsigned *ret_columns) {
2269
a0c314d6 2270 _cleanup_close_ int nonblock_input_fd = -EBADF;
13cb6641 2271 int r;
a0c314d6 2272
3390be38
LP
2273 assert(input_fd >= 0);
2274 assert(output_fd >= 0);
2275
3390be38
LP
2276 /* Tries to determine the terminal dimension by means of ANSI sequences rather than TIOCGWINSZ
2277 * ioctl(). Why bother with this? The ioctl() information is often incorrect on serial terminals
2278 * (since there's no handshake or protocol to determine the right dimensions in RS232), but since the
2279 * ANSI sequences are interpreted by the final terminal instead of an intermediary tty driver they
2280 * should be more accurate.
2281 *
2282 * Unfortunately there's no direct ANSI sequence to query terminal dimensions. But we can hack around
2283 * it: we position the cursor briefly at an absolute location very far down and very far to the
2284 * right, and then read back where we actually ended up. Because cursor locations are capped at the
2285 * terminal width/height we should then see the right values. In order to not risk integer overflows
2286 * in terminal applications we'll use INT16_MAX-1 as location to jump to — hopefully a value that is
2287 * large enough for any real-life terminals, but small enough to not overflow anything or be
2288 * recognized as a "niche" value. (Note that the dimension fields in "struct winsize" are 16bit only,
2289 * too). */
2290
2291 if (terminal_is_dumb())
2292 return -EOPNOTSUPP;
2293
2294 r = terminal_verify_same(input_fd, output_fd);
2295 if (r < 0)
2296 return log_debug_errno(r, "Called with distinct input/output fds: %m");
2297
2298 struct termios old_termios;
2299 if (tcgetattr(input_fd, &old_termios) < 0)
a4eb5094 2300 return log_debug_errno(errno, "Failed to get terminal settings: %m");
3390be38
LP
2301
2302 struct termios new_termios = old_termios;
2303 termios_disable_echo(&new_termios);
2304
f789b17e 2305 if (tcsetattr(input_fd, TCSANOW, &new_termios) < 0)
a4eb5094 2306 return log_debug_errno(errno, "Failed to set new terminal settings: %m");
3390be38
LP
2307
2308 unsigned saved_row = 0, saved_column = 0;
2309
2310 r = loop_write(output_fd,
2311 "\x1B[6n" /* Request cursor position (DSR/CPR) */
2312 "\x1B[32766;32766H" /* Position cursor really far to the right and to the bottom, but let's stay within the 16bit signed range */
2313 "\x1B[6n", /* Request cursor position again */
2314 SIZE_MAX);
2315 if (r < 0)
2316 goto finish;
2317
a0c314d6
LP
2318 /* Open a 2nd input fd, in non-blocking mode, so that we won't ever hang in read() should someone
2319 * else process the POLLIN. */
2320
13cb6641
MY
2321 nonblock_input_fd = r = fd_reopen(input_fd, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
2322 if (r < 0)
2323 goto finish;
a0c314d6 2324
5321b957 2325 usec_t end = usec_add(now(CLOCK_MONOTONIC), CONSOLE_REPLY_WAIT_USEC);
3390be38
LP
2326 char buf[STRLEN("\x1B[1;1R")]; /* The shortest valid reply possible */
2327 size_t buf_full = 0;
2328 CursorPositionContext context = {};
2329
2330 for (bool first = true;; first = false) {
2331 if (buf_full == 0) {
2332 usec_t n = now(CLOCK_MONOTONIC);
3390be38
LP
2333 if (n >= end) {
2334 r = -EOPNOTSUPP;
2335 goto finish;
2336 }
2337
a0c314d6 2338 r = fd_wait_for_event(nonblock_input_fd, POLLIN, usec_sub_unsigned(end, n));
3390be38
LP
2339 if (r < 0)
2340 goto finish;
2341 if (r == 0) {
2342 r = -EOPNOTSUPP;
2343 goto finish;
2344 }
2345
2346 /* On the first try, read multiple characters, i.e. the shortest valid
2347 * reply. Afterwards read byte-wise, since we don't want to read too much, and
2348 * unnecessarily drop too many characters from the input queue. */
a0c314d6 2349 ssize_t l = read(nonblock_input_fd, buf, first ? sizeof(buf) : 1);
3390be38 2350 if (l < 0) {
a0c314d6
LP
2351 if (errno == EAGAIN)
2352 continue;
2353
3390be38
LP
2354 r = -errno;
2355 goto finish;
2356 }
2357
2358 assert((size_t) l <= sizeof(buf));
2359 buf_full = l;
2360 }
2361
2362 size_t processed;
2363 r = scan_cursor_position_response(&context, buf, buf_full, &processed);
2364 if (r < 0)
2365 goto finish;
2366
2367 assert(processed <= buf_full);
2368 buf_full -= processed;
2369 memmove(buf, buf + processed, buf_full);
2370
2371 if (r > 0) {
2372 if (saved_row == 0) {
2373 assert(saved_column == 0);
2374
2375 /* First sequence, this is the cursor position before we set it somewhere
2376 * into the void at the bottom right. Let's save where we are so that we can
2377 * return later. */
2378
2379 /* Superficial validity checks */
2380 if (context.row <= 0 || context.column <= 0 || context.row >= 32766 || context.column >= 32766) {
2381 r = -ENODATA;
2382 goto finish;
2383 }
2384
2385 saved_row = context.row;
2386 saved_column = context.column;
2387
2388 /* Reset state */
2389 context = (CursorPositionContext) {};
2390 } else {
2391 /* Second sequence, this is the cursor position after we set it somewhere
2392 * into the void at the bottom right. */
2393
2394 /* Superficial validity checks (no particular reason to check for < 4, it's
2395 * just a way to look for unreasonably small values) */
2396 if (context.row < 4 || context.column < 4 || context.row >= 32766 || context.column >= 32766) {
2397 r = -ENODATA;
2398 goto finish;
2399 }
2400
2401 if (ret_rows)
2402 *ret_rows = context.row;
2403 if (ret_columns)
2404 *ret_columns = context.column;
2405
2406 r = 0;
2407 goto finish;
2408 }
2409 }
2410 }
2411
2412finish:
2413 /* Restore cursor position */
2414 if (saved_row > 0 && saved_column > 0)
2415 RET_GATHER(r, terminal_set_cursor_position(output_fd, saved_row, saved_column));
2416
f789b17e 2417 RET_GATHER(r, RET_NERRNO(tcsetattr(input_fd, TCSANOW, &old_termios)));
3390be38
LP
2418 return r;
2419}
63c631d7
LP
2420
2421int terminal_fix_size(int input_fd, int output_fd) {
2422 unsigned rows, columns;
2423 int r;
2424
2425 /* Tries to update the current terminal dimensions to the ones reported via ANSI sequences */
2426
2427 r = terminal_verify_same(input_fd, output_fd);
2428 if (r < 0)
2429 return r;
2430
2431 struct winsize ws = {};
2432 if (ioctl(output_fd, TIOCGWINSZ, &ws) < 0)
2433 return log_debug_errno(errno, "Failed to query terminal dimensions, ignoring: %m");
2434
2435 r = terminal_get_size_by_dsr(input_fd, output_fd, &rows, &columns);
2436 if (r < 0)
2437 return log_debug_errno(r, "Failed to acquire terminal dimensions via ANSI sequences, not adjusting terminal dimensions: %m");
2438
2439 if (ws.ws_row == rows && ws.ws_col == columns) {
2440 log_debug("Terminal dimensions reported via ANSI sequences match currently set terminal dimensions, not changing.");
2441 return 0;
2442 }
2443
2444 ws.ws_col = columns;
2445 ws.ws_row = rows;
2446
2447 if (ioctl(output_fd, TIOCSWINSZ, &ws) < 0)
2448 return log_debug_errno(errno, "Failed to update terminal dimensions, ignoring: %m");
2449
2450 log_debug("Fixed terminal dimensions to %ux%u based on ANSI sequence information.", columns, rows);
2451 return 1;
2452}
ce3a1593 2453
5321b957
ZJS
2454#define MAX_TERMINFO_LENGTH 64
2455/* python -c 'print("".join(hex(ord(i))[2:] for i in "name").upper())' */
2456#define DCS_TERMINFO_Q ANSI_DCS "+q" "6E616D65" ANSI_ST
2457/* The answer is either 0+r… (invalid) or 1+r… (OK). */
2458#define DCS_TERMINFO_R0 ANSI_DCS "0+r" ANSI_ST
2459#define DCS_TERMINFO_R1 ANSI_DCS "1+r" "6E616D65" "=" /* This is followed by Pt ST. */
2460assert_cc(STRLEN(DCS_TERMINFO_R0) <= STRLEN(DCS_TERMINFO_R1 ANSI_ST));
2461
2462static int scan_terminfo_response(
2463 const char *buf,
2464 size_t size,
2465 char **ret_name) {
2466 int r;
2467
2468 assert(buf);
2469 assert(ret_name);
2470
2471 /* Check if we have enough space for the shortest possible answer. */
2472 if (size < STRLEN(DCS_TERMINFO_R0))
2473 return -EAGAIN;
2474
2475 /* Check if the terminating sequence is present */
2476 if (memcmp(buf + size - STRLEN(ANSI_ST), ANSI_ST, STRLEN(ANSI_ST)) != 0)
2477 return -EAGAIN;
2478
2479 if (size <= STRLEN(DCS_TERMINFO_R1 ANSI_ST))
2480 return -EINVAL; /* The answer is invalid or empty */
2481
2482 if (memcmp(buf, DCS_TERMINFO_R1, STRLEN(DCS_TERMINFO_R1)) != 0)
2483 return -EINVAL; /* The answer is not valid */
2484
2485 _cleanup_free_ void *dec = NULL;
2486 size_t dec_size;
2487 r = unhexmem_full(buf + STRLEN(DCS_TERMINFO_R1), size - STRLEN(DCS_TERMINFO_R1 ANSI_ST),
2488 /* secure= */ false,
2489 &dec, &dec_size);
2490 if (r < 0)
2491 return r;
2492
2493 assert(((const char *) dec)[dec_size] == '\0'); /* unhexmem appends NUL for our convenience */
2494 if (memchr(dec, '\0', dec_size) || string_has_cc(dec, NULL) || !filename_is_valid(dec))
2495 return -EUCLEAN;
2496
2497 *ret_name = TAKE_PTR(dec);
2498 return 0;
2499}
2500
2501int terminal_get_terminfo_by_dcs(int fd, char **ret_name) {
2502 int r;
2503
2504 assert(fd >= 0);
2505 assert(ret_name);
2506
2507 /* Note: fd must be in non-blocking read-write mode! */
2508
2509 struct termios old_termios;
2510 if (tcgetattr(fd, &old_termios) < 0)
2511 return -errno;
2512
2513 struct termios new_termios = old_termios;
2514 termios_disable_echo(&new_termios);
2515
f789b17e 2516 if (tcsetattr(fd, TCSANOW, &new_termios) < 0)
5321b957
ZJS
2517 return -errno;
2518
2519 r = loop_write(fd, DCS_TERMINFO_Q, SIZE_MAX);
2520 if (r < 0)
2521 goto finish;
2522
2523 usec_t end = usec_add(now(CLOCK_MONOTONIC), CONSOLE_REPLY_WAIT_USEC);
2524 char buf[STRLEN(DCS_TERMINFO_R1) + MAX_TERMINFO_LENGTH + STRLEN(ANSI_ST)];
2525 size_t bytes = 0;
2526
2527 for (;;) {
2528 usec_t n = now(CLOCK_MONOTONIC);
2529 if (n >= end) {
2530 r = -EOPNOTSUPP;
2531 break;
2532 }
2533
2534 r = fd_wait_for_event(fd, POLLIN, usec_sub_unsigned(end, n));
2535 if (r < 0)
2536 break;
2537 if (r == 0) {
2538 r = -EOPNOTSUPP;
2539 break;
2540 }
2541
2542 /* On the first read, read multiple characters, i.e. the shortest valid reply. Afterwards
2543 * read byte by byte, since we don't want to read too much and drop characters from the input
2544 * queue. */
2545 ssize_t l = read(fd, buf + bytes, bytes == 0 ? STRLEN(DCS_TERMINFO_R0) : 1);
2546 if (l < 0) {
2547 if (errno == EAGAIN)
2548 continue;
2549 r = -errno;
2550 break;
2551 }
2552
2553 assert((size_t) l <= sizeof(buf) - bytes);
2554 bytes += l;
2555
2556 r = scan_terminfo_response(buf, bytes, ret_name);
2557 if (r != -EAGAIN)
2558 break;
2559
2560 if (bytes == sizeof(buf)) {
2561 r = -EOPNOTSUPP; /* The response has the right prefix, but we didn't find a valid
3ec92f2f 2562 * answer with a terminator in the allotted space. Something is
5321b957
ZJS
2563 * wrong, possibly some unrelated bytes got injected into the
2564 * answer. */
2565 break;
2566 }
2567 }
2568
2569finish:
2570 /* We ignore failure here. We already got a reply and if cleanup fails, we can't help that. */
f789b17e 2571 (void) tcsetattr(fd, TCSANOW, &old_termios);
5321b957
ZJS
2572 return r;
2573}
2574
e3b050a5
ZJS
2575int have_terminfo_file(const char *name) {
2576 /* This is a heuristic check if we have the file, using the directory layout used on
2577 * current Linux systems. Checks for other layouts can be added later if appropriate. */
2578 int r;
2579
2580 assert(filename_is_valid(name));
2581
2582 _cleanup_free_ char *p = path_join("/usr/share/terminfo", CHAR_TO_STR(name[0]), name);
2583 if (!p)
2584 return log_oom_debug();
2585
2586 r = RET_NERRNO(access(p, F_OK));
2587 if (r == -ENOENT)
2588 return false;
2589 if (r < 0)
2590 return r;
2591 return true;
2592}
2593
2594int query_term_for_tty(const char *tty, char **ret_term) {
2595 _cleanup_free_ char *dcs_term = NULL;
2596 int r;
2597
2598 assert(tty);
2599 assert(ret_term);
2600
2601 if (tty_is_vc_resolve(tty))
2602 return strdup_to(ret_term, "linux");
2603
2604 /* Try to query the terminal implementation that we're on. This will not work in all
2605 * cases, which is fine, since this is intended to be used as a fallback. */
2606
2607 _cleanup_close_ int tty_fd = open_terminal(tty, O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK);
2608 if (tty_fd < 0)
2609 return log_debug_errno(tty_fd, "Failed to open %s to query terminfo: %m", tty);
2610
2611 r = terminal_get_terminfo_by_dcs(tty_fd, &dcs_term);
2612 if (r < 0)
2613 return log_debug_errno(r, "Failed to query %s for terminfo: %m", tty);
2614
2615 r = have_terminfo_file(dcs_term);
2616 if (r < 0)
2617 return log_debug_errno(r, "Failed to look for terminfo %s: %m", dcs_term);
2618 if (r == 0)
2619 return log_info_errno(SYNTHETIC_ERRNO(ENODATA),
2620 "Terminfo %s not found for %s.", dcs_term, tty);
2621
2622 *ret_term = TAKE_PTR(dcs_term);
2623 return 0;
2624}
2625
ce3a1593
LP
2626int terminal_is_pty_fd(int fd) {
2627 int r;
2628
2629 assert(fd >= 0);
2630
2631 /* Returns true if we are looking at a pty, i.e. if it's backed by the /dev/pts/ file system */
2632
2633 if (!isatty_safe(fd))
2634 return false;
2635
2636 r = is_fs_type_at(fd, NULL, DEVPTS_SUPER_MAGIC);
2637 if (r != 0)
2638 return r;
2639
2640 /* The ptmx device is weird, it exists twice, once inside and once outside devpts. To detect the
2641 * latter case, let's fire off an ioctl() that only works on ptmx devices. */
2642
2643 int v;
2644 if (ioctl(fd, TIOCGPKT, &v) < 0) {
2645 if (ERRNO_IS_NOT_SUPPORTED(errno))
2646 return false;
2647
2648 return -errno;
2649 }
2650
2651 return true;
2652}
fc9dc71a 2653
1d522f1a 2654int pty_open_peer(int fd, int mode) {
fc9dc71a
LP
2655 assert(fd >= 0);
2656
2657 /* Opens the peer PTY using the new race-free TIOCGPTPEER ioctl() (kernel 4.13).
2658 *
2659 * This is safe to be called on TTYs from other namespaces. */
2660
2661 assert((mode & (O_CREAT|O_PATH|O_DIRECTORY|O_TMPFILE)) == 0);
2662
2663 /* This replicates the EIO retry logic of open_terminal() in a modified way. */
2664 for (unsigned c = 0;; c++) {
2665 int peer_fd = ioctl(fd, TIOCGPTPEER, mode);
2666 if (peer_fd >= 0)
2667 return peer_fd;
2668
fc9dc71a
LP
2669 if (errno != EIO)
2670 return -errno;
2671
2672 /* Max 1s in total */
2673 if (c >= 20)
2674 return -EIO;
2675
2676 (void) usleep_safe(50 * USEC_PER_MSEC);
2677 }
2678}