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

systemd / systemd / 29790165204

20 Jul 2026 10:30PM UTC coverage: 72.837% (-0.05%) from 72.889%
29790165204

push

github

yuwata
hwdb: classify PlayStation controller audio as controller form-factor

Add Sony PlayStation controller entries to 70-sound-card.hwdb so that their
ALSA sound devices are tagged with SOUND_FORM_FACTOR=controller:
 - DualSense (054c:0ce6)
 - DualSense Edge (054c:0df2)
 - DualShock 4 CUH-ZCT1x (054c:05c4)
 - DualShock 4 CUH-ZCT2x (054c:09cc)

These controllers expose USB audio but are neither headsets nor speakers.
Pinning them in the hwdb ensures they are identified correctly before any
fallback matching occurs.

345467 of 474302 relevant lines covered (72.84%)

1388252.76 hits per line

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

46.82
/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 when reading/writing ANSI sequences from/to the console */
48
#define CONSOLE_ANSI_SEQUENCE_TIMEOUT_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) {
10,267,024✔
57
        assert(fd >= 0);
10,267,024✔
58

59
        if (isatty(fd))
10,267,024✔
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)
10,219,048✔
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));
10,219,039✔
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) {
7✔
238
        _cleanup_free_ char *found = NULL;
7✔
239
        bool partial = false;
7✔
240

241
        assert(ret);
7✔
242

243
        string = strempty(string);
7✔
244

245
        STRV_FOREACH(c, completions) {
861✔
246

247
                /* Ignore entries that are not actually completions */
248
                if (!startswith(*c, string))
854✔
249
                        continue;
×
250

251
                /* Store first completion that matches */
252
                if (!found) {
854✔
253
                        found = strdup(*c);
4✔
254
                        if (!found)
4✔
255
                                return -ENOMEM;
256

257
                        continue;
4✔
258
                }
259

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

266
                found[n] = 0;
850✔
267
                partial = true;
850✔
268
        }
269

270
        *ret = TAKE_PTR(found);
7✔
271

272
        if (!*ret)
7✔
273
                return COMPLETION_NONE;
274
        if (partial)
4✔
275
                return COMPLETION_PARTIAL;
276

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

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

286
int ask_string_full(
7✔
287
                char **ret,
288
                const char *prefill,
289
                GetCompletionsCallback get_completions,
290
                void *userdata,
291
                const char *text, ...) {
292

293
        va_list ap;
7✔
294
        int r;
7✔
295

296
        assert(ret);
7✔
297
        assert(text);
7✔
298

299
        _cleanup_free_ char *string = NULL;
7✔
300
        size_t n = 0;
7✔
301

302
        if (prefill) {
7✔
303
                /* Prefill query with explicit data if specified */
304

305
                string = strdup(prefill);
×
306
                if (!string)
×
307
                        return -ENOMEM;
308

309
                n = strlen(string);
×
310

311
        } else if (get_completions) {
7✔
312
                /* Otherwise, figure out what string to preselect the query with */
313
                _cleanup_strv_free_ char **completions = NULL;
×
314
                r = get_completions("", GET_COMPLETIONS_PRESELECT, &completions, userdata);
7✔
315
                if (r < 0)
7✔
316
                        return r;
317

318
                CompletionResult cr = pick_completion(string, completions, &string);
7✔
319
                if (cr < 0)
7✔
320
                        return cr;
321

322
                n = strlen_ptr(string);
11✔
323
        }
324

325
        /* Output the prompt */
326
        fputs(ansi_highlight(), stdout);
14✔
327
        va_start(ap, text);
7✔
328
        vprintf(text, ap);
7✔
329
        va_end(ap);
7✔
330
        fputs(ansi_normal(), stdout);
14✔
331
        if (string)
7✔
332
                fputs(string, stdout);
4✔
333
        fflush(stdout);
7✔
334

335
        /* Do interactive logic only if stdin + stdout are connected to the same place. And yes, we could use
336
         * STDIN_FILENO and STDOUT_FILENO here, but let's be overly correct for once, after all libc allows
337
         * swapping out stdin/stdout. */
338
        int fd_input = fileno(stdin);
7✔
339
        int fd_output = fileno(stdout);
7✔
340
        struct termios old_termios = TERMIOS_NULL;
7✔
341
        CLEANUP_TERMIOS_RESET(fd_input, old_termios);
7✔
342

343
        if (fd_input < 0 || fd_output < 0 || same_fd(fd_input, fd_output) <= 0)
7✔
344
                goto fallback;
7✔
345

346
        /* Try to disable echo, which also tells us if this even is a terminal */
347
        if (tcgetattr(fd_input, &old_termios) < 0) {
×
348
                old_termios = TERMIOS_NULL;
×
349
                goto fallback;
×
350
        }
351

352
        struct termios new_termios = old_termios;
×
353
        termios_disable_echo(&new_termios);
×
354
        if (tcsetattr(fd_input, TCSANOW, &new_termios) < 0)
×
355
                return -errno;
×
356

357
        for (;;) {
×
358
                int c = fgetc(stdin);
×
359

360
                /* On EOF or NUL, end the request, don't output anything anymore */
361
                if (IN_SET(c, EOF, 0))
×
362
                        break;
363

364
                /* On Return also end the request, but make this visible */
365
                if (IN_SET(c, '\n', '\r')) {
×
366
                        fputc('\n', stdout);
×
367
                        break;
368
                }
369

370
                if (c == '\t') {
×
371
                        /* Tab */
372

373
                        _cleanup_strv_free_ char **completions = NULL;
×
374
                        if (get_completions) {
×
375
                                r = get_completions(string, /* flags= */ 0, &completions, userdata);
×
376
                                if (r < 0)
×
377
                                        return r;
378
                        }
379

380
                        _cleanup_free_ char *new_string = NULL;
×
381
                        CompletionResult cr = pick_completion(string, completions, &new_string);
×
382
                        if (cr < 0)
×
383
                                return cr;
384
                        if (IN_SET(cr, COMPLETION_PARTIAL, COMPLETION_FULL)) {
×
385
                                /* Output the new suffix we learned */
386
                                fputs(ASSERT_PTR(startswith(new_string, strempty(string))), stdout);
×
387

388
                                /* And update the whole string */
389
                                free_and_replace(string, new_string);
×
390
                                n = strlen(string);
×
391
                        }
392
                        if (cr == COMPLETION_NONE)
×
393
                                fputc('\a', stdout); /* BEL */
×
394

395
                        if (IN_SET(cr, COMPLETION_PARTIAL, COMPLETION_ALREADY)) {
×
396
                                /* If this worked only partially, or if the user hit TAB even though we were
397
                                 * complete already, then show the remaining options (in the latter case just
398
                                 * the one). */
399
                                fputc('\n', stdout);
×
400

401
                                _cleanup_strv_free_ char **filtered = strv_filter_prefix(completions, string);
×
402
                                if (!filtered)
×
403
                                        return -ENOMEM;
404

405
                                r = show_menu(filtered,
×
406
                                              /* n_columns= */ SIZE_MAX,
407
                                              /* column_width= */ SIZE_MAX,
408
                                              /* ellipsize_percentage= */ 0,
409
                                              /* grey_prefix= */ string,
410
                                              /* with_numbers= */ false);
411
                                if (r < 0)
×
412
                                        return r;
413

414
                                /* Show the prompt again */
415
                                fputs(ansi_highlight(), stdout);
×
416
                                va_start(ap, text);
×
417
                                vprintf(text, ap);
×
418
                                va_end(ap);
×
419
                                fputs(ansi_normal(), stdout);
×
420
                                fputs(string, stdout);
×
421
                        }
422

423
                } else if (IN_SET(c, '\b', 127)) {
×
424
                        /* Backspace */
425

426
                        if (n == 0)
×
427
                                fputc('\a', stdout); /* BEL */
×
428
                        else {
429
                                size_t m = utf8_last_length(string, n);
×
430

431
                                char *e = string + n - m;
×
432
                                clear_by_backspace(utf8_console_width(e));
×
433

434
                                *e = 0;
×
435
                                n -= m;
×
436
                        }
437

438
                } else if (c == 21) {
×
439
                        /* Ctrl-u → erase all input */
440

441
                        clear_by_backspace(utf8_console_width(string));
×
442
                        if (string)
×
443
                                string[n = 0] = 0;
×
444
                        else
445
                                assert(n == 0);
×
446

447
                } else if (c == 4) {
×
448
                        /* Ctrl-d → cancel this field input */
449

450
                        return -ECANCELED;
451

452
                } else if (char_is_cc(c) || n >= LINE_MAX)
×
453
                        /* refuse control characters and too long strings */
454
                        fputc('\a', stdout); /* BEL */
×
455
                else {
456
                        /* Regular char */
457

458
                        if (!GREEDY_REALLOC(string, n+2))
×
459
                                return -ENOMEM;
460

461
                        string[n++] = (char) c;
×
462
                        string[n] = 0;
×
463

464
                        fputc(c, stdout);
×
465
                }
466

467
                fflush(stdout);
×
468
        }
469

470
        if (!string) {
×
471
                string = strdup("");
×
472
                if (!string)
×
473
                        return -ENOMEM;
474
        }
475

476
        *ret = TAKE_PTR(string);
×
477
        return 0;
×
478

479
fallback:
7✔
480
        /* A simple fallback without TTY magic */
481
        string = mfree(string);
7✔
482
        r = read_line(stdin, LONG_LINE_MAX, &string);
7✔
483
        if (r < 0)
7✔
484
                return r;
485
        if (r == 0)
7✔
486
                return -EIO;
487

488
        *ret = TAKE_PTR(string);
7✔
489
        return 0;
7✔
490
}
491

492
bool any_key_to_proceed(void) {
×
493

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

498
        fputc('\n', stdout);
×
499
        fputs(ansi_highlight_magenta(), stdout);
×
500
        fputs("-- Press any key to proceed --", stdout);
×
501
        fputs(ansi_normal(), stdout);
×
502
        fputc('\n', stdout);
×
503
        fflush(stdout);
×
504

505
        char key = 0;
×
506
        (void) read_one_char(stdin, &key, USEC_INFINITY, /* echo= */ false, /* need_nl= */ NULL);
×
507

508
        fputc('\n', stdout);
×
509
        fflush(stdout);
×
510

511
        return key != 'q';
×
512
}
513

514
static size_t widest_list_element(char *const*l) {
16✔
515
        size_t w = 0;
16✔
516

517
        /* Returns the largest console width of all elements in 'l' */
518

519
        STRV_FOREACH(i, l)
85✔
520
                w = MAX(w, utf8_console_width(*i));
69✔
521

522
        return w;
16✔
523
}
524

525
int show_menu(char **x,
17✔
526
              size_t n_columns,
527
              size_t column_width,
528
              unsigned ellipsize_percentage,
529
              const char *grey_prefix,
530
              bool with_numbers) {
531

532
        assert(n_columns > 0);
17✔
533
        assert(column_width > 0);
17✔
534

535
        if (n_columns == SIZE_MAX)
17✔
536
                n_columns = 3;
×
537

538
        if (column_width == SIZE_MAX) {
17✔
539
                size_t widest = widest_list_element(x);
16✔
540

541
                /* If not specified, derive column width from screen width */
542
                size_t column_max = (columns()-1) / n_columns;
16✔
543

544
                /* Subtract room for numbers */
545
                if (with_numbers)
16✔
546
                        column_max = LESS_BY(column_max, 6U);
14✔
547

548
                /* If columns would get too tight let's make this a linear list instead. */
549
                if (column_max < 10 && widest > 10) {
16✔
550
                        n_columns = 1;
1✔
551
                        column_max = columns()-1;
1✔
552

553
                        if (with_numbers)
1✔
554
                                column_max = LESS_BY(column_max, 6U);
1✔
555
                }
556

557
                /* Never make this narrower than 10 characters */
558
                column_max = MAX(column_max, 10U);
16✔
559
                column_width = CLAMP(widest+1, 10U, column_max);
16✔
560
        }
561

562
        size_t n = strv_length(x);
17✔
563
        size_t per_column = DIV_ROUND_UP(n, n_columns);
17✔
564

565
        size_t break_lines = lines();
17✔
566
        if (break_lines > 2)
17✔
567
                break_lines--;
17✔
568

569
        /* The first page gets two extra lines, since we want to show a title */
570
        size_t break_modulo = break_lines;
17✔
571
        if (break_modulo > 3)
17✔
572
                break_modulo -= 3;
17✔
573

574
        for (size_t i = 0; i < per_column; i++) {
54✔
575

576
                for (size_t j = 0; j < n_columns; j++) {
110✔
577
                        _cleanup_free_ char *e = NULL;
73✔
578

579
                        size_t p = j * per_column + i;
89✔
580
                        if (p >= n)
89✔
581
                                break;
582

583
                        e = ellipsize(x[p], column_width, ellipsize_percentage);
73✔
584
                        if (!e)
73✔
585
                                return -ENOMEM;
×
586

587
                        if (with_numbers)
73✔
588
                                printf("%s%4zu)%s ",
132✔
589
                                       ansi_grey(),
590
                                       p + 1,
591
                                       ansi_normal());
592

593
                        if (grey_prefix && startswith(e, grey_prefix)) {
73✔
594
                                size_t k = MIN(strlen(grey_prefix), column_width);
3✔
595
                                printf("%s%.*s%s",
6✔
596
                                       ansi_grey(),
597
                                       (int) k, e,
598
                                       ansi_normal());
599
                                printf("%-*s",
3✔
600
                                       (int) (column_width - k), e+k);
3✔
601
                        } else
602
                                printf("%-*s", (int) column_width, e);
70✔
603
                }
604

605
                putchar('\n');
37✔
606

607
                /* on the first screen we reserve 2 extra lines for the title */
608
                if (i % break_lines == break_modulo)
37✔
609
                        if (!any_key_to_proceed())
×
610
                                return 0;
611
        }
612

613
        return 0;
614
}
615

616
int open_terminal(const char *name, int mode) {
46,416✔
617
        _cleanup_close_ int fd = -EBADF;
46,416✔
618

619
        /*
620
         * If a TTY is in the process of being closed opening it might cause EIO. This is horribly awful, but
621
         * unlikely to be changed in the kernel. Hence we work around this problem by retrying a couple of
622
         * times.
623
         *
624
         * https://bugs.launchpad.net/ubuntu/+source/linux/+bug/554172/comments/245
625
         */
626

627
        assert((mode & (O_CREAT|O_PATH|O_DIRECTORY|O_TMPFILE)) == 0);
46,416✔
628

629
        for (unsigned c = 0;; c++) {
×
630
                fd = open(name, mode, 0);
46,416✔
631
                if (fd >= 0)
46,416✔
632
                        break;
633

634
                if (errno != EIO)
177✔
635
                        return -errno;
177✔
636

637
                /* Max 1s in total */
638
                if (c >= 20)
×
639
                        return -EIO;
640

641
                (void) usleep_safe(50 * USEC_PER_MSEC);
×
642
        }
643

644
        if (!isatty_safe(fd))
46,239✔
645
                return -ENOTTY;
1✔
646

647
        return TAKE_FD(fd);
648
}
649

650
int acquire_terminal(
203✔
651
                const char *name,
652
                AcquireTerminalFlags flags,
653
                usec_t timeout) {
654

655
        _cleanup_close_ int notify = -EBADF, fd = -EBADF;
203✔
656
        usec_t ts = USEC_INFINITY;
203✔
657
        int r, wd = -1;
203✔
658

659
        assert(name);
203✔
660

661
        AcquireTerminalFlags mode = flags & _ACQUIRE_TERMINAL_MODE_MASK;
203✔
662
        assert(IN_SET(mode, ACQUIRE_TERMINAL_TRY, ACQUIRE_TERMINAL_FORCE, ACQUIRE_TERMINAL_WAIT));
203✔
663
        assert(mode == ACQUIRE_TERMINAL_WAIT || !FLAGS_SET(flags, ACQUIRE_TERMINAL_WATCH_SIGTERM));
203✔
664

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

674
        if (mode == ACQUIRE_TERMINAL_WAIT) {
×
675
                notify = inotify_init1(IN_CLOEXEC | IN_NONBLOCK);
203✔
676
                if (notify < 0)
203✔
677
                        return -errno;
×
678

679
                wd = inotify_add_watch(notify, name, IN_CLOSE);
203✔
680
                if (wd < 0)
203✔
681
                        return -errno;
×
682

683
                if (timeout != USEC_INFINITY)
203✔
684
                        ts = now(CLOCK_MONOTONIC);
×
685
        }
686

687
        /* If we are called with ACQUIRE_TERMINAL_WATCH_SIGTERM we'll unblock SIGTERM during ppoll() temporarily */
688
        sigset_t poll_ss;
203✔
689
        assert_se(sigprocmask(SIG_SETMASK, /* newset= */ NULL, &poll_ss) >= 0);
203✔
690
        if (flags & ACQUIRE_TERMINAL_WATCH_SIGTERM) {
203✔
691
                assert_se(sigismember(&poll_ss, SIGTERM) > 0);
×
692
                assert_se(sigdelset(&poll_ss, SIGTERM) >= 0);
×
693
        }
694

695
        for (;;) {
203✔
696
                if (notify >= 0) {
203✔
697
                        r = flush_fd(notify);
203✔
698
                        if (r < 0)
203✔
699
                                return r;
×
700
                }
701

702
                /* We pass here O_NOCTTY only so that we can check the return value TIOCSCTTY and have a reliable way
703
                 * to figure out if we successfully became the controlling process of the tty */
704
                fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC);
203✔
705
                if (fd < 0)
203✔
706
                        return fd;
707

708
                /* Temporarily ignore SIGHUP, so that we don't get SIGHUP'ed if we already own the tty. */
709
                struct sigaction sa_old;
203✔
710
                assert_se(sigaction(SIGHUP, &sigaction_ignore, &sa_old) >= 0);
203✔
711

712
                /* First, try to get the tty */
713
                r = RET_NERRNO(ioctl(fd, TIOCSCTTY, mode == ACQUIRE_TERMINAL_FORCE));
203✔
714

715
                /* Reset signal handler to old value */
716
                assert_se(sigaction(SIGHUP, &sa_old, NULL) >= 0);
203✔
717

718
                /* Success? Exit the loop now! */
719
                if (r >= 0)
203✔
720
                        break;
721

722
                /* Any failure besides -EPERM? Fail, regardless of the mode. */
723
                if (r != -EPERM)
×
724
                        return r;
725

726
                if (flags & ACQUIRE_TERMINAL_PERMISSIVE) /* If we are in permissive mode, then EPERM is fine, turn this
×
727
                                                          * into a success. Note that EPERM is also returned if we
728
                                                          * already are the owner of the TTY. */
729
                        break;
730

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

734
                assert(notify >= 0);
×
735
                assert(wd >= 0);
×
736

737
                for (;;) {
×
738
                        usec_t left;
×
739
                        if (timeout == USEC_INFINITY)
×
740
                                left = USEC_INFINITY;
741
                        else {
742
                                assert(ts != USEC_INFINITY);
×
743

744
                                usec_t n = usec_sub_unsigned(now(CLOCK_MONOTONIC), ts);
×
745
                                if (n >= timeout)
×
746
                                        return -ETIMEDOUT;
×
747

748
                                left = timeout - n;
×
749
                        }
750

751
                        r = ppoll_usec_full(
×
752
                                        &(struct pollfd) {
×
753
                                                .fd = notify,
754
                                                .events = POLLIN,
755
                                        },
756
                                        /* n_fds= */ 1,
757
                                        left,
758
                                        &poll_ss);
759
                        if (r < 0)
×
760
                                return r;
761
                        if (r == 0)
×
762
                                return -ETIMEDOUT;
763

764
                        union inotify_event_buffer buffer;
×
765
                        ssize_t l;
×
766
                        l = read(notify, &buffer, sizeof(buffer));
×
767
                        if (l < 0) {
×
768
                                if (ERRNO_IS_TRANSIENT(errno))
×
769
                                        continue;
×
770

771
                                return -errno;
×
772
                        }
773

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

778
                                if (e->wd != wd || !(e->mask & IN_CLOSE)) /* Safety checks */
×
779
                                        return -EIO;
×
780
                        }
781

782
                        break;
×
783
                }
784

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

790
        return TAKE_FD(fd);
203✔
791
}
792

793
int release_terminal(void) {
86✔
794
        _cleanup_close_ int fd = -EBADF;
86✔
795
        int r;
86✔
796

797
        fd = open("/dev/tty", O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK);
86✔
798
        if (fd < 0)
86✔
799
                return -errno;
65✔
800

801
        /* Temporarily ignore SIGHUP, so that we don't get SIGHUP'ed
802
         * by our own TIOCNOTTY */
803
        struct sigaction sa_old;
21✔
804
        assert_se(sigaction(SIGHUP, &sigaction_ignore, &sa_old) >= 0);
21✔
805

806
        r = RET_NERRNO(ioctl(fd, TIOCNOTTY));
21✔
807

808
        assert_se(sigaction(SIGHUP, &sa_old, NULL) >= 0);
21✔
809

810
        return r;
811
}
812

813
int terminal_new_session(void) {
5✔
814

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

822
        if (!isatty_safe(STDIN_FILENO))
5✔
823
                return -ENXIO;
824

825
        (void) setsid();
4✔
826
        return RET_NERRNO(ioctl(STDIN_FILENO, TIOCSCTTY, 0));
4✔
827
}
828

829
void terminal_detach_session(void) {
86✔
830
        (void) setsid();
86✔
831
        (void) release_terminal();
86✔
832
}
86✔
833

834
int terminal_vhangup_fd(int fd) {
108✔
835
        assert(fd >= 0);
108✔
836
        return RET_NERRNO(ioctl(fd, TIOCVHANGUP));
108✔
837
}
838

839
int terminal_vhangup(const char *tty) {
×
840
        _cleanup_close_ int fd = -EBADF;
×
841

842
        assert(tty);
×
843

844
        fd = open_terminal(tty, O_RDWR|O_NOCTTY|O_CLOEXEC);
×
845
        if (fd < 0)
×
846
                return fd;
847

848
        return terminal_vhangup_fd(fd);
×
849
}
850

851
int vt_disallocate(const char *tty_path) {
61✔
852
        assert(tty_path);
61✔
853

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

857
        int ttynr = vtnr_from_tty(tty_path);
61✔
858
        if (ttynr > 0) {
61✔
859
                _cleanup_close_ int fd = open_terminal("/dev/tty0", O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK);
61✔
860
                if (fd < 0)
61✔
861
                        return fd;
862

863
                /* Try to deallocate */
864
                if (ioctl(fd, VT_DISALLOCATE, ttynr) >= 0)
61✔
865
                        return 0;
866
                if (errno != EBUSY)
61✔
867
                        return -errno;
×
868
        }
869

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

873
        _cleanup_close_ int fd2 = open_terminal(tty_path, O_WRONLY|O_NOCTTY|O_CLOEXEC|O_NONBLOCK);
122✔
874
        if (fd2 < 0)
61✔
875
                return fd2;
876

877
        return loop_write_full(fd2,
61✔
878
                               "\033[r"   /* clear scrolling region */
879
                               "\033[H"   /* move home */
880
                               "\033[3J"  /* clear screen including scrollback, requires Linux 2.6.40 */
881
                               "\033c",   /* reset to initial state */
882
                               SIZE_MAX,
883
                               CONSOLE_ANSI_SEQUENCE_TIMEOUT_USEC);
884
}
885

886
static int vt_default_utf8(void) {
1,006✔
887
        /* Read the default VT UTF8 setting from the kernel */
888
        return read_boolean_file("/sys/module/vt/parameters/default_utf8");
1,006✔
889
}
890

891
static int vt_reset_keyboard(int fd) {
503✔
892
        int r, kb;
503✔
893

894
        assert(fd >= 0);
503✔
895

896
        /* If we can't read the default, then default to Unicode. It's 2024 after all. */
897
        r = vt_default_utf8();
503✔
898
        if (r < 0)
503✔
899
                log_debug_errno(r, "Failed to determine kernel VT UTF-8 mode, assuming enabled: %m");
199✔
900

901
        kb = vt_default_utf8() != 0 ? K_UNICODE : K_XLATE;
503✔
902
        return RET_NERRNO(ioctl(fd, KDSKBMODE, kb));
503✔
903
}
904

905
static int terminal_reset_ioctl(int fd, bool switch_to_text) {
503✔
906
        struct termios termios;
503✔
907
        int r;
503✔
908

909
        /* Set terminal to some sane defaults */
910

911
        assert(fd >= 0);
503✔
912

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

916
        /* Disable exclusive mode, just in case */
917
        if (ioctl(fd, TIOCNXCL) < 0)
503✔
918
                log_debug_errno(errno, "TIOCNXCL ioctl failed on TTY, ignoring: %m");
2✔
919

920
        /* Switch to text mode */
921
        if (switch_to_text)
503✔
922
                if (ioctl(fd, KDSETMODE, KD_TEXT) < 0)
239✔
923
                        log_debug_errno(errno, "KDSETMODE ioctl for switching to text mode failed on TTY, ignoring: %m");
178✔
924

925
        /* Set default keyboard mode */
926
        r = vt_reset_keyboard(fd);
503✔
927
        if (r < 0)
503✔
928
                log_debug_errno(r, "Failed to reset VT keyboard, ignoring: %m");
388✔
929

930
        if (tcgetattr(fd, &termios) < 0) {
503✔
931
                r = log_debug_errno(errno, "Failed to get terminal parameters: %m");
2✔
932
                goto finish;
2✔
933
        }
934

935
        /* We only reset the stuff that matters to the software. How
936
         * hardware is set up we don't touch assuming that somebody
937
         * else will do that for us */
938

939
        termios.c_iflag &= ~(IGNBRK | BRKINT | ISTRIP | INLCR | IGNCR | IUCLC);
501✔
940
        termios.c_iflag |= ICRNL | IMAXBEL | IUTF8;
501✔
941
        termios.c_oflag |= ONLCR | OPOST;
501✔
942
        termios.c_cflag |= CREAD;
501✔
943
        termios.c_lflag = ISIG | ICANON | IEXTEN | ECHO | ECHOE | ECHOK | ECHOCTL | ECHOKE;
501✔
944

945
        termios.c_cc[VINTR]    =   03;  /* ^C */
501✔
946
        termios.c_cc[VQUIT]    =  034;  /* ^\ */
501✔
947
        termios.c_cc[VERASE]   = 0177;
501✔
948
        termios.c_cc[VKILL]    =  025;  /* ^X */
501✔
949
        termios.c_cc[VEOF]     =   04;  /* ^D */
501✔
950
        termios.c_cc[VSTART]   =  021;  /* ^Q */
501✔
951
        termios.c_cc[VSTOP]    =  023;  /* ^S */
501✔
952
        termios.c_cc[VSUSP]    =  032;  /* ^Z */
501✔
953
        termios.c_cc[VLNEXT]   =  026;  /* ^V */
501✔
954
        termios.c_cc[VWERASE]  =  027;  /* ^W */
501✔
955
        termios.c_cc[VREPRINT] =  022;  /* ^R */
501✔
956
        termios.c_cc[VEOL]     =    0;
501✔
957
        termios.c_cc[VEOL2]    =    0;
501✔
958

959
        termios.c_cc[VTIME]  = 0;
501✔
960
        termios.c_cc[VMIN]   = 1;
501✔
961

962
        r = RET_NERRNO(tcsetattr(fd, TCSANOW, &termios));
501✔
963
        if (r < 0)
×
964
                log_debug_errno(r, "Failed to set terminal parameters: %m");
×
965

966
finish:
×
967
        /* Just in case, flush all crap out */
968
        (void) tcflush(fd, TCIOFLUSH);
503✔
969

970
        return r;
503✔
971
}
972

973
int terminal_reset_ansi_seq(int fd) {
491✔
974
        _cleanup_(nonblock_resetp) int nonblock_reset = -EBADF;
×
975
        int r;
491✔
976

977
        assert(fd >= 0);
491✔
978

979
        if (getenv_terminal_is_dumb())
491✔
980
                return 0;
981

982
        r = fd_nonblock(fd, true);
×
983
        if (r < 0)
×
984
                return log_debug_errno(r, "Failed to set terminal to non-blocking mode: %m");
×
985
        if (r > 0)
×
986
                nonblock_reset = fd;
×
987

988
        r = loop_write_full(fd,
×
989
                            "\033[!p"              /* soft terminal reset */
990
                            ANSI_OSC "104" ANSI_ST /* reset color palette via OSC 104 */
991
                            ANSI_NORMAL            /* reset colors */
992
                            "\033[?7h"             /* enable line-wrapping */
993
                            "\033[1G"              /* place cursor at beginning of current line */
994
                            "\033[0J",             /* erase till end of screen */
995
                            SIZE_MAX,
996
                            CONSOLE_ANSI_SEQUENCE_TIMEOUT_USEC);
997
        if (r < 0)
×
998
                log_debug_errno(r, "Failed to reset terminal through ANSI sequences: %m");
491✔
999

1000
        return r;
1001
}
1002

1003
void reset_dev_console_fd(int fd, bool switch_to_text) {
53✔
1004
        int r;
53✔
1005

1006
        assert(fd >= 0);
53✔
1007

1008
        _cleanup_close_ int lock_fd = lock_dev_console();
53✔
1009
        if (lock_fd < 0)
53✔
1010
                log_debug_errno(lock_fd, "Failed to lock /dev/console, ignoring: %m");
×
1011

1012
        r = terminal_reset_ioctl(fd, switch_to_text);
53✔
1013
        if (r < 0)
53✔
1014
                log_warning_errno(r, "Failed to reset /dev/console, ignoring: %m");
×
1015

1016
        unsigned rows, cols;
53✔
1017
        r = proc_cmdline_tty_size("/dev/console", &rows, &cols);
53✔
1018
        if (r < 0)
53✔
1019
                log_warning_errno(r, "Failed to get /dev/console size, ignoring: %m");
×
1020
        else if (r > 0) {
53✔
1021
                r = terminal_set_size_fd(fd, NULL, rows, cols);
34✔
1022
                if (r < 0)
34✔
1023
                        log_warning_errno(r, "Failed to set configured terminal size on /dev/console, ignoring: %m");
×
1024
        } else
1025
                (void) terminal_fix_size(fd, fd);
19✔
1026

1027
        r = terminal_reset_ansi_seq(fd);
53✔
1028
        if (r < 0)
53✔
1029
                log_warning_errno(r, "Failed to reset /dev/console using ANSI sequences, ignoring: %m");
53✔
1030
}
53✔
1031

1032
int lock_dev_console(void) {
654✔
1033
        _cleanup_close_ int fd = -EBADF;
654✔
1034
        int r;
654✔
1035

1036
        /* NB: We do not use O_NOFOLLOW here, because some container managers might place a symlink to some
1037
         * pty in /dev/console, in which case it should be fine to lock the target TTY. */
1038
        fd = open_terminal("/dev/console", O_RDONLY|O_CLOEXEC|O_NOCTTY);
654✔
1039
        if (fd < 0)
654✔
1040
                return fd;
1041

1042
        r = lock_generic(fd, LOCK_BSD, LOCK_EX);
654✔
1043
        if (r < 0)
654✔
1044
                return r;
×
1045

1046
        return TAKE_FD(fd);
1047
}
1048

1049
int make_console_stdio(void) {
×
1050
        int fd, r;
×
1051

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

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

1060
                r = make_null_stdio();
×
1061
                if (r < 0)
×
1062
                        return log_error_errno(r, "Failed to make /dev/null stdin/stdout/stderr: %m");
×
1063

1064
        } else {
1065
                reset_dev_console_fd(fd, /* switch_to_text= */ true);
×
1066

1067
                r = rearrange_stdio(fd, fd, fd); /* This invalidates 'fd' both on success and on failure. */
×
1068
                if (r < 0)
×
1069
                        return log_error_errno(r, "Failed to make terminal stdin/stdout/stderr: %m");
×
1070
        }
1071

1072
        reset_terminal_feature_caches();
×
1073
        return 0;
×
1074
}
1075

1076
static int vtnr_from_tty_raw(const char *tty, unsigned *ret) {
353✔
1077
        assert(tty);
353✔
1078

1079
        tty = skip_dev_prefix(tty);
353✔
1080

1081
        const char *e = startswith(tty, "tty");
353✔
1082
        if (!e)
353✔
1083
                return -EINVAL;
1084

1085
        return safe_atou(e, ret);
311✔
1086
}
1087

1088
int vtnr_from_tty(const char *tty) {
213✔
1089
        unsigned u;
213✔
1090
        int r;
213✔
1091

1092
        assert(tty);
213✔
1093

1094
        r = vtnr_from_tty_raw(tty, &u);
213✔
1095
        if (r < 0)
213✔
1096
                return r;
213✔
1097
        if (!vtnr_is_valid(u))
213✔
1098
                return -ERANGE;
1099

1100
        return (int) u;
213✔
1101
}
1102

1103
bool tty_is_vc(const char *tty) {
140✔
1104
        assert(tty);
140✔
1105

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

1111
        return vtnr_from_tty_raw(tty, /* ret= */ NULL) >= 0;
140✔
1112
}
1113

1114
bool tty_is_console(const char *tty) {
477✔
1115
        assert(tty);
477✔
1116

1117
        return streq(skip_dev_prefix(tty), "console");
477✔
1118
}
1119

1120
int resolve_dev_console(char **ret) {
205✔
1121
        int r;
205✔
1122

1123
        assert(ret);
205✔
1124

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

1130
        _cleanup_free_ char *chased = NULL;
205✔
1131
        r = chase("/dev/console", /* root= */ NULL, /* flags= */ 0, &chased, /* ret_fd= */ NULL);
205✔
1132
        if (r < 0)
205✔
1133
                return r;
1134
        if (!path_equal(chased, "/dev/console")) {
205✔
1135
                *ret = TAKE_PTR(chased);
81✔
1136
                return 0;
81✔
1137
        }
1138

1139
        r = path_is_read_only_fs("/sys");
124✔
1140
        if (r < 0)
124✔
1141
                return r;
1142
        if (r > 0)
124✔
1143
                return -ENOMEDIUM;
1144

1145
        _cleanup_free_ char *active = NULL;
124✔
1146
        r = read_one_line_file("/sys/class/tty/console/active", &active);
124✔
1147
        if (r < 0)
124✔
1148
                return r;
1149
        if (r == 0)
124✔
1150
                return -ENXIO;
1151

1152
        /* If multiple log outputs are configured the last one is what /dev/console points to */
1153
        const char *tty = strrchr(active, ' ');
124✔
1154
        if (tty)
124✔
1155
                tty++;
×
1156
        else
1157
                tty = active;
1158

1159
        if (streq(tty, "tty0")) {
124✔
1160
                active = mfree(active);
×
1161

1162
                /* Get the active VC (e.g. tty1) */
1163
                r = read_one_line_file("/sys/class/tty/tty0/active", &active);
×
1164
                if (r < 0)
×
1165
                        return r;
1166
                if (r == 0)
×
1167
                        return -ENXIO;
1168

1169
                tty = active;
×
1170
        }
1171

1172
        _cleanup_free_ char *path = NULL;
124✔
1173
        path = path_join("/dev", tty);
124✔
1174
        if (!path)
124✔
1175
                return -ENOMEM;
1176

1177
        *ret = TAKE_PTR(path);
124✔
1178
        return 0;
124✔
1179
}
1180

1181
int get_kernel_consoles(char ***ret) {
1✔
1182
        _cleanup_strv_free_ char **l = NULL;
×
1183
        _cleanup_free_ char *line = NULL;
1✔
1184
        int r;
1✔
1185

1186
        assert(ret);
1✔
1187

1188
        /* If /sys/ is mounted read-only this means we are running in some kind of container environment.
1189
         * In that case /sys/ would reflect the host system, not us, hence ignore the data we can read from it. */
1190
        if (path_is_read_only_fs("/sys") > 0)
1✔
1191
                goto fallback;
1✔
1192

1193
        r = read_one_line_file("/sys/class/tty/console/active", &line);
×
1194
        if (r < 0)
×
1195
                return r;
1196

1197
        for (const char *p = line;;) {
×
1198
                _cleanup_free_ char *tty = NULL, *path = NULL;
×
1199

1200
                r = extract_first_word(&p, &tty, NULL, 0);
×
1201
                if (r < 0)
×
1202
                        return r;
1203
                if (r == 0)
×
1204
                        break;
1205

1206
                if (streq(tty, "tty0")) {
×
1207
                        tty = mfree(tty);
×
1208
                        r = read_one_line_file("/sys/class/tty/tty0/active", &tty);
×
1209
                        if (r < 0)
×
1210
                                return r;
1211
                        if (r == 0) {
×
1212
                                log_debug("No VT active, skipping /dev/tty0.");
×
1213
                                continue;
×
1214
                        }
1215
                }
1216

1217
                path = path_join("/dev", tty);
×
1218
                if (!path)
×
1219
                        return -ENOMEM;
1220

1221
                if (access(path, F_OK) < 0) {
×
1222
                        log_debug_errno(errno, "Console device %s is not accessible, skipping: %m", path);
×
1223
                        continue;
×
1224
                }
1225

1226
                r = strv_consume(&l, TAKE_PTR(path));
×
1227
                if (r < 0)
×
1228
                        return r;
1229
        }
1230

1231
        if (strv_isempty(l)) {
×
1232
                log_debug("No devices found for system console");
×
1233
                goto fallback;
×
1234
        }
1235

1236
        *ret = TAKE_PTR(l);
×
1237
        return strv_length(*ret);
×
1238

1239
fallback:
1✔
1240
        r = strv_extend(&l, "/dev/console");
1✔
1241
        if (r < 0)
1✔
1242
                return r;
1243

1244
        *ret = TAKE_PTR(l);
1✔
1245
        return 0;
1✔
1246
}
1247

1248
bool tty_is_vc_resolve(const char *tty) {
57✔
1249
        _cleanup_free_ char *resolved = NULL;
57✔
1250

1251
        assert(tty);
57✔
1252

1253
        if (streq(skip_dev_prefix(tty), "console")) {
57✔
1254
                if (resolve_dev_console(&resolved) < 0)
1✔
1255
                        return false;
1256

1257
                tty = resolved;
1✔
1258
        }
1259

1260
        return tty_is_vc(tty);
57✔
1261
}
1262

1263
int fd_columns(int fd) {
1,503✔
1264
        struct winsize ws = {};
1,503✔
1265

1266
        if (fd < 0)
1,503✔
1267
                return -EBADF;
1268

1269
        if (ioctl(fd, TIOCGWINSZ, &ws) < 0)
1,503✔
1270
                return -errno;
1,503✔
1271

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

1275
        return ws.ws_col;
×
1276
}
1277

1278
int getenv_columns(void) {
1,864✔
1279
        int r;
1,864✔
1280

1281
        const char *e = getenv("COLUMNS");
1,864✔
1282
        if (!e)
1,864✔
1283
                return -ENXIO;
1,864✔
1284

1285
        unsigned c;
17✔
1286
        r = safe_atou_bounded(e, 1, USHRT_MAX, &c);
17✔
1287
        if (r < 0)
17✔
1288
                return r;
1289

1290
        return (int) c;
17✔
1291
}
1292

1293
unsigned columns(void) {
371,069✔
1294

1295
        if (cached_columns > 0)
371,069✔
1296
                return cached_columns;
369,549✔
1297

1298
        int c = getenv_columns();
1,520✔
1299
        if (c < 0) {
1,520✔
1300
                c = fd_columns(STDOUT_FILENO);
1,503✔
1301
                if (c < 0)
1,503✔
1302
                        c = 80;
1303
        }
1304

1305
        assert(c > 0);
17✔
1306

1307
        cached_columns = c;
1,520✔
1308
        return cached_columns;
1,520✔
1309
}
1310

1311
int fd_lines(int fd) {
222✔
1312
        struct winsize ws = {};
222✔
1313

1314
        if (fd < 0)
222✔
1315
                return -EBADF;
1316

1317
        if (ioctl(fd, TIOCGWINSZ, &ws) < 0)
222✔
1318
                return -errno;
222✔
1319

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

1323
        return ws.ws_row;
×
1324
}
1325

1326
unsigned lines(void) {
239✔
1327
        const char *e;
239✔
1328
        int l;
239✔
1329

1330
        if (cached_lines > 0)
239✔
1331
                return cached_lines;
×
1332

1333
        l = 0;
239✔
1334
        e = getenv("LINES");
239✔
1335
        if (e)
239✔
1336
                (void) safe_atoi(e, &l);
17✔
1337

1338
        if (l <= 0 || l > USHRT_MAX) {
239✔
1339
                l = fd_lines(STDOUT_FILENO);
222✔
1340
                if (l <= 0)
222✔
1341
                        l = 24;
222✔
1342
        }
1343

1344
        cached_lines = l;
239✔
1345
        return cached_lines;
239✔
1346
}
1347

1348
int terminal_set_size_fd(int fd, const char *ident, unsigned rows, unsigned cols) {
604✔
1349
        struct winsize ws;
604✔
1350

1351
        assert(fd >= 0);
604✔
1352

1353
        if (!ident)
604✔
1354
                ident = "TTY";
92✔
1355

1356
        if (rows == UINT_MAX && cols == UINT_MAX)
604✔
1357
                return 0;
604✔
1358

1359
        if (ioctl(fd, TIOCGWINSZ, &ws) < 0)
177✔
1360
                return log_debug_errno(errno,
×
1361
                                       "TIOCGWINSZ ioctl for getting %s size failed, not setting terminal size: %m",
1362
                                       ident);
1363

1364
        if (rows == UINT_MAX)
177✔
1365
                rows = ws.ws_row;
×
1366
        else if (rows > USHRT_MAX)
177✔
1367
                rows = USHRT_MAX;
×
1368

1369
        if (cols == UINT_MAX)
177✔
1370
                cols = ws.ws_col;
×
1371
        else if (cols > USHRT_MAX)
177✔
1372
                cols = USHRT_MAX;
×
1373

1374
        if (rows == ws.ws_row && cols == ws.ws_col)
177✔
1375
                return 0;
1376

1377
        ws.ws_row = rows;
177✔
1378
        ws.ws_col = cols;
177✔
1379

1380
        if (ioctl(fd, TIOCSWINSZ, &ws) < 0)
177✔
1381
                return log_debug_errno(errno, "TIOCSWINSZ ioctl for setting %s size failed: %m", ident);
×
1382

1383
        return 0;
1384
}
1385

1386
int proc_cmdline_tty_size(const char *tty, unsigned *ret_rows, unsigned *ret_cols) {
565✔
1387
        _cleanup_free_ char *rowskey = NULL, *rowsvalue = NULL, *colskey = NULL, *colsvalue = NULL;
565✔
1388
        unsigned rows = UINT_MAX, cols = UINT_MAX;
565✔
1389
        int r;
565✔
1390

1391
        assert(tty);
565✔
1392

1393
        if (!ret_rows && !ret_cols)
565✔
1394
                return 0;
1395

1396
        tty = skip_dev_prefix(tty);
565✔
1397
        if (path_startswith(tty, "pts/"))
565✔
1398
                return -EMEDIUMTYPE;
1399
        if (!in_charset(tty, ALPHANUMERICAL))
565✔
1400
                return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
×
1401
                                       "TTY name '%s' contains non-alphanumeric characters, not searching kernel cmdline for size.", tty);
1402

1403
        rowskey = strjoin("systemd.tty.rows.", tty);
565✔
1404
        if (!rowskey)
565✔
1405
                return -ENOMEM;
1406

1407
        colskey = strjoin("systemd.tty.columns.", tty);
565✔
1408
        if (!colskey)
565✔
1409
                return -ENOMEM;
1410

1411
        r = proc_cmdline_get_key_many(/* flags= */ 0,
565✔
1412
                                      rowskey, &rowsvalue,
1413
                                      colskey, &colsvalue);
1414
        if (r < 0)
565✔
1415
                return log_debug_errno(r, "Failed to read TTY size of %s from kernel cmdline: %m", tty);
×
1416

1417
        if (rowsvalue) {
565✔
1418
                r = safe_atou(rowsvalue, &rows);
177✔
1419
                if (r < 0)
177✔
1420
                        return log_debug_errno(r, "Failed to parse %s=%s: %m", rowskey, rowsvalue);
×
1421
        }
1422

1423
        if (colsvalue) {
565✔
1424
                r = safe_atou(colsvalue, &cols);
177✔
1425
                if (r < 0)
177✔
1426
                        return log_debug_errno(r, "Failed to parse %s=%s: %m", colskey, colsvalue);
×
1427
        }
1428

1429
        if (ret_rows)
565✔
1430
                *ret_rows = rows;
565✔
1431
        if (ret_cols)
565✔
1432
                *ret_cols = cols;
565✔
1433

1434
        return rows != UINT_MAX || cols != UINT_MAX;
565✔
1435
}
1436

1437
/* intended to be used as a SIGWINCH sighandler */
1438
void columns_lines_cache_reset(int signum) {
×
1439
        cached_columns = 0;
×
1440
        cached_lines = 0;
×
1441
}
×
1442

1443
void reset_terminal_feature_caches(void) {
41✔
1444
        cached_columns = 0;
41✔
1445
        cached_lines = 0;
41✔
1446

1447
        cached_on_tty = -1;
41✔
1448
        cached_on_dev_null = -1;
41✔
1449

1450
        reset_ansi_feature_caches();
41✔
1451
}
41✔
1452

1453
bool on_tty(void) {
5,314,689✔
1454

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

1461
        if (cached_on_tty < 0)
5,314,689✔
1462
                cached_on_tty =
39,019✔
1463
                        isatty_safe(STDOUT_FILENO) &&
39,059✔
1464
                        isatty_safe(STDERR_FILENO);
40✔
1465

1466
        return cached_on_tty;
5,314,689✔
1467
}
1468

1469
int getttyname_malloc(int fd, char **ret) {
639✔
1470
        char path[PATH_MAX]; /* PATH_MAX is counted *with* the trailing NUL byte */
639✔
1471
        int r;
639✔
1472

1473
        assert(fd >= 0);
639✔
1474
        assert(ret);
639✔
1475

1476
        r = ttyname_r(fd, path, sizeof path); /* positive error */
639✔
1477
        assert(r >= 0);
639✔
1478
        if (r == ERANGE)
639✔
1479
                return -ENAMETOOLONG;
639✔
1480
        if (r > 0)
639✔
1481
                return -r;
629✔
1482

1483
        return strdup_to(ret, skip_dev_prefix(path));
10✔
1484
}
1485

1486
int getttyname_harder(int fd, char **ret) {
31✔
1487
        _cleanup_free_ char *s = NULL;
31✔
1488
        int r;
31✔
1489

1490
        assert(ret);
31✔
1491

1492
        r = getttyname_malloc(fd, &s);
31✔
1493
        if (r < 0)
31✔
1494
                return r;
1495

1496
        if (streq(s, "tty"))
×
1497
                return get_ctty(0, NULL, ret);
×
1498

1499
        *ret = TAKE_PTR(s);
×
1500
        return 0;
×
1501
}
1502

1503
int get_ctty_devnr(pid_t pid, dev_t *ret) {
8,127✔
1504
        _cleanup_free_ char *line = NULL;
8,127✔
1505
        unsigned long ttynr;
8,127✔
1506
        const char *p;
8,127✔
1507
        int r;
8,127✔
1508

1509
        assert(pid >= 0);
8,127✔
1510

1511
        p = procfs_file_alloca(pid, "stat");
39,939✔
1512
        r = read_one_line_file(p, &line);
8,127✔
1513
        if (r < 0)
8,127✔
1514
                return r;
1515

1516
        p = strrchr(line, ')');
8,127✔
1517
        if (!p)
8,127✔
1518
                return -EIO;
1519

1520
        p++;
8,127✔
1521

1522
        if (sscanf(p, " "
8,127✔
1523
                   "%*c "  /* state */
1524
                   "%*d "  /* ppid */
1525
                   "%*d "  /* pgrp */
1526
                   "%*d "  /* session */
1527
                   "%lu ", /* ttynr */
1528
                   &ttynr) != 1)
1529
                return -EIO;
1530

1531
        if (devnum_is_zero(ttynr))
8,127✔
1532
                return -ENXIO;
1533

1534
        if (ret)
4✔
1535
                *ret = (dev_t) ttynr;
2✔
1536

1537
        return 0;
1538
}
1539

1540
int get_ctty(pid_t pid, dev_t *ret_devnr, char **ret) {
38✔
1541
        char pty[STRLEN("/dev/pts/") + DECIMAL_STR_MAX(dev_t) + 1];
38✔
1542
        _cleanup_free_ char *buf = NULL;
38✔
1543
        const char *fn = NULL, *w;
38✔
1544
        dev_t devnr;
38✔
1545
        int r;
38✔
1546

1547
        r = get_ctty_devnr(pid, &devnr);
38✔
1548
        if (r < 0)
38✔
1549
                return r;
1550

1551
        r = device_path_make_canonical(S_IFCHR, devnr, &buf);
2✔
1552
        if (r < 0) {
2✔
1553
                struct stat st;
2✔
1554

1555
                if (r != -ENOENT) /* No symlink for this in /dev/char/? */
2✔
1556
                        return r;
×
1557

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

1566
                if (stat(pty, &st) < 0) {
2✔
1567
                        if (errno != ENOENT)
×
1568
                                return -errno;
×
1569

1570
                } else if (S_ISCHR(st.st_mode) && devnr == st.st_rdev) /* Bingo! */
2✔
1571
                        fn = pty;
1572

1573
                if (!fn) {
1574
                        /* Doesn't exist, or not a PTY? Probably something similar to the PTYs which have no
1575
                         * symlink in /dev/char/. Let's return something vaguely useful. */
1576
                        r = device_path_make_major_minor(S_IFCHR, devnr, &buf);
×
1577
                        if (r < 0)
×
1578
                                return r;
1579

1580
                        fn = buf;
×
1581
                }
1582
        } else
1583
                fn = buf;
×
1584

1585
        w = path_startswith(fn, "/dev/");
2✔
1586
        if (!w)
2✔
1587
                return -EINVAL;
1588

1589
        if (ret) {
2✔
1590
                r = strdup_to(ret, w);
2✔
1591
                if (r < 0)
2✔
1592
                        return r;
1593
        }
1594

1595
        if (ret_devnr)
2✔
1596
                *ret_devnr = devnr;
×
1597

1598
        return 0;
1599
}
1600

1601
int ptsname_malloc(int fd, char **ret) {
147✔
1602
        assert(fd >= 0);
147✔
1603
        assert(ret);
147✔
1604

1605
        for (size_t l = 50;;) {
×
1606
                _cleanup_free_ char *c = NULL;
×
1607

1608
                c = new(char, l);
147✔
1609
                if (!c)
147✔
1610
                        return -ENOMEM;
1611

1612
                if (ptsname_r(fd, c, l) >= 0) {
147✔
1613
                        *ret = TAKE_PTR(c);
147✔
1614
                        return 0;
147✔
1615
                }
1616
                if (errno != ERANGE)
×
1617
                        return -errno;
×
1618

1619
                if (!MUL_ASSIGN_SAFE(&l, 2))
×
1620
                        return -ENOMEM;
1621
        }
1622
}
1623

1624
int openpt_allocate(int flags, char **ret_peer_path) {
151✔
1625
        _cleanup_close_ int fd = -EBADF;
151✔
1626
        int r;
151✔
1627

1628
        fd = posix_openpt(flags|O_NOCTTY|O_CLOEXEC);
151✔
1629
        if (fd < 0)
151✔
1630
                return -errno;
×
1631

1632
        _cleanup_free_ char *p = NULL;
151✔
1633
        if (ret_peer_path) {
151✔
1634
                r = ptsname_malloc(fd, &p);
147✔
1635
                if (r < 0)
147✔
1636
                        return r;
1637

1638
                if (!path_startswith(p, "/dev/pts/"))
147✔
1639
                        return -EINVAL;
1640
        }
1641

1642
        if (unlockpt(fd) < 0)
151✔
1643
                return -errno;
×
1644

1645
        if (ret_peer_path)
151✔
1646
                *ret_peer_path = TAKE_PTR(p);
147✔
1647

1648
        return TAKE_FD(fd);
1649
}
1650

1651
static int ptsname_namespace(int pty, char **ret) {
×
1652
        int no = -1;
×
1653

1654
        assert(pty >= 0);
×
1655
        assert(ret);
×
1656

1657
        /* Like ptsname(), but doesn't assume that the path is
1658
         * accessible in the local namespace. */
1659

1660
        if (ioctl(pty, TIOCGPTN, &no) < 0)
×
1661
                return -errno;
×
1662

1663
        if (no < 0)
×
1664
                return -EIO;
1665

1666
        if (asprintf(ret, "/dev/pts/%i", no) < 0)
×
1667
                return -ENOMEM;
×
1668

1669
        return 0;
1670
}
1671

1672
int openpt_allocate_in_namespace(
×
1673
                const PidRef *pidref,
1674
                int flags,
1675
                char **ret_peer_path) {
1676

1677
        _cleanup_close_ int pidnsfd = -EBADF, mntnsfd = -EBADF, usernsfd = -EBADF, rootfd = -EBADF, fd = -EBADF;
×
1678
        _cleanup_close_pair_ int pair[2] = EBADF_PAIR;
×
1679
        int r;
×
1680

1681
        assert(pidref);
×
1682

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

1687
        if (socketpair(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0, pair) < 0)
×
1688
                return -errno;
×
1689

1690
        r = namespace_fork(
×
1691
                        "(sd-openptns)",
1692
                        "(sd-openpt)",
1693
                        FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGKILL|FORK_WAIT,
1694
                        pidnsfd,
1695
                        mntnsfd,
1696
                        /* netns_fd= */ -EBADF,
1697
                        usernsfd,
1698
                        rootfd,
1699
                        /* ret= */ NULL);
1700
        if (r < 0)
×
1701
                return r;
1702
        if (r == 0) {
×
1703
                pair[0] = safe_close(pair[0]);
×
1704

1705
                fd = openpt_allocate(flags, /* ret_peer_path= */ NULL);
×
1706
                if (fd < 0)
×
1707
                        _exit(EXIT_FAILURE);
×
1708

1709
                if (send_one_fd(pair[1], fd, 0) < 0)
×
1710
                        _exit(EXIT_FAILURE);
×
1711

1712
                _exit(EXIT_SUCCESS);
×
1713
        }
1714

1715
        pair[1] = safe_close(pair[1]);
×
1716

1717
        fd = receive_one_fd(pair[0], 0);
×
1718
        if (fd < 0)
×
1719
                return fd;
1720

1721
        if (ret_peer_path) {
×
1722
                r = ptsname_namespace(fd, ret_peer_path);
×
1723
                if (r < 0)
×
1724
                        return r;
×
1725
        }
1726

1727
        return TAKE_FD(fd);
1728
}
1729

1730
static bool on_dev_null(void) {
47,994✔
1731
        struct stat dst, ost, est;
47,994✔
1732

1733
        if (cached_on_dev_null >= 0)
47,994✔
1734
                return cached_on_dev_null;
9,119✔
1735

1736
        if (stat("/dev/null", &dst) < 0 || fstat(STDOUT_FILENO, &ost) < 0 || fstat(STDERR_FILENO, &est) < 0)
38,875✔
1737
                cached_on_dev_null = false;
1✔
1738
        else
1739
                cached_on_dev_null = stat_inode_same(&dst, &ost) && stat_inode_same(&dst, &est);
43,351✔
1740

1741
        return cached_on_dev_null;
38,875✔
1742
}
1743

1744
bool term_env_valid(const char *term) {
×
1745
        /* Checks if the specified $TERM value is suitable for propagation, i.e. is not empty, not set to
1746
         * "unknown" (as is common in CI), and only contains characters valid in terminal type names.
1747
         * Valid $TERM values are things like "xterm-256color", "linux", "screen.xterm-256color", i.e.
1748
         * alphanumeric characters, hyphens, underscores, dots, and plus signs. */
1749
        return !isempty(term) &&
×
1750
                !streq(term, "unknown") &&
×
1751
                in_charset(term, ALPHANUMERICAL "-_+.");
×
1752
}
1753

1754
bool getenv_terminal_is_dumb(void) {
17,166✔
1755
        const char *e;
17,166✔
1756

1757
        e = getenv("TERM");
17,166✔
1758
        if (!e)
17,166✔
1759
                return true;
1760

1761
        return streq(e, "dumb");
1,671✔
1762
}
1763

1764
bool terminal_is_dumb(void) {
48,034✔
1765
        if (!on_tty() && !on_dev_null())
48,034✔
1766
                return true;
1767

1768
        return getenv_terminal_is_dumb();
75✔
1769
}
1770

1771
bool dev_console_colors_enabled(void) {
×
1772
        _cleanup_free_ char *s = NULL;
×
1773
        ColorMode m;
×
1774

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

1782
        m = parse_systemd_colors();
×
1783
        if (m >= 0)
×
1784
                return m;
×
1785

1786
        if (getenv("NO_COLOR"))
×
1787
                return false;
1788

1789
        if (getenv_for_pid(1, "TERM", &s) <= 0)
×
1790
                (void) proc_cmdline_get_key("TERM", 0, &s);
×
1791

1792
        return !streq_ptr(s, "dumb");
×
1793
}
1794

1795
int vt_restore(int fd) {
×
1796

1797
        static const struct vt_mode mode = {
×
1798
                .mode = VT_AUTO,
1799
        };
1800

1801
        int r, ret = 0;
×
1802

1803
        assert(fd >= 0);
×
1804

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

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

1811
        r = vt_reset_keyboard(fd);
×
1812
        if (r < 0)
×
1813
                RET_GATHER(ret, log_debug_errno(r, "Failed to reset keyboard mode, ignoring: %m"));
×
1814

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

1818
        r = fchmod_and_chown(fd, TTY_MODE, 0, GID_INVALID);
×
1819
        if (r < 0)
×
1820
                RET_GATHER(ret, log_debug_errno(r, "Failed to chmod()/chown() VT, ignoring: %m"));
×
1821

1822
        return ret;
1823
}
1824

1825
int vt_release(int fd, bool restore) {
×
1826
        assert(fd >= 0);
×
1827

1828
        /* This function releases the VT by acknowledging the VT-switch signal
1829
         * sent by the kernel and optionally reset the VT in text and auto
1830
         * VT-switching modes. */
1831

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

1835
        if (ioctl(fd, VT_RELDISP, 1) < 0)
×
1836
                return -errno;
×
1837

1838
        if (restore)
×
1839
                return vt_restore(fd);
×
1840

1841
        return 0;
1842
}
1843

1844
void get_log_colors(int priority, const char **on, const char **off, const char **highlight) {
307,669✔
1845
        /* Note that this will initialize output variables only when there's something to output.
1846
         * The caller must pre-initialize to "" or NULL as appropriate. */
1847

1848
        if (priority <= LOG_ERR) {
307,669✔
1849
                if (on)
10,024✔
1850
                        *on = ansi_highlight_red();
20,048✔
1851
                if (off)
10,024✔
1852
                        *off = ansi_normal();
20,048✔
1853
                if (highlight)
10,024✔
1854
                        *highlight = ansi_highlight();
×
1855

1856
        } else if (priority <= LOG_WARNING) {
297,645✔
1857
                if (on)
568✔
1858
                        *on = ansi_highlight_yellow();
568✔
1859
                if (off)
568✔
1860
                        *off = ansi_normal();
1,136✔
1861
                if (highlight)
568✔
1862
                        *highlight = ansi_highlight();
×
1863

1864
        } else if (priority <= LOG_NOTICE) {
297,077✔
1865
                if (on)
1,128✔
1866
                        *on = ansi_highlight();
2,256✔
1867
                if (off)
1,128✔
1868
                        *off = ansi_normal();
2,256✔
1869
                if (highlight)
1,128✔
1870
                        *highlight = ansi_highlight_red();
×
1871

1872
        } else if (priority >= LOG_DEBUG) {
295,949✔
1873
                if (on)
238,552✔
1874
                        *on = ansi_grey();
238,552✔
1875
                if (off)
238,552✔
1876
                        *off = ansi_normal();
477,104✔
1877
                if (highlight)
238,552✔
1878
                        *highlight = ansi_highlight_red();
×
1879
        }
1880
}
307,669✔
1881

1882
int terminal_set_cursor_position(int fd, unsigned row, unsigned column) {
×
1883
        assert(fd >= 0);
×
1884

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

1888
        return loop_write(fd, cursor_position, SIZE_MAX);
×
1889
}
1890

1891
static int terminal_verify_same(int input_fd, int output_fd) {
×
1892
        int r;
×
1893

1894
        assert(input_fd >= 0);
×
1895
        assert(output_fd >= 0);
×
1896

1897
        /* Validates that the specified fds reference the same TTY */
1898

1899
        if (input_fd != output_fd) {
×
1900
                struct stat sti;
×
1901
                if (fstat(input_fd, &sti) < 0)
×
1902
                        return -errno;
×
1903

1904
                r = stat_verify_char(&sti); /* TTYs are character devices */
×
1905
                if (r < 0)
×
1906
                        return r;
1907

1908
                struct stat sto;
×
1909
                if (fstat(output_fd, &sto) < 0)
×
1910
                        return -errno;
×
1911

1912
                r = stat_verify_char(&sto);
×
1913
                if (r < 0)
×
1914
                        return r;
1915

1916
                if (sti.st_rdev != sto.st_rdev)
×
1917
                        return -ENOLINK;
1918
        }
1919

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

1923
        return 0;
1924
}
1925

1926
typedef enum CursorPositionState {
1927
        CURSOR_TEXT,
1928
        CURSOR_ESCAPE,
1929
        CURSOR_ROW,
1930
        CURSOR_COLUMN,
1931
} CursorPositionState;
1932

1933
typedef struct CursorPositionContext {
1934
        CursorPositionState state;
1935
        unsigned row, column;
1936
} CursorPositionContext;
1937

1938
static int scan_cursor_position_response(
×
1939
                CursorPositionContext *context,
1940
                const char *buf,
1941
                size_t size,
1942
                size_t *ret_processed) {
1943

1944
        assert(context);
×
1945
        assert(buf);
×
1946
        assert(ret_processed);
×
1947

1948
        for (size_t i = 0; i < size; i++) {
×
1949
                char c = buf[i];
×
1950

1951
                switch (context->state) {
×
1952

1953
                case CURSOR_TEXT:
×
1954
                        context->state = c == '\x1B' ? CURSOR_ESCAPE : CURSOR_TEXT;
×
1955
                        break;
×
1956

1957
                case CURSOR_ESCAPE:
×
1958
                        context->state = c == '[' ? CURSOR_ROW : CURSOR_TEXT;
×
1959
                        break;
×
1960

1961
                case CURSOR_ROW:
×
1962
                        if (c == ';')
×
1963
                                context->state = context->row > 0 ? CURSOR_COLUMN : CURSOR_TEXT;
×
1964
                        else {
1965
                                int d = undecchar(c);
×
1966

1967
                                /* We read a decimal character, let's suffix it to the number we so far read,
1968
                                 * but let's do an overflow check first. */
1969
                                if (d < 0 || context->row > (UINT_MAX-d)/10)
×
1970
                                        context->state = CURSOR_TEXT;
×
1971
                                else
1972
                                        context->row = context->row * 10 + d;
×
1973
                        }
1974
                        break;
1975

1976
                case CURSOR_COLUMN:
×
1977
                        if (c == 'R') {
×
1978
                                if (context->column > 0) {
×
1979
                                        *ret_processed = i + 1;
×
1980
                                        return 1; /* success! */
×
1981
                                }
1982

1983
                                context->state = CURSOR_TEXT;
×
1984
                        } else {
1985
                                int d = undecchar(c);
×
1986

1987
                                /* As above, add the decimal character to our column number */
1988
                                if (d < 0 || context->column > (UINT_MAX-d)/10)
×
1989
                                        context->state = CURSOR_TEXT;
×
1990
                                else
1991
                                        context->column = context->column * 10 + d;
×
1992
                        }
1993

1994
                        break;
1995
                }
1996

1997
                /* Reset any positions we might have picked up */
1998
                if (IN_SET(context->state, CURSOR_TEXT, CURSOR_ESCAPE))
×
1999
                        context->row = context->column = 0;
×
2000
        }
2001

2002
        *ret_processed = size;
×
2003
        return 0; /* all good, but not enough data yet */
×
2004
}
2005

2006
int terminal_get_cursor_position(
×
2007
                int input_fd,
2008
                int output_fd,
2009
                unsigned *ret_row,
2010
                unsigned *ret_column) {
2011

2012
        _cleanup_close_ int nonblock_input_fd = -EBADF;
×
2013
        int r;
×
2014

2015
        assert(input_fd >= 0);
×
2016
        assert(output_fd >= 0);
×
2017

2018
        if (getenv_terminal_is_dumb())
×
2019
                return -EOPNOTSUPP;
2020

2021
        r = terminal_verify_same(input_fd, output_fd);
×
2022
        if (r < 0)
×
2023
                return log_debug_errno(r, "Called with distinct input/output fds: %m");
×
2024

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

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

2033
        struct termios new_termios = old_termios;
×
2034
        termios_disable_echo(&new_termios);
×
2035

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

2039
        /* Request cursor position (DSR/CPR) */
2040
        r = loop_write(output_fd, "\x1B[6n", SIZE_MAX);
×
2041
        if (r < 0)
×
2042
                return r;
2043

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

2047
        nonblock_input_fd = r = fd_reopen(input_fd, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
×
2048
        if (r < 0)
×
2049
                return r;
2050

2051
        usec_t end = usec_add(now(CLOCK_MONOTONIC), CONSOLE_ANSI_SEQUENCE_TIMEOUT_USEC);
×
2052
        char buf[STRLEN("\x1B[1;1R")]; /* The shortest valid reply possible */
×
2053
        size_t buf_full = 0;
×
2054
        CursorPositionContext context = {};
×
2055

2056
        for (bool first = true;; first = false) {
×
2057
                if (buf_full == 0) {
×
2058
                        usec_t n = now(CLOCK_MONOTONIC);
×
2059
                        if (n >= end)
×
2060
                                return -EOPNOTSUPP;
×
2061

2062
                        r = fd_wait_for_event(nonblock_input_fd, POLLIN, usec_sub_unsigned(end, n));
×
2063
                        if (r < 0)
×
2064
                                return r;
2065
                        if (r == 0)
×
2066
                                return -EOPNOTSUPP;
2067

2068
                        /* On the first try, read multiple characters, i.e. the shortest valid
2069
                         * reply. Afterwards read byte-wise, since we don't want to read too much, and
2070
                         * unnecessarily drop too many characters from the input queue. */
2071
                        ssize_t l = read(nonblock_input_fd, buf, first ? sizeof(buf) : 1);
×
2072
                        if (l < 0) {
×
2073
                                if (errno == EAGAIN)
×
2074
                                        continue;
×
2075

2076
                                return -errno;
×
2077
                        }
2078

2079
                        assert((size_t) l <= sizeof(buf));
×
2080
                        buf_full = l;
2081
                }
2082

2083
                size_t processed;
×
2084
                r = scan_cursor_position_response(&context, buf, buf_full, &processed);
×
2085
                if (r < 0)
×
2086
                        return r;
2087

2088
                assert(processed <= buf_full);
×
2089
                buf_full -= processed;
×
2090
                memmove(buf, buf + processed, buf_full);
×
2091

2092
                if (r > 0) {
×
2093
                        /* Superficial validity check */
2094
                        if (context.row >= 32766 || context.column >= 32766)
×
2095
                                return -ENODATA;
2096

2097
                        if (ret_row)
×
2098
                                *ret_row = context.row;
×
2099
                        if (ret_column)
×
2100
                                *ret_column = context.column;
×
2101

2102
                        return 0;
2103
                }
2104
        }
2105
}
2106

2107
int terminal_reset_defensive(int fd, TerminalResetFlags flags) {
457✔
2108
        int r = 0;
457✔
2109

2110
        assert(fd >= 0);
457✔
2111
        assert(!FLAGS_SET(flags, TERMINAL_RESET_AVOID_ANSI_SEQ|TERMINAL_RESET_FORCE_ANSI_SEQ));
457✔
2112

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

2116
        if (!isatty_safe(fd))
457✔
2117
                return -ENOTTY;
457✔
2118

2119
        RET_GATHER(r, terminal_reset_ioctl(fd, FLAGS_SET(flags, TERMINAL_RESET_SWITCH_TO_TEXT)));
450✔
2120

2121
        if (!FLAGS_SET(flags, TERMINAL_RESET_AVOID_ANSI_SEQ) &&
450✔
2122
            (FLAGS_SET(flags, TERMINAL_RESET_FORCE_ANSI_SEQ) || !getenv_terminal_is_dumb()))
438✔
2123
                RET_GATHER(r, terminal_reset_ansi_seq(fd));
438✔
2124

2125
        return r;
2126
}
2127

2128
int terminal_reset_defensive_locked(int fd, TerminalResetFlags flags) {
6✔
2129
        assert(fd >= 0);
6✔
2130

2131
        _cleanup_close_ int lock_fd = lock_dev_console();
6✔
2132
        if (lock_fd < 0)
6✔
2133
                log_debug_errno(lock_fd, "Failed to acquire lock for /dev/console, ignoring: %m");
×
2134

2135
        return terminal_reset_defensive(fd, flags);
6✔
2136
}
2137

2138
void termios_disable_echo(struct termios *termios) {
1✔
2139
        assert(termios);
1✔
2140

2141
        termios->c_lflag &= ~(ICANON|ECHO);
1✔
2142
        termios->c_cc[VMIN] = 1;
1✔
2143
        termios->c_cc[VTIME] = 0;
1✔
2144
}
1✔
2145

2146
static bool termios_is_null(const struct termios *t) {
9✔
2147
        if (!t)
9✔
2148
                return true;
2149

2150
        return t->c_iflag == UINT_MAX &&
17✔
2151
               t->c_oflag == UINT_MAX &&
8✔
2152
               t->c_cflag == UINT_MAX &&
26✔
2153
               t->c_lflag == UINT_MAX;
8✔
2154
}
2155

2156
void termios_reset(const TermiosResetContext *c) {
325✔
2157
        assert(c);
325✔
2158

2159
        PROTECT_ERRNO;
325✔
2160

2161
        if (c->fd && *c->fd >= 0 && !termios_is_null(c->termios))
325✔
2162
                (void) tcsetattr(*c->fd, TCSANOW, c->termios);
1✔
2163
}
325✔
2164

2165
typedef enum BackgroundColorState {
2166
        BACKGROUND_TEXT,
2167
        BACKGROUND_ESCAPE,
2168
        BACKGROUND_BRACKET,
2169
        BACKGROUND_FIRST_ONE,
2170
        BACKGROUND_SECOND_ONE,
2171
        BACKGROUND_SEMICOLON,
2172
        BACKGROUND_R,
2173
        BACKGROUND_G,
2174
        BACKGROUND_B,
2175
        BACKGROUND_RED,
2176
        BACKGROUND_GREEN,
2177
        BACKGROUND_BLUE,
2178
        BACKGROUND_STRING_TERMINATOR,
2179
} BackgroundColorState;
2180

2181
typedef struct BackgroundColorContext {
2182
        BackgroundColorState state;
2183
        uint32_t red, green, blue;
2184
        unsigned red_bits, green_bits, blue_bits;
2185
} BackgroundColorContext;
2186

2187
static int scan_background_color_response(
×
2188
                BackgroundColorContext *context,
2189
                const char *buf,
2190
                size_t size,
2191
                size_t *ret_processed) {
2192

2193
        assert(context);
×
2194
        assert(buf);
×
2195
        assert(ret_processed);
×
2196

2197
        for (size_t i = 0; i < size; i++) {
×
2198
                char c = buf[i];
×
2199

2200
                switch (context->state) {
×
2201

2202
                case BACKGROUND_TEXT:
×
2203
                        context->state = c == '\x1B' ? BACKGROUND_ESCAPE : BACKGROUND_TEXT;
×
2204
                        break;
×
2205

2206
                case BACKGROUND_ESCAPE:
×
2207
                        context->state = c == ']' ? BACKGROUND_BRACKET : BACKGROUND_TEXT;
×
2208
                        break;
×
2209

2210
                case BACKGROUND_BRACKET:
×
2211
                        context->state = c == '1' ? BACKGROUND_FIRST_ONE : BACKGROUND_TEXT;
×
2212
                        break;
×
2213

2214
                case BACKGROUND_FIRST_ONE:
×
2215
                        context->state = c == '1' ? BACKGROUND_SECOND_ONE : BACKGROUND_TEXT;
×
2216
                        break;
×
2217

2218
                case BACKGROUND_SECOND_ONE:
×
2219
                        context->state = c == ';' ? BACKGROUND_SEMICOLON : BACKGROUND_TEXT;
×
2220
                        break;
×
2221

2222
                case BACKGROUND_SEMICOLON:
×
2223
                        context->state = c == 'r' ? BACKGROUND_R : BACKGROUND_TEXT;
×
2224
                        break;
×
2225

2226
                case BACKGROUND_R:
×
2227
                        context->state = c == 'g' ? BACKGROUND_G : BACKGROUND_TEXT;
×
2228
                        break;
×
2229

2230
                case BACKGROUND_G:
×
2231
                        context->state = c == 'b' ? BACKGROUND_B : BACKGROUND_TEXT;
×
2232
                        break;
×
2233

2234
                case BACKGROUND_B:
×
2235
                        context->state = c == ':' ? BACKGROUND_RED : BACKGROUND_TEXT;
×
2236
                        break;
×
2237

2238
                case BACKGROUND_RED:
×
2239
                        if (c == '/')
×
2240
                                context->state = context->red_bits > 0 ? BACKGROUND_GREEN : BACKGROUND_TEXT;
×
2241
                        else {
2242
                                int d = unhexchar(c);
×
2243
                                if (d < 0 || context->red_bits >= sizeof(context->red)*8)
×
2244
                                        context->state = BACKGROUND_TEXT;
×
2245
                                else {
2246
                                        context->red = (context->red << 4) | d;
×
2247
                                        context->red_bits += 4;
×
2248
                                }
2249
                        }
2250
                        break;
2251

2252
                case BACKGROUND_GREEN:
×
2253
                        if (c == '/')
×
2254
                                context->state = context->green_bits > 0 ? BACKGROUND_BLUE : BACKGROUND_TEXT;
×
2255
                        else {
2256
                                int d = unhexchar(c);
×
2257
                                if (d < 0 || context->green_bits >= sizeof(context->green)*8)
×
2258
                                        context->state = BACKGROUND_TEXT;
×
2259
                                else {
2260
                                        context->green = (context->green << 4) | d;
×
2261
                                        context->green_bits += 4;
×
2262
                                }
2263
                        }
2264
                        break;
2265

2266
                case BACKGROUND_BLUE:
×
2267
                        if (c == '\x07') {
×
2268
                                if (context->blue_bits > 0) {
×
2269
                                        *ret_processed = i + 1;
×
2270
                                        return 1; /* success! */
×
2271
                                }
2272

2273
                                context->state = BACKGROUND_TEXT;
×
2274
                        } else if (c == '\x1b')
×
2275
                                context->state = context->blue_bits > 0 ? BACKGROUND_STRING_TERMINATOR : BACKGROUND_TEXT;
×
2276
                        else {
2277
                                int d = unhexchar(c);
×
2278
                                if (d < 0 || context->blue_bits >= sizeof(context->blue)*8)
×
2279
                                        context->state = BACKGROUND_TEXT;
×
2280
                                else {
2281
                                        context->blue = (context->blue << 4) | d;
×
2282
                                        context->blue_bits += 4;
×
2283
                                }
2284
                        }
2285
                        break;
2286

2287
                case BACKGROUND_STRING_TERMINATOR:
×
2288
                        if (c == '\\') {
×
2289
                                *ret_processed = i + 1;
×
2290
                                return 1; /* success! */
×
2291
                        }
2292

2293
                        context->state = c == ']' ? BACKGROUND_ESCAPE : BACKGROUND_TEXT;
×
2294
                        break;
×
2295

2296
                }
2297

2298
                /* Reset any colors we might have picked up */
2299
                if (IN_SET(context->state, BACKGROUND_TEXT, BACKGROUND_ESCAPE)) {
×
2300
                        /* reset color */
2301
                        context->red = context->green = context->blue = 0;
×
2302
                        context->red_bits = context->green_bits = context->blue_bits = 0;
×
2303
                }
2304
        }
2305

2306
        *ret_processed = size;
×
2307
        return 0; /* all good, but not enough data yet */
×
2308
}
2309

2310
int get_default_background_color(double *ret_red, double *ret_green, double *ret_blue) {
180✔
2311
        int r;
180✔
2312

2313
        assert(ret_red);
180✔
2314
        assert(ret_green);
180✔
2315
        assert(ret_blue);
180✔
2316

2317
        if (!colors_enabled())
180✔
2318
                return -EOPNOTSUPP;
180✔
2319

2320
        r = terminal_verify_same(STDIN_FILENO, STDOUT_FILENO);
×
2321
        if (r < 0)
×
2322
                return r;
2323

2324
        if (streq_ptr(getenv("TERM"), "linux")) {
×
2325
                /* Linux console is black */
2326
                *ret_red = *ret_green = *ret_blue = 0.0;
×
2327
                return 0;
×
2328
        }
2329

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

2336
        struct termios old_termios = TERMIOS_NULL;
×
2337
        CLEANUP_TERMIOS_RESET(nonblock_input_fd, old_termios);
×
2338

2339
        if (tcgetattr(nonblock_input_fd, &old_termios) < 0)
×
2340
                return -errno;
×
2341

2342
        struct termios new_termios = old_termios;
×
2343
        termios_disable_echo(&new_termios);
×
2344

2345
        if (tcsetattr(nonblock_input_fd, TCSANOW, &new_termios) < 0)
×
2346
                return -errno;
×
2347

2348
        r = loop_write(STDOUT_FILENO, ANSI_OSC "11;?" ANSI_ST, SIZE_MAX);
×
2349
        if (r < 0)
×
2350
                return r;
2351

2352
        usec_t end = usec_add(now(CLOCK_MONOTONIC), CONSOLE_ANSI_SEQUENCE_TIMEOUT_USEC);
×
2353
        char buf[STRLEN(ANSI_OSC "11;rgb:0/0/0" ANSI_ST)]; /* shortest possible reply */
×
2354
        size_t buf_full = 0;
×
2355
        BackgroundColorContext context = {};
×
2356

2357
        for (bool first = true;; first = false) {
×
2358
                if (buf_full == 0) {
×
2359
                        usec_t n = now(CLOCK_MONOTONIC);
×
2360
                        if (n >= end)
×
2361
                                return -EOPNOTSUPP;
×
2362

2363
                        r = fd_wait_for_event(nonblock_input_fd, POLLIN, usec_sub_unsigned(end, n));
×
2364
                        if (r < 0)
×
2365
                                return r;
2366
                        if (r == 0)
×
2367
                                return -EOPNOTSUPP;
2368

2369
                        /* On the first try, read multiple characters, i.e. the shortest valid
2370
                         * reply. Afterwards read byte-wise, since we don't want to read too much, and
2371
                         * unnecessarily drop too many characters from the input queue. */
2372
                        ssize_t l = read(nonblock_input_fd, buf, first ? sizeof(buf) : 1);
×
2373
                        if (l < 0) {
×
2374
                                if (errno == EAGAIN)
×
2375
                                        continue;
×
2376
                                return -errno;
×
2377
                        }
2378

2379
                        assert((size_t) l <= sizeof(buf));
×
2380
                        buf_full = l;
2381
                }
2382

2383
                size_t processed;
×
2384
                r = scan_background_color_response(&context, buf, buf_full, &processed);
×
2385
                if (r < 0)
×
2386
                        return r;
2387

2388
                assert(processed <= buf_full);
×
2389
                buf_full -= processed;
×
2390
                memmove(buf, buf + processed, buf_full);
×
2391

2392
                if (r > 0) {
×
2393
                        assert(context.red_bits > 0);
×
2394
                        *ret_red = (double) context.red / ((UINT64_C(1) << context.red_bits) - 1);
×
2395
                        assert(context.green_bits > 0);
×
2396
                        *ret_green = (double) context.green / ((UINT64_C(1) << context.green_bits) - 1);
×
2397
                        assert(context.blue_bits > 0);
×
2398
                        *ret_blue = (double) context.blue / ((UINT64_C(1) << context.blue_bits) - 1);
×
2399
                        return 0;
×
2400
                }
2401
        }
2402
}
2403

2404
/* Determine terminal dimensions by means of ANSI sequences: save the cursor via DECSC, position it far
2405
 * to the bottom right (clamped to actual terminal dimensions), read back via DSR where we ended up, and
2406
 * restore cursor via DECRC. Only needs a single DSR round-trip, and always restores the cursor regardless
2407
 * of whether the response is received.
2408
 *
2409
 * Caller must have already opened a non-blocking input fd and configured termios (echo/icanon off). */
2410
static int terminal_query_size_by_dsr(
×
2411
                int nonblock_input_fd,
2412
                int output_fd,
2413
                unsigned *ret_rows,
2414
                unsigned *ret_columns) {
2415

2416
        int r;
×
2417

2418
        assert(nonblock_input_fd >= 0);
×
2419
        assert(output_fd >= 0);
×
2420

2421
        /* Use DECSC/DECRC to save/restore cursor instead of querying position via DSR. This way the cursor
2422
         * is always restored — even on timeout — and we only need one DSR response instead of two. */
2423
        r = loop_write_full(output_fd,
×
2424
                            "\x1B" "7"              /* DECSC: save cursor position */
2425
                            "\x1B[32766;32766H"     /* CUP: position cursor far to the right and to the bottom, staying within 16bit signed range */
2426
                            "\x1B[6n"               /* DSR: request cursor position (CPR) */
2427
                            "\x1B" "8",             /* DECRC: restore cursor position */
2428
                            SIZE_MAX,
2429
                            CONSOLE_ANSI_SEQUENCE_TIMEOUT_USEC);
2430
        if (r < 0)
×
2431
                return r;
×
2432

2433
        usec_t end = usec_add(now(CLOCK_MONOTONIC), CONSOLE_ANSI_SEQUENCE_TIMEOUT_USEC);
×
2434
        char buf[STRLEN("\x1B[1;1R")]; /* The shortest valid reply possible */
×
2435
        size_t buf_full = 0;
×
2436
        CursorPositionContext context = {};
×
2437

2438
        for (bool first = true;; first = false) {
×
2439
                if (buf_full == 0) {
×
2440
                        usec_t n = now(CLOCK_MONOTONIC);
×
2441
                        if (n >= end)
×
2442
                                return -EOPNOTSUPP;
×
2443

2444
                        r = fd_wait_for_event(nonblock_input_fd, POLLIN, usec_sub_unsigned(end, n));
×
2445
                        if (r < 0)
×
2446
                                return r;
2447
                        if (r == 0)
×
2448
                                return -EOPNOTSUPP;
2449

2450
                        /* On the first try, read multiple characters, i.e. the shortest valid
2451
                         * reply. Afterwards read byte-wise, since we don't want to read too much, and
2452
                         * unnecessarily drop too many characters from the input queue. */
2453
                        ssize_t l = read(nonblock_input_fd, buf, first ? sizeof(buf) : 1);
×
2454
                        if (l < 0) {
×
2455
                                if (errno == EAGAIN)
×
2456
                                        continue;
×
2457

2458
                                return -errno;
×
2459
                        }
2460

2461
                        assert((size_t) l <= sizeof(buf));
×
2462
                        buf_full = l;
2463
                }
2464

2465
                size_t processed;
×
2466
                r = scan_cursor_position_response(&context, buf, buf_full, &processed);
×
2467
                if (r < 0)
×
2468
                        return r;
2469

2470
                assert(processed <= buf_full);
×
2471
                buf_full -= processed;
×
2472
                memmove(buf, buf + processed, buf_full);
×
2473

2474
                if (r > 0) {
×
2475
                        /* Superficial validity checks (no particular reason to check for < 4, it's
2476
                         * just a way to look for unreasonably small values) */
2477
                        if (context.row < 4 || context.column < 4 || context.row >= 32766 || context.column >= 32766)
×
2478
                                return -ENODATA;
2479

2480
                        if (ret_rows)
×
2481
                                *ret_rows = context.row;
×
2482
                        if (ret_columns)
×
2483
                                *ret_columns = context.column;
×
2484

2485
                        return 0;
2486
                }
2487
        }
2488
}
2489

2490
/* Common setup for ANSI terminal queries: validate the fds, open a non-blocking input fd, and configure
2491
 * termios with echo and canonical mode disabled. Caller must restore termios and close the fd when done. */
2492
static int terminal_prepare_query(
316✔
2493
                int input_fd,
2494
                int output_fd,
2495
                int *ret_nonblock_fd,
2496
                struct termios *ret_saved_termios) {
2497

2498
        int r;
316✔
2499

2500
        assert(input_fd >= 0);
316✔
2501
        assert(output_fd >= 0);
316✔
2502
        assert(ret_nonblock_fd);
316✔
2503
        assert(ret_saved_termios);
316✔
2504

2505
        /* Use getenv_terminal_is_dumb() instead of terminal_is_dumb() here since we operate on an
2506
         * explicitly passed fd, not on stdio. terminal_is_dumb() additionally checks on_tty() which
2507
         * tests whether *stderr* is a tty — that's irrelevant when we're querying a directly opened
2508
         * terminal such as /dev/console. */
2509
        if (getenv_terminal_is_dumb())
316✔
2510
                return -EOPNOTSUPP;
316✔
2511

2512
        r = terminal_verify_same(input_fd, output_fd);
×
2513
        if (r < 0)
×
2514
                return log_debug_errno(r, "Called with distinct input/output fds: %m");
×
2515

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

2522
        if (tcgetattr(nonblock_input_fd, ret_saved_termios) < 0)
×
2523
                return log_debug_errno(errno, "Failed to get terminal settings: %m");
×
2524

2525
        struct termios new_termios = *ret_saved_termios;
×
2526
        termios_disable_echo(&new_termios);
×
2527

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

2531
        *ret_nonblock_fd = TAKE_FD(nonblock_input_fd);
×
2532
        return 0;
×
2533
}
2534

2535
/*
2536
 * See https://terminalguide.namepad.de/seq/csi_st-18/,
2537
 * https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Functions-using-CSI-_-ordered-by-the-final-character_s_.
2538
 */
2539
#define CSI18_Q  "\x1B[18t"               /* Report the size of the text area in characters */
2540
#define CSI18_Rp "\x1B[8;"                /* Reply prefix */
2541
#define CSI18_R0 CSI18_Rp "1;1t"          /* Shortest reply */
2542
#define CSI18_R1 CSI18_Rp "32766;32766t"  /* Longest reply */
2543

2544
static int scan_text_area_size_response(
×
2545
                const char *buf,
2546
                size_t size,
2547
                unsigned *ret_rows,
2548
                unsigned *ret_columns) {
2549

2550
        assert(buf);
×
2551
        assert(ret_rows);
×
2552
        assert(ret_columns);
×
2553

2554
        /* Check if we have enough space for the shortest possible answer. */
2555
        if (size < STRLEN(CSI18_R0))
×
2556
                return -EAGAIN;
×
2557

2558
        /* Check if the terminating sequence is present */
2559
        if (buf[size - 1] != 't')
×
2560
                return -EAGAIN;
2561

2562
        unsigned short rows, columns;
×
2563
        if (sscanf(buf, CSI18_Rp "%hu;%hut", &rows, &columns) != 2)
×
2564
                return -EINVAL;
2565

2566
        *ret_rows = rows;
×
2567
        *ret_columns = columns;
×
2568
        return 0;
×
2569
}
2570

2571
/* Determine terminal dimensions by means of an ANSI CSI 18 sequence.
2572
 *
2573
 * Caller must have already opened a non-blocking input fd and configured termios (echo/icanon off). */
2574
static int terminal_query_size_by_csi18(
×
2575
                int nonblock_input_fd,
2576
                int output_fd,
2577
                unsigned *ret_rows,
2578
                unsigned *ret_columns) {
2579

2580
        int r;
×
2581

2582
        assert(nonblock_input_fd >= 0);
×
2583
        assert(output_fd >= 0);
×
2584

2585
        r = loop_write_full(output_fd, CSI18_Q, SIZE_MAX, CONSOLE_ANSI_SEQUENCE_TIMEOUT_USEC);
×
2586
        if (r < 0)
×
2587
                return r;
×
2588

2589
        usec_t end = usec_add(now(CLOCK_MONOTONIC), CONSOLE_ANSI_SEQUENCE_TIMEOUT_USEC);
×
2590
        char buf[STRLEN(CSI18_R1)];
×
2591
        size_t bytes = 0;
×
2592

2593
        for (;;) {
×
2594
                usec_t n = now(CLOCK_MONOTONIC);
×
2595
                if (n >= end)
×
2596
                        return -EOPNOTSUPP;
2597

2598
                r = fd_wait_for_event(nonblock_input_fd, POLLIN, usec_sub_unsigned(end, n));
×
2599
                if (r < 0)
×
2600
                        return r;
2601
                if (r == 0)
×
2602
                        return -EOPNOTSUPP;
2603

2604
                /* On the first read, read multiple characters, i.e. the shortest valid reply. Afterwards
2605
                 * read byte by byte, since we don't want to read too much and drop characters from the input
2606
                 * queue. */
2607
                ssize_t l = read(nonblock_input_fd, buf + bytes, bytes == 0 ? STRLEN(CSI18_R0) : 1);
×
2608
                if (l < 0) {
×
2609
                        if (errno == EAGAIN)
×
2610
                                continue;
×
2611
                        return -errno;
×
2612
                }
2613

2614
                assert((size_t) l <= sizeof(buf) - bytes);
×
2615
                bytes += l;
×
2616

2617
                r = scan_text_area_size_response(buf, bytes, ret_rows, ret_columns);
×
2618
                if (r != -EAGAIN)
×
2619
                        return r;
2620

2621
                if (bytes == sizeof(buf))
×
2622
                        return -EOPNOTSUPP; /* The response has the right prefix, but we didn't find a valid
2623
                                             * answer with a terminator in the allotted space. Something is
2624
                                             * wrong, possibly some unrelated bytes got injected into the
2625
                                             * answer. */
2626
        }
2627
}
2628

2629
int terminal_get_size(
316✔
2630
                int input_fd,
2631
                int output_fd,
2632
                unsigned *ret_rows,
2633
                unsigned *ret_columns,
2634
                bool try_dsr,
2635
                bool try_csi18) {
2636

2637
        _cleanup_close_ int nonblock_input_fd = -EBADF;
316✔
2638
        struct termios old_termios = TERMIOS_NULL;
316✔
2639
        CLEANUP_TERMIOS_RESET(nonblock_input_fd, old_termios);
316✔
2640
        _cleanup_(nonblock_resetp) int nonblock_reset = -EBADF;
316✔
2641
        int r;
316✔
2642

2643
        assert(try_dsr || try_csi18);
316✔
2644

2645
        r = terminal_prepare_query(input_fd, output_fd, &nonblock_input_fd, &old_termios);
316✔
2646
        if (r < 0)
316✔
2647
                return r;
2648

2649
        /* Put the output fd in non-blocking mode with a write timeout, to avoid blocking indefinitely on
2650
         * write if the terminal is not consuming data (e.g. serial console with flow control). */
2651
        r = fd_nonblock(output_fd, true);
×
2652
        if (r < 0)
×
2653
                return log_debug_errno(r, "Failed to set terminal to non-blocking mode: %m");
×
2654
        if (r > 0)
×
2655
                nonblock_reset = output_fd;
×
2656

2657
        /* Flush any stale input that might confuse the response parsers. */
2658
        (void) tcflush(nonblock_input_fd, TCIFLUSH);
×
2659

2660
        if (try_csi18) {
×
2661
                r = terminal_query_size_by_csi18(nonblock_input_fd, output_fd, ret_rows, ret_columns);
×
2662
                if (r >= 0)
×
2663
                        return r;
2664

2665
                /* Query failed. Flush any outstanding input. */
2666
                (void) tcflush(nonblock_input_fd, TCIFLUSH);
×
2667

2668
                if (!IN_SET(r, -EOPNOTSUPP, -EINVAL))
×
2669
                        return r;
2670
        }
2671

2672
        if (try_dsr) {
×
2673
                r = terminal_query_size_by_dsr(nonblock_input_fd, output_fd, ret_rows, ret_columns);
×
2674
                if (r >= 0)
×
2675
                        return r;
2676

2677
                /* Query failed. Flush any outstanding input. */
2678
                (void) tcflush(nonblock_input_fd, TCIFLUSH);
×
2679
        }
2680

2681
        return r;
2682
}
2683

2684
int terminal_fix_size(int input_fd, int output_fd) {
20✔
2685
        unsigned rows, columns;
20✔
2686
        int r;
20✔
2687

2688
        /* Tries to update the current terminal dimensions to the ones reported via ANSI sequences.
2689
         *
2690
         * Why bother with this? The ioctl() information is often incorrect on serial terminals (since
2691
         * there's no handshake or protocol to determine the right dimensions in RS232), but since the ANSI
2692
         * sequences are interpreted by the final terminal instead of an intermediary tty driver they should
2693
         * be more accurate.
2694
         */
2695

2696
        struct winsize ws = {};
20✔
2697
        if (ioctl(output_fd, TIOCGWINSZ, &ws) < 0)
20✔
2698
                return log_debug_errno(errno, "Failed to query terminal dimensions, ignoring: %m");
1✔
2699

2700
        r = terminal_get_size(input_fd, output_fd, &rows, &columns, /* try_dsr= */ true, /* try_csi18= */ true);
19✔
2701
        if (r < 0)
19✔
2702
                return log_debug_errno(r, "Failed to acquire terminal dimensions via ANSI sequences, not adjusting terminal dimensions: %m");
19✔
2703

2704
        if (ws.ws_row == rows && ws.ws_col == columns) {
×
2705
                log_debug("Terminal dimensions reported via ANSI sequences match currently set terminal dimensions, not changing.");
×
2706
                return 0;
2707
        }
2708

2709
        ws.ws_col = columns;
×
2710
        ws.ws_row = rows;
×
2711

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

2715
        log_debug("Fixed terminal dimensions to %ux%u based on ANSI sequence information.", columns, rows);
×
2716
        return 1;
2717
}
2718

2719
#define MAX_TERMINFO_LENGTH 64
2720
/* python -c 'print("".join(hex(ord(i))[2:] for i in "name").upper())' */
2721
#define DCS_TERMINFO_Q ANSI_DCS "+q" "6E616D65" ANSI_ST
2722
/* The answer is either 0+r… (invalid) or 1+r… (OK). */
2723
#define DCS_TERMINFO_R0 ANSI_DCS "0+r" ANSI_ST
2724
#define DCS_TERMINFO_R1 ANSI_DCS "1+r" "6E616D65" "=" /* This is followed by Pt ST. */
2725
assert_cc(STRLEN(DCS_TERMINFO_R0) <= STRLEN(DCS_TERMINFO_R1 ANSI_ST));
2726

2727
static int scan_terminfo_response(
×
2728
                const char *buf,
2729
                size_t size,
2730
                char **ret_name) {
2731
        int r;
×
2732

2733
        assert(buf);
×
2734
        assert(ret_name);
×
2735

2736
        /* Check if we have enough space for the shortest possible answer. */
2737
        if (size < STRLEN(DCS_TERMINFO_R0))
×
2738
                return -EAGAIN;
×
2739

2740
        /* Check if the terminating sequence is present */
2741
        if (memcmp(buf + size - STRLEN(ANSI_ST), ANSI_ST, STRLEN(ANSI_ST)) != 0)
×
2742
                return -EAGAIN;
2743

2744
        if (size <= STRLEN(DCS_TERMINFO_R1 ANSI_ST))
×
2745
                return -EINVAL;  /* The answer is invalid or empty */
2746

2747
        if (memcmp(buf, DCS_TERMINFO_R1, STRLEN(DCS_TERMINFO_R1)) != 0)
×
2748
                return -EINVAL;  /* The answer is not valid */
2749

2750
        _cleanup_free_ void *dec = NULL;
×
2751
        size_t dec_size;
×
2752
        r = unhexmem_full(buf + STRLEN(DCS_TERMINFO_R1), size - STRLEN(DCS_TERMINFO_R1 ANSI_ST),
×
2753
                          /* secure= */ false,
2754
                          &dec, &dec_size);
2755
        if (r < 0)
×
2756
                return r;
2757

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

2762
        *ret_name = TAKE_PTR(dec);
×
2763
        return 0;
×
2764
}
2765

2766
int terminal_get_terminfo_by_dcs(int fd, char **ret_name) {
2✔
2767
        int r;
2✔
2768

2769
        assert(fd >= 0);
2✔
2770
        assert(ret_name);
2✔
2771

2772
        /* Note: fd must be in non-blocking read-write mode! */
2773

2774
        struct termios old_termios = TERMIOS_NULL;
2✔
2775
        CLEANUP_TERMIOS_RESET(fd, old_termios);
2✔
2776

2777
        if (tcgetattr(fd, &old_termios) < 0)
2✔
2778
                return -errno;
1✔
2779

2780
        struct termios new_termios = old_termios;
1✔
2781
        termios_disable_echo(&new_termios);
1✔
2782

2783
        if (tcsetattr(fd, TCSANOW, &new_termios) < 0)
1✔
2784
                return -errno;
×
2785

2786
        r = loop_write(fd, DCS_TERMINFO_Q, SIZE_MAX);
1✔
2787
        if (r < 0)
1✔
2788
                return r;
2789

2790
        usec_t end = usec_add(now(CLOCK_MONOTONIC), CONSOLE_ANSI_SEQUENCE_TIMEOUT_USEC);
1✔
2791
        char buf[STRLEN(DCS_TERMINFO_R1) + MAX_TERMINFO_LENGTH + STRLEN(ANSI_ST)];
1✔
2792
        size_t bytes = 0;
1✔
2793

2794
        for (;;) {
1✔
2795
                usec_t n = now(CLOCK_MONOTONIC);
1✔
2796
                if (n >= end)
1✔
2797
                        return -EOPNOTSUPP;
2798

2799
                r = fd_wait_for_event(fd, POLLIN, usec_sub_unsigned(end, n));
2✔
2800
                if (r < 0)
1✔
2801
                        return r;
2802
                if (r == 0)
1✔
2803
                        return -EOPNOTSUPP;
2804

2805
                /* On the first read, read multiple characters, i.e. the shortest valid reply. Afterwards
2806
                 * read byte by byte, since we don't want to read too much and drop characters from the input
2807
                 * queue. */
2808
                ssize_t l = read(fd, buf + bytes, bytes == 0 ? STRLEN(DCS_TERMINFO_R0) : 1);
×
2809
                if (l < 0) {
×
2810
                        if (errno == EAGAIN)
×
2811
                                continue;
×
2812
                        return -errno;
×
2813
                }
2814

2815
                assert((size_t) l <= sizeof(buf) - bytes);
×
2816
                bytes += l;
×
2817

2818
                r = scan_terminfo_response(buf, bytes, ret_name);
×
2819
                if (r != -EAGAIN)
×
2820
                        return r;
2821

2822
                if (bytes == sizeof(buf))
×
2823
                        return -EOPNOTSUPP; /* The response has the right prefix, but we didn't find a valid
2824
                                             * answer with a terminator in the allotted space. Something is
2825
                                             * wrong, possibly some unrelated bytes got injected into the
2826
                                             * answer. */
2827
        }
2828
}
2829

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

2835
        assert(filename_is_valid(name));
5✔
2836

2837
        _cleanup_free_ char *p = path_join("/usr/share/terminfo", CHAR_TO_STR(name[0]), name);
10✔
2838
        if (!p)
5✔
2839
                return log_oom_debug();
×
2840

2841
        r = RET_NERRNO(access(p, F_OK));
5✔
2842
        if (r == -ENOENT)
1✔
2843
                return false;
2844
        if (r < 0)
4✔
2845
                return r;
×
2846
        return true;
2847
}
2848

2849
int query_term_for_tty(const char *tty, char **ret_term) {
57✔
2850
        _cleanup_free_ char *dcs_term = NULL;
57✔
2851
        int r;
57✔
2852

2853
        assert(tty);
57✔
2854
        assert(ret_term);
57✔
2855

2856
        if (tty_is_vc_resolve(tty))
57✔
2857
                return strdup_to(ret_term, "linux");
54✔
2858

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

2862
        _cleanup_close_ int tty_fd = open_terminal(tty, O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK);
60✔
2863
        if (tty_fd < 0)
3✔
2864
                return log_debug_errno(tty_fd, "Failed to open %s to query terminfo: %m", tty);
2✔
2865

2866
        r = terminal_get_terminfo_by_dcs(tty_fd, &dcs_term);
1✔
2867
        if (r < 0)
1✔
2868
                return log_debug_errno(r, "Failed to query %s for terminfo: %m", tty);
1✔
2869

2870
        r = have_terminfo_file(dcs_term);
×
2871
        if (r < 0)
×
2872
                return log_debug_errno(r, "Failed to look for terminfo %s: %m", dcs_term);
×
2873
        if (r == 0)
×
2874
                return log_info_errno(SYNTHETIC_ERRNO(ENODATA),
×
2875
                                      "Terminfo %s not found for %s.", dcs_term, tty);
2876

2877
        *ret_term = TAKE_PTR(dcs_term);
×
2878
        return 0;
×
2879
}
2880

2881
int terminal_is_pty_fd(int fd) {
3✔
2882
        int r;
3✔
2883

2884
        assert(fd >= 0);
3✔
2885

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

2888
        if (!isatty_safe(fd))
3✔
2889
                return false;
3✔
2890

2891
        r = is_fs_type_at(fd, NULL, DEVPTS_SUPER_MAGIC);
2✔
2892
        if (r != 0)
2✔
2893
                return r;
2894

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

2898
        int v;
×
2899
        if (ioctl(fd, TIOCGPKT, &v) < 0) {
×
2900
                if (ERRNO_IS_NOT_SUPPORTED(errno))
×
2901
                        return false;
2902

2903
                return -errno;
×
2904
        }
2905

2906
        return true;
2907
}
2908

2909
int pty_open_peer(int fd, int mode) {
31✔
2910
        assert(fd >= 0);
31✔
2911

2912
        /* Opens the peer PTY using the new race-free TIOCGPTPEER ioctl() (kernel 4.13).
2913
         *
2914
         * This is safe to be called on TTYs from other namespaces. */
2915

2916
        assert((mode & (O_CREAT|O_PATH|O_DIRECTORY|O_TMPFILE)) == 0);
31✔
2917

2918
        /* This replicates the EIO retry logic of open_terminal() in a modified way. */
2919
        for (unsigned c = 0;; c++) {
×
2920
                int peer_fd = ioctl(fd, TIOCGPTPEER, mode);
31✔
2921
                if (peer_fd >= 0)
31✔
2922
                        return peer_fd;
2923

2924
                if (errno != EIO)
×
2925
                        return -errno;
×
2926

2927
                /* Max 1s in total */
2928
                if (c >= 20)
×
2929
                        return -EIO;
2930

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

© 2026 Coveralls, Inc