• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

systemd / systemd / 23877209284

01 Apr 2026 05:23PM UTC coverage: 72.343% (-0.06%) from 72.404%
23877209284

push

github

bluca
hwdb/keyboard: fix enter key for X+ piccolo

The main enter key gives a code for keypad one... Map it to
regular enter key.

318395 of 440116 relevant lines covered (72.34%)

1157750.86 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

41.26
/src/basic/terminal-util.c
1
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2

3
#include <fcntl.h>
4
#include <linux/kd.h>
5
#include <linux/magic.h>
6
#include <linux/tiocl.h>
7
#include <linux/vt.h>
8
#include <poll.h>
9
#include <signal.h>
10
#include <stdlib.h>
11
#include <sys/inotify.h>
12
#include <sys/ioctl.h>
13
#include <sys/sysmacros.h>
14
#include <termios.h>
15
#include <time.h>
16
#include <unistd.h>
17

18
#include "alloc-util.h"
19
#include "ansi-color.h"
20
#include "chase.h"
21
#include "devnum-util.h"
22
#include "errno-util.h"
23
#include "extract-word.h"
24
#include "fd-util.h"
25
#include "fileio.h"
26
#include "fs-util.h"
27
#include "hexdecoct.h"
28
#include "inotify-util.h"
29
#include "io-util.h"
30
#include "log.h"
31
#include "namespace-util.h"
32
#include "parse-util.h"
33
#include "path-util.h"
34
#include "pidref.h"
35
#include "proc-cmdline.h"
36
#include "process-util.h"
37
#include "signal-util.h"
38
#include "socket-util.h"
39
#include "stat-util.h"
40
#include "stdio-util.h"
41
#include "string-util.h"
42
#include "strv.h"
43
#include "terminal-util.h"
44
#include "time-util.h"
45
#include "utf8.h"
46

47
/* How much to wait for a reply to a terminal sequence */
48
#define CONSOLE_REPLY_WAIT_USEC  (333 * USEC_PER_MSEC)
49

50
static volatile unsigned cached_columns = 0;
51
static volatile unsigned cached_lines = 0;
52

53
static volatile int cached_on_tty = -1;
54
static volatile int cached_on_dev_null = -1;
55

56
bool isatty_safe(int fd) {
5,519,107✔
57
        assert(fd >= 0);
5,519,107✔
58

59
        if (isatty(fd))
5,519,107✔
60
                return true;
61

62
        /* Linux/glibc returns EIO for hung up TTY on isatty(). Which is wrong, the thing doesn't stop being
63
         * a TTY after all, just because it is temporarily hung up. Let's work around this here, until this
64
         * is fixed in glibc. See: https://sourceware.org/bugzilla/show_bug.cgi?id=32103 */
65
        if (errno == EIO)
5,467,189✔
66
                return true;
67

68
        /* Be resilient if we're working on stdio, since they're set up by parent process. */
69
        assert(errno != EBADF || IN_SET(fd, STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO));
5,467,180✔
70

71
        return false;
72
}
73

74
int chvt(int vt) {
×
75
        _cleanup_close_ int fd = -EBADF;
×
76

77
        /* Switch to the specified vt number. If the VT is specified <= 0 switch to the VT the kernel log messages go,
78
         * if that's configured. */
79

80
        fd = open_terminal("/dev/tty0", O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK);
×
81
        if (fd < 0)
×
82
                return fd;
83

84
        if (vt <= 0) {
×
85
                int tiocl[2] = {
×
86
                        TIOCL_GETKMSGREDIRECT,
87
                        0
88
                };
89

90
                if (ioctl(fd, TIOCLINUX, tiocl) < 0)
×
91
                        return -errno;
×
92

93
                vt = tiocl[0] <= 0 ? 1 : tiocl[0];
×
94
        }
95

96
        return RET_NERRNO(ioctl(fd, VT_ACTIVATE, vt));
×
97
}
98

99
int read_one_char(FILE *f, char *ret, usec_t t, bool echo, bool *need_nl) {
4✔
100
        _cleanup_free_ char *line = NULL;
4✔
101
        struct termios old_termios;
4✔
102
        int r, fd;
4✔
103

104
        assert(ret);
4✔
105

106
        if (!f)
4✔
107
                f = stdin;
×
108

109
        /* If this is a terminal, then switch canonical mode off, so that we can read a single
110
         * character. (Note that fmemopen() streams do not have an fd associated with them, let's handle that
111
         * nicely.) If 'echo' is false we'll also disable ECHO mode so that the pressed key is not made
112
         * visible to the user. */
113
        fd = fileno(f);
4✔
114
        if (fd >= 0 && tcgetattr(fd, &old_termios) >= 0) {
4✔
115
                struct termios new_termios = old_termios;
×
116

117
                new_termios.c_lflag &= ~(ICANON|(echo ? 0 : ECHO));
×
118
                new_termios.c_cc[VMIN] = 1;
×
119
                new_termios.c_cc[VTIME] = 0;
×
120

121
                if (tcsetattr(fd, TCSANOW, &new_termios) >= 0) {
×
122
                        char c;
×
123

124
                        if (t != USEC_INFINITY) {
×
125
                                if (fd_wait_for_event(fd, POLLIN, t) <= 0) {
×
126
                                        (void) tcsetattr(fd, TCSANOW, &old_termios);
×
127
                                        return -ETIMEDOUT;
×
128
                                }
129
                        }
130

131
                        r = safe_fgetc(f, &c);
×
132
                        (void) tcsetattr(fd, TCSANOW, &old_termios);
×
133
                        if (r < 0)
×
134
                                return r;
135
                        if (r == 0)
×
136
                                return -EIO;
137

138
                        if (need_nl)
×
139
                                *need_nl = c != '\n';
×
140

141
                        *ret = c;
×
142
                        return 0;
×
143
                }
144
        }
145

146
        if (t != USEC_INFINITY && fd >= 0) {
4✔
147
                /* Let's wait the specified amount of time for input. When we have no fd we skip this, under
148
                 * the assumption that this is an fmemopen() stream or so where waiting doesn't make sense
149
                 * anyway, as the data is either already in the stream or cannot possible be placed there
150
                 * while we access the stream */
151

152
                if (fd_wait_for_event(fd, POLLIN, t) <= 0)
4✔
153
                        return -ETIMEDOUT;
154
        }
155

156
        /* If this is not a terminal, then read a full line instead */
157

158
        r = read_line(f, 16, &line); /* longer than necessary, to eat up UTF-8 chars/vt100 key sequences */
4✔
159
        if (r < 0)
4✔
160
                return r;
161
        if (r == 0)
4✔
162
                return -EIO;
163

164
        if (strlen(line) != 1)
3✔
165
                return -EBADMSG;
166

167
        if (need_nl)
1✔
168
                *need_nl = false;
1✔
169

170
        *ret = line[0];
1✔
171
        return 0;
1✔
172
}
173

174
#define DEFAULT_ASK_REFRESH_USEC (2*USEC_PER_SEC)
175

176
int ask_char(char *ret, const char *replies, const char *fmt, ...) {
×
177
        int r;
×
178

179
        assert(ret);
×
180
        assert(replies);
×
181
        assert(fmt);
×
182

183
        for (;;) {
×
184
                va_list ap;
×
185
                char c;
×
186
                bool need_nl = true;
×
187

188
                fputs(ansi_highlight(), stdout);
×
189

190
                putchar('\r');
×
191

192
                va_start(ap, fmt);
×
193
                vprintf(fmt, ap);
×
194
                va_end(ap);
×
195

196
                fputs(ansi_normal(), stdout);
×
197

198
                fflush(stdout);
×
199

200
                r = read_one_char(stdin, &c, DEFAULT_ASK_REFRESH_USEC, /* echo= */ true, &need_nl);
×
201
                if (r < 0) {
×
202

203
                        if (r == -ETIMEDOUT)
×
204
                                continue;
×
205

206
                        if (r == -EBADMSG) {
×
207
                                puts("Bad input, please try again.");
×
208
                                continue;
×
209
                        }
210

211
                        putchar('\n');
×
212
                        return r;
×
213
                }
214

215
                if (need_nl)
×
216
                        putchar('\n');
×
217

218
                if (strchr(replies, c)) {
×
219
                        *ret = c;
×
220
                        return 0;
×
221
                }
222

223
                puts("Read unexpected character, please try again.");
×
224
        }
225
}
226

227
typedef enum CompletionResult{
228
        COMPLETION_ALREADY,       /* the input string is already complete */
229
        COMPLETION_FULL,          /* completed the input string to be complete now */
230
        COMPLETION_PARTIAL,       /* completed the input string so that is still incomplete */
231
        COMPLETION_NONE,          /* found no matching completion */
232
        _COMPLETION_RESULT_MAX,
233
        _COMPLETION_RESULT_INVALID = -EINVAL,
234
        _COMPLETION_RESULT_ERRNO_MAX = -ERRNO_MAX,
235
} CompletionResult;
236

237
static CompletionResult pick_completion(const char *string, char *const*completions, char **ret) {
×
238
        _cleanup_free_ char *found = NULL;
×
239
        bool partial = false;
×
240

241
        string = strempty(string);
×
242

243
        STRV_FOREACH(c, completions) {
×
244

245
                /* Ignore entries that are not actually completions */
246
                if (!startswith(*c, string))
×
247
                        continue;
×
248

249
                /* Store first completion that matches */
250
                if (!found) {
×
251
                        found = strdup(*c);
×
252
                        if (!found)
×
253
                                return -ENOMEM;
254

255
                        continue;
×
256
                }
257

258
                /* If there's another completion that works truncate the one we already found by common
259
                 * prefix */
260
                size_t n = str_common_prefix(found, *c);
×
261
                if (n == SIZE_MAX)
×
262
                        continue;
×
263

264
                found[n] = 0;
×
265
                partial = true;
×
266
        }
267

268
        *ret = TAKE_PTR(found);
×
269

270
        if (!*ret)
×
271
                return COMPLETION_NONE;
272
        if (partial)
×
273
                return COMPLETION_PARTIAL;
274

275
        return streq(string, *ret) ? COMPLETION_ALREADY : COMPLETION_FULL;
×
276
}
277

278
static void clear_by_backspace(size_t n) {
×
279
        /* Erase the specified number of character cells backwards on the terminal */
280
        for (size_t i = 0; i < n; i++)
×
281
                fputs("\b \b", stdout);
×
282
}
×
283

284
int ask_string_full(
7✔
285
                char **ret,
286
                GetCompletionsCallback get_completions,
287
                void *userdata,
288
                const char *text, ...) {
289

290
        va_list ap;
7✔
291
        int r;
7✔
292

293
        assert(ret);
7✔
294
        assert(text);
7✔
295

296
        /* Output the prompt */
297
        fputs(ansi_highlight(), stdout);
14✔
298
        va_start(ap, text);
7✔
299
        vprintf(text, ap);
7✔
300
        va_end(ap);
7✔
301
        fputs(ansi_normal(), stdout);
14✔
302
        fflush(stdout);
7✔
303

304
        _cleanup_free_ char *string = NULL;
7✔
305
        size_t n = 0;
7✔
306

307
        /* Do interactive logic only if stdin + stdout are connected to the same place. And yes, we could use
308
         * STDIN_FILENO and STDOUT_FILENO here, but let's be overly correct for once, after all libc allows
309
         * swapping out stdin/stdout. */
310
        int fd_input = fileno(stdin);
7✔
311
        int fd_output = fileno(stdout);
7✔
312
        struct termios old_termios = TERMIOS_NULL;
7✔
313
        CLEANUP_TERMIOS_RESET(fd_input, old_termios);
7✔
314

315
        if (fd_input < 0 || fd_output < 0 || same_fd(fd_input, fd_output) <= 0)
7✔
316
                goto fallback;
7✔
317

318
        /* Try to disable echo, which also tells us if this even is a terminal */
319
        if (tcgetattr(fd_input, &old_termios) < 0) {
×
320
                old_termios = TERMIOS_NULL;
×
321
                goto fallback;
×
322
        }
323

324
        struct termios new_termios = old_termios;
×
325
        termios_disable_echo(&new_termios);
×
326
        if (tcsetattr(fd_input, TCSANOW, &new_termios) < 0)
×
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
                                        return r;
350
                        }
351

352
                        _cleanup_free_ char *new_string = NULL;
×
353
                        CompletionResult cr = pick_completion(string, completions, &new_string);
×
354
                        if (cr < 0)
×
355
                                return cr;
356
                        if (IN_SET(cr, COMPLETION_PARTIAL, COMPLETION_FULL)) {
×
357
                                /* Output the new suffix we learned */
358
                                fputs(ASSERT_PTR(startswith(new_string, strempty(string))), stdout);
×
359

360
                                /* And update the whole string */
361
                                free_and_replace(string, new_string);
×
362
                                n = strlen(string);
×
363
                        }
364
                        if (cr == COMPLETION_NONE)
×
365
                                fputc('\a', stdout); /* BEL */
×
366

367
                        if (IN_SET(cr, COMPLETION_PARTIAL, COMPLETION_ALREADY)) {
×
368
                                /* If this worked only partially, or if the user hit TAB even though we were
369
                                 * complete already, then show the remaining options (in the latter case just
370
                                 * the one). */
371
                                fputc('\n', stdout);
×
372

373
                                _cleanup_strv_free_ char **filtered = strv_filter_prefix(completions, string);
×
374
                                if (!filtered)
×
375
                                        return -ENOMEM;
376

377
                                r = show_menu(filtered,
×
378
                                              /* n_columns= */ SIZE_MAX,
379
                                              /* column_width= */ SIZE_MAX,
380
                                              /* ellipsize_percentage= */ 0,
381
                                              /* grey_prefix= */ string,
382
                                              /* with_numbers= */ false);
383
                                if (r < 0)
×
384
                                        return r;
385

386
                                /* Show the prompt again */
387
                                fputs(ansi_highlight(), stdout);
×
388
                                va_start(ap, text);
×
389
                                vprintf(text, ap);
×
390
                                va_end(ap);
×
391
                                fputs(ansi_normal(), stdout);
×
392
                                fputs(string, stdout);
×
393
                        }
394

395
                } else if (IN_SET(c, '\b', 127)) {
×
396
                        /* Backspace */
397

398
                        if (n == 0)
×
399
                                fputc('\a', stdout); /* BEL */
×
400
                        else {
401
                                size_t m = utf8_last_length(string, n);
×
402

403
                                char *e = string + n - m;
×
404
                                clear_by_backspace(utf8_console_width(e));
×
405

406
                                *e = 0;
×
407
                                n -= m;
×
408
                        }
409

410
                } else if (c == 21) {
×
411
                        /* Ctrl-u → erase all input */
412

413
                        clear_by_backspace(utf8_console_width(string));
×
414
                        if (string)
×
415
                                string[n = 0] = 0;
×
416
                        else
417
                                assert(n == 0);
×
418

419
                } else if (c == 4) {
×
420
                        /* Ctrl-d → cancel this field input */
421

422
                        return -ECANCELED;
423

424
                } else if (char_is_cc(c) || n >= LINE_MAX)
×
425
                        /* refuse control characters and too long strings */
426
                        fputc('\a', stdout); /* BEL */
×
427
                else {
428
                        /* Regular char */
429

430
                        if (!GREEDY_REALLOC(string, n+2))
×
431
                                return -ENOMEM;
432

433
                        string[n++] = (char) c;
×
434
                        string[n] = 0;
×
435

436
                        fputc(c, stdout);
×
437
                }
438

439
                fflush(stdout);
×
440
        }
441

442
        if (!string) {
×
443
                string = strdup("");
×
444
                if (!string)
×
445
                        return -ENOMEM;
446
        }
447

448
        *ret = TAKE_PTR(string);
×
449
        return 0;
×
450

451
fallback:
7✔
452
        /* A simple fallback without TTY magic */
453
        r = read_line(stdin, LONG_LINE_MAX, &string);
7✔
454
        if (r < 0)
7✔
455
                return r;
456
        if (r == 0)
7✔
457
                return -EIO;
458

459
        *ret = TAKE_PTR(string);
7✔
460
        return 0;
7✔
461
}
462

463
bool any_key_to_proceed(void) {
×
464

465
        /* Insert a new line here as well as to when the user inputs, as this is also used during the boot up
466
         * sequence when status messages may be interleaved with the current program output. This ensures
467
         * that the status messages aren't appended on the same line as this message. */
468

469
        fputc('\n', stdout);
×
470
        fputs(ansi_highlight_magenta(), stdout);
×
471
        fputs("-- Press any key to proceed --", stdout);
×
472
        fputs(ansi_normal(), stdout);
×
473
        fputc('\n', stdout);
×
474
        fflush(stdout);
×
475

476
        char key = 0;
×
477
        (void) read_one_char(stdin, &key, USEC_INFINITY, /* echo= */ false, /* need_nl= */ NULL);
×
478

479
        fputc('\n', stdout);
×
480
        fflush(stdout);
×
481

482
        return key != 'q';
×
483
}
484

485
static size_t widest_list_element(char *const*l) {
×
486
        size_t w = 0;
×
487

488
        /* Returns the largest console width of all elements in 'l' */
489

490
        STRV_FOREACH(i, l)
×
491
                w = MAX(w, utf8_console_width(*i));
×
492

493
        return w;
×
494
}
495

496
int show_menu(char **x,
×
497
              size_t n_columns,
498
              size_t column_width,
499
              unsigned ellipsize_percentage,
500
              const char *grey_prefix,
501
              bool with_numbers) {
502

503
        assert(n_columns > 0);
×
504

505
        if (n_columns == SIZE_MAX)
×
506
                n_columns = 3;
×
507

508
        if (column_width == SIZE_MAX) {
×
509
                size_t widest = widest_list_element(x);
×
510

511
                /* If not specified, derive column width from screen width */
512
                size_t column_max = (columns()-1) / n_columns;
×
513

514
                /* Subtract room for numbers */
515
                if (with_numbers && column_max > 6)
×
516
                        column_max -= 6;
×
517

518
                /* If columns would get too tight let's make this a linear list instead. */
519
                if (column_max < 10 && widest > 10) {
×
520
                        n_columns = 1;
×
521
                        column_max = columns()-1;
×
522

523
                        if (with_numbers && column_max > 6)
×
524
                                column_max -= 6;
×
525
                }
526

527
                column_width = CLAMP(widest+1, 10U, column_max);
×
528
        }
529

530
        size_t n = strv_length(x);
×
531
        size_t per_column = DIV_ROUND_UP(n, n_columns);
×
532

533
        size_t break_lines = lines();
×
534
        if (break_lines > 2)
×
535
                break_lines--;
×
536

537
        /* The first page gets two extra lines, since we want to show
538
         * a title */
539
        size_t break_modulo = break_lines;
×
540
        if (break_modulo > 3)
×
541
                break_modulo -= 3;
×
542

543
        for (size_t i = 0; i < per_column; i++) {
×
544

545
                for (size_t j = 0; j < n_columns; j++) {
×
546
                        _cleanup_free_ char *e = NULL;
×
547

548
                        if (j * per_column + i >= n)
×
549
                                break;
550

551
                        e = ellipsize(x[j * per_column + i], column_width, ellipsize_percentage);
×
552
                        if (!e)
×
553
                                return -ENOMEM;
×
554

555
                        if (with_numbers)
×
556
                                printf("%s%4zu)%s ",
×
557
                                       ansi_grey(),
558
                                       j * per_column + i + 1,
559
                                       ansi_normal());
560

561
                        if (grey_prefix && startswith(e, grey_prefix)) {
×
562
                                size_t k = MIN(strlen(grey_prefix), column_width);
×
563
                                printf("%s%.*s%s",
×
564
                                       ansi_grey(),
565
                                       (int) k, e,
566
                                       ansi_normal());
567
                                printf("%-*s",
×
568
                                       (int) (column_width - k), e+k);
×
569
                        } else
570
                                printf("%-*s", (int) column_width, e);
×
571
                }
572

573
                putchar('\n');
×
574

575
                /* on the first screen we reserve 2 extra lines for the title */
576
                if (i % break_lines == break_modulo)
×
577
                        if (!any_key_to_proceed())
×
578
                                return 0;
579
        }
580

581
        return 0;
582
}
583

584
int open_terminal(const char *name, int mode) {
51,677✔
585
        _cleanup_close_ int fd = -EBADF;
51,677✔
586

587
        /*
588
         * If a TTY is in the process of being closed opening it might cause EIO. This is horribly awful, but
589
         * unlikely to be changed in the kernel. Hence we work around this problem by retrying a couple of
590
         * times.
591
         *
592
         * https://bugs.launchpad.net/ubuntu/+source/linux/+bug/554172/comments/245
593
         */
594

595
        assert((mode & (O_CREAT|O_PATH|O_DIRECTORY|O_TMPFILE)) == 0);
51,677✔
596

597
        for (unsigned c = 0;; c++) {
×
598
                fd = open(name, mode, 0);
51,677✔
599
                if (fd >= 0)
51,677✔
600
                        break;
601

602
                if (errno != EIO)
1,163✔
603
                        return -errno;
1,163✔
604

605
                /* Max 1s in total */
606
                if (c >= 20)
×
607
                        return -EIO;
608

609
                (void) usleep_safe(50 * USEC_PER_MSEC);
×
610
        }
611

612
        if (!isatty_safe(fd))
50,514✔
613
                return -ENOTTY;
1✔
614

615
        return TAKE_FD(fd);
616
}
617

618
int acquire_terminal(
187✔
619
                const char *name,
620
                AcquireTerminalFlags flags,
621
                usec_t timeout) {
622

623
        _cleanup_close_ int notify = -EBADF, fd = -EBADF;
187✔
624
        usec_t ts = USEC_INFINITY;
187✔
625
        int r, wd = -1;
187✔
626

627
        assert(name);
187✔
628

629
        AcquireTerminalFlags mode = flags & _ACQUIRE_TERMINAL_MODE_MASK;
187✔
630
        assert(IN_SET(mode, ACQUIRE_TERMINAL_TRY, ACQUIRE_TERMINAL_FORCE, ACQUIRE_TERMINAL_WAIT));
187✔
631
        assert(mode == ACQUIRE_TERMINAL_WAIT || !FLAGS_SET(flags, ACQUIRE_TERMINAL_WATCH_SIGTERM));
187✔
632

633
        /* We use inotify to be notified when the tty is closed. We create the watch before checking if we can actually
634
         * acquire it, so that we don't lose any event.
635
         *
636
         * Note: strictly speaking this actually watches for the device being closed, it does *not* really watch
637
         * whether a tty loses its controlling process. However, unless some rogue process uses TIOCNOTTY on /dev/tty
638
         * *after* closing its tty otherwise this will not become a problem. As long as the administrator makes sure to
639
         * not configure any service on the same tty as an untrusted user this should not be a problem. (Which they
640
         * probably should not do anyway.) */
641

642
        if (mode == ACQUIRE_TERMINAL_WAIT) {
×
643
                notify = inotify_init1(IN_CLOEXEC | IN_NONBLOCK);
187✔
644
                if (notify < 0)
187✔
645
                        return -errno;
×
646

647
                wd = inotify_add_watch(notify, name, IN_CLOSE);
187✔
648
                if (wd < 0)
187✔
649
                        return -errno;
×
650

651
                if (timeout != USEC_INFINITY)
187✔
652
                        ts = now(CLOCK_MONOTONIC);
×
653
        }
654

655
        /* If we are called with ACQUIRE_TERMINAL_WATCH_SIGTERM we'll unblock SIGTERM during ppoll() temporarily */
656
        sigset_t poll_ss;
187✔
657
        assert_se(sigprocmask(SIG_SETMASK, /* newset= */ NULL, &poll_ss) >= 0);
187✔
658
        if (flags & ACQUIRE_TERMINAL_WATCH_SIGTERM) {
187✔
659
                assert_se(sigismember(&poll_ss, SIGTERM) > 0);
×
660
                assert_se(sigdelset(&poll_ss, SIGTERM) >= 0);
×
661
        }
662

663
        for (;;) {
187✔
664
                if (notify >= 0) {
187✔
665
                        r = flush_fd(notify);
187✔
666
                        if (r < 0)
187✔
667
                                return r;
×
668
                }
669

670
                /* We pass here O_NOCTTY only so that we can check the return value TIOCSCTTY and have a reliable way
671
                 * to figure out if we successfully became the controlling process of the tty */
672
                fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC);
187✔
673
                if (fd < 0)
187✔
674
                        return fd;
675

676
                /* Temporarily ignore SIGHUP, so that we don't get SIGHUP'ed if we already own the tty. */
677
                struct sigaction sa_old;
187✔
678
                assert_se(sigaction(SIGHUP, &sigaction_ignore, &sa_old) >= 0);
187✔
679

680
                /* First, try to get the tty */
681
                r = RET_NERRNO(ioctl(fd, TIOCSCTTY, mode == ACQUIRE_TERMINAL_FORCE));
187✔
682

683
                /* Reset signal handler to old value */
684
                assert_se(sigaction(SIGHUP, &sa_old, NULL) >= 0);
187✔
685

686
                /* Success? Exit the loop now! */
687
                if (r >= 0)
187✔
688
                        break;
689

690
                /* Any failure besides -EPERM? Fail, regardless of the mode. */
691
                if (r != -EPERM)
×
692
                        return r;
693

694
                if (flags & ACQUIRE_TERMINAL_PERMISSIVE) /* If we are in permissive mode, then EPERM is fine, turn this
×
695
                                                          * into a success. Note that EPERM is also returned if we
696
                                                          * already are the owner of the TTY. */
697
                        break;
698

699
                if (mode != ACQUIRE_TERMINAL_WAIT) /* If we are in TRY or FORCE mode, then propagate EPERM as EPERM */
×
700
                        return r;
701

702
                assert(notify >= 0);
×
703
                assert(wd >= 0);
×
704

705
                for (;;) {
×
706
                        usec_t left;
×
707
                        if (timeout == USEC_INFINITY)
×
708
                                left = USEC_INFINITY;
709
                        else {
710
                                assert(ts != USEC_INFINITY);
×
711

712
                                usec_t n = usec_sub_unsigned(now(CLOCK_MONOTONIC), ts);
×
713
                                if (n >= timeout)
×
714
                                        return -ETIMEDOUT;
×
715

716
                                left = timeout - n;
×
717
                        }
718

719
                        r = ppoll_usec_full(
×
720
                                        &(struct pollfd) {
×
721
                                                .fd = notify,
722
                                                .events = POLLIN,
723
                                        },
724
                                        /* n_fds= */ 1,
725
                                        left,
726
                                        &poll_ss);
727
                        if (r < 0)
×
728
                                return r;
729
                        if (r == 0)
×
730
                                return -ETIMEDOUT;
731

732
                        union inotify_event_buffer buffer;
×
733
                        ssize_t l;
×
734
                        l = read(notify, &buffer, sizeof(buffer));
×
735
                        if (l < 0) {
×
736
                                if (ERRNO_IS_TRANSIENT(errno))
×
737
                                        continue;
×
738

739
                                return -errno;
×
740
                        }
741

742
                        FOREACH_INOTIFY_EVENT(e, buffer, l) {
×
743
                                if (e->mask & IN_Q_OVERFLOW) /* If we hit an inotify queue overflow, simply check if the terminal is up for grabs now. */
×
744
                                        break;
745

746
                                if (e->wd != wd || !(e->mask & IN_CLOSE)) /* Safety checks */
×
747
                                        return -EIO;
×
748
                        }
749

750
                        break;
×
751
                }
752

753
                /* We close the tty fd here since if the old session ended our handle will be dead. It's important that
754
                 * we do this after sleeping, so that we don't enter an endless loop. */
755
                fd = safe_close(fd);
×
756
        }
757

758
        return TAKE_FD(fd);
187✔
759
}
760

761
int release_terminal(void) {
56✔
762
        _cleanup_close_ int fd = -EBADF;
56✔
763
        int r;
56✔
764

765
        fd = open("/dev/tty", O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK);
56✔
766
        if (fd < 0)
56✔
767
                return -errno;
35✔
768

769
        /* Temporarily ignore SIGHUP, so that we don't get SIGHUP'ed
770
         * by our own TIOCNOTTY */
771
        struct sigaction sa_old;
21✔
772
        assert_se(sigaction(SIGHUP, &sigaction_ignore, &sa_old) >= 0);
21✔
773

774
        r = RET_NERRNO(ioctl(fd, TIOCNOTTY));
21✔
775

776
        assert_se(sigaction(SIGHUP, &sa_old, NULL) >= 0);
21✔
777

778
        return r;
779
}
780

781
int terminal_new_session(void) {
5✔
782

783
        /* Make us the new session leader, and set stdin tty to be our controlling terminal.
784
         *
785
         * Why stdin? Well, the ctty logic is relevant for signal delivery mostly, i.e. if people hit C-c
786
         * or the line is hung up. Such events are basically just a form of input, via a side channel
787
         * (that side channel being signal delivery, i.e. SIGINT, SIGHUP et al). Hence we focus on input,
788
         * not output here. */
789

790
        if (!isatty_safe(STDIN_FILENO))
5✔
791
                return -ENXIO;
792

793
        (void) setsid();
4✔
794
        return RET_NERRNO(ioctl(STDIN_FILENO, TIOCSCTTY, 0));
4✔
795
}
796

797
void terminal_detach_session(void) {
56✔
798
        (void) setsid();
56✔
799
        (void) release_terminal();
56✔
800
}
56✔
801

802
int terminal_vhangup_fd(int fd) {
96✔
803
        assert(fd >= 0);
96✔
804
        return RET_NERRNO(ioctl(fd, TIOCVHANGUP));
96✔
805
}
806

807
int terminal_vhangup(const char *tty) {
×
808
        _cleanup_close_ int fd = -EBADF;
×
809

810
        assert(tty);
×
811

812
        fd = open_terminal(tty, O_RDWR|O_NOCTTY|O_CLOEXEC);
×
813
        if (fd < 0)
×
814
                return fd;
815

816
        return terminal_vhangup_fd(fd);
×
817
}
818

819
int vt_disallocate(const char *tty_path) {
49✔
820
        assert(tty_path);
49✔
821

822
        /* Deallocate the VT if possible. If not possible (i.e. because it is the active one), at least clear
823
         * it entirely (including the scrollback buffer). */
824

825
        int ttynr = vtnr_from_tty(tty_path);
49✔
826
        if (ttynr > 0) {
49✔
827
                _cleanup_close_ int fd = open_terminal("/dev/tty0", O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK);
49✔
828
                if (fd < 0)
49✔
829
                        return fd;
830

831
                /* Try to deallocate */
832
                if (ioctl(fd, VT_DISALLOCATE, ttynr) >= 0)
49✔
833
                        return 0;
834
                if (errno != EBUSY)
49✔
835
                        return -errno;
×
836
        }
837

838
        /* So this is not a VT (in which case we cannot deallocate it), or we failed to deallocate. Let's at
839
         * least clear the screen. */
840

841
        _cleanup_close_ int fd2 = open_terminal(tty_path, O_WRONLY|O_NOCTTY|O_CLOEXEC|O_NONBLOCK);
98✔
842
        if (fd2 < 0)
49✔
843
                return fd2;
844

845
        return loop_write_full(fd2,
49✔
846
                               "\033[r"   /* clear scrolling region */
847
                               "\033[H"   /* move home */
848
                               "\033[3J"  /* clear screen including scrollback, requires Linux 2.6.40 */
849
                               "\033c",   /* reset to initial state */
850
                               SIZE_MAX,
851
                               100 * USEC_PER_MSEC);
852
}
853

854
static int vt_default_utf8(void) {
886✔
855
        _cleanup_free_ char *b = NULL;
886✔
856
        int r;
886✔
857

858
        /* Read the default VT UTF8 setting from the kernel */
859

860
        r = read_one_line_file("/sys/module/vt/parameters/default_utf8", &b);
886✔
861
        if (r < 0)
886✔
862
                return r;
863

864
        return parse_boolean(b);
496✔
865
}
866

867
static int vt_reset_keyboard(int fd) {
443✔
868
        int r, kb;
443✔
869

870
        assert(fd >= 0);
443✔
871

872
        /* If we can't read the default, then default to Unicode. It's 2024 after all. */
873
        r = vt_default_utf8();
443✔
874
        if (r < 0)
443✔
875
                log_debug_errno(r, "Failed to determine kernel VT UTF-8 mode, assuming enabled: %m");
195✔
876

877
        kb = vt_default_utf8() != 0 ? K_UNICODE : K_XLATE;
443✔
878
        return RET_NERRNO(ioctl(fd, KDSKBMODE, kb));
443✔
879
}
880

881
static int terminal_reset_ioctl(int fd, bool switch_to_text) {
443✔
882
        struct termios termios;
443✔
883
        int r;
443✔
884

885
        /* Set terminal to some sane defaults */
886

887
        assert(fd >= 0);
443✔
888

889
        /* We leave locked terminal attributes untouched, so that Plymouth may set whatever it wants to set,
890
         * and we don't interfere with that. */
891

892
        /* Disable exclusive mode, just in case */
893
        if (ioctl(fd, TIOCNXCL) < 0)
443✔
894
                log_debug_errno(errno, "TIOCNXCL ioctl failed on TTY, ignoring: %m");
2✔
895

896
        /* Switch to text mode */
897
        if (switch_to_text)
443✔
898
                if (ioctl(fd, KDSETMODE, KD_TEXT) < 0)
214✔
899
                        log_debug_errno(errno, "KDSETMODE ioctl for switching to text mode failed on TTY, ignoring: %m");
165✔
900

901
        /* Set default keyboard mode */
902
        r = vt_reset_keyboard(fd);
443✔
903
        if (r < 0)
443✔
904
                log_debug_errno(r, "Failed to reset VT keyboard, ignoring: %m");
348✔
905

906
        if (tcgetattr(fd, &termios) < 0) {
443✔
907
                r = log_debug_errno(errno, "Failed to get terminal parameters: %m");
2✔
908
                goto finish;
2✔
909
        }
910

911
        /* We only reset the stuff that matters to the software. How
912
         * hardware is set up we don't touch assuming that somebody
913
         * else will do that for us */
914

915
        termios.c_iflag &= ~(IGNBRK | BRKINT | ISTRIP | INLCR | IGNCR | IUCLC);
441✔
916
        termios.c_iflag |= ICRNL | IMAXBEL | IUTF8;
441✔
917
        termios.c_oflag |= ONLCR | OPOST;
441✔
918
        termios.c_cflag |= CREAD;
441✔
919
        termios.c_lflag = ISIG | ICANON | IEXTEN | ECHO | ECHOE | ECHOK | ECHOCTL | ECHOKE;
441✔
920

921
        termios.c_cc[VINTR]    =   03;  /* ^C */
441✔
922
        termios.c_cc[VQUIT]    =  034;  /* ^\ */
441✔
923
        termios.c_cc[VERASE]   = 0177;
441✔
924
        termios.c_cc[VKILL]    =  025;  /* ^X */
441✔
925
        termios.c_cc[VEOF]     =   04;  /* ^D */
441✔
926
        termios.c_cc[VSTART]   =  021;  /* ^Q */
441✔
927
        termios.c_cc[VSTOP]    =  023;  /* ^S */
441✔
928
        termios.c_cc[VSUSP]    =  032;  /* ^Z */
441✔
929
        termios.c_cc[VLNEXT]   =  026;  /* ^V */
441✔
930
        termios.c_cc[VWERASE]  =  027;  /* ^W */
441✔
931
        termios.c_cc[VREPRINT] =  022;  /* ^R */
441✔
932
        termios.c_cc[VEOL]     =    0;
441✔
933
        termios.c_cc[VEOL2]    =    0;
441✔
934

935
        termios.c_cc[VTIME]  = 0;
441✔
936
        termios.c_cc[VMIN]   = 1;
441✔
937

938
        r = RET_NERRNO(tcsetattr(fd, TCSANOW, &termios));
441✔
939
        if (r < 0)
×
940
                log_debug_errno(r, "Failed to set terminal parameters: %m");
×
941

942
finish:
×
943
        /* Just in case, flush all crap out */
944
        (void) tcflush(fd, TCIOFLUSH);
443✔
945

946
        return r;
443✔
947
}
948

949
int terminal_reset_ansi_seq(int fd) {
437✔
950
        int r, k;
437✔
951

952
        assert(fd >= 0);
437✔
953

954
        if (getenv_terminal_is_dumb())
437✔
955
                return 0;
956

957
        r = fd_nonblock(fd, true);
×
958
        if (r < 0)
×
959
                return log_debug_errno(r, "Failed to set terminal to non-blocking mode: %m");
×
960

961
        k = loop_write_full(fd,
×
962
                            "\033[!p"              /* soft terminal reset */
963
                            ANSI_OSC "104" ANSI_ST /* reset color palette via OSC 104 */
964
                            ANSI_NORMAL            /* reset colors */
965
                            "\033[?7h"             /* enable line-wrapping */
966
                            "\033[1G"              /* place cursor at beginning of current line */
967
                            "\033[0J",             /* erase till end of screen */
968
                            SIZE_MAX,
969
                            100 * USEC_PER_MSEC);
970
        if (k < 0)
×
971
                log_debug_errno(k, "Failed to reset terminal through ANSI sequences: %m");
×
972

973
        if (r > 0) {
×
974
                r = fd_nonblock(fd, false);
×
975
                if (r < 0)
×
976
                        log_debug_errno(r, "Failed to set terminal back to blocking mode: %m");
×
977
        }
978

979
        return k < 0 ? k : r;
×
980
}
981

982
void reset_dev_console_fd(int fd, bool switch_to_text) {
37✔
983
        int r;
37✔
984

985
        assert(fd >= 0);
37✔
986

987
        _cleanup_close_ int lock_fd = lock_dev_console();
37✔
988
        if (lock_fd < 0)
37✔
989
                log_debug_errno(lock_fd, "Failed to lock /dev/console, ignoring: %m");
×
990

991
        r = terminal_reset_ioctl(fd, switch_to_text);
37✔
992
        if (r < 0)
37✔
993
                log_warning_errno(r, "Failed to reset /dev/console, ignoring: %m");
×
994

995
        unsigned rows, cols;
37✔
996
        r = proc_cmdline_tty_size("/dev/console", &rows, &cols);
37✔
997
        if (r < 0)
37✔
998
                log_warning_errno(r, "Failed to get /dev/console size, ignoring: %m");
×
999
        else if (r > 0) {
37✔
1000
                r = terminal_set_size_fd(fd, NULL, rows, cols);
37✔
1001
                if (r < 0)
37✔
1002
                        log_warning_errno(r, "Failed to set configured terminal size on /dev/console, ignoring: %m");
×
1003
        } else
1004
                (void) terminal_fix_size(fd, fd);
×
1005

1006
        r = terminal_reset_ansi_seq(fd);
37✔
1007
        if (r < 0)
37✔
1008
                log_warning_errno(r, "Failed to reset /dev/console using ANSI sequences, ignoring: %m");
37✔
1009
}
37✔
1010

1011
int lock_dev_console(void) {
580✔
1012
        _cleanup_close_ int fd = -EBADF;
580✔
1013
        int r;
580✔
1014

1015
        /* NB: We do not use O_NOFOLLOW here, because some container managers might place a symlink to some
1016
         * pty in /dev/console, in which case it should be fine to lock the target TTY. */
1017
        fd = open_terminal("/dev/console", O_RDONLY|O_CLOEXEC|O_NOCTTY);
580✔
1018
        if (fd < 0)
580✔
1019
                return fd;
1020

1021
        r = lock_generic(fd, LOCK_BSD, LOCK_EX);
580✔
1022
        if (r < 0)
580✔
1023
                return r;
×
1024

1025
        return TAKE_FD(fd);
1026
}
1027

1028
int make_console_stdio(void) {
×
1029
        int fd, r;
×
1030

1031
        /* Make /dev/console the controlling terminal and stdin/stdout/stderr, if we can. If we can't use
1032
         * /dev/null instead. This is particularly useful if /dev/console is turned off, e.g. if console=null
1033
         * is specified on the kernel command line. */
1034

1035
        fd = acquire_terminal("/dev/console", ACQUIRE_TERMINAL_FORCE|ACQUIRE_TERMINAL_PERMISSIVE, USEC_INFINITY);
×
1036
        if (fd < 0) {
×
1037
                log_warning_errno(fd, "Failed to acquire terminal, using /dev/null stdin/stdout/stderr instead: %m");
×
1038

1039
                r = make_null_stdio();
×
1040
                if (r < 0)
×
1041
                        return log_error_errno(r, "Failed to make /dev/null stdin/stdout/stderr: %m");
×
1042

1043
        } else {
1044
                reset_dev_console_fd(fd, /* switch_to_text= */ true);
×
1045

1046
                r = rearrange_stdio(fd, fd, fd); /* This invalidates 'fd' both on success and on failure. */
×
1047
                if (r < 0)
×
1048
                        return log_error_errno(r, "Failed to make terminal stdin/stdout/stderr: %m");
×
1049
        }
1050

1051
        reset_terminal_feature_caches();
×
1052
        return 0;
×
1053
}
1054

1055
static int vtnr_from_tty_raw(const char *tty, unsigned *ret) {
302✔
1056
        assert(tty);
302✔
1057

1058
        tty = skip_dev_prefix(tty);
302✔
1059

1060
        const char *e = startswith(tty, "tty");
302✔
1061
        if (!e)
302✔
1062
                return -EINVAL;
1063

1064
        return safe_atou(e, ret);
262✔
1065
}
1066

1067
int vtnr_from_tty(const char *tty) {
177✔
1068
        unsigned u;
177✔
1069
        int r;
177✔
1070

1071
        assert(tty);
177✔
1072

1073
        r = vtnr_from_tty_raw(tty, &u);
177✔
1074
        if (r < 0)
177✔
1075
                return r;
177✔
1076
        if (!vtnr_is_valid(u))
177✔
1077
                return -ERANGE;
1078

1079
        return (int) u;
177✔
1080
}
1081

1082
bool tty_is_vc(const char *tty) {
125✔
1083
        assert(tty);
125✔
1084

1085
        /* NB: for >= 0 values no range check is conducted here, on the assumption that the caller will
1086
         * either extract vtnr through vtnr_from_tty() later where ERANGE would be reported, or doesn't care
1087
         * about whether it's strictly valid, but only asking "does this fall into the vt category?", for which
1088
         * "yes" seems to be a better answer. */
1089

1090
        return vtnr_from_tty_raw(tty, /* ret= */ NULL) >= 0;
125✔
1091
}
1092

1093
bool tty_is_console(const char *tty) {
443✔
1094
        assert(tty);
443✔
1095

1096
        return streq(skip_dev_prefix(tty), "console");
443✔
1097
}
1098

1099
int resolve_dev_console(char **ret) {
181✔
1100
        int r;
181✔
1101

1102
        assert(ret);
181✔
1103

1104
        /* Resolve where /dev/console is pointing to. If /dev/console is a symlink (like in container
1105
         * managers), we'll just resolve the symlink. If it's a real device node, we'll use if
1106
         * /sys/class/tty/tty0/active, but only if /sys/ is actually ours (i.e. not read-only-mounted which
1107
         * is a sign for container setups). */
1108

1109
        _cleanup_free_ char *chased = NULL;
181✔
1110
        r = chase("/dev/console", /* root= */ NULL, /* flags= */ 0, &chased, /* ret_fd= */ NULL);
181✔
1111
        if (r < 0)
181✔
1112
                return r;
1113
        if (!path_equal(chased, "/dev/console")) {
181✔
1114
                *ret = TAKE_PTR(chased);
81✔
1115
                return 0;
81✔
1116
        }
1117

1118
        r = path_is_read_only_fs("/sys");
100✔
1119
        if (r < 0)
100✔
1120
                return r;
1121
        if (r > 0)
100✔
1122
                return -ENOMEDIUM;
1123

1124
        _cleanup_free_ char *active = NULL;
100✔
1125
        r = read_one_line_file("/sys/class/tty/console/active", &active);
100✔
1126
        if (r < 0)
100✔
1127
                return r;
1128
        if (r == 0)
100✔
1129
                return -ENXIO;
1130

1131
        /* If multiple log outputs are configured the last one is what /dev/console points to */
1132
        const char *tty = strrchr(active, ' ');
100✔
1133
        if (tty)
100✔
1134
                tty++;
×
1135
        else
1136
                tty = active;
1137

1138
        if (streq(tty, "tty0")) {
100✔
1139
                active = mfree(active);
×
1140

1141
                /* Get the active VC (e.g. tty1) */
1142
                r = read_one_line_file("/sys/class/tty/tty0/active", &active);
×
1143
                if (r < 0)
×
1144
                        return r;
1145
                if (r == 0)
×
1146
                        return -ENXIO;
1147

1148
                tty = active;
×
1149
        }
1150

1151
        _cleanup_free_ char *path = NULL;
100✔
1152
        path = path_join("/dev", tty);
100✔
1153
        if (!path)
100✔
1154
                return -ENOMEM;
1155

1156
        *ret = TAKE_PTR(path);
100✔
1157
        return 0;
100✔
1158
}
1159

1160
int get_kernel_consoles(char ***ret) {
×
1161
        _cleanup_strv_free_ char **l = NULL;
×
1162
        _cleanup_free_ char *line = NULL;
×
1163
        int r;
×
1164

1165
        assert(ret);
×
1166

1167
        /* If /sys/ is mounted read-only this means we are running in some kind of container environment.
1168
         * In that case /sys/ would reflect the host system, not us, hence ignore the data we can read from it. */
1169
        if (path_is_read_only_fs("/sys") > 0)
×
1170
                goto fallback;
×
1171

1172
        r = read_one_line_file("/sys/class/tty/console/active", &line);
×
1173
        if (r < 0)
×
1174
                return r;
1175

1176
        for (const char *p = line;;) {
×
1177
                _cleanup_free_ char *tty = NULL, *path = NULL;
×
1178

1179
                r = extract_first_word(&p, &tty, NULL, 0);
×
1180
                if (r < 0)
×
1181
                        return r;
1182
                if (r == 0)
×
1183
                        break;
1184

1185
                if (streq(tty, "tty0")) {
×
1186
                        tty = mfree(tty);
×
1187
                        r = read_one_line_file("/sys/class/tty/tty0/active", &tty);
×
1188
                        if (r < 0)
×
1189
                                return r;
1190
                        if (r == 0) {
×
1191
                                log_debug("No VT active, skipping /dev/tty0.");
×
1192
                                continue;
×
1193
                        }
1194
                }
1195

1196
                path = path_join("/dev", tty);
×
1197
                if (!path)
×
1198
                        return -ENOMEM;
1199

1200
                if (access(path, F_OK) < 0) {
×
1201
                        log_debug_errno(errno, "Console device %s is not accessible, skipping: %m", path);
×
1202
                        continue;
×
1203
                }
1204

1205
                r = strv_consume(&l, TAKE_PTR(path));
×
1206
                if (r < 0)
×
1207
                        return r;
1208
        }
1209

1210
        if (strv_isempty(l)) {
×
1211
                log_debug("No devices found for system console");
×
1212
                goto fallback;
×
1213
        }
1214

1215
        *ret = TAKE_PTR(l);
×
1216
        return strv_length(*ret);
×
1217

1218
fallback:
×
1219
        r = strv_extend(&l, "/dev/console");
×
1220
        if (r < 0)
×
1221
                return r;
1222

1223
        *ret = TAKE_PTR(l);
×
1224
        return 0;
×
1225
}
1226

1227
bool tty_is_vc_resolve(const char *tty) {
49✔
1228
        _cleanup_free_ char *resolved = NULL;
49✔
1229

1230
        assert(tty);
49✔
1231

1232
        if (streq(skip_dev_prefix(tty), "console")) {
49✔
1233
                if (resolve_dev_console(&resolved) < 0)
1✔
1234
                        return false;
1235

1236
                tty = resolved;
1✔
1237
        }
1238

1239
        return tty_is_vc(tty);
49✔
1240
}
1241

1242
int fd_columns(int fd) {
1,399✔
1243
        struct winsize ws = {};
1,399✔
1244

1245
        if (fd < 0)
1,399✔
1246
                return -EBADF;
1247

1248
        if (ioctl(fd, TIOCGWINSZ, &ws) < 0)
1,399✔
1249
                return -errno;
1,399✔
1250

1251
        if (ws.ws_col <= 0)
×
1252
                return -ENODATA; /* some tty types come up with invalid row/column initially, return a recognizable error for that */
1253

1254
        return ws.ws_col;
×
1255
}
1256

1257
int getenv_columns(void) {
1,681✔
1258
        int r;
1,681✔
1259

1260
        const char *e = getenv("COLUMNS");
1,681✔
1261
        if (!e)
1,681✔
1262
                return -ENXIO;
1,681✔
1263

1264
        unsigned c;
2✔
1265
        r = safe_atou_bounded(e, 1, USHRT_MAX, &c);
2✔
1266
        if (r < 0)
2✔
1267
                return r;
1268

1269
        return (int) c;
2✔
1270
}
1271

1272
unsigned columns(void) {
267,876✔
1273

1274
        if (cached_columns > 0)
267,876✔
1275
                return cached_columns;
266,475✔
1276

1277
        int c = getenv_columns();
1,401✔
1278
        if (c < 0) {
1,401✔
1279
                c = fd_columns(STDOUT_FILENO);
1,399✔
1280
                if (c < 0)
1,399✔
1281
                        c = 80;
1282
        }
1283

1284
        assert(c > 0);
2✔
1285

1286
        cached_columns = c;
1,401✔
1287
        return cached_columns;
1,401✔
1288
}
1289

1290
int fd_lines(int fd) {
208✔
1291
        struct winsize ws = {};
208✔
1292

1293
        if (fd < 0)
208✔
1294
                return -EBADF;
1295

1296
        if (ioctl(fd, TIOCGWINSZ, &ws) < 0)
208✔
1297
                return -errno;
208✔
1298

1299
        if (ws.ws_row <= 0)
×
1300
                return -ENODATA; /* some tty types come up with invalid row/column initially, return a recognizable error for that */
1301

1302
        return ws.ws_row;
×
1303
}
1304

1305
unsigned lines(void) {
208✔
1306
        const char *e;
208✔
1307
        int l;
208✔
1308

1309
        if (cached_lines > 0)
208✔
1310
                return cached_lines;
×
1311

1312
        l = 0;
208✔
1313
        e = getenv("LINES");
208✔
1314
        if (e)
208✔
1315
                (void) safe_atoi(e, &l);
×
1316

1317
        if (l <= 0 || l > USHRT_MAX) {
208✔
1318
                l = fd_lines(STDOUT_FILENO);
208✔
1319
                if (l <= 0)
208✔
1320
                        l = 24;
208✔
1321
        }
1322

1323
        cached_lines = l;
208✔
1324
        return cached_lines;
208✔
1325
}
1326

1327
int terminal_set_size_fd(int fd, const char *ident, unsigned rows, unsigned cols) {
560✔
1328
        struct winsize ws;
560✔
1329

1330
        assert(fd >= 0);
560✔
1331

1332
        if (!ident)
560✔
1333
                ident = "TTY";
87✔
1334

1335
        if (rows == UINT_MAX && cols == UINT_MAX)
560✔
1336
                return 0;
560✔
1337

1338
        if (ioctl(fd, TIOCGWINSZ, &ws) < 0)
415✔
1339
                return log_debug_errno(errno,
×
1340
                                       "TIOCGWINSZ ioctl for getting %s size failed, not setting terminal size: %m",
1341
                                       ident);
1342

1343
        if (rows == UINT_MAX)
415✔
1344
                rows = ws.ws_row;
×
1345
        else if (rows > USHRT_MAX)
415✔
1346
                rows = USHRT_MAX;
×
1347

1348
        if (cols == UINT_MAX)
415✔
1349
                cols = ws.ws_col;
×
1350
        else if (cols > USHRT_MAX)
415✔
1351
                cols = USHRT_MAX;
×
1352

1353
        if (rows == ws.ws_row && cols == ws.ws_col)
415✔
1354
                return 0;
1355

1356
        ws.ws_row = rows;
140✔
1357
        ws.ws_col = cols;
140✔
1358

1359
        if (ioctl(fd, TIOCSWINSZ, &ws) < 0)
140✔
1360
                return log_debug_errno(errno, "TIOCSWINSZ ioctl for setting %s size failed: %m", ident);
×
1361

1362
        return 0;
1363
}
1364

1365
int proc_cmdline_tty_size(const char *tty, unsigned *ret_rows, unsigned *ret_cols) {
510✔
1366
        _cleanup_free_ char *rowskey = NULL, *rowsvalue = NULL, *colskey = NULL, *colsvalue = NULL;
510✔
1367
        unsigned rows = UINT_MAX, cols = UINT_MAX;
510✔
1368
        int r;
510✔
1369

1370
        assert(tty);
510✔
1371

1372
        if (!ret_rows && !ret_cols)
510✔
1373
                return 0;
1374

1375
        tty = skip_dev_prefix(tty);
510✔
1376
        if (path_startswith(tty, "pts/"))
510✔
1377
                return -EMEDIUMTYPE;
1378
        if (!in_charset(tty, ALPHANUMERICAL))
510✔
1379
                return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
×
1380
                                       "TTY name '%s' contains non-alphanumeric characters, not searching kernel cmdline for size.", tty);
1381

1382
        rowskey = strjoin("systemd.tty.rows.", tty);
510✔
1383
        if (!rowskey)
510✔
1384
                return -ENOMEM;
1385

1386
        colskey = strjoin("systemd.tty.columns.", tty);
510✔
1387
        if (!colskey)
510✔
1388
                return -ENOMEM;
1389

1390
        r = proc_cmdline_get_key_many(/* flags= */ 0,
510✔
1391
                                      rowskey, &rowsvalue,
1392
                                      colskey, &colsvalue);
1393
        if (r < 0)
510✔
1394
                return log_debug_errno(r, "Failed to read TTY size of %s from kernel cmdline: %m", tty);
×
1395

1396
        if (rowsvalue) {
510✔
1397
                r = safe_atou(rowsvalue, &rows);
415✔
1398
                if (r < 0)
415✔
1399
                        return log_debug_errno(r, "Failed to parse %s=%s: %m", rowskey, rowsvalue);
×
1400
        }
1401

1402
        if (colsvalue) {
510✔
1403
                r = safe_atou(colsvalue, &cols);
415✔
1404
                if (r < 0)
415✔
1405
                        return log_debug_errno(r, "Failed to parse %s=%s: %m", colskey, colsvalue);
×
1406
        }
1407

1408
        if (ret_rows)
510✔
1409
                *ret_rows = rows;
510✔
1410
        if (ret_cols)
510✔
1411
                *ret_cols = cols;
510✔
1412

1413
        return rows != UINT_MAX || cols != UINT_MAX;
510✔
1414
}
1415

1416
/* intended to be used as a SIGWINCH sighandler */
1417
void columns_lines_cache_reset(int signum) {
×
1418
        cached_columns = 0;
×
1419
        cached_lines = 0;
×
1420
}
×
1421

1422
void reset_terminal_feature_caches(void) {
24✔
1423
        cached_columns = 0;
24✔
1424
        cached_lines = 0;
24✔
1425

1426
        cached_on_tty = -1;
24✔
1427
        cached_on_dev_null = -1;
24✔
1428

1429
        reset_ansi_feature_caches();
24✔
1430
}
24✔
1431

1432
bool on_tty(void) {
7,476,466✔
1433

1434
        /* We check both stdout and stderr, so that situations where pipes on the shell are used are reliably
1435
         * recognized, regardless if only the output or the errors are piped to some place. Since on_tty() is generally
1436
         * used to default to a safer, non-interactive, non-color mode of operation it's probably good to be defensive
1437
         * here, and check for both. Note that we don't check for STDIN_FILENO, because it should fine to use fancy
1438
         * terminal functionality when outputting stuff, even if the input is piped to us. */
1439

1440
        if (cached_on_tty < 0)
7,476,466✔
1441
                cached_on_tty =
33,717✔
1442
                        isatty_safe(STDOUT_FILENO) &&
33,754✔
1443
                        isatty_safe(STDERR_FILENO);
37✔
1444

1445
        return cached_on_tty;
7,476,466✔
1446
}
1447

1448
int getttyname_malloc(int fd, char **ret) {
571✔
1449
        char path[PATH_MAX]; /* PATH_MAX is counted *with* the trailing NUL byte */
571✔
1450
        int r;
571✔
1451

1452
        assert(fd >= 0);
571✔
1453
        assert(ret);
571✔
1454

1455
        r = ttyname_r(fd, path, sizeof path); /* positive error */
571✔
1456
        assert(r >= 0);
571✔
1457
        if (r == ERANGE)
571✔
1458
                return -ENAMETOOLONG;
571✔
1459
        if (r > 0)
571✔
1460
                return -r;
564✔
1461

1462
        return strdup_to(ret, skip_dev_prefix(path));
7✔
1463
}
1464

1465
int getttyname_harder(int fd, char **ret) {
24✔
1466
        _cleanup_free_ char *s = NULL;
24✔
1467
        int r;
24✔
1468

1469
        r = getttyname_malloc(fd, &s);
24✔
1470
        if (r < 0)
24✔
1471
                return r;
1472

1473
        if (streq(s, "tty"))
×
1474
                return get_ctty(0, NULL, ret);
×
1475

1476
        *ret = TAKE_PTR(s);
×
1477
        return 0;
×
1478
}
1479

1480
int get_ctty_devnr(pid_t pid, dev_t *ret) {
5,017✔
1481
        _cleanup_free_ char *line = NULL;
5,017✔
1482
        unsigned long ttynr;
5,017✔
1483
        const char *p;
5,017✔
1484
        int r;
5,017✔
1485

1486
        assert(pid >= 0);
5,017✔
1487

1488
        p = procfs_file_alloca(pid, "stat");
24,745✔
1489
        r = read_one_line_file(p, &line);
5,017✔
1490
        if (r < 0)
5,017✔
1491
                return r;
1492

1493
        p = strrchr(line, ')');
5,017✔
1494
        if (!p)
5,017✔
1495
                return -EIO;
1496

1497
        p++;
5,017✔
1498

1499
        if (sscanf(p, " "
5,017✔
1500
                   "%*c "  /* state */
1501
                   "%*d "  /* ppid */
1502
                   "%*d "  /* pgrp */
1503
                   "%*d "  /* session */
1504
                   "%lu ", /* ttynr */
1505
                   &ttynr) != 1)
1506
                return -EIO;
1507

1508
        if (devnum_is_zero(ttynr))
5,017✔
1509
                return -ENXIO;
1510

1511
        if (ret)
4✔
1512
                *ret = (dev_t) ttynr;
2✔
1513

1514
        return 0;
1515
}
1516

1517
int get_ctty(pid_t pid, dev_t *ret_devnr, char **ret) {
31✔
1518
        char pty[STRLEN("/dev/pts/") + DECIMAL_STR_MAX(dev_t) + 1];
31✔
1519
        _cleanup_free_ char *buf = NULL;
31✔
1520
        const char *fn = NULL, *w;
31✔
1521
        dev_t devnr;
31✔
1522
        int r;
31✔
1523

1524
        r = get_ctty_devnr(pid, &devnr);
31✔
1525
        if (r < 0)
31✔
1526
                return r;
1527

1528
        r = device_path_make_canonical(S_IFCHR, devnr, &buf);
2✔
1529
        if (r < 0) {
2✔
1530
                struct stat st;
2✔
1531

1532
                if (r != -ENOENT) /* No symlink for this in /dev/char/? */
2✔
1533
                        return r;
×
1534

1535
                /* Maybe this is PTY? PTY devices are not listed in /dev/char/, as they don't follow the
1536
                 * Linux device model and hence device_path_make_canonical() doesn't work for them. Let's
1537
                 * assume this is a PTY for a moment, and check if the device node this would then map to in
1538
                 * /dev/pts/ matches the one we are looking for. This way we don't have to hardcode the major
1539
                 * number (which is 136 btw), but we still rely on the fact that PTY numbers map directly to
1540
                 * the minor number of the pty. */
1541
                xsprintf(pty, "/dev/pts/%u", minor(devnr));
2✔
1542

1543
                if (stat(pty, &st) < 0) {
2✔
1544
                        if (errno != ENOENT)
×
1545
                                return -errno;
×
1546

1547
                } else if (S_ISCHR(st.st_mode) && devnr == st.st_rdev) /* Bingo! */
2✔
1548
                        fn = pty;
1549

1550
                if (!fn) {
1551
                        /* Doesn't exist, or not a PTY? Probably something similar to the PTYs which have no
1552
                         * symlink in /dev/char/. Let's return something vaguely useful. */
1553
                        r = device_path_make_major_minor(S_IFCHR, devnr, &buf);
×
1554
                        if (r < 0)
×
1555
                                return r;
1556

1557
                        fn = buf;
×
1558
                }
1559
        } else
1560
                fn = buf;
×
1561

1562
        w = path_startswith(fn, "/dev/");
2✔
1563
        if (!w)
2✔
1564
                return -EINVAL;
1565

1566
        if (ret) {
2✔
1567
                r = strdup_to(ret, w);
2✔
1568
                if (r < 0)
2✔
1569
                        return r;
1570
        }
1571

1572
        if (ret_devnr)
2✔
1573
                *ret_devnr = devnr;
×
1574

1575
        return 0;
1576
}
1577

1578
int ptsname_malloc(int fd, char **ret) {
136✔
1579
        assert(fd >= 0);
136✔
1580
        assert(ret);
136✔
1581

1582
        for (size_t l = 50;;) {
×
1583
                _cleanup_free_ char *c = NULL;
×
1584

1585
                c = new(char, l);
136✔
1586
                if (!c)
136✔
1587
                        return -ENOMEM;
1588

1589
                if (ptsname_r(fd, c, l) >= 0) {
136✔
1590
                        *ret = TAKE_PTR(c);
136✔
1591
                        return 0;
136✔
1592
                }
1593
                if (errno != ERANGE)
×
1594
                        return -errno;
×
1595

1596
                if (!MUL_ASSIGN_SAFE(&l, 2))
×
1597
                        return -ENOMEM;
1598
        }
1599
}
1600

1601
int openpt_allocate(int flags, char **ret_peer_path) {
140✔
1602
        _cleanup_close_ int fd = -EBADF;
140✔
1603
        int r;
140✔
1604

1605
        fd = posix_openpt(flags|O_NOCTTY|O_CLOEXEC);
140✔
1606
        if (fd < 0)
140✔
1607
                return -errno;
×
1608

1609
        _cleanup_free_ char *p = NULL;
140✔
1610
        if (ret_peer_path) {
140✔
1611
                r = ptsname_malloc(fd, &p);
136✔
1612
                if (r < 0)
136✔
1613
                        return r;
1614

1615
                if (!path_startswith(p, "/dev/pts/"))
136✔
1616
                        return -EINVAL;
1617
        }
1618

1619
        if (unlockpt(fd) < 0)
140✔
1620
                return -errno;
×
1621

1622
        if (ret_peer_path)
140✔
1623
                *ret_peer_path = TAKE_PTR(p);
136✔
1624

1625
        return TAKE_FD(fd);
1626
}
1627

1628
static int ptsname_namespace(int pty, char **ret) {
×
1629
        int no = -1;
×
1630

1631
        assert(pty >= 0);
×
1632
        assert(ret);
×
1633

1634
        /* Like ptsname(), but doesn't assume that the path is
1635
         * accessible in the local namespace. */
1636

1637
        if (ioctl(pty, TIOCGPTN, &no) < 0)
×
1638
                return -errno;
×
1639

1640
        if (no < 0)
×
1641
                return -EIO;
1642

1643
        if (asprintf(ret, "/dev/pts/%i", no) < 0)
×
1644
                return -ENOMEM;
×
1645

1646
        return 0;
1647
}
1648

1649
int openpt_allocate_in_namespace(
×
1650
                const PidRef *pidref,
1651
                int flags,
1652
                char **ret_peer_path) {
1653

1654
        _cleanup_close_ int pidnsfd = -EBADF, mntnsfd = -EBADF, usernsfd = -EBADF, rootfd = -EBADF, fd = -EBADF;
×
1655
        _cleanup_close_pair_ int pair[2] = EBADF_PAIR;
×
1656
        int r;
×
1657

1658
        r = pidref_namespace_open(pidref, &pidnsfd, &mntnsfd, /* ret_netns_fd= */ NULL, &usernsfd, &rootfd);
×
1659
        if (r < 0)
×
1660
                return log_debug_errno(r, "Failed to open namespaces of PID "PID_FMT": %m", pidref->pid);
×
1661

1662
        if (socketpair(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0, pair) < 0)
×
1663
                return -errno;
×
1664

1665
        r = namespace_fork(
×
1666
                        "(sd-openptns)",
1667
                        "(sd-openpt)",
1668
                        FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGKILL|FORK_WAIT,
1669
                        pidnsfd,
1670
                        mntnsfd,
1671
                        /* netns_fd= */ -EBADF,
1672
                        usernsfd,
1673
                        rootfd,
1674
                        /* ret= */ NULL);
1675
        if (r < 0)
×
1676
                return r;
1677
        if (r == 0) {
×
1678
                pair[0] = safe_close(pair[0]);
×
1679

1680
                fd = openpt_allocate(flags, /* ret_peer_path= */ NULL);
×
1681
                if (fd < 0)
×
1682
                        _exit(EXIT_FAILURE);
×
1683

1684
                if (send_one_fd(pair[1], fd, 0) < 0)
×
1685
                        _exit(EXIT_FAILURE);
×
1686

1687
                _exit(EXIT_SUCCESS);
×
1688
        }
1689

1690
        pair[1] = safe_close(pair[1]);
×
1691

1692
        fd = receive_one_fd(pair[0], 0);
×
1693
        if (fd < 0)
×
1694
                return fd;
1695

1696
        if (ret_peer_path) {
×
1697
                r = ptsname_namespace(fd, ret_peer_path);
×
1698
                if (r < 0)
×
1699
                        return r;
×
1700
        }
1701

1702
        return TAKE_FD(fd);
1703
}
1704

1705
static bool on_dev_null(void) {
41,136✔
1706
        struct stat dst, ost, est;
41,136✔
1707

1708
        if (cached_on_dev_null >= 0)
41,136✔
1709
                return cached_on_dev_null;
7,547✔
1710

1711
        if (stat("/dev/null", &dst) < 0 || fstat(STDOUT_FILENO, &ost) < 0 || fstat(STDERR_FILENO, &est) < 0)
33,589✔
1712
                cached_on_dev_null = false;
1✔
1713
        else
1714
                cached_on_dev_null = stat_inode_same(&dst, &ost) && stat_inode_same(&dst, &est);
37,423✔
1715

1716
        return cached_on_dev_null;
33,589✔
1717
}
1718

1719
bool getenv_terminal_is_dumb(void) {
13,535✔
1720
        const char *e;
13,535✔
1721

1722
        e = getenv("TERM");
13,535✔
1723
        if (!e)
13,535✔
1724
                return true;
1725

1726
        return streq(e, "dumb");
1,322✔
1727
}
1728

1729
bool terminal_is_dumb(void) {
41,173✔
1730
        if (!on_tty() && !on_dev_null())
41,173✔
1731
                return true;
1732

1733
        return getenv_terminal_is_dumb();
158✔
1734
}
1735

1736
bool dev_console_colors_enabled(void) {
×
1737
        _cleanup_free_ char *s = NULL;
×
1738
        ColorMode m;
×
1739

1740
        /* Returns true if we assume that color is supported on /dev/console.
1741
         *
1742
         * For that we first check if we explicitly got told to use colors or not, by checking $SYSTEMD_COLORS. If that
1743
         * isn't set we check whether PID 1 has $TERM set, and if not, whether TERM is set on the kernel command
1744
         * line. If we find $TERM set we assume color if it's not set to "dumb", similarly to how regular
1745
         * colors_enabled() operates. */
1746

1747
        m = parse_systemd_colors();
×
1748
        if (m >= 0)
×
1749
                return m;
×
1750

1751
        if (getenv("NO_COLOR"))
×
1752
                return false;
1753

1754
        if (getenv_for_pid(1, "TERM", &s) <= 0)
×
1755
                (void) proc_cmdline_get_key("TERM", 0, &s);
×
1756

1757
        return !streq_ptr(s, "dumb");
×
1758
}
1759

1760
int vt_restore(int fd) {
×
1761

1762
        static const struct vt_mode mode = {
×
1763
                .mode = VT_AUTO,
1764
        };
1765

1766
        int r, ret = 0;
×
1767

1768
        assert(fd >= 0);
×
1769

1770
        if (!isatty_safe(fd))
×
1771
                return log_debug_errno(SYNTHETIC_ERRNO(ENOTTY), "Asked to restore the VT for an fd that does not refer to a terminal.");
×
1772

1773
        if (ioctl(fd, KDSETMODE, KD_TEXT) < 0)
×
1774
                RET_GATHER(ret, log_debug_errno(errno, "Failed to set VT to text mode, ignoring: %m"));
×
1775

1776
        r = vt_reset_keyboard(fd);
×
1777
        if (r < 0)
×
1778
                RET_GATHER(ret, log_debug_errno(r, "Failed to reset keyboard mode, ignoring: %m"));
×
1779

1780
        if (ioctl(fd, VT_SETMODE, &mode) < 0)
×
1781
                RET_GATHER(ret, log_debug_errno(errno, "Failed to set VT_AUTO mode, ignoring: %m"));
×
1782

1783
        r = fchmod_and_chown(fd, TTY_MODE, 0, GID_INVALID);
×
1784
        if (r < 0)
×
1785
                RET_GATHER(ret, log_debug_errno(r, "Failed to chmod()/chown() VT, ignoring: %m"));
×
1786

1787
        return ret;
1788
}
1789

1790
int vt_release(int fd, bool restore) {
×
1791
        assert(fd >= 0);
×
1792

1793
        /* This function releases the VT by acknowledging the VT-switch signal
1794
         * sent by the kernel and optionally reset the VT in text and auto
1795
         * VT-switching modes. */
1796

1797
        if (!isatty_safe(fd))
×
1798
                return log_debug_errno(SYNTHETIC_ERRNO(ENOTTY), "Asked to release the VT for an fd that does not refer to a terminal.");
×
1799

1800
        if (ioctl(fd, VT_RELDISP, 1) < 0)
×
1801
                return -errno;
×
1802

1803
        if (restore)
×
1804
                return vt_restore(fd);
×
1805

1806
        return 0;
1807
}
1808

1809
void get_log_colors(int priority, const char **on, const char **off, const char **highlight) {
225,876✔
1810
        /* Note that this will initialize output variables only when there's something to output.
1811
         * The caller must pre-initialize to "" or NULL as appropriate. */
1812

1813
        if (priority <= LOG_ERR) {
225,876✔
1814
                if (on)
6,471✔
1815
                        *on = ansi_highlight_red();
12,942✔
1816
                if (off)
6,471✔
1817
                        *off = ansi_normal();
12,942✔
1818
                if (highlight)
6,471✔
1819
                        *highlight = ansi_highlight();
×
1820

1821
        } else if (priority <= LOG_WARNING) {
219,405✔
1822
                if (on)
972✔
1823
                        *on = ansi_highlight_yellow();
972✔
1824
                if (off)
972✔
1825
                        *off = ansi_normal();
1,944✔
1826
                if (highlight)
972✔
1827
                        *highlight = ansi_highlight();
×
1828

1829
        } else if (priority <= LOG_NOTICE) {
218,433✔
1830
                if (on)
1,078✔
1831
                        *on = ansi_highlight();
2,156✔
1832
                if (off)
1,078✔
1833
                        *off = ansi_normal();
2,156✔
1834
                if (highlight)
1,078✔
1835
                        *highlight = ansi_highlight_red();
×
1836

1837
        } else if (priority >= LOG_DEBUG) {
217,355✔
1838
                if (on)
169,194✔
1839
                        *on = ansi_grey();
169,194✔
1840
                if (off)
169,194✔
1841
                        *off = ansi_normal();
338,388✔
1842
                if (highlight)
169,194✔
1843
                        *highlight = ansi_highlight_red();
×
1844
        }
1845
}
225,876✔
1846

1847
int terminal_set_cursor_position(int fd, unsigned row, unsigned column) {
×
1848
        assert(fd >= 0);
×
1849

1850
        char cursor_position[STRLEN("\x1B[" ";" "H") + DECIMAL_STR_MAX(unsigned) * 2 + 1];
×
1851
        xsprintf(cursor_position, "\x1B[%u;%uH", row, column);
×
1852

1853
        return loop_write(fd, cursor_position, SIZE_MAX);
×
1854
}
1855

1856
static int terminal_verify_same(int input_fd, int output_fd) {
×
1857
        assert(input_fd >= 0);
×
1858
        assert(output_fd >= 0);
×
1859

1860
        /* Validates that the specified fds reference the same TTY */
1861

1862
        if (input_fd != output_fd) {
×
1863
                struct stat sti;
×
1864
                if (fstat(input_fd, &sti) < 0)
×
1865
                        return -errno;
×
1866

1867
                if (!S_ISCHR(sti.st_mode)) /* TTYs are character devices */
×
1868
                        return -ENOTTY;
1869

1870
                struct stat sto;
×
1871
                if (fstat(output_fd, &sto) < 0)
×
1872
                        return -errno;
×
1873

1874
                if (!S_ISCHR(sto.st_mode))
×
1875
                        return -ENOTTY;
1876

1877
                if (sti.st_rdev != sto.st_rdev)
×
1878
                        return -ENOLINK;
1879
        }
1880

1881
        if (!isatty_safe(input_fd)) /* The check above was just for char device, but now let's ensure it's actually a tty */
×
1882
                return -ENOTTY;
×
1883

1884
        return 0;
1885
}
1886

1887
typedef enum CursorPositionState {
1888
        CURSOR_TEXT,
1889
        CURSOR_ESCAPE,
1890
        CURSOR_ROW,
1891
        CURSOR_COLUMN,
1892
} CursorPositionState;
1893

1894
typedef struct CursorPositionContext {
1895
        CursorPositionState state;
1896
        unsigned row, column;
1897
} CursorPositionContext;
1898

1899
static int scan_cursor_position_response(
×
1900
                CursorPositionContext *context,
1901
                const char *buf,
1902
                size_t size,
1903
                size_t *ret_processed) {
1904

1905
        assert(context);
×
1906
        assert(buf);
×
1907
        assert(ret_processed);
×
1908

1909
        for (size_t i = 0; i < size; i++) {
×
1910
                char c = buf[i];
×
1911

1912
                switch (context->state) {
×
1913

1914
                case CURSOR_TEXT:
×
1915
                        context->state = c == '\x1B' ? CURSOR_ESCAPE : CURSOR_TEXT;
×
1916
                        break;
×
1917

1918
                case CURSOR_ESCAPE:
×
1919
                        context->state = c == '[' ? CURSOR_ROW : CURSOR_TEXT;
×
1920
                        break;
×
1921

1922
                case CURSOR_ROW:
×
1923
                        if (c == ';')
×
1924
                                context->state = context->row > 0 ? CURSOR_COLUMN : CURSOR_TEXT;
×
1925
                        else {
1926
                                int d = undecchar(c);
×
1927

1928
                                /* We read a decimal character, let's suffix it to the number we so far read,
1929
                                 * but let's do an overflow check first. */
1930
                                if (d < 0 || context->row > (UINT_MAX-d)/10)
×
1931
                                        context->state = CURSOR_TEXT;
×
1932
                                else
1933
                                        context->row = context->row * 10 + d;
×
1934
                        }
1935
                        break;
1936

1937
                case CURSOR_COLUMN:
×
1938
                        if (c == 'R') {
×
1939
                                if (context->column > 0) {
×
1940
                                        *ret_processed = i + 1;
×
1941
                                        return 1; /* success! */
×
1942
                                }
1943

1944
                                context->state = CURSOR_TEXT;
×
1945
                        } else {
1946
                                int d = undecchar(c);
×
1947

1948
                                /* As above, add the decimal character to our column number */
1949
                                if (d < 0 || context->column > (UINT_MAX-d)/10)
×
1950
                                        context->state = CURSOR_TEXT;
×
1951
                                else
1952
                                        context->column = context->column * 10 + d;
×
1953
                        }
1954

1955
                        break;
1956
                }
1957

1958
                /* Reset any positions we might have picked up */
1959
                if (IN_SET(context->state, CURSOR_TEXT, CURSOR_ESCAPE))
×
1960
                        context->row = context->column = 0;
×
1961
        }
1962

1963
        *ret_processed = size;
×
1964
        return 0; /* all good, but not enough data yet */
×
1965
}
1966

1967
int terminal_get_cursor_position(
×
1968
                int input_fd,
1969
                int output_fd,
1970
                unsigned *ret_row,
1971
                unsigned *ret_column) {
1972

1973
        _cleanup_close_ int nonblock_input_fd = -EBADF;
×
1974
        int r;
×
1975

1976
        assert(input_fd >= 0);
×
1977
        assert(output_fd >= 0);
×
1978

1979
        if (terminal_is_dumb())
×
1980
                return -EOPNOTSUPP;
1981

1982
        r = terminal_verify_same(input_fd, output_fd);
×
1983
        if (r < 0)
×
1984
                return log_debug_errno(r, "Called with distinct input/output fds: %m");
×
1985

1986
        /* Failure to reset the terminal is ignored here and in similar cases below.
1987
         * We already have our result; if cleanup fails it doesn't change the validity of the result. */
1988
        struct termios old_termios = TERMIOS_NULL;
×
1989
        CLEANUP_TERMIOS_RESET(input_fd, old_termios);
×
1990

1991
        if (tcgetattr(input_fd, &old_termios) < 0)
×
1992
                return log_debug_errno(errno, "Failed to get terminal settings: %m");
×
1993

1994
        struct termios new_termios = old_termios;
×
1995
        termios_disable_echo(&new_termios);
×
1996

1997
        if (tcsetattr(input_fd, TCSANOW, &new_termios) < 0)
×
1998
                return log_debug_errno(errno, "Failed to set new terminal settings: %m");
×
1999

2000
        /* Request cursor position (DSR/CPR) */
2001
        r = loop_write(output_fd, "\x1B[6n", SIZE_MAX);
×
2002
        if (r < 0)
×
2003
                return r;
2004

2005
        /* Open a 2nd input fd, in non-blocking mode, so that we won't ever hang in read() should someone
2006
         * else process the POLLIN. */
2007

2008
        nonblock_input_fd = r = fd_reopen(input_fd, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
×
2009
        if (r < 0)
×
2010
                return r;
2011

2012
        usec_t end = usec_add(now(CLOCK_MONOTONIC), CONSOLE_REPLY_WAIT_USEC);
×
2013
        char buf[STRLEN("\x1B[1;1R")]; /* The shortest valid reply possible */
×
2014
        size_t buf_full = 0;
×
2015
        CursorPositionContext context = {};
×
2016

2017
        for (bool first = true;; first = false) {
×
2018
                if (buf_full == 0) {
×
2019
                        usec_t n = now(CLOCK_MONOTONIC);
×
2020
                        if (n >= end)
×
2021
                                return -EOPNOTSUPP;
×
2022

2023
                        r = fd_wait_for_event(nonblock_input_fd, POLLIN, usec_sub_unsigned(end, n));
×
2024
                        if (r < 0)
×
2025
                                return r;
2026
                        if (r == 0)
×
2027
                                return -EOPNOTSUPP;
2028

2029
                        /* On the first try, read multiple characters, i.e. the shortest valid
2030
                         * reply. Afterwards read byte-wise, since we don't want to read too much, and
2031
                         * unnecessarily drop too many characters from the input queue. */
2032
                        ssize_t l = read(nonblock_input_fd, buf, first ? sizeof(buf) : 1);
×
2033
                        if (l < 0) {
×
2034
                                if (errno == EAGAIN)
×
2035
                                        continue;
×
2036

2037
                                return -errno;
×
2038
                        }
2039

2040
                        assert((size_t) l <= sizeof(buf));
×
2041
                        buf_full = l;
2042
                }
2043

2044
                size_t processed;
×
2045
                r = scan_cursor_position_response(&context, buf, buf_full, &processed);
×
2046
                if (r < 0)
×
2047
                        return r;
2048

2049
                assert(processed <= buf_full);
×
2050
                buf_full -= processed;
×
2051
                memmove(buf, buf + processed, buf_full);
×
2052

2053
                if (r > 0) {
×
2054
                        /* Superficial validity check */
2055
                        if (context.row >= 32766 || context.column >= 32766)
×
2056
                                return -ENODATA;
2057

2058
                        if (ret_row)
×
2059
                                *ret_row = context.row;
×
2060
                        if (ret_column)
×
2061
                                *ret_column = context.column;
×
2062

2063
                        return 0;
×
2064
                }
2065
        }
2066
}
2067

2068
int terminal_reset_defensive(int fd, TerminalResetFlags flags) {
413✔
2069
        int r = 0;
413✔
2070

2071
        assert(fd >= 0);
413✔
2072
        assert(!FLAGS_SET(flags, TERMINAL_RESET_AVOID_ANSI_SEQ|TERMINAL_RESET_FORCE_ANSI_SEQ));
413✔
2073

2074
        /* Resets the terminal comprehensively, i.e. via both ioctl()s and via ANSI sequences, but do so only
2075
         * if $TERM is unset or set to "dumb" */
2076

2077
        if (!isatty_safe(fd))
413✔
2078
                return -ENOTTY;
413✔
2079

2080
        RET_GATHER(r, terminal_reset_ioctl(fd, FLAGS_SET(flags, TERMINAL_RESET_SWITCH_TO_TEXT)));
406✔
2081

2082
        if (!FLAGS_SET(flags, TERMINAL_RESET_AVOID_ANSI_SEQ) &&
406✔
2083
            (FLAGS_SET(flags, TERMINAL_RESET_FORCE_ANSI_SEQ) || !getenv_terminal_is_dumb()))
400✔
2084
                RET_GATHER(r, terminal_reset_ansi_seq(fd));
400✔
2085

2086
        return r;
2087
}
2088

2089
int terminal_reset_defensive_locked(int fd, TerminalResetFlags flags) {
6✔
2090
        assert(fd >= 0);
6✔
2091

2092
        _cleanup_close_ int lock_fd = lock_dev_console();
6✔
2093
        if (lock_fd < 0)
6✔
2094
                log_debug_errno(lock_fd, "Failed to acquire lock for /dev/console, ignoring: %m");
×
2095

2096
        return terminal_reset_defensive(fd, flags);
6✔
2097
}
2098

2099
void termios_disable_echo(struct termios *termios) {
1✔
2100
        assert(termios);
1✔
2101

2102
        termios->c_lflag &= ~(ICANON|ECHO);
1✔
2103
        termios->c_cc[VMIN] = 1;
1✔
2104
        termios->c_cc[VTIME] = 0;
1✔
2105
}
1✔
2106

2107
static bool termios_is_null(const struct termios *t) {
9✔
2108
        if (!t)
9✔
2109
                return true;
2110

2111
        return t->c_iflag == UINT_MAX &&
17✔
2112
               t->c_oflag == UINT_MAX &&
8✔
2113
               t->c_cflag == UINT_MAX &&
26✔
2114
               t->c_lflag == UINT_MAX;
8✔
2115
}
2116

2117
void termios_reset(const TermiosResetContext *c) {
110✔
2118
        assert(c);
110✔
2119

2120
        if (c->fd && *c->fd >= 0 && !termios_is_null(c->termios))
110✔
2121
                (void) tcsetattr(*c->fd, TCSANOW, c->termios);
1✔
2122
}
110✔
2123

2124
typedef enum BackgroundColorState {
2125
        BACKGROUND_TEXT,
2126
        BACKGROUND_ESCAPE,
2127
        BACKGROUND_BRACKET,
2128
        BACKGROUND_FIRST_ONE,
2129
        BACKGROUND_SECOND_ONE,
2130
        BACKGROUND_SEMICOLON,
2131
        BACKGROUND_R,
2132
        BACKGROUND_G,
2133
        BACKGROUND_B,
2134
        BACKGROUND_RED,
2135
        BACKGROUND_GREEN,
2136
        BACKGROUND_BLUE,
2137
        BACKGROUND_STRING_TERMINATOR,
2138
} BackgroundColorState;
2139

2140
typedef struct BackgroundColorContext {
2141
        BackgroundColorState state;
2142
        uint32_t red, green, blue;
2143
        unsigned red_bits, green_bits, blue_bits;
2144
} BackgroundColorContext;
2145

2146
static int scan_background_color_response(
×
2147
                BackgroundColorContext *context,
2148
                const char *buf,
2149
                size_t size,
2150
                size_t *ret_processed) {
2151

2152
        assert(context);
×
2153
        assert(buf);
×
2154
        assert(ret_processed);
×
2155

2156
        for (size_t i = 0; i < size; i++) {
×
2157
                char c = buf[i];
×
2158

2159
                switch (context->state) {
×
2160

2161
                case BACKGROUND_TEXT:
×
2162
                        context->state = c == '\x1B' ? BACKGROUND_ESCAPE : BACKGROUND_TEXT;
×
2163
                        break;
×
2164

2165
                case BACKGROUND_ESCAPE:
×
2166
                        context->state = c == ']' ? BACKGROUND_BRACKET : BACKGROUND_TEXT;
×
2167
                        break;
×
2168

2169
                case BACKGROUND_BRACKET:
×
2170
                        context->state = c == '1' ? BACKGROUND_FIRST_ONE : BACKGROUND_TEXT;
×
2171
                        break;
×
2172

2173
                case BACKGROUND_FIRST_ONE:
×
2174
                        context->state = c == '1' ? BACKGROUND_SECOND_ONE : BACKGROUND_TEXT;
×
2175
                        break;
×
2176

2177
                case BACKGROUND_SECOND_ONE:
×
2178
                        context->state = c == ';' ? BACKGROUND_SEMICOLON : BACKGROUND_TEXT;
×
2179
                        break;
×
2180

2181
                case BACKGROUND_SEMICOLON:
×
2182
                        context->state = c == 'r' ? BACKGROUND_R : BACKGROUND_TEXT;
×
2183
                        break;
×
2184

2185
                case BACKGROUND_R:
×
2186
                        context->state = c == 'g' ? BACKGROUND_G : BACKGROUND_TEXT;
×
2187
                        break;
×
2188

2189
                case BACKGROUND_G:
×
2190
                        context->state = c == 'b' ? BACKGROUND_B : BACKGROUND_TEXT;
×
2191
                        break;
×
2192

2193
                case BACKGROUND_B:
×
2194
                        context->state = c == ':' ? BACKGROUND_RED : BACKGROUND_TEXT;
×
2195
                        break;
×
2196

2197
                case BACKGROUND_RED:
×
2198
                        if (c == '/')
×
2199
                                context->state = context->red_bits > 0 ? BACKGROUND_GREEN : BACKGROUND_TEXT;
×
2200
                        else {
2201
                                int d = unhexchar(c);
×
2202
                                if (d < 0 || context->red_bits >= sizeof(context->red)*8)
×
2203
                                        context->state = BACKGROUND_TEXT;
×
2204
                                else {
2205
                                        context->red = (context->red << 4) | d;
×
2206
                                        context->red_bits += 4;
×
2207
                                }
2208
                        }
2209
                        break;
2210

2211
                case BACKGROUND_GREEN:
×
2212
                        if (c == '/')
×
2213
                                context->state = context->green_bits > 0 ? BACKGROUND_BLUE : BACKGROUND_TEXT;
×
2214
                        else {
2215
                                int d = unhexchar(c);
×
2216
                                if (d < 0 || context->green_bits >= sizeof(context->green)*8)
×
2217
                                        context->state = BACKGROUND_TEXT;
×
2218
                                else {
2219
                                        context->green = (context->green << 4) | d;
×
2220
                                        context->green_bits += 4;
×
2221
                                }
2222
                        }
2223
                        break;
2224

2225
                case BACKGROUND_BLUE:
×
2226
                        if (c == '\x07') {
×
2227
                                if (context->blue_bits > 0) {
×
2228
                                        *ret_processed = i + 1;
×
2229
                                        return 1; /* success! */
×
2230
                                }
2231

2232
                                context->state = BACKGROUND_TEXT;
×
2233
                        } else if (c == '\x1b')
×
2234
                                context->state = context->blue_bits > 0 ? BACKGROUND_STRING_TERMINATOR : BACKGROUND_TEXT;
×
2235
                        else {
2236
                                int d = unhexchar(c);
×
2237
                                if (d < 0 || context->blue_bits >= sizeof(context->blue)*8)
×
2238
                                        context->state = BACKGROUND_TEXT;
×
2239
                                else {
2240
                                        context->blue = (context->blue << 4) | d;
×
2241
                                        context->blue_bits += 4;
×
2242
                                }
2243
                        }
2244
                        break;
2245

2246
                case BACKGROUND_STRING_TERMINATOR:
×
2247
                        if (c == '\\') {
×
2248
                                *ret_processed = i + 1;
×
2249
                                return 1; /* success! */
×
2250
                        }
2251

2252
                        context->state = c == ']' ? BACKGROUND_ESCAPE : BACKGROUND_TEXT;
×
2253
                        break;
×
2254

2255
                }
2256

2257
                /* Reset any colors we might have picked up */
2258
                if (IN_SET(context->state, BACKGROUND_TEXT, BACKGROUND_ESCAPE)) {
×
2259
                        /* reset color */
2260
                        context->red = context->green = context->blue = 0;
×
2261
                        context->red_bits = context->green_bits = context->blue_bits = 0;
×
2262
                }
2263
        }
2264

2265
        *ret_processed = size;
×
2266
        return 0; /* all good, but not enough data yet */
×
2267
}
2268

2269
int get_default_background_color(double *ret_red, double *ret_green, double *ret_blue) {
170✔
2270
        int r;
170✔
2271

2272
        assert(ret_red);
170✔
2273
        assert(ret_green);
170✔
2274
        assert(ret_blue);
170✔
2275

2276
        if (!colors_enabled())
170✔
2277
                return -EOPNOTSUPP;
170✔
2278

2279
        r = terminal_verify_same(STDIN_FILENO, STDOUT_FILENO);
×
2280
        if (r < 0)
×
2281
                return r;
2282

2283
        if (streq_ptr(getenv("TERM"), "linux")) {
×
2284
                /* Linux console is black */
2285
                *ret_red = *ret_green = *ret_blue = 0.0;
×
2286
                return 0;
×
2287
        }
2288

2289
        /* Open a 2nd input fd, in non-blocking mode, so that we won't ever hang in read()
2290
         * should someone else process the POLLIN. Do all subsequent operations on the new fd. */
2291
        _cleanup_close_ int nonblock_input_fd = r = fd_reopen(STDIN_FILENO, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
170✔
2292
        if (r < 0)
×
2293
                return r;
2294

2295
        struct termios old_termios = TERMIOS_NULL;
×
2296
        CLEANUP_TERMIOS_RESET(nonblock_input_fd, old_termios);
×
2297

2298
        if (tcgetattr(nonblock_input_fd, &old_termios) < 0)
×
2299
                return -errno;
×
2300

2301
        struct termios new_termios = old_termios;
×
2302
        termios_disable_echo(&new_termios);
×
2303

2304
        if (tcsetattr(nonblock_input_fd, TCSANOW, &new_termios) < 0)
×
2305
                return -errno;
×
2306

2307
        r = loop_write(STDOUT_FILENO, ANSI_OSC "11;?" ANSI_ST, SIZE_MAX);
×
2308
        if (r < 0)
×
2309
                return r;
2310

2311
        usec_t end = usec_add(now(CLOCK_MONOTONIC), CONSOLE_REPLY_WAIT_USEC);
×
2312
        char buf[STRLEN(ANSI_OSC "11;rgb:0/0/0" ANSI_ST)]; /* shortest possible reply */
×
2313
        size_t buf_full = 0;
×
2314
        BackgroundColorContext context = {};
×
2315

2316
        for (bool first = true;; first = false) {
×
2317
                if (buf_full == 0) {
×
2318
                        usec_t n = now(CLOCK_MONOTONIC);
×
2319
                        if (n >= end)
×
2320
                                return -EOPNOTSUPP;
×
2321

2322
                        r = fd_wait_for_event(nonblock_input_fd, POLLIN, usec_sub_unsigned(end, n));
×
2323
                        if (r < 0)
×
2324
                                return r;
2325
                        if (r == 0)
×
2326
                                return -EOPNOTSUPP;
2327

2328
                        /* On the first try, read multiple characters, i.e. the shortest valid
2329
                         * reply. Afterwards read byte-wise, since we don't want to read too much, and
2330
                         * unnecessarily drop too many characters from the input queue. */
2331
                        ssize_t l = read(nonblock_input_fd, buf, first ? sizeof(buf) : 1);
×
2332
                        if (l < 0) {
×
2333
                                if (errno == EAGAIN)
×
2334
                                        continue;
×
2335
                                return -errno;
×
2336
                        }
2337

2338
                        assert((size_t) l <= sizeof(buf));
×
2339
                        buf_full = l;
2340
                }
2341

2342
                size_t processed;
×
2343
                r = scan_background_color_response(&context, buf, buf_full, &processed);
×
2344
                if (r < 0)
×
2345
                        return r;
2346

2347
                assert(processed <= buf_full);
×
2348
                buf_full -= processed;
×
2349
                memmove(buf, buf + processed, buf_full);
×
2350

2351
                if (r > 0) {
×
2352
                        assert(context.red_bits > 0);
×
2353
                        *ret_red = (double) context.red / ((UINT64_C(1) << context.red_bits) - 1);
×
2354
                        assert(context.green_bits > 0);
×
2355
                        *ret_green = (double) context.green / ((UINT64_C(1) << context.green_bits) - 1);
×
2356
                        assert(context.blue_bits > 0);
×
2357
                        *ret_blue = (double) context.blue / ((UINT64_C(1) << context.blue_bits) - 1);
×
2358
                        return 0;
×
2359
                }
2360
        }
2361
}
2362

2363
/* Determine terminal dimensions by means of ANSI sequences: save the cursor via DECSC, position it far
2364
 * to the bottom right (clamped to actual terminal dimensions), read back via DSR where we ended up, and
2365
 * restore cursor via DECRC. Only needs a single DSR round-trip, and always restores the cursor regardless
2366
 * of whether the response is received.
2367
 *
2368
 * Caller must have already opened a non-blocking input fd and configured termios (echo/icanon off). */
2369
static int terminal_query_size_by_dsr(
×
2370
                int nonblock_input_fd,
2371
                int output_fd,
2372
                unsigned *ret_rows,
2373
                unsigned *ret_columns) {
2374

2375
        int r;
×
2376

2377
        assert(nonblock_input_fd >= 0);
×
2378
        assert(output_fd >= 0);
×
2379

2380
        /* Use DECSC/DECRC to save/restore cursor instead of querying position via DSR. This way the cursor
2381
         * is always restored — even on timeout — and we only need one DSR response instead of two. */
2382
        r = loop_write(output_fd,
×
2383
                       "\x1B" "7"              /* DECSC: save cursor position */
2384
                       "\x1B[32766;32766H"     /* CUP: position cursor far to the right and to the bottom, staying within 16bit signed range */
2385
                       "\x1B[6n"               /* DSR: request cursor position (CPR) */
2386
                       "\x1B" "8",             /* DECRC: restore cursor position */
2387
                       SIZE_MAX);
2388
        if (r < 0)
×
2389
                return r;
×
2390

2391
        usec_t end = usec_add(now(CLOCK_MONOTONIC), CONSOLE_REPLY_WAIT_USEC);
×
2392
        char buf[STRLEN("\x1B[1;1R")]; /* The shortest valid reply possible */
×
2393
        size_t buf_full = 0;
×
2394
        CursorPositionContext context = {};
×
2395

2396
        for (bool first = true;; first = false) {
×
2397
                if (buf_full == 0) {
×
2398
                        usec_t n = now(CLOCK_MONOTONIC);
×
2399
                        if (n >= end)
×
2400
                                return -EOPNOTSUPP;
×
2401

2402
                        r = fd_wait_for_event(nonblock_input_fd, POLLIN, usec_sub_unsigned(end, n));
×
2403
                        if (r < 0)
×
2404
                                return r;
2405
                        if (r == 0)
×
2406
                                return -EOPNOTSUPP;
2407

2408
                        /* On the first try, read multiple characters, i.e. the shortest valid
2409
                         * reply. Afterwards read byte-wise, since we don't want to read too much, and
2410
                         * unnecessarily drop too many characters from the input queue. */
2411
                        ssize_t l = read(nonblock_input_fd, buf, first ? sizeof(buf) : 1);
×
2412
                        if (l < 0) {
×
2413
                                if (errno == EAGAIN)
×
2414
                                        continue;
×
2415

2416
                                return -errno;
×
2417
                        }
2418

2419
                        assert((size_t) l <= sizeof(buf));
×
2420
                        buf_full = l;
2421
                }
2422

2423
                size_t processed;
×
2424
                r = scan_cursor_position_response(&context, buf, buf_full, &processed);
×
2425
                if (r < 0)
×
2426
                        return r;
2427

2428
                assert(processed <= buf_full);
×
2429
                buf_full -= processed;
×
2430
                memmove(buf, buf + processed, buf_full);
×
2431

2432
                if (r > 0) {
×
2433
                        /* Superficial validity checks (no particular reason to check for < 4, it's
2434
                         * just a way to look for unreasonably small values) */
2435
                        if (context.row < 4 || context.column < 4 || context.row >= 32766 || context.column >= 32766)
×
2436
                                return -ENODATA;
2437

2438
                        if (ret_rows)
×
2439
                                *ret_rows = context.row;
×
2440
                        if (ret_columns)
×
2441
                                *ret_columns = context.column;
×
2442

2443
                        return 0;
×
2444
                }
2445
        }
2446
}
2447

2448
/* Common setup for ANSI terminal queries: validate the fds, open a non-blocking input fd, and configure
2449
 * termios with echo and canonical mode disabled. Caller must restore termios and close the fd when done. */
2450
static int terminal_prepare_query(
101✔
2451
                int input_fd,
2452
                int output_fd,
2453
                int *ret_nonblock_fd,
2454
                struct termios *ret_saved_termios) {
2455

2456
        int r;
101✔
2457

2458
        assert(input_fd >= 0);
101✔
2459
        assert(output_fd >= 0);
101✔
2460
        assert(ret_nonblock_fd);
101✔
2461
        assert(ret_saved_termios);
101✔
2462

2463
        if (terminal_is_dumb())
101✔
2464
                return -EOPNOTSUPP;
101✔
2465

2466
        r = terminal_verify_same(input_fd, output_fd);
×
2467
        if (r < 0)
×
2468
                return log_debug_errno(r, "Called with distinct input/output fds: %m");
×
2469

2470
        /* Open a 2nd input fd, in non-blocking mode, so that we won't ever hang in read()
2471
         * should someone else process the POLLIN. Do all subsequent operations on the new fd. */
2472
        _cleanup_close_ int nonblock_input_fd = r = fd_reopen(input_fd, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
101✔
2473
        if (r < 0)
×
2474
                return r;
2475

2476
        if (tcgetattr(nonblock_input_fd, ret_saved_termios) < 0)
×
2477
                return log_debug_errno(errno, "Failed to get terminal settings: %m");
×
2478

2479
        struct termios new_termios = *ret_saved_termios;
×
2480
        termios_disable_echo(&new_termios);
×
2481

2482
        if (tcsetattr(nonblock_input_fd, TCSANOW, &new_termios) < 0)
×
2483
                return log_debug_errno(errno, "Failed to set new terminal settings: %m");
×
2484

2485
        *ret_nonblock_fd = TAKE_FD(nonblock_input_fd);
×
2486
        return 0;
×
2487
}
2488

2489
/*
2490
 * See https://terminalguide.namepad.de/seq/csi_st-18/,
2491
 * https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Functions-using-CSI-_-ordered-by-the-final-character_s_.
2492
 */
2493
#define CSI18_Q  "\x1B[18t"               /* Report the size of the text area in characters */
2494
#define CSI18_Rp "\x1B[8;"                /* Reply prefix */
2495
#define CSI18_R0 CSI18_Rp "1;1t"          /* Shortest reply */
2496
#define CSI18_R1 CSI18_Rp "32766;32766t"  /* Longest reply */
2497

2498
static int scan_text_area_size_response(
×
2499
                const char *buf,
2500
                size_t size,
2501
                unsigned *ret_rows,
2502
                unsigned *ret_columns) {
2503

2504
        assert(buf);
×
2505
        assert(ret_rows);
×
2506
        assert(ret_columns);
×
2507

2508
        /* Check if we have enough space for the shortest possible answer. */
2509
        if (size < STRLEN(CSI18_R0))
×
2510
                return -EAGAIN;
×
2511

2512
        /* Check if the terminating sequence is present */
2513
        if (buf[size - 1] != 't')
×
2514
                return -EAGAIN;
2515

2516
        unsigned short rows, columns;
×
2517
        if (sscanf(buf, CSI18_Rp "%hu;%hut", &rows, &columns) != 2)
×
2518
                return -EINVAL;
2519

2520
        *ret_rows = rows;
×
2521
        *ret_columns = columns;
×
2522
        return 0;
×
2523
}
2524

2525
/* Determine terminal dimensions by means of an ANSI CSI 18 sequence.
2526
 *
2527
 * Caller must have already opened a non-blocking input fd and configured termios (echo/icanon off). */
2528
static int terminal_query_size_by_csi18(
×
2529
                int nonblock_input_fd,
2530
                int output_fd,
2531
                unsigned *ret_rows,
2532
                unsigned *ret_columns) {
2533

2534
        int r;
×
2535

2536
        assert(nonblock_input_fd >= 0);
×
2537
        assert(output_fd >= 0);
×
2538

2539
        r = loop_write(output_fd, CSI18_Q, SIZE_MAX);
×
2540
        if (r < 0)
×
2541
                return r;
×
2542

2543
        usec_t end = usec_add(now(CLOCK_MONOTONIC), CONSOLE_REPLY_WAIT_USEC);
×
2544
        char buf[STRLEN(CSI18_R1)];
×
2545
        size_t bytes = 0;
×
2546

2547
        for (;;) {
×
2548
                usec_t n = now(CLOCK_MONOTONIC);
×
2549
                if (n >= end)
×
2550
                        return -EOPNOTSUPP;
2551

2552
                r = fd_wait_for_event(nonblock_input_fd, POLLIN, usec_sub_unsigned(end, n));
×
2553
                if (r < 0)
×
2554
                        return r;
2555
                if (r == 0)
×
2556
                        return -EOPNOTSUPP;
2557

2558
                /* On the first read, read multiple characters, i.e. the shortest valid reply. Afterwards
2559
                 * read byte by byte, since we don't want to read too much and drop characters from the input
2560
                 * queue. */
2561
                ssize_t l = read(nonblock_input_fd, buf + bytes, bytes == 0 ? STRLEN(CSI18_R0) : 1);
×
2562
                if (l < 0) {
×
2563
                        if (errno == EAGAIN)
×
2564
                                continue;
×
2565
                        return -errno;
×
2566
                }
2567

2568
                assert((size_t) l <= sizeof(buf) - bytes);
×
2569
                bytes += l;
×
2570

2571
                r = scan_text_area_size_response(buf, bytes, ret_rows, ret_columns);
×
2572
                if (r != -EAGAIN)
×
2573
                        return r;
2574

2575
                if (bytes == sizeof(buf))
×
2576
                        return -EOPNOTSUPP; /* The response has the right prefix, but we didn't find a valid
2577
                                             * answer with a terminator in the allotted space. Something is
2578
                                             * wrong, possibly some unrelated bytes got injected into the
2579
                                             * answer. */
2580
        }
2581
}
2582

2583
int terminal_get_size(
101✔
2584
                int input_fd,
2585
                int output_fd,
2586
                unsigned *ret_rows,
2587
                unsigned *ret_columns,
2588
                bool try_dsr,
2589
                bool try_csi18) {
2590

2591
        _cleanup_close_ int nonblock_input_fd = -EBADF;
101✔
2592
        struct termios old_termios = TERMIOS_NULL;
101✔
2593
        CLEANUP_TERMIOS_RESET(nonblock_input_fd, old_termios);
101✔
2594
        int r;
101✔
2595

2596
        assert(try_dsr || try_csi18);
101✔
2597

2598
        r = terminal_prepare_query(input_fd, output_fd, &nonblock_input_fd, &old_termios);
101✔
2599
        if (r < 0)
101✔
2600
                return r;
2601

2602
        /* Flush any stale input that might confuse the response parsers. */
2603
        (void) tcflush(nonblock_input_fd, TCIFLUSH);
×
2604

2605
        if (try_csi18) {
×
2606
                r = terminal_query_size_by_csi18(nonblock_input_fd, output_fd, ret_rows, ret_columns);
×
2607
                if (r >= 0)
×
2608
                        return r;
2609

2610
                /* Query failed. Flush any outstanding input. */
2611
                (void) tcflush(nonblock_input_fd, TCIFLUSH);
×
2612

2613
                if (!IN_SET(r, -EOPNOTSUPP, -EINVAL))
×
2614
                        return r;
2615
        }
2616

2617
        if (try_dsr) {
×
2618
                r = terminal_query_size_by_dsr(nonblock_input_fd, output_fd, ret_rows, ret_columns);
×
2619
                if (r >= 0)
×
2620
                        return r;
2621

2622
                /* Query failed. Flush any outstanding input. */
2623
                (void) tcflush(nonblock_input_fd, TCIFLUSH);
×
2624
        }
2625

2626
        return r;
2627
}
2628

2629
int terminal_fix_size(int input_fd, int output_fd) {
1✔
2630
        unsigned rows, columns;
1✔
2631
        int r;
1✔
2632

2633
        /* Tries to update the current terminal dimensions to the ones reported via ANSI sequences.
2634
         *
2635
         * Why bother with this? The ioctl() information is often incorrect on serial terminals (since
2636
         * there's no handshake or protocol to determine the right dimensions in RS232), but since the ANSI
2637
         * sequences are interpreted by the final terminal instead of an intermediary tty driver they should
2638
         * be more accurate.
2639
         */
2640

2641
        struct winsize ws = {};
1✔
2642
        if (ioctl(output_fd, TIOCGWINSZ, &ws) < 0)
1✔
2643
                return log_debug_errno(errno, "Failed to query terminal dimensions, ignoring: %m");
1✔
2644

2645
        r = terminal_get_size(input_fd, output_fd, &rows, &columns, /* try_dsr= */ true, /* try_csi18= */ true);
×
2646
        if (r < 0)
×
2647
                return log_debug_errno(r, "Failed to acquire terminal dimensions via ANSI sequences, not adjusting terminal dimensions: %m");
×
2648

2649
        if (ws.ws_row == rows && ws.ws_col == columns) {
×
2650
                log_debug("Terminal dimensions reported via ANSI sequences match currently set terminal dimensions, not changing.");
×
2651
                return 0;
×
2652
        }
2653

2654
        ws.ws_col = columns;
×
2655
        ws.ws_row = rows;
×
2656

2657
        if (ioctl(output_fd, TIOCSWINSZ, &ws) < 0)
×
2658
                return log_debug_errno(errno, "Failed to update terminal dimensions, ignoring: %m");
×
2659

2660
        log_debug("Fixed terminal dimensions to %ux%u based on ANSI sequence information.", columns, rows);
×
2661
        return 1;
2662
}
2663

2664
#define MAX_TERMINFO_LENGTH 64
2665
/* python -c 'print("".join(hex(ord(i))[2:] for i in "name").upper())' */
2666
#define DCS_TERMINFO_Q ANSI_DCS "+q" "6E616D65" ANSI_ST
2667
/* The answer is either 0+r… (invalid) or 1+r… (OK). */
2668
#define DCS_TERMINFO_R0 ANSI_DCS "0+r" ANSI_ST
2669
#define DCS_TERMINFO_R1 ANSI_DCS "1+r" "6E616D65" "=" /* This is followed by Pt ST. */
2670
assert_cc(STRLEN(DCS_TERMINFO_R0) <= STRLEN(DCS_TERMINFO_R1 ANSI_ST));
2671

2672
static int scan_terminfo_response(
×
2673
                const char *buf,
2674
                size_t size,
2675
                char **ret_name) {
2676
        int r;
×
2677

2678
        assert(buf);
×
2679
        assert(ret_name);
×
2680

2681
        /* Check if we have enough space for the shortest possible answer. */
2682
        if (size < STRLEN(DCS_TERMINFO_R0))
×
2683
                return -EAGAIN;
×
2684

2685
        /* Check if the terminating sequence is present */
2686
        if (memcmp(buf + size - STRLEN(ANSI_ST), ANSI_ST, STRLEN(ANSI_ST)) != 0)
×
2687
                return -EAGAIN;
2688

2689
        if (size <= STRLEN(DCS_TERMINFO_R1 ANSI_ST))
×
2690
                return -EINVAL;  /* The answer is invalid or empty */
2691

2692
        if (memcmp(buf, DCS_TERMINFO_R1, STRLEN(DCS_TERMINFO_R1)) != 0)
×
2693
                return -EINVAL;  /* The answer is not valid */
2694

2695
        _cleanup_free_ void *dec = NULL;
×
2696
        size_t dec_size;
×
2697
        r = unhexmem_full(buf + STRLEN(DCS_TERMINFO_R1), size - STRLEN(DCS_TERMINFO_R1 ANSI_ST),
×
2698
                          /* secure= */ false,
2699
                          &dec, &dec_size);
2700
        if (r < 0)
×
2701
                return r;
2702

2703
        assert(((const char *) dec)[dec_size] == '\0'); /* unhexmem appends NUL for our convenience */
×
2704
        if (memchr(dec, '\0', dec_size) || string_has_cc(dec, NULL) || !filename_is_valid(dec))
×
2705
                return -EUCLEAN;
2706

2707
        *ret_name = TAKE_PTR(dec);
×
2708
        return 0;
×
2709
}
2710

2711
int terminal_get_terminfo_by_dcs(int fd, char **ret_name) {
2✔
2712
        int r;
2✔
2713

2714
        assert(fd >= 0);
2✔
2715
        assert(ret_name);
2✔
2716

2717
        /* Note: fd must be in non-blocking read-write mode! */
2718

2719
        struct termios old_termios = TERMIOS_NULL;
2✔
2720
        CLEANUP_TERMIOS_RESET(fd, old_termios);
2✔
2721

2722
        if (tcgetattr(fd, &old_termios) < 0)
2✔
2723
                return -errno;
1✔
2724

2725
        struct termios new_termios = old_termios;
1✔
2726
        termios_disable_echo(&new_termios);
1✔
2727

2728
        if (tcsetattr(fd, TCSANOW, &new_termios) < 0)
1✔
2729
                return -errno;
×
2730

2731
        r = loop_write(fd, DCS_TERMINFO_Q, SIZE_MAX);
1✔
2732
        if (r < 0)
1✔
2733
                return r;
2734

2735
        usec_t end = usec_add(now(CLOCK_MONOTONIC), CONSOLE_REPLY_WAIT_USEC);
1✔
2736
        char buf[STRLEN(DCS_TERMINFO_R1) + MAX_TERMINFO_LENGTH + STRLEN(ANSI_ST)];
1✔
2737
        size_t bytes = 0;
1✔
2738

2739
        for (;;) {
1✔
2740
                usec_t n = now(CLOCK_MONOTONIC);
1✔
2741
                if (n >= end)
1✔
2742
                        return -EOPNOTSUPP;
2743

2744
                r = fd_wait_for_event(fd, POLLIN, usec_sub_unsigned(end, n));
2✔
2745
                if (r < 0)
1✔
2746
                        return r;
2747
                if (r == 0)
1✔
2748
                        return -EOPNOTSUPP;
2749

2750
                /* On the first read, read multiple characters, i.e. the shortest valid reply. Afterwards
2751
                 * read byte by byte, since we don't want to read too much and drop characters from the input
2752
                 * queue. */
2753
                ssize_t l = read(fd, buf + bytes, bytes == 0 ? STRLEN(DCS_TERMINFO_R0) : 1);
×
2754
                if (l < 0) {
×
2755
                        if (errno == EAGAIN)
×
2756
                                continue;
×
2757
                        return -errno;
×
2758
                }
2759

2760
                assert((size_t) l <= sizeof(buf) - bytes);
×
2761
                bytes += l;
×
2762

2763
                r = scan_terminfo_response(buf, bytes, ret_name);
×
2764
                if (r != -EAGAIN)
×
2765
                        return r;
2766

2767
                if (bytes == sizeof(buf))
×
2768
                        return -EOPNOTSUPP; /* The response has the right prefix, but we didn't find a valid
2769
                                             * answer with a terminator in the allotted space. Something is
2770
                                             * wrong, possibly some unrelated bytes got injected into the
2771
                                             * answer. */
2772
        }
2773
}
2774

2775
int have_terminfo_file(const char *name) {
5✔
2776
        /* This is a heuristic check if we have the file, using the directory layout used on
2777
         * current Linux systems. Checks for other layouts can be added later if appropriate. */
2778
        int r;
5✔
2779

2780
        assert(filename_is_valid(name));
5✔
2781

2782
        _cleanup_free_ char *p = path_join("/usr/share/terminfo", CHAR_TO_STR(name[0]), name);
10✔
2783
        if (!p)
5✔
2784
                return log_oom_debug();
×
2785

2786
        r = RET_NERRNO(access(p, F_OK));
5✔
2787
        if (r == -ENOENT)
1✔
2788
                return false;
2789
        if (r < 0)
4✔
2790
                return r;
×
2791
        return true;
2792
}
2793

2794
int query_term_for_tty(const char *tty, char **ret_term) {
49✔
2795
        _cleanup_free_ char *dcs_term = NULL;
49✔
2796
        int r;
49✔
2797

2798
        assert(tty);
49✔
2799
        assert(ret_term);
49✔
2800

2801
        if (tty_is_vc_resolve(tty))
49✔
2802
                return strdup_to(ret_term, "linux");
46✔
2803

2804
        /* Try to query the terminal implementation that we're on. This will not work in all
2805
         * cases, which is fine, since this is intended to be used as a fallback. */
2806

2807
        _cleanup_close_ int tty_fd = open_terminal(tty, O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK);
52✔
2808
        if (tty_fd < 0)
3✔
2809
                return log_debug_errno(tty_fd, "Failed to open %s to query terminfo: %m", tty);
2✔
2810

2811
        r = terminal_get_terminfo_by_dcs(tty_fd, &dcs_term);
1✔
2812
        if (r < 0)
1✔
2813
                return log_debug_errno(r, "Failed to query %s for terminfo: %m", tty);
1✔
2814

2815
        r = have_terminfo_file(dcs_term);
×
2816
        if (r < 0)
×
2817
                return log_debug_errno(r, "Failed to look for terminfo %s: %m", dcs_term);
×
2818
        if (r == 0)
×
2819
                return log_info_errno(SYNTHETIC_ERRNO(ENODATA),
×
2820
                                      "Terminfo %s not found for %s.", dcs_term, tty);
2821

2822
        *ret_term = TAKE_PTR(dcs_term);
×
2823
        return 0;
×
2824
}
2825

2826
int terminal_is_pty_fd(int fd) {
3✔
2827
        int r;
3✔
2828

2829
        assert(fd >= 0);
3✔
2830

2831
        /* Returns true if we are looking at a pty, i.e. if it's backed by the /dev/pts/ file system */
2832

2833
        if (!isatty_safe(fd))
3✔
2834
                return false;
3✔
2835

2836
        r = is_fs_type_at(fd, NULL, DEVPTS_SUPER_MAGIC);
2✔
2837
        if (r != 0)
2✔
2838
                return r;
2839

2840
        /* The ptmx device is weird, it exists twice, once inside and once outside devpts. To detect the
2841
         * latter case, let's fire off an ioctl() that only works on ptmx devices. */
2842

2843
        int v;
×
2844
        if (ioctl(fd, TIOCGPKT, &v) < 0) {
×
2845
                if (ERRNO_IS_NOT_SUPPORTED(errno))
×
2846
                        return false;
2847

2848
                return -errno;
×
2849
        }
2850

2851
        return true;
2852
}
2853

2854
int pty_open_peer(int fd, int mode) {
27✔
2855
        assert(fd >= 0);
27✔
2856

2857
        /* Opens the peer PTY using the new race-free TIOCGPTPEER ioctl() (kernel 4.13).
2858
         *
2859
         * This is safe to be called on TTYs from other namespaces. */
2860

2861
        assert((mode & (O_CREAT|O_PATH|O_DIRECTORY|O_TMPFILE)) == 0);
27✔
2862

2863
        /* This replicates the EIO retry logic of open_terminal() in a modified way. */
2864
        for (unsigned c = 0;; c++) {
×
2865
                int peer_fd = ioctl(fd, TIOCGPTPEER, mode);
27✔
2866
                if (peer_fd >= 0)
27✔
2867
                        return peer_fd;
2868

2869
                if (errno != EIO)
×
2870
                        return -errno;
×
2871

2872
                /* Max 1s in total */
2873
                if (c >= 20)
×
2874
                        return -EIO;
2875

2876
                (void) usleep_safe(50 * USEC_PER_MSEC);
×
2877
        }
2878
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc