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

systemd / systemd / 15263807472

26 May 2025 08:53PM UTC coverage: 72.046% (-0.002%) from 72.048%
15263807472

push

github

yuwata
src/core/manager.c: log preset activity on first boot

This gives us a little more information about what units were enabled
or disabled on that first boot and will be useful for OS developers
tracking down the source of unit state.

An example with this enabled looks like:

```
NET: Registered PF_VSOCK protocol family
systemd[1]: Applying preset policy.
systemd[1]: Unit /etc/systemd/system/dnsmasq.service is masked, ignoring.
systemd[1]: Unit /etc/systemd/system/systemd-repart.service is masked, ignoring.
systemd[1]: Removed '/etc/systemd/system/sockets.target.wants/systemd-resolved-monitor.socket'.
systemd[1]: Removed '/etc/systemd/system/sockets.target.wants/systemd-resolved-varlink.socket'.
systemd[1]: Created symlink '/etc/systemd/system/multi-user.target.wants/var-mnt-workdir.mount' → '/etc/systemd/system/var-mnt-workdir.mount'.
systemd[1]: Created symlink '/etc/systemd/system/multi-user.target.wants/var-mnt-workdir\x2dtmp.mount' → '/etc/systemd/system/var-mnt-workdir\x2dtmp.mount'.
systemd[1]: Created symlink '/etc/systemd/system/afterburn-sshkeys.target.requires/afterburn-sshkeys@core.service' → '/usr/lib/systemd/system/afterburn-sshkeys@.service'.
systemd[1]: Created symlink '/etc/systemd/system/sockets.target.wants/systemd-resolved-varlink.socket' → '/usr/lib/systemd/system/systemd-resolved-varlink.socket'.
systemd[1]: Created symlink '/etc/systemd/system/sockets.target.wants/systemd-resolved-monitor.socket' → '/usr/lib/systemd/system/systemd-resolved-monitor.socket'.
systemd[1]: Populated /etc with preset unit settings.
```

Considering it only happens on first boot and not on every boot I think
the extra information is worth the extra verbosity in the logs just for
that boot.

5 of 6 new or added lines in 1 file covered. (83.33%)

5463 existing lines in 165 files now uncovered.

299151 of 415222 relevant lines covered (72.05%)

702386.45 hits per line

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

42.69
/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/tiocl.h>
6
#include <linux/vt.h>
7
#include <poll.h>
8
#include <signal.h>
9
#include <stdlib.h>
10
#include <sys/inotify.h>
11
#include <sys/ioctl.h>
12
#include <sys/sysmacros.h>
13
#include <termios.h>
14
#include <time.h>
15
#include <unistd.h>
16

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

46
#define ANSI_RESET_CURSOR                          \
47
        "\033?25h"     /* turn on cursor */        \
48
        "\033?12l"     /* reset cursor blinking */ \
49
        "\033 1q"      /* reset cursor style */
50

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

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

57
bool isatty_safe(int fd) {
5,480,698✔
58
        assert(fd >= 0);
5,480,698✔
59

60
        if (isatty(fd))
5,480,698✔
61
                return true;
62

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

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

72
        return false;
73
}
74

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

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

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

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

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

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

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

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

105
        assert(ret);
10✔
106

107
        if (!f)
10✔
UNCOV
108
                f = stdin;
×
109

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

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

UNCOV
122
                if (tcsetattr(fd, TCSADRAIN, &new_termios) >= 0) {
×
UNCOV
123
                        char c;
×
124

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

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

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

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

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

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

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

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

165
        if (strlen(line) != 1)
8✔
166
                return -EBADMSG;
167

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

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

175
#define DEFAULT_ASK_REFRESH_USEC (2*USEC_PER_SEC)
176

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

UNCOV
180
        assert(ret);
×
UNCOV
181
        assert(replies);
×
UNCOV
182
        assert(fmt);
×
183

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

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

UNCOV
191
                putchar('\r');
×
192

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

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

199
                fflush(stdout);
×
200

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

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

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

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

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

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

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

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

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

UNCOV
242
        string = strempty(string);
×
243

UNCOV
244
        STRV_FOREACH(c, completions) {
×
245

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

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

256
                        continue;
×
257
                }
258

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

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

UNCOV
269
        *ret = TAKE_PTR(found);
×
270

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

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

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

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

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

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

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

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

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

316
        /* Try to disable echo, which also tells us if this even is a terminal */
UNCOV
317
        struct termios old_termios;
×
UNCOV
318
        if (tcgetattr(fd_input, &old_termios) < 0)
×
UNCOV
319
                goto fallback;
×
320

UNCOV
321
        struct termios new_termios = old_termios;
×
UNCOV
322
        termios_disable_echo(&new_termios);
×
UNCOV
323
        if (tcsetattr(fd_input, TCSADRAIN, &new_termios) < 0)
×
UNCOV
324
                return -errno;
×
325

UNCOV
326
        for (;;) {
×
UNCOV
327
                int c = fgetc(stdin);
×
328

329
                /* On EOF or NUL, end the request, don't output anything anymore */
330
                if (IN_SET(c, EOF, 0))
×
331
                        break;
332

333
                /* On Return also end the request, but make this visible */
334
                if (IN_SET(c, '\n', '\r')) {
×
335
                        fputc('\n', stdout);
×
336
                        break;
337
                }
338

339
                if (c == '\t') {
×
340
                        /* Tab */
341

342
                        _cleanup_strv_free_ char **completions = NULL;
×
UNCOV
343
                        if (get_completions) {
×
UNCOV
344
                                r = get_completions(string, &completions, userdata);
×
UNCOV
345
                                if (r < 0)
×
346
                                        goto fail;
×
347
                        }
348

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

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

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

372
                                _cleanup_strv_free_ char **filtered = strv_filter_prefix(completions, string);
×
373
                                if (!filtered) {
×
UNCOV
374
                                        r = -ENOMEM;
×
375
                                        goto fail;
×
376
                                }
377

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

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

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

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

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

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

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

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

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

423
                        r = -ECANCELED;
×
UNCOV
424
                        goto fail;
×
425

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

432
                        if (!GREEDY_REALLOC(string, n+2)) {
×
UNCOV
433
                                r = -ENOMEM;
×
UNCOV
434
                                goto fail;
×
435
                        }
436

UNCOV
437
                        string[n++] = (char) c;
×
438
                        string[n] = 0;
×
439

440
                        fputc(c, stdout);
×
441
                }
442

UNCOV
443
                fflush(stdout);
×
444
        }
445

446
        if (tcsetattr(fd_input, TCSADRAIN, &old_termios) < 0)
×
UNCOV
447
                return -errno;
×
448

449
        if (!string) {
×
450
                string = strdup("");
×
UNCOV
451
                if (!string)
×
452
                        return -ENOMEM;
453
        }
454

455
        *ret = TAKE_PTR(string);
×
UNCOV
456
        return 0;
×
457

458
fail:
×
459
        (void) tcsetattr(fd_input, TCSADRAIN, &old_termios);
×
UNCOV
460
        return r;
×
461

462
fallback:
7✔
463
        /* A simple fallback without TTY magic */
464
        r = read_line(stdin, LONG_LINE_MAX, &string);
7✔
465
        if (r < 0)
7✔
466
                return r;
467
        if (r == 0)
7✔
468
                return -EIO;
469

470
        *ret = TAKE_PTR(string);
7✔
471
        return 0;
7✔
472
}
473

474
bool any_key_to_proceed(void) {
6✔
475

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

480
        fputc('\n', stdout);
6✔
481
        fputs(ansi_highlight_magenta(), stdout);
12✔
482
        fputs("-- Press any key to proceed --", stdout);
6✔
483
        fputs(ansi_normal(), stdout);
12✔
484
        fputc('\n', stdout);
6✔
485
        fflush(stdout);
6✔
486

487
        char key = 0;
6✔
488
        (void) read_one_char(stdin, &key, USEC_INFINITY, /* echo= */ false, /* need_nl= */ NULL);
6✔
489

490
        fputc('\n', stdout);
6✔
491
        fflush(stdout);
6✔
492

493
        return key != 'q';
6✔
494
}
495

UNCOV
496
static size_t widest_list_element(char *const*l) {
×
UNCOV
497
        size_t w = 0;
×
498

499
        /* Returns the largest console width of all elements in 'l' */
500

UNCOV
501
        STRV_FOREACH(i, l)
×
UNCOV
502
                w = MAX(w, utf8_console_width(*i));
×
503

UNCOV
504
        return w;
×
505
}
506

UNCOV
507
int show_menu(char **x,
×
508
              size_t n_columns,
509
              size_t column_width,
510
              unsigned ellipsize_percentage,
511
              const char *grey_prefix,
512
              bool with_numbers) {
513

514
        assert(n_columns > 0);
×
515

516
        if (n_columns == SIZE_MAX)
×
UNCOV
517
                n_columns = 3;
×
518

519
        if (column_width == SIZE_MAX) {
×
UNCOV
520
                size_t widest = widest_list_element(x);
×
521

522
                /* If not specified, derive column width from screen width */
UNCOV
523
                size_t column_max = (columns()-1) / n_columns;
×
524

525
                /* Subtract room for numbers */
526
                if (with_numbers && column_max > 6)
×
UNCOV
527
                        column_max -= 6;
×
528

529
                /* If columns would get too tight let's make this a linear list instead. */
UNCOV
530
                if (column_max < 10 && widest > 10) {
×
531
                        n_columns = 1;
×
532
                        column_max = columns()-1;
×
533

UNCOV
534
                        if (with_numbers && column_max > 6)
×
535
                                column_max -= 6;
×
536
                }
537

538
                column_width = CLAMP(widest+1, 10U, column_max);
×
539
        }
540

UNCOV
541
        size_t n = strv_length(x);
×
542
        size_t per_column = DIV_ROUND_UP(n, n_columns);
×
543

544
        size_t break_lines = lines();
×
UNCOV
545
        if (break_lines > 2)
×
546
                break_lines--;
×
547

548
        /* The first page gets two extra lines, since we want to show
549
         * a title */
550
        size_t break_modulo = break_lines;
×
UNCOV
551
        if (break_modulo > 3)
×
UNCOV
552
                break_modulo -= 3;
×
553

554
        for (size_t i = 0; i < per_column; i++) {
×
555

556
                for (size_t j = 0; j < n_columns; j++) {
×
557
                        _cleanup_free_ char *e = NULL;
×
558

UNCOV
559
                        if (j * per_column + i >= n)
×
560
                                break;
561

562
                        e = ellipsize(x[j * per_column + i], column_width, ellipsize_percentage);
×
563
                        if (!e)
×
564
                                return -ENOMEM;
×
565

566
                        if (with_numbers)
×
UNCOV
567
                                printf("%s%4zu)%s ",
×
568
                                       ansi_grey(),
569
                                       j * per_column + i + 1,
570
                                       ansi_normal());
571

UNCOV
572
                        if (grey_prefix && startswith(e, grey_prefix)) {
×
UNCOV
573
                                size_t k = MIN(strlen(grey_prefix), column_width);
×
574
                                printf("%s%.*s%s",
×
575
                                       ansi_grey(),
576
                                       (int) k, e,
577
                                       ansi_normal());
578
                                printf("%-*s",
×
579
                                       (int) (column_width - k), e+k);
×
580
                        } else
UNCOV
581
                                printf("%-*s", (int) column_width, e);
×
582
                }
583

584
                putchar('\n');
×
585

586
                /* on the first screen we reserve 2 extra lines for the title */
UNCOV
587
                if (i % break_lines == break_modulo)
×
UNCOV
588
                        if (!any_key_to_proceed())
×
589
                                return 0;
590
        }
591

592
        return 0;
593
}
594

595
int open_terminal(const char *name, int mode) {
50,155✔
596
        _cleanup_close_ int fd = -EBADF;
50,155✔
597

598
        /*
599
         * If a TTY is in the process of being closed opening it might cause EIO. This is horribly awful, but
600
         * unlikely to be changed in the kernel. Hence we work around this problem by retrying a couple of
601
         * times.
602
         *
603
         * https://bugs.launchpad.net/ubuntu/+source/linux/+bug/554172/comments/245
604
         */
605

606
        assert((mode & (O_CREAT|O_PATH|O_DIRECTORY|O_TMPFILE)) == 0);
50,155✔
607

UNCOV
608
        for (unsigned c = 0;; c++) {
×
609
                fd = open(name, mode, 0);
50,155✔
610
                if (fd >= 0)
50,155✔
611
                        break;
612

613
                if (errno != EIO)
2,860✔
614
                        return -errno;
2,860✔
615

616
                /* Max 1s in total */
UNCOV
617
                if (c >= 20)
×
618
                        return -EIO;
619

620
                (void) usleep_safe(50 * USEC_PER_MSEC);
×
621
        }
622

623
        if (!isatty_safe(fd))
47,295✔
UNCOV
624
                return -ENOTTY;
×
625

626
        return TAKE_FD(fd);
627
}
628

629
int acquire_terminal(
366✔
630
                const char *name,
631
                AcquireTerminalFlags flags,
632
                usec_t timeout) {
633

634
        _cleanup_close_ int notify = -EBADF, fd = -EBADF;
366✔
635
        usec_t ts = USEC_INFINITY;
366✔
636
        int r, wd = -1;
366✔
637

638
        assert(name);
366✔
639

640
        AcquireTerminalFlags mode = flags & _ACQUIRE_TERMINAL_MODE_MASK;
366✔
641
        assert(IN_SET(mode, ACQUIRE_TERMINAL_TRY, ACQUIRE_TERMINAL_FORCE, ACQUIRE_TERMINAL_WAIT));
366✔
642
        assert(mode == ACQUIRE_TERMINAL_WAIT || !FLAGS_SET(flags, ACQUIRE_TERMINAL_WATCH_SIGTERM));
366✔
643

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

UNCOV
653
        if (mode == ACQUIRE_TERMINAL_WAIT) {
×
654
                notify = inotify_init1(IN_CLOEXEC | IN_NONBLOCK);
366✔
655
                if (notify < 0)
366✔
UNCOV
656
                        return -errno;
×
657

658
                wd = inotify_add_watch(notify, name, IN_CLOSE);
366✔
659
                if (wd < 0)
366✔
660
                        return -errno;
23✔
661

662
                if (timeout != USEC_INFINITY)
343✔
UNCOV
663
                        ts = now(CLOCK_MONOTONIC);
×
664
        }
665

666
        /* If we are called with ACQUIRE_TERMINAL_WATCH_SIGTERM we'll unblock SIGTERM during ppoll() temporarily */
667
        sigset_t poll_ss;
343✔
668
        assert_se(sigprocmask(SIG_SETMASK, /* newset= */ NULL, &poll_ss) >= 0);
343✔
669
        if (flags & ACQUIRE_TERMINAL_WATCH_SIGTERM) {
343✔
UNCOV
670
                assert_se(sigismember(&poll_ss, SIGTERM) > 0);
×
UNCOV
671
                assert_se(sigdelset(&poll_ss, SIGTERM) >= 0);
×
672
        }
673

674
        for (;;) {
343✔
675
                if (notify >= 0) {
343✔
676
                        r = flush_fd(notify);
343✔
677
                        if (r < 0)
343✔
UNCOV
678
                                return r;
×
679
                }
680

681
                /* We pass here O_NOCTTY only so that we can check the return value TIOCSCTTY and have a reliable way
682
                 * to figure out if we successfully became the controlling process of the tty */
683
                fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC);
343✔
684
                if (fd < 0)
343✔
685
                        return fd;
686

687
                /* Temporarily ignore SIGHUP, so that we don't get SIGHUP'ed if we already own the tty. */
688
                struct sigaction sa_old;
343✔
689
                assert_se(sigaction(SIGHUP, &sigaction_ignore, &sa_old) >= 0);
343✔
690

691
                /* First, try to get the tty */
692
                r = RET_NERRNO(ioctl(fd, TIOCSCTTY, mode == ACQUIRE_TERMINAL_FORCE));
343✔
693

694
                /* Reset signal handler to old value */
695
                assert_se(sigaction(SIGHUP, &sa_old, NULL) >= 0);
343✔
696

697
                /* Success? Exit the loop now! */
698
                if (r >= 0)
343✔
699
                        break;
700

701
                /* Any failure besides -EPERM? Fail, regardless of the mode. */
UNCOV
702
                if (r != -EPERM)
×
703
                        return r;
704

UNCOV
705
                if (flags & ACQUIRE_TERMINAL_PERMISSIVE) /* If we are in permissive mode, then EPERM is fine, turn this
×
706
                                                          * into a success. Note that EPERM is also returned if we
707
                                                          * already are the owner of the TTY. */
708
                        break;
709

UNCOV
710
                if (mode != ACQUIRE_TERMINAL_WAIT) /* If we are in TRY or FORCE mode, then propagate EPERM as EPERM */
×
711
                        return r;
712

UNCOV
713
                assert(notify >= 0);
×
714
                assert(wd >= 0);
×
715

UNCOV
716
                for (;;) {
×
717
                        usec_t left;
×
UNCOV
718
                        if (timeout == USEC_INFINITY)
×
719
                                left = USEC_INFINITY;
720
                        else {
UNCOV
721
                                assert(ts != USEC_INFINITY);
×
722

UNCOV
723
                                usec_t n = usec_sub_unsigned(now(CLOCK_MONOTONIC), ts);
×
UNCOV
724
                                if (n >= timeout)
×
725
                                        return -ETIMEDOUT;
×
726

UNCOV
727
                                left = timeout - n;
×
728
                        }
729

730
                        r = ppoll_usec_full(
×
UNCOV
731
                                        &(struct pollfd) {
×
732
                                                .fd = notify,
733
                                                .events = POLLIN,
734
                                        },
735
                                        /* n_pollfds = */ 1,
736
                                        left,
737
                                        &poll_ss);
UNCOV
738
                        if (r < 0)
×
739
                                return r;
UNCOV
740
                        if (r == 0)
×
741
                                return -ETIMEDOUT;
742

743
                        union inotify_event_buffer buffer;
×
UNCOV
744
                        ssize_t l;
×
UNCOV
745
                        l = read(notify, &buffer, sizeof(buffer));
×
UNCOV
746
                        if (l < 0) {
×
UNCOV
747
                                if (ERRNO_IS_TRANSIENT(errno))
×
UNCOV
748
                                        continue;
×
749

750
                                return -errno;
×
751
                        }
752

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

757
                                if (e->wd != wd || !(e->mask & IN_CLOSE)) /* Safety checks */
×
758
                                        return -EIO;
×
759
                        }
760

UNCOV
761
                        break;
×
762
                }
763

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

769
        return TAKE_FD(fd);
343✔
770
}
771

772
int release_terminal(void) {
63✔
773
        _cleanup_close_ int fd = -EBADF;
63✔
774
        int r;
63✔
775

776
        fd = open("/dev/tty", O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK);
63✔
777
        if (fd < 0)
63✔
778
                return -errno;
43✔
779

780
        /* Temporarily ignore SIGHUP, so that we don't get SIGHUP'ed
781
         * by our own TIOCNOTTY */
782
        struct sigaction sa_old;
20✔
783
        assert_se(sigaction(SIGHUP, &sigaction_ignore, &sa_old) >= 0);
20✔
784

785
        r = RET_NERRNO(ioctl(fd, TIOCNOTTY));
20✔
786

787
        assert_se(sigaction(SIGHUP, &sa_old, NULL) >= 0);
20✔
788

789
        return r;
790
}
791

792
int terminal_new_session(void) {
5✔
793

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

801
        if (!isatty_safe(STDIN_FILENO))
5✔
802
                return -ENXIO;
803

804
        (void) setsid();
4✔
805
        return RET_NERRNO(ioctl(STDIN_FILENO, TIOCSCTTY, 0));
4✔
806
}
807

808
void terminal_detach_session(void) {
63✔
809
        (void) setsid();
63✔
810
        (void) release_terminal();
63✔
811
}
63✔
812

813
int terminal_vhangup_fd(int fd) {
157✔
814
        assert(fd >= 0);
157✔
815
        return RET_NERRNO(ioctl(fd, TIOCVHANGUP));
157✔
816
}
817

UNCOV
818
int terminal_vhangup(const char *tty) {
×
UNCOV
819
        _cleanup_close_ int fd = -EBADF;
×
820

UNCOV
821
        assert(tty);
×
822

UNCOV
823
        fd = open_terminal(tty, O_RDWR|O_NOCTTY|O_CLOEXEC);
×
UNCOV
824
        if (fd < 0)
×
825
                return fd;
826

UNCOV
827
        return terminal_vhangup_fd(fd);
×
828
}
829

830
int vt_disallocate(const char *tty_path) {
83✔
831
        assert(tty_path);
83✔
832

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

836
        int ttynr = vtnr_from_tty(tty_path);
83✔
837
        if (ttynr > 0) {
83✔
838
                _cleanup_close_ int fd = open_terminal("/dev/tty0", O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK);
83✔
839
                if (fd < 0)
83✔
840
                        return fd;
841

842
                /* Try to deallocate */
843
                if (ioctl(fd, VT_DISALLOCATE, ttynr) >= 0)
83✔
844
                        return 0;
845
                if (errno != EBUSY)
83✔
UNCOV
846
                        return -errno;
×
847
        }
848

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

852
        _cleanup_close_ int fd2 = open_terminal(tty_path, O_WRONLY|O_NOCTTY|O_CLOEXEC|O_NONBLOCK);
166✔
853
        if (fd2 < 0)
83✔
854
                return fd2;
855

856
        return loop_write_full(fd2,
83✔
857
                               ANSI_RESET_CURSOR
858
                               "\033[r"   /* clear scrolling region */
859
                               "\033[H"   /* move home */
860
                               "\033[3J"  /* clear screen including scrollback, requires Linux 2.6.40 */
861
                               "\033c",   /* reset to initial state */
862
                               SIZE_MAX,
863
                               100 * USEC_PER_MSEC);
864
}
865

866
static int vt_default_utf8(void) {
686✔
867
        _cleanup_free_ char *b = NULL;
686✔
868
        int r;
686✔
869

870
        /* Read the default VT UTF8 setting from the kernel */
871

872
        r = read_one_line_file("/sys/module/vt/parameters/default_utf8", &b);
686✔
873
        if (r < 0)
686✔
874
                return r;
875

876
        return parse_boolean(b);
376✔
877
}
878

879
static int vt_reset_keyboard(int fd) {
343✔
880
        int r, kb;
343✔
881

882
        assert(fd >= 0);
343✔
883

884
        /* If we can't read the default, then default to Unicode. It's 2024 after all. */
885
        r = vt_default_utf8();
343✔
886
        if (r < 0)
343✔
887
                log_debug_errno(r, "Failed to determine kernel VT UTF-8 mode, assuming enabled: %m");
155✔
888

889
        kb = vt_default_utf8() != 0 ? K_UNICODE : K_XLATE;
343✔
890
        return RET_NERRNO(ioctl(fd, KDSKBMODE, kb));
343✔
891
}
892

893
static int terminal_reset_ioctl(int fd, bool switch_to_text) {
343✔
894
        struct termios termios;
343✔
895
        int r;
343✔
896

897
        /* Set terminal to some sane defaults */
898

899
        assert(fd >= 0);
343✔
900

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

904
        /* Disable exclusive mode, just in case */
905
        if (ioctl(fd, TIOCNXCL) < 0)
343✔
906
                log_debug_errno(errno, "TIOCNXCL ioctl failed on TTY, ignoring: %m");
2✔
907

908
        /* Switch to text mode */
909
        if (switch_to_text)
343✔
910
                if (ioctl(fd, KDSETMODE, KD_TEXT) < 0)
162✔
911
                        log_debug_errno(errno, "KDSETMODE ioctl for switching to text mode failed on TTY, ignoring: %m");
79✔
912

913
        /* Set default keyboard mode */
914
        r = vt_reset_keyboard(fd);
343✔
915
        if (r < 0)
343✔
916
                log_debug_errno(r, "Failed to reset VT keyboard, ignoring: %m");
180✔
917

918
        if (tcgetattr(fd, &termios) < 0) {
343✔
919
                r = log_debug_errno(errno, "Failed to get terminal parameters: %m");
2✔
920
                goto finish;
2✔
921
        }
922

923
        /* We only reset the stuff that matters to the software. How
924
         * hardware is set up we don't touch assuming that somebody
925
         * else will do that for us */
926

927
        termios.c_iflag &= ~(IGNBRK | BRKINT | ISTRIP | INLCR | IGNCR | IUCLC);
341✔
928
        termios.c_iflag |= ICRNL | IMAXBEL | IUTF8;
341✔
929
        termios.c_oflag |= ONLCR | OPOST;
341✔
930
        termios.c_cflag |= CREAD;
341✔
931
        termios.c_lflag = ISIG | ICANON | IEXTEN | ECHO | ECHOE | ECHOK | ECHOCTL | ECHOKE;
341✔
932

933
        termios.c_cc[VINTR]    =   03;  /* ^C */
341✔
934
        termios.c_cc[VQUIT]    =  034;  /* ^\ */
341✔
935
        termios.c_cc[VERASE]   = 0177;
341✔
936
        termios.c_cc[VKILL]    =  025;  /* ^X */
341✔
937
        termios.c_cc[VEOF]     =   04;  /* ^D */
341✔
938
        termios.c_cc[VSTART]   =  021;  /* ^Q */
341✔
939
        termios.c_cc[VSTOP]    =  023;  /* ^S */
341✔
940
        termios.c_cc[VSUSP]    =  032;  /* ^Z */
341✔
941
        termios.c_cc[VLNEXT]   =  026;  /* ^V */
341✔
942
        termios.c_cc[VWERASE]  =  027;  /* ^W */
341✔
943
        termios.c_cc[VREPRINT] =  022;  /* ^R */
341✔
944
        termios.c_cc[VEOL]     =    0;
341✔
945
        termios.c_cc[VEOL2]    =    0;
341✔
946

947
        termios.c_cc[VTIME]  = 0;
341✔
948
        termios.c_cc[VMIN]   = 1;
341✔
949

950
        r = RET_NERRNO(tcsetattr(fd, TCSANOW, &termios));
341✔
UNCOV
951
        if (r < 0)
×
UNCOV
952
                log_debug_errno(r, "Failed to set terminal parameters: %m");
×
953

UNCOV
954
finish:
×
955
        /* Just in case, flush all crap out */
956
        (void) tcflush(fd, TCIOFLUSH);
343✔
957

958
        return r;
343✔
959
}
960

961
static int terminal_reset_ansi_seq(int fd) {
337✔
962
        int r, k;
337✔
963

964
        assert(fd >= 0);
337✔
965

966
        if (getenv_terminal_is_dumb())
337✔
967
                return 0;
968

UNCOV
969
        r = fd_nonblock(fd, true);
×
UNCOV
970
        if (r < 0)
×
UNCOV
971
                return log_debug_errno(r, "Failed to set terminal to non-blocking mode: %m");
×
972

UNCOV
973
        k = loop_write_full(fd,
×
974
                            ANSI_RESET_CURSOR
975
                            "\033[!p"      /* soft terminal reset */
976
                            "\033]104\007" /* reset colors */
977
                            "\033[?7h"     /* enable line-wrapping */
978
                            "\033[1G"      /* place cursor at beginning of current line */
979
                            "\033[0J",     /* erase till end of screen */
980
                            SIZE_MAX,
981
                            100 * USEC_PER_MSEC);
UNCOV
982
        if (k < 0)
×
UNCOV
983
                log_debug_errno(k, "Failed to reset terminal through ANSI sequences: %m");
×
984

UNCOV
985
        if (r > 0) {
×
UNCOV
986
                r = fd_nonblock(fd, false);
×
UNCOV
987
                if (r < 0)
×
UNCOV
988
                        log_debug_errno(r, "Failed to set terminal back to blocking mode: %m");
×
989
        }
990

UNCOV
991
        return k < 0 ? k : r;
×
992
}
993

994
void reset_dev_console_fd(int fd, bool switch_to_text) {
33✔
995
        int r;
33✔
996

997
        assert(fd >= 0);
33✔
998

999
        _cleanup_close_ int lock_fd = lock_dev_console();
33✔
1000
        if (lock_fd < 0)
33✔
UNCOV
1001
                log_debug_errno(lock_fd, "Failed to lock /dev/console, ignoring: %m");
×
1002

1003
        r = terminal_reset_ioctl(fd, switch_to_text);
33✔
1004
        if (r < 0)
33✔
UNCOV
1005
                log_warning_errno(r, "Failed to reset /dev/console, ignoring: %m");
×
1006

1007
        unsigned rows, cols;
33✔
1008
        r = proc_cmdline_tty_size("/dev/console", &rows, &cols);
33✔
1009
        if (r < 0)
33✔
UNCOV
1010
                log_warning_errno(r, "Failed to get /dev/console size, ignoring: %m");
×
1011
        else if (r > 0) {
33✔
1012
                r = terminal_set_size_fd(fd, NULL, rows, cols);
33✔
1013
                if (r < 0)
33✔
UNCOV
1014
                        log_warning_errno(r, "Failed to set configured terminal size on /dev/console, ignoring: %m");
×
1015
        } else
UNCOV
1016
                (void) terminal_fix_size(fd, fd);
×
1017

1018
        r = terminal_reset_ansi_seq(fd);
33✔
1019
        if (r < 0)
33✔
1020
                log_warning_errno(r, "Failed to reset /dev/console using ANSI sequences, ignoring: %m");
33✔
1021
}
33✔
1022

1023
int lock_dev_console(void) {
921✔
1024
        _cleanup_close_ int fd = -EBADF;
921✔
1025
        int r;
921✔
1026

1027
        /* NB: We do not use O_NOFOLLOW here, because some container managers might place a symlink to some
1028
         * pty in /dev/console, in which case it should be fine to lock the target TTY. */
1029
        fd = open_terminal("/dev/console", O_RDONLY|O_CLOEXEC|O_NOCTTY);
921✔
1030
        if (fd < 0)
921✔
1031
                return fd;
1032

1033
        r = lock_generic(fd, LOCK_BSD, LOCK_EX);
921✔
1034
        if (r < 0)
921✔
UNCOV
1035
                return r;
×
1036

1037
        return TAKE_FD(fd);
1038
}
1039

UNCOV
1040
int make_console_stdio(void) {
×
UNCOV
1041
        int fd, r;
×
1042

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

1047
        fd = acquire_terminal("/dev/console", ACQUIRE_TERMINAL_FORCE|ACQUIRE_TERMINAL_PERMISSIVE, USEC_INFINITY);
×
1048
        if (fd < 0) {
×
UNCOV
1049
                log_warning_errno(fd, "Failed to acquire terminal, using /dev/null stdin/stdout/stderr instead: %m");
×
1050

UNCOV
1051
                r = make_null_stdio();
×
UNCOV
1052
                if (r < 0)
×
UNCOV
1053
                        return log_error_errno(r, "Failed to make /dev/null stdin/stdout/stderr: %m");
×
1054

1055
        } else {
1056
                reset_dev_console_fd(fd, /* switch_to_text= */ true);
×
1057

1058
                r = rearrange_stdio(fd, fd, fd); /* This invalidates 'fd' both on success and on failure. */
×
1059
                if (r < 0)
×
1060
                        return log_error_errno(r, "Failed to make terminal stdin/stdout/stderr: %m");
×
1061
        }
1062

1063
        reset_terminal_feature_caches();
×
UNCOV
1064
        return 0;
×
1065
}
1066

1067
static int vtnr_from_tty_raw(const char *tty, unsigned *ret) {
310✔
1068
        assert(tty);
310✔
1069

1070
        tty = skip_dev_prefix(tty);
310✔
1071

1072
        const char *e = startswith(tty, "tty");
310✔
1073
        if (!e)
310✔
1074
                return -EINVAL;
1075

1076
        return safe_atou(e, ret);
270✔
1077
}
1078

1079
int vtnr_from_tty(const char *tty) {
195✔
1080
        unsigned u;
195✔
1081
        int r;
195✔
1082

1083
        assert(tty);
195✔
1084

1085
        r = vtnr_from_tty_raw(tty, &u);
195✔
1086
        if (r < 0)
195✔
1087
                return r;
195✔
1088
        if (!vtnr_is_valid(u))
195✔
1089
                return -ERANGE;
1090

1091
        return (int) u;
195✔
1092
}
1093

1094
bool tty_is_vc(const char *tty) {
115✔
1095
        assert(tty);
115✔
1096

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

1102
        return vtnr_from_tty_raw(tty, /* ret = */ NULL) >= 0;
115✔
1103
}
1104

1105
bool tty_is_console(const char *tty) {
400✔
1106
        assert(tty);
400✔
1107

1108
        return streq(skip_dev_prefix(tty), "console");
400✔
1109
}
1110

1111
int resolve_dev_console(char **ret) {
304✔
1112
        int r;
304✔
1113

1114
        assert(ret);
304✔
1115

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

1121
        _cleanup_free_ char *chased = NULL;
304✔
1122
        r = chase("/dev/console", /* root= */ NULL, /* flags= */ 0,  &chased, /* ret_fd= */ NULL);
304✔
1123
        if (r < 0)
304✔
1124
                return r;
1125
        if (!path_equal(chased, "/dev/console")) {
304✔
1126
                *ret = TAKE_PTR(chased);
151✔
1127
                return 0;
151✔
1128
        }
1129

1130
        r = path_is_read_only_fs("/sys");
153✔
1131
        if (r < 0)
153✔
1132
                return r;
1133
        if (r > 0)
153✔
1134
                return -ENOMEDIUM;
1135

1136
        _cleanup_free_ char *active = NULL;
153✔
1137
        r = read_one_line_file("/sys/class/tty/console/active", &active);
153✔
1138
        if (r < 0)
153✔
1139
                return r;
1140

1141
        /* If multiple log outputs are configured the last one is what /dev/console points to */
1142
        const char *tty = strrchr(active, ' ');
153✔
1143
        if (tty)
153✔
UNCOV
1144
                tty++;
×
1145
        else
1146
                tty = active;
1147

1148
        if (streq(tty, "tty0")) {
153✔
UNCOV
1149
                active = mfree(active);
×
1150

1151
                /* Get the active VC (e.g. tty1) */
UNCOV
1152
                r = read_one_line_file("/sys/class/tty/tty0/active", &active);
×
UNCOV
1153
                if (r < 0)
×
1154
                        return r;
1155

1156
                tty = active;
×
1157
        }
1158

1159
        _cleanup_free_ char *path = NULL;
153✔
1160
        path = path_join("/dev", tty);
153✔
1161
        if (!path)
153✔
1162
                return -ENOMEM;
1163

1164
        *ret = TAKE_PTR(path);
153✔
1165
        return 0;
153✔
1166
}
1167

UNCOV
1168
int get_kernel_consoles(char ***ret) {
×
UNCOV
1169
        _cleanup_strv_free_ char **l = NULL;
×
UNCOV
1170
        _cleanup_free_ char *line = NULL;
×
UNCOV
1171
        int r;
×
1172

UNCOV
1173
        assert(ret);
×
1174

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

1180
        r = read_one_line_file("/sys/class/tty/console/active", &line);
×
UNCOV
1181
        if (r < 0)
×
1182
                return r;
1183

1184
        for (const char *p = line;;) {
×
1185
                _cleanup_free_ char *tty = NULL, *path = NULL;
×
1186

1187
                r = extract_first_word(&p, &tty, NULL, 0);
×
1188
                if (r < 0)
×
1189
                        return r;
UNCOV
1190
                if (r == 0)
×
1191
                        break;
1192

UNCOV
1193
                if (streq(tty, "tty0")) {
×
1194
                        tty = mfree(tty);
×
1195
                        r = read_one_line_file("/sys/class/tty/tty0/active", &tty);
×
UNCOV
1196
                        if (r < 0)
×
1197
                                return r;
1198
                }
1199

1200
                path = path_join("/dev", tty);
×
1201
                if (!path)
×
1202
                        return -ENOMEM;
1203

UNCOV
1204
                if (access(path, F_OK) < 0) {
×
UNCOV
1205
                        log_debug_errno(errno, "Console device %s is not accessible, skipping: %m", path);
×
UNCOV
1206
                        continue;
×
1207
                }
1208

UNCOV
1209
                r = strv_consume(&l, TAKE_PTR(path));
×
UNCOV
1210
                if (r < 0)
×
1211
                        return r;
1212
        }
1213

UNCOV
1214
        if (strv_isempty(l)) {
×
UNCOV
1215
                log_debug("No devices found for system console");
×
1216
                goto fallback;
×
1217
        }
1218

UNCOV
1219
        *ret = TAKE_PTR(l);
×
UNCOV
1220
        return strv_length(*ret);
×
1221

1222
fallback:
×
1223
        r = strv_extend(&l, "/dev/console");
×
UNCOV
1224
        if (r < 0)
×
1225
                return r;
1226

1227
        *ret = TAKE_PTR(l);
×
UNCOV
1228
        return 0;
×
1229
}
1230

1231
bool tty_is_vc_resolve(const char *tty) {
49✔
1232
        _cleanup_free_ char *resolved = NULL;
49✔
1233

1234
        assert(tty);
49✔
1235

1236
        if (streq(skip_dev_prefix(tty), "console")) {
49✔
1237
                if (resolve_dev_console(&resolved) < 0)
2✔
1238
                        return false;
1239

1240
                tty = resolved;
2✔
1241
        }
1242

1243
        return tty_is_vc(tty);
49✔
1244
}
1245

1246
const char* default_term_for_tty(const char *tty) {
65✔
1247
        return tty && tty_is_vc_resolve(tty) ? "linux" : "vt220";
65✔
1248
}
1249

1250
int fd_columns(int fd) {
1,230✔
1251
        struct winsize ws = {};
1,230✔
1252

1253
        if (fd < 0)
1,230✔
1254
                return -EBADF;
1255

1256
        if (ioctl(fd, TIOCGWINSZ, &ws) < 0)
1,230✔
1257
                return -errno;
1,230✔
1258

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

UNCOV
1262
        return ws.ws_col;
×
1263
}
1264

1265
int getenv_columns(void) {
1,426✔
1266
        int r;
1,426✔
1267

1268
        const char *e = getenv("COLUMNS");
1,426✔
1269
        if (!e)
1,426✔
1270
                return -ENXIO;
1,426✔
1271

1272
        unsigned c;
1✔
1273
        r = safe_atou_bounded(e, 1, USHRT_MAX, &c);
1✔
1274
        if (r < 0)
1✔
1275
                return r;
1276

1277
        return (int) c;
1✔
1278
}
1279

1280
unsigned columns(void) {
201,389✔
1281

1282
        if (cached_columns > 0)
201,389✔
1283
                return cached_columns;
200,158✔
1284

1285
        int c = getenv_columns();
1,231✔
1286
        if (c < 0) {
1,231✔
1287
                c = fd_columns(STDOUT_FILENO);
1,230✔
1288
                if (c < 0)
1,230✔
1289
                        c = 80;
1290
        }
1291

1292
        assert(c > 0);
1✔
1293

1294
        cached_columns = c;
1,231✔
1295
        return cached_columns;
1,231✔
1296
}
1297

1298
int fd_lines(int fd) {
156✔
1299
        struct winsize ws = {};
156✔
1300

1301
        if (fd < 0)
156✔
1302
                return -EBADF;
1303

1304
        if (ioctl(fd, TIOCGWINSZ, &ws) < 0)
156✔
1305
                return -errno;
156✔
1306

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

UNCOV
1310
        return ws.ws_row;
×
1311
}
1312

1313
unsigned lines(void) {
156✔
1314
        const char *e;
156✔
1315
        int l;
156✔
1316

1317
        if (cached_lines > 0)
156✔
UNCOV
1318
                return cached_lines;
×
1319

1320
        l = 0;
156✔
1321
        e = getenv("LINES");
156✔
1322
        if (e)
156✔
UNCOV
1323
                (void) safe_atoi(e, &l);
×
1324

1325
        if (l <= 0 || l > USHRT_MAX) {
156✔
1326
                l = fd_lines(STDOUT_FILENO);
156✔
1327
                if (l <= 0)
156✔
1328
                        l = 24;
156✔
1329
        }
1330

1331
        cached_lines = l;
156✔
1332
        return cached_lines;
156✔
1333
}
1334

1335
int terminal_set_size_fd(int fd, const char *ident, unsigned rows, unsigned cols) {
1,091✔
1336
        struct winsize ws;
1,091✔
1337

1338
        assert(fd >= 0);
1,091✔
1339

1340
        if (!ident)
1,091✔
1341
                ident = "TTY";
65✔
1342

1343
        if (rows == UINT_MAX && cols == UINT_MAX)
1,091✔
1344
                return 0;
1,091✔
1345

1346
        if (ioctl(fd, TIOCGWINSZ, &ws) < 0)
896✔
UNCOV
1347
                return log_debug_errno(errno,
×
1348
                                       "TIOCGWINSZ ioctl for getting %s size failed, not setting terminal size: %m",
1349
                                       ident);
1350

1351
        if (rows == UINT_MAX)
896✔
UNCOV
1352
                rows = ws.ws_row;
×
1353
        else if (rows > USHRT_MAX)
896✔
1354
                rows = USHRT_MAX;
×
1355

1356
        if (cols == UINT_MAX)
896✔
UNCOV
1357
                cols = ws.ws_col;
×
1358
        else if (cols > USHRT_MAX)
896✔
1359
                cols = USHRT_MAX;
×
1360

1361
        if (rows == ws.ws_row && cols == ws.ws_col)
896✔
1362
                return 0;
1363

1364
        ws.ws_row = rows;
368✔
1365
        ws.ws_col = cols;
368✔
1366

1367
        if (ioctl(fd, TIOCSWINSZ, &ws) < 0)
368✔
UNCOV
1368
                return log_debug_errno(errno, "TIOCSWINSZ ioctl for setting %s size failed: %m", ident);
×
1369

1370
        return 0;
1371
}
1372

1373
int proc_cmdline_tty_size(const char *tty, unsigned *ret_rows, unsigned *ret_cols) {
1,059✔
1374
        _cleanup_free_ char *rowskey = NULL, *rowsvalue = NULL, *colskey = NULL, *colsvalue = NULL;
1,059✔
1375
        unsigned rows = UINT_MAX, cols = UINT_MAX;
1,059✔
1376
        int r;
1,059✔
1377

1378
        assert(tty);
1,059✔
1379

1380
        if (!ret_rows && !ret_cols)
1,059✔
1381
                return 0;
1382

1383
        tty = skip_dev_prefix(tty);
1,059✔
1384
        if (path_startswith(tty, "pts/"))
1,059✔
1385
                return -EMEDIUMTYPE;
1386
        if (!in_charset(tty, ALPHANUMERICAL))
1,059✔
UNCOV
1387
                return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
×
1388
                                       "TTY name '%s' contains non-alphanumeric characters, not searching kernel cmdline for size.", tty);
1389

1390
        rowskey = strjoin("systemd.tty.rows.", tty);
1,059✔
1391
        if (!rowskey)
1,059✔
1392
                return -ENOMEM;
1393

1394
        colskey = strjoin("systemd.tty.columns.", tty);
1,059✔
1395
        if (!colskey)
1,059✔
1396
                return -ENOMEM;
1397

1398
        r = proc_cmdline_get_key_many(/* flags = */ 0,
1,059✔
1399
                                      rowskey, &rowsvalue,
1400
                                      colskey, &colsvalue);
1401
        if (r < 0)
1,059✔
UNCOV
1402
                return log_debug_errno(r, "Failed to read TTY size of %s from kernel cmdline: %m", tty);
×
1403

1404
        if (rowsvalue) {
1,059✔
1405
                r = safe_atou(rowsvalue, &rows);
896✔
1406
                if (r < 0)
896✔
UNCOV
1407
                        return log_debug_errno(r, "Failed to parse %s=%s: %m", rowskey, rowsvalue);
×
1408
        }
1409

1410
        if (colsvalue) {
1,059✔
1411
                r = safe_atou(colsvalue, &cols);
896✔
1412
                if (r < 0)
896✔
UNCOV
1413
                        return log_debug_errno(r, "Failed to parse %s=%s: %m", colskey, colsvalue);
×
1414
        }
1415

1416
        if (ret_rows)
1,059✔
1417
                *ret_rows = rows;
1,059✔
1418
        if (ret_cols)
1,059✔
1419
                *ret_cols = cols;
1,059✔
1420

1421
        return rows != UINT_MAX || cols != UINT_MAX;
1,059✔
1422
}
1423

1424
/* intended to be used as a SIGWINCH sighandler */
UNCOV
1425
void columns_lines_cache_reset(int signum) {
×
UNCOV
1426
        cached_columns = 0;
×
UNCOV
1427
        cached_lines = 0;
×
UNCOV
1428
}
×
1429

1430
void reset_terminal_feature_caches(void) {
14✔
1431
        cached_columns = 0;
14✔
1432
        cached_lines = 0;
14✔
1433

1434
        cached_on_tty = -1;
14✔
1435
        cached_on_dev_null = -1;
14✔
1436

1437
        reset_ansi_feature_caches();
14✔
1438
}
14✔
1439

1440
bool on_tty(void) {
7,881,566✔
1441

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

1448
        if (cached_on_tty < 0)
7,881,566✔
1449
                cached_on_tty =
46,350✔
1450
                        isatty_safe(STDOUT_FILENO) &&
46,365✔
1451
                        isatty_safe(STDERR_FILENO);
15✔
1452

1453
        return cached_on_tty;
7,881,566✔
1454
}
1455

1456
int getttyname_malloc(int fd, char **ret) {
424✔
1457
        char path[PATH_MAX]; /* PATH_MAX is counted *with* the trailing NUL byte */
424✔
1458
        int r;
424✔
1459

1460
        assert(fd >= 0);
424✔
1461
        assert(ret);
424✔
1462

1463
        r = ttyname_r(fd, path, sizeof path); /* positive error */
424✔
1464
        assert(r >= 0);
424✔
1465
        if (r == ERANGE)
424✔
1466
                return -ENAMETOOLONG;
424✔
1467
        if (r > 0)
424✔
1468
                return -r;
417✔
1469

1470
        return strdup_to(ret, skip_dev_prefix(path));
7✔
1471
}
1472

1473
int getttyname_harder(int fd, char **ret) {
22✔
1474
        _cleanup_free_ char *s = NULL;
22✔
1475
        int r;
22✔
1476

1477
        r = getttyname_malloc(fd, &s);
22✔
1478
        if (r < 0)
22✔
1479
                return r;
1480

UNCOV
1481
        if (streq(s, "tty"))
×
UNCOV
1482
                return get_ctty(0, NULL, ret);
×
1483

UNCOV
1484
        *ret = TAKE_PTR(s);
×
UNCOV
1485
        return 0;
×
1486
}
1487

1488
int get_ctty_devnr(pid_t pid, dev_t *ret) {
3,168✔
1489
        _cleanup_free_ char *line = NULL;
3,168✔
1490
        unsigned long ttynr;
3,168✔
1491
        const char *p;
3,168✔
1492
        int r;
3,168✔
1493

1494
        assert(pid >= 0);
3,168✔
1495

1496
        p = procfs_file_alloca(pid, "stat");
15,544✔
1497
        r = read_one_line_file(p, &line);
3,168✔
1498
        if (r < 0)
3,168✔
1499
                return r;
1500

1501
        p = strrchr(line, ')');
3,168✔
1502
        if (!p)
3,168✔
1503
                return -EIO;
1504

1505
        p++;
3,168✔
1506

1507
        if (sscanf(p, " "
3,168✔
1508
                   "%*c "  /* state */
1509
                   "%*d "  /* ppid */
1510
                   "%*d "  /* pgrp */
1511
                   "%*d "  /* session */
1512
                   "%lu ", /* ttynr */
1513
                   &ttynr) != 1)
1514
                return -EIO;
1515

1516
        if (devnum_is_zero(ttynr))
3,168✔
1517
                return -ENXIO;
1518

1519
        if (ret)
4✔
1520
                *ret = (dev_t) ttynr;
2✔
1521

1522
        return 0;
1523
}
1524

1525
int get_ctty(pid_t pid, dev_t *ret_devnr, char **ret) {
30✔
1526
        char pty[STRLEN("/dev/pts/") + DECIMAL_STR_MAX(dev_t) + 1];
30✔
1527
        _cleanup_free_ char *buf = NULL;
30✔
1528
        const char *fn = NULL, *w;
30✔
1529
        dev_t devnr;
30✔
1530
        int r;
30✔
1531

1532
        r = get_ctty_devnr(pid, &devnr);
30✔
1533
        if (r < 0)
30✔
1534
                return r;
1535

1536
        r = device_path_make_canonical(S_IFCHR, devnr, &buf);
2✔
1537
        if (r < 0) {
2✔
1538
                struct stat st;
2✔
1539

1540
                if (r != -ENOENT) /* No symlink for this in /dev/char/? */
2✔
UNCOV
1541
                        return r;
×
1542

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

1551
                if (stat(pty, &st) < 0) {
2✔
UNCOV
1552
                        if (errno != ENOENT)
×
UNCOV
1553
                                return -errno;
×
1554

1555
                } else if (S_ISCHR(st.st_mode) && devnr == st.st_rdev) /* Bingo! */
2✔
1556
                        fn = pty;
1557

1558
                if (!fn) {
1559
                        /* Doesn't exist, or not a PTY? Probably something similar to the PTYs which have no
1560
                         * symlink in /dev/char/. Let's return something vaguely useful. */
UNCOV
1561
                        r = device_path_make_major_minor(S_IFCHR, devnr, &buf);
×
UNCOV
1562
                        if (r < 0)
×
1563
                                return r;
1564

UNCOV
1565
                        fn = buf;
×
1566
                }
1567
        } else
1568
                fn = buf;
×
1569

1570
        w = path_startswith(fn, "/dev/");
2✔
1571
        if (!w)
2✔
1572
                return -EINVAL;
1573

1574
        if (ret) {
2✔
1575
                r = strdup_to(ret, w);
2✔
1576
                if (r < 0)
2✔
1577
                        return r;
1578
        }
1579

1580
        if (ret_devnr)
2✔
UNCOV
1581
                *ret_devnr = devnr;
×
1582

1583
        return 0;
1584
}
1585

1586
int ptsname_malloc(int fd, char **ret) {
117✔
1587
        size_t l = 100;
117✔
1588

1589
        assert(fd >= 0);
117✔
1590
        assert(ret);
117✔
1591

1592
        for (;;) {
117✔
1593
                char *c;
117✔
1594

1595
                c = new(char, l);
117✔
1596
                if (!c)
117✔
1597
                        return -ENOMEM;
1598

1599
                if (ptsname_r(fd, c, l) == 0) {
117✔
1600
                        *ret = c;
117✔
1601
                        return 0;
117✔
1602
                }
UNCOV
1603
                if (errno != ERANGE) {
×
UNCOV
1604
                        free(c);
×
UNCOV
1605
                        return -errno;
×
1606
                }
1607

UNCOV
1608
                free(c);
×
1609

1610
                if (l > SIZE_MAX / 2)
×
1611
                        return -ENOMEM;
1612

UNCOV
1613
                l *= 2;
×
1614
        }
1615
}
1616

1617
int openpt_allocate(int flags, char **ret_peer_path) {
121✔
1618
        _cleanup_close_ int fd = -EBADF;
121✔
1619
        int r;
121✔
1620

1621
        fd = posix_openpt(flags|O_NOCTTY|O_CLOEXEC);
121✔
1622
        if (fd < 0)
121✔
UNCOV
1623
                return -errno;
×
1624

1625
        _cleanup_free_ char *p = NULL;
121✔
1626
        if (ret_peer_path) {
121✔
1627
                r = ptsname_malloc(fd, &p);
117✔
1628
                if (r < 0)
117✔
1629
                        return r;
1630

1631
                if (!path_startswith(p, "/dev/pts/"))
117✔
1632
                        return -EINVAL;
1633
        }
1634

1635
        if (unlockpt(fd) < 0)
121✔
UNCOV
1636
                return -errno;
×
1637

1638
        if (ret_peer_path)
121✔
1639
                *ret_peer_path = TAKE_PTR(p);
117✔
1640

1641
        return TAKE_FD(fd);
1642
}
1643

UNCOV
1644
static int ptsname_namespace(int pty, char **ret) {
×
UNCOV
1645
        int no = -1;
×
1646

UNCOV
1647
        assert(pty >= 0);
×
UNCOV
1648
        assert(ret);
×
1649

1650
        /* Like ptsname(), but doesn't assume that the path is
1651
         * accessible in the local namespace. */
1652

UNCOV
1653
        if (ioctl(pty, TIOCGPTN, &no) < 0)
×
1654
                return -errno;
×
1655

UNCOV
1656
        if (no < 0)
×
1657
                return -EIO;
1658

UNCOV
1659
        if (asprintf(ret, "/dev/pts/%i", no) < 0)
×
1660
                return -ENOMEM;
×
1661

1662
        return 0;
1663
}
1664

UNCOV
1665
int openpt_allocate_in_namespace(
×
1666
                const PidRef *pidref,
1667
                int flags,
1668
                char **ret_peer_path) {
1669

UNCOV
1670
        _cleanup_close_ int pidnsfd = -EBADF, mntnsfd = -EBADF, usernsfd = -EBADF, rootfd = -EBADF, fd = -EBADF;
×
UNCOV
1671
        _cleanup_close_pair_ int pair[2] = EBADF_PAIR;
×
1672
        int r;
×
1673

UNCOV
1674
        r = pidref_namespace_open(pidref, &pidnsfd, &mntnsfd, /* ret_netns_fd = */ NULL, &usernsfd, &rootfd);
×
UNCOV
1675
        if (r < 0)
×
1676
                return r;
1677

1678
        if (socketpair(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0, pair) < 0)
×
1679
                return -errno;
×
1680

1681
        r = namespace_fork(
×
1682
                        "(sd-openptns)",
1683
                        "(sd-openpt)",
1684
                        /* except_fds= */ NULL,
1685
                        /* n_except_fds= */ 0,
1686
                        FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGKILL|FORK_WAIT,
1687
                        pidnsfd,
1688
                        mntnsfd,
1689
                        /* netns_fd= */ -EBADF,
1690
                        usernsfd,
1691
                        rootfd,
1692
                        /* ret_pid= */ NULL);
UNCOV
1693
        if (r < 0)
×
1694
                return r;
UNCOV
1695
        if (r == 0) {
×
UNCOV
1696
                pair[0] = safe_close(pair[0]);
×
1697

UNCOV
1698
                fd = openpt_allocate(flags, /* ret_peer_path= */ NULL);
×
UNCOV
1699
                if (fd < 0)
×
1700
                        _exit(EXIT_FAILURE);
×
1701

1702
                if (send_one_fd(pair[1], fd, 0) < 0)
×
1703
                        _exit(EXIT_FAILURE);
×
1704

1705
                _exit(EXIT_SUCCESS);
×
1706
        }
1707

UNCOV
1708
        pair[1] = safe_close(pair[1]);
×
1709

1710
        fd = receive_one_fd(pair[0], 0);
×
UNCOV
1711
        if (fd < 0)
×
1712
                return fd;
1713

UNCOV
1714
        if (ret_peer_path) {
×
1715
                r = ptsname_namespace(fd, ret_peer_path);
×
UNCOV
1716
                if (r < 0)
×
1717
                        return r;
×
1718
        }
1719

1720
        return TAKE_FD(fd);
1721
}
1722

1723
static bool on_dev_null(void) {
56,377✔
1724
        struct stat dst, ost, est;
56,377✔
1725

1726
        if (cached_on_dev_null >= 0)
56,377✔
1727
                return cached_on_dev_null;
10,096✔
1728

1729
        if (stat("/dev/null", &dst) < 0 || fstat(STDOUT_FILENO, &ost) < 0 || fstat(STDERR_FILENO, &est) < 0)
46,281✔
1730
                cached_on_dev_null = false;
1✔
1731
        else
1732
                cached_on_dev_null = stat_inode_same(&dst, &ost) && stat_inode_same(&dst, &est);
50,109✔
1733

1734
        return cached_on_dev_null;
46,281✔
1735
}
1736

1737
bool getenv_terminal_is_dumb(void) {
12,888✔
1738
        const char *e;
12,888✔
1739

1740
        e = getenv("TERM");
12,888✔
1741
        if (!e)
12,888✔
1742
                return true;
1743

1744
        return streq(e, "dumb");
11,815✔
1745
}
1746

1747
bool terminal_is_dumb(void) {
56,384✔
1748
        if (!on_tty() && !on_dev_null())
56,384✔
1749
                return true;
1750

1751
        return getenv_terminal_is_dumb();
194✔
1752
}
1753

UNCOV
1754
bool dev_console_colors_enabled(void) {
×
UNCOV
1755
        _cleanup_free_ char *s = NULL;
×
UNCOV
1756
        ColorMode m;
×
1757

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

UNCOV
1765
        m = parse_systemd_colors();
×
UNCOV
1766
        if (m >= 0)
×
UNCOV
1767
                return m;
×
1768

UNCOV
1769
        if (getenv("NO_COLOR"))
×
1770
                return false;
1771

1772
        if (getenv_for_pid(1, "TERM", &s) <= 0)
×
1773
                (void) proc_cmdline_get_key("TERM", 0, &s);
×
1774

UNCOV
1775
        return !streq_ptr(s, "dumb");
×
1776
}
1777

UNCOV
1778
int vt_restore(int fd) {
×
1779

1780
        static const struct vt_mode mode = {
×
1781
                .mode = VT_AUTO,
1782
        };
1783

UNCOV
1784
        int r, ret = 0;
×
1785

UNCOV
1786
        assert(fd >= 0);
×
1787

UNCOV
1788
        if (!isatty_safe(fd))
×
UNCOV
1789
                return log_debug_errno(SYNTHETIC_ERRNO(ENOTTY), "Asked to restore the VT for an fd that does not refer to a terminal: %m");
×
1790

1791
        if (ioctl(fd, KDSETMODE, KD_TEXT) < 0)
×
UNCOV
1792
                RET_GATHER(ret, log_debug_errno(errno, "Failed to set VT to text mode, ignoring: %m"));
×
1793

UNCOV
1794
        r = vt_reset_keyboard(fd);
×
1795
        if (r < 0)
×
1796
                RET_GATHER(ret, log_debug_errno(r, "Failed to reset keyboard mode, ignoring: %m"));
×
1797

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

1801
        r = fchmod_and_chown(fd, TTY_MODE, 0, GID_INVALID);
×
1802
        if (r < 0)
×
1803
                RET_GATHER(ret, log_debug_errno(r, "Failed to chmod()/chown() VT, ignoring: %m"));
×
1804

1805
        return ret;
1806
}
1807

1808
int vt_release(int fd, bool restore) {
×
1809
        assert(fd >= 0);
×
1810

1811
        /* This function releases the VT by acknowledging the VT-switch signal
1812
         * sent by the kernel and optionally reset the VT in text and auto
1813
         * VT-switching modes. */
1814

1815
        if (!isatty_safe(fd))
×
1816
                return log_debug_errno(SYNTHETIC_ERRNO(ENOTTY), "Asked to release the VT for an fd that does not refer to a terminal: %m");
×
1817

UNCOV
1818
        if (ioctl(fd, VT_RELDISP, 1) < 0)
×
UNCOV
1819
                return -errno;
×
1820

UNCOV
1821
        if (restore)
×
1822
                return vt_restore(fd);
×
1823

1824
        return 0;
1825
}
1826

1827
void get_log_colors(int priority, const char **on, const char **off, const char **highlight) {
178,481✔
1828
        /* Note that this will initialize output variables only when there's something to output.
1829
         * The caller must pre-initialize to "" or NULL as appropriate. */
1830

1831
        if (priority <= LOG_ERR) {
178,481✔
1832
                if (on)
6,385✔
1833
                        *on = ansi_highlight_red();
12,770✔
1834
                if (off)
6,385✔
1835
                        *off = ansi_normal();
12,770✔
1836
                if (highlight)
6,385✔
UNCOV
1837
                        *highlight = ansi_highlight();
×
1838

1839
        } else if (priority <= LOG_WARNING) {
172,096✔
1840
                if (on)
947✔
1841
                        *on = ansi_highlight_yellow();
947✔
1842
                if (off)
947✔
1843
                        *off = ansi_normal();
1,894✔
1844
                if (highlight)
947✔
UNCOV
1845
                        *highlight = ansi_highlight();
×
1846

1847
        } else if (priority <= LOG_NOTICE) {
171,149✔
1848
                if (on)
875✔
1849
                        *on = ansi_highlight();
1,750✔
1850
                if (off)
875✔
1851
                        *off = ansi_normal();
1,750✔
1852
                if (highlight)
875✔
UNCOV
1853
                        *highlight = ansi_highlight_red();
×
1854

1855
        } else if (priority >= LOG_DEBUG) {
170,274✔
1856
                if (on)
130,863✔
1857
                        *on = ansi_grey();
130,863✔
1858
                if (off)
130,863✔
1859
                        *off = ansi_normal();
261,726✔
1860
                if (highlight)
130,863✔
UNCOV
1861
                        *highlight = ansi_highlight_red();
×
1862
        }
1863
}
178,481✔
1864

UNCOV
1865
int terminal_set_cursor_position(int fd, unsigned row, unsigned column) {
×
UNCOV
1866
        assert(fd >= 0);
×
1867

1868
        char cursor_position[STRLEN("\x1B[" ";" "H") + DECIMAL_STR_MAX(unsigned) * 2 + 1];
×
UNCOV
1869
        xsprintf(cursor_position, "\x1B[%u;%uH", row, column);
×
1870

UNCOV
1871
        return loop_write(fd, cursor_position, SIZE_MAX);
×
1872
}
1873

1874
int terminal_reset_defensive(int fd, TerminalResetFlags flags) {
317✔
1875
        int r = 0;
317✔
1876

1877
        assert(fd >= 0);
317✔
1878
        assert(!FLAGS_SET(flags, TERMINAL_RESET_AVOID_ANSI_SEQ|TERMINAL_RESET_FORCE_ANSI_SEQ));
317✔
1879

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

1883
        if (!isatty_safe(fd))
317✔
1884
                return -ENOTTY;
317✔
1885

1886
        RET_GATHER(r, terminal_reset_ioctl(fd, FLAGS_SET(flags, TERMINAL_RESET_SWITCH_TO_TEXT)));
310✔
1887

1888
        if (!FLAGS_SET(flags, TERMINAL_RESET_AVOID_ANSI_SEQ) &&
310✔
1889
            (FLAGS_SET(flags, TERMINAL_RESET_FORCE_ANSI_SEQ) || !getenv_terminal_is_dumb()))
304✔
1890
                RET_GATHER(r, terminal_reset_ansi_seq(fd));
304✔
1891

1892
        return r;
1893
}
1894

1895
int terminal_reset_defensive_locked(int fd, TerminalResetFlags flags) {
6✔
1896
        assert(fd >= 0);
6✔
1897

1898
        _cleanup_close_ int lock_fd = lock_dev_console();
6✔
1899
        if (lock_fd < 0)
6✔
UNCOV
1900
                log_debug_errno(lock_fd, "Failed to acquire lock for /dev/console, ignoring: %m");
×
1901

1902
        return terminal_reset_defensive(fd, flags);
6✔
1903
}
1904

UNCOV
1905
void termios_disable_echo(struct termios *termios) {
×
UNCOV
1906
        assert(termios);
×
1907

UNCOV
1908
        termios->c_lflag &= ~(ICANON|ECHO);
×
UNCOV
1909
        termios->c_cc[VMIN] = 1;
×
UNCOV
1910
        termios->c_cc[VTIME] = 0;
×
UNCOV
1911
}
×
1912

1913
static int terminal_verify_same(int input_fd, int output_fd) {
1✔
1914
        assert(input_fd >= 0);
1✔
1915
        assert(output_fd >= 0);
1✔
1916

1917
        /* Validates that the specified fds reference the same TTY */
1918

1919
        if (input_fd != output_fd) {
1✔
1920
                struct stat sti;
1✔
1921
                if (fstat(input_fd, &sti) < 0)
1✔
1922
                        return -errno;
1✔
1923

1924
                if (!S_ISCHR(sti.st_mode)) /* TTYs are character devices */
1✔
1925
                        return -ENOTTY;
1926

1927
                struct stat sto;
1✔
1928
                if (fstat(output_fd, &sto) < 0)
1✔
UNCOV
1929
                        return -errno;
×
1930

1931
                if (!S_ISCHR(sto.st_mode))
1✔
1932
                        return -ENOTTY;
1933

UNCOV
1934
                if (sti.st_rdev != sto.st_rdev)
×
1935
                        return -ENOLINK;
1936
        }
1937

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

1941
        return 0;
1942
}
1943

1944
typedef enum BackgroundColorState {
1945
        BACKGROUND_TEXT,
1946
        BACKGROUND_ESCAPE,
1947
        BACKGROUND_BRACKET,
1948
        BACKGROUND_FIRST_ONE,
1949
        BACKGROUND_SECOND_ONE,
1950
        BACKGROUND_SEMICOLON,
1951
        BACKGROUND_R,
1952
        BACKGROUND_G,
1953
        BACKGROUND_B,
1954
        BACKGROUND_RED,
1955
        BACKGROUND_GREEN,
1956
        BACKGROUND_BLUE,
1957
        BACKGROUND_STRING_TERMINATOR,
1958
} BackgroundColorState;
1959

1960
typedef struct BackgroundColorContext {
1961
        BackgroundColorState state;
1962
        uint32_t red, green, blue;
1963
        unsigned red_bits, green_bits, blue_bits;
1964
} BackgroundColorContext;
1965

UNCOV
1966
static int scan_background_color_response(
×
1967
                BackgroundColorContext *context,
1968
                const char *buf,
1969
                size_t size,
1970
                size_t *ret_processed) {
1971

UNCOV
1972
        assert(context);
×
1973
        assert(buf);
×
UNCOV
1974
        assert(ret_processed);
×
1975

UNCOV
1976
        for (size_t i = 0; i < size; i++) {
×
UNCOV
1977
                char c = buf[i];
×
1978

1979
                switch (context->state) {
×
1980

1981
                case BACKGROUND_TEXT:
×
UNCOV
1982
                        context->state = c == '\x1B' ? BACKGROUND_ESCAPE : BACKGROUND_TEXT;
×
1983
                        break;
×
1984

UNCOV
1985
                case BACKGROUND_ESCAPE:
×
1986
                        context->state = c == ']' ? BACKGROUND_BRACKET : BACKGROUND_TEXT;
×
UNCOV
1987
                        break;
×
1988

1989
                case BACKGROUND_BRACKET:
×
1990
                        context->state = c == '1' ? BACKGROUND_FIRST_ONE : BACKGROUND_TEXT;
×
UNCOV
1991
                        break;
×
1992

1993
                case BACKGROUND_FIRST_ONE:
×
1994
                        context->state = c == '1' ? BACKGROUND_SECOND_ONE : BACKGROUND_TEXT;
×
UNCOV
1995
                        break;
×
1996

1997
                case BACKGROUND_SECOND_ONE:
×
1998
                        context->state = c == ';' ? BACKGROUND_SEMICOLON : BACKGROUND_TEXT;
×
UNCOV
1999
                        break;
×
2000

2001
                case BACKGROUND_SEMICOLON:
×
2002
                        context->state = c == 'r' ? BACKGROUND_R : BACKGROUND_TEXT;
×
UNCOV
2003
                        break;
×
2004

2005
                case BACKGROUND_R:
×
2006
                        context->state = c == 'g' ? BACKGROUND_G : BACKGROUND_TEXT;
×
UNCOV
2007
                        break;
×
2008

2009
                case BACKGROUND_G:
×
2010
                        context->state = c == 'b' ? BACKGROUND_B : BACKGROUND_TEXT;
×
UNCOV
2011
                        break;
×
2012

2013
                case BACKGROUND_B:
×
2014
                        context->state = c == ':' ? BACKGROUND_RED : BACKGROUND_TEXT;
×
UNCOV
2015
                        break;
×
2016

2017
                case BACKGROUND_RED:
×
2018
                        if (c == '/')
×
UNCOV
2019
                                context->state = context->red_bits > 0 ? BACKGROUND_GREEN : BACKGROUND_TEXT;
×
2020
                        else {
2021
                                int d = unhexchar(c);
×
2022
                                if (d < 0 || context->red_bits >= sizeof(context->red)*8)
×
UNCOV
2023
                                        context->state = BACKGROUND_TEXT;
×
2024
                                else {
2025
                                        context->red = (context->red << 4) | d;
×
2026
                                        context->red_bits += 4;
×
2027
                                }
2028
                        }
2029
                        break;
2030

UNCOV
2031
                case BACKGROUND_GREEN:
×
2032
                        if (c == '/')
×
2033
                                context->state = context->green_bits > 0 ? BACKGROUND_BLUE : BACKGROUND_TEXT;
×
2034
                        else {
UNCOV
2035
                                int d = unhexchar(c);
×
UNCOV
2036
                                if (d < 0 || context->green_bits >= sizeof(context->green)*8)
×
UNCOV
2037
                                        context->state = BACKGROUND_TEXT;
×
2038
                                else {
2039
                                        context->green = (context->green << 4) | d;
×
2040
                                        context->green_bits += 4;
×
2041
                                }
2042
                        }
2043
                        break;
2044

UNCOV
2045
                case BACKGROUND_BLUE:
×
2046
                        if (c == '\x07') {
×
2047
                                if (context->blue_bits > 0) {
×
UNCOV
2048
                                        *ret_processed = i + 1;
×
UNCOV
2049
                                        return 1; /* success! */
×
2050
                                }
2051

2052
                                context->state = BACKGROUND_TEXT;
×
2053
                        } else if (c == '\x1b')
×
2054
                                context->state = context->blue_bits > 0 ? BACKGROUND_STRING_TERMINATOR : BACKGROUND_TEXT;
×
2055
                        else {
2056
                                int d = unhexchar(c);
×
UNCOV
2057
                                if (d < 0 || context->blue_bits >= sizeof(context->blue)*8)
×
UNCOV
2058
                                        context->state = BACKGROUND_TEXT;
×
2059
                                else {
2060
                                        context->blue = (context->blue << 4) | d;
×
2061
                                        context->blue_bits += 4;
×
2062
                                }
2063
                        }
2064
                        break;
2065

UNCOV
2066
                case BACKGROUND_STRING_TERMINATOR:
×
2067
                        if (c == '\\') {
×
2068
                                *ret_processed = i + 1;
×
UNCOV
2069
                                return 1; /* success! */
×
2070
                        }
2071

UNCOV
2072
                        context->state = c == ']' ? BACKGROUND_ESCAPE : BACKGROUND_TEXT;
×
2073
                        break;
×
2074

2075
                }
2076

2077
                /* Reset any colors we might have picked up */
UNCOV
2078
                if (IN_SET(context->state, BACKGROUND_TEXT, BACKGROUND_ESCAPE)) {
×
2079
                        /* reset color */
2080
                        context->red = context->green = context->blue = 0;
×
UNCOV
2081
                        context->red_bits = context->green_bits = context->blue_bits = 0;
×
2082
                }
2083
        }
2084

2085
        *ret_processed = size;
×
UNCOV
2086
        return 0; /* all good, but not enough data yet */
×
2087
}
2088

2089
int get_default_background_color(double *ret_red, double *ret_green, double *ret_blue) {
125✔
2090
        _cleanup_close_ int nonblock_input_fd = -EBADF;
125✔
2091
        int r;
125✔
2092

2093
        assert(ret_red);
125✔
2094
        assert(ret_green);
125✔
2095
        assert(ret_blue);
125✔
2096

2097
        if (!colors_enabled())
125✔
2098
                return -EOPNOTSUPP;
2099

UNCOV
2100
        r = terminal_verify_same(STDIN_FILENO, STDOUT_FILENO);
×
UNCOV
2101
        if (r < 0)
×
2102
                return r;
2103

UNCOV
2104
        if (streq_ptr(getenv("TERM"), "linux")) {
×
2105
                /* Linux console is black */
UNCOV
2106
                *ret_red = *ret_green = *ret_blue = 0.0;
×
2107
                return 0;
×
2108
        }
2109

UNCOV
2110
        struct termios old_termios;
×
2111
        if (tcgetattr(STDIN_FILENO, &old_termios) < 0)
×
UNCOV
2112
                return -errno;
×
2113

2114
        struct termios new_termios = old_termios;
×
UNCOV
2115
        termios_disable_echo(&new_termios);
×
2116

2117
        if (tcsetattr(STDIN_FILENO, TCSADRAIN, &new_termios) < 0)
×
2118
                return -errno;
×
2119

UNCOV
2120
        r = loop_write(STDOUT_FILENO, ANSI_OSC "11;?" ANSI_ST, SIZE_MAX);
×
2121
        if (r < 0)
×
2122
                goto finish;
×
2123

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

2127
        nonblock_input_fd = r = fd_reopen(STDIN_FILENO, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
×
2128
        if (r < 0)
×
2129
                goto finish;
×
2130

UNCOV
2131
        usec_t end = usec_add(now(CLOCK_MONOTONIC), 333 * USEC_PER_MSEC);
×
UNCOV
2132
        char buf[STRLEN(ANSI_OSC "11;rgb:0/0/0" ANSI_ST)]; /* shortest possible reply */
×
UNCOV
2133
        size_t buf_full = 0;
×
2134
        BackgroundColorContext context = {};
×
2135

2136
        for (bool first = true;; first = false) {
×
UNCOV
2137
                if (buf_full == 0) {
×
2138
                        usec_t n = now(CLOCK_MONOTONIC);
×
2139
                        if (n >= end) {
×
2140
                                r = -EOPNOTSUPP;
×
2141
                                goto finish;
×
2142
                        }
2143

2144
                        r = fd_wait_for_event(nonblock_input_fd, POLLIN, usec_sub_unsigned(end, n));
×
2145
                        if (r < 0)
×
2146
                                goto finish;
×
2147
                        if (r == 0) {
×
2148
                                r = -EOPNOTSUPP;
×
UNCOV
2149
                                goto finish;
×
2150
                        }
2151

2152
                        /* On the first try, read multiple characters, i.e. the shortest valid
2153
                         * reply. Afterwards read byte-wise, since we don't want to read too much, and
2154
                         * unnecessarily drop too many characters from the input queue. */
2155
                        ssize_t l = read(nonblock_input_fd, buf, first ? sizeof(buf) : 1);
×
2156
                        if (l < 0) {
×
UNCOV
2157
                                if (errno == EAGAIN)
×
UNCOV
2158
                                        continue;
×
UNCOV
2159
                                r = -errno;
×
UNCOV
2160
                                goto finish;
×
2161
                        }
2162

2163
                        assert((size_t) l <= sizeof(buf));
×
2164
                        buf_full = l;
2165
                }
2166

2167
                size_t processed;
×
UNCOV
2168
                r = scan_background_color_response(&context, buf, buf_full, &processed);
×
UNCOV
2169
                if (r < 0)
×
2170
                        goto finish;
×
2171

UNCOV
2172
                assert(processed <= buf_full);
×
UNCOV
2173
                buf_full -= processed;
×
2174
                memmove(buf, buf + processed, buf_full);
×
2175

2176
                if (r > 0) {
×
2177
                        assert(context.red_bits > 0);
×
UNCOV
2178
                        *ret_red = (double) context.red / ((UINT64_C(1) << context.red_bits) - 1);
×
2179
                        assert(context.green_bits > 0);
×
2180
                        *ret_green = (double) context.green / ((UINT64_C(1) << context.green_bits) - 1);
×
2181
                        assert(context.blue_bits > 0);
×
UNCOV
2182
                        *ret_blue = (double) context.blue / ((UINT64_C(1) << context.blue_bits) - 1);
×
2183
                        r = 0;
×
2184
                        goto finish;
×
2185
                }
2186
        }
2187

2188
finish:
×
2189
        RET_GATHER(r, RET_NERRNO(tcsetattr(STDIN_FILENO, TCSADRAIN, &old_termios)));
×
2190
        return r;
2191
}
2192

2193
typedef enum CursorPositionState {
2194
        CURSOR_TEXT,
2195
        CURSOR_ESCAPE,
2196
        CURSOR_ROW,
2197
        CURSOR_COLUMN,
2198
} CursorPositionState;
2199

2200
typedef struct CursorPositionContext {
2201
        CursorPositionState state;
2202
        unsigned row, column;
2203
} CursorPositionContext;
2204

UNCOV
2205
static int scan_cursor_position_response(
×
2206
                CursorPositionContext *context,
2207
                const char *buf,
2208
                size_t size,
2209
                size_t *ret_processed) {
2210

UNCOV
2211
        assert(context);
×
2212
        assert(buf);
×
UNCOV
2213
        assert(ret_processed);
×
2214

UNCOV
2215
        for (size_t i = 0; i < size; i++) {
×
UNCOV
2216
                char c = buf[i];
×
2217

2218
                switch (context->state) {
×
2219

2220
                case CURSOR_TEXT:
×
UNCOV
2221
                        context->state = c == '\x1B' ? CURSOR_ESCAPE : CURSOR_TEXT;
×
2222
                        break;
×
2223

UNCOV
2224
                case CURSOR_ESCAPE:
×
2225
                        context->state = c == '[' ? CURSOR_ROW : CURSOR_TEXT;
×
UNCOV
2226
                        break;
×
2227

2228
                case CURSOR_ROW:
×
2229
                        if (c == ';')
×
UNCOV
2230
                                context->state = context->row > 0 ? CURSOR_COLUMN : CURSOR_TEXT;
×
2231
                        else {
2232
                                int d = undecchar(c);
×
2233

2234
                                /* We read a decimal character, let's suffix it to the number we so far read,
2235
                                 * but let's do an overflow check first. */
2236
                                if (d < 0 || context->row > (UINT_MAX-d)/10)
×
2237
                                        context->state = CURSOR_TEXT;
×
2238
                                else
2239
                                        context->row = context->row * 10 + d;
×
2240
                        }
2241
                        break;
2242

2243
                case CURSOR_COLUMN:
×
2244
                        if (c == 'R') {
×
UNCOV
2245
                                if (context->column > 0) {
×
2246
                                        *ret_processed = i + 1;
×
UNCOV
2247
                                        return 1; /* success! */
×
2248
                                }
2249

2250
                                context->state = CURSOR_TEXT;
×
2251
                        } else {
2252
                                int d = undecchar(c);
×
2253

2254
                                /* As above, add the decimal character to our column number */
UNCOV
2255
                                if (d < 0 || context->column > (UINT_MAX-d)/10)
×
UNCOV
2256
                                        context->state = CURSOR_TEXT;
×
2257
                                else
UNCOV
2258
                                        context->column = context->column * 10 + d;
×
2259
                        }
2260

2261
                        break;
2262
                }
2263

2264
                /* Reset any positions we might have picked up */
2265
                if (IN_SET(context->state, CURSOR_TEXT, CURSOR_ESCAPE))
×
UNCOV
2266
                        context->row = context->column = 0;
×
2267
        }
2268

UNCOV
2269
        *ret_processed = size;
×
UNCOV
2270
        return 0; /* all good, but not enough data yet */
×
2271
}
2272

2273
int terminal_get_size_by_dsr(
168✔
2274
                int input_fd,
2275
                int output_fd,
2276
                unsigned *ret_rows,
2277
                unsigned *ret_columns) {
2278

2279
        _cleanup_close_ int nonblock_input_fd = -EBADF;
168✔
2280
        int r;
168✔
2281

2282
        assert(input_fd >= 0);
168✔
2283
        assert(output_fd >= 0);
168✔
2284

2285
        /* Tries to determine the terminal dimension by means of ANSI sequences rather than TIOCGWINSZ
2286
         * ioctl(). Why bother with this? The ioctl() information is often incorrect on serial terminals
2287
         * (since there's no handshake or protocol to determine the right dimensions in RS232), but since the
2288
         * ANSI sequences are interpreted by the final terminal instead of an intermediary tty driver they
2289
         * should be more accurate.
2290
         *
2291
         * Unfortunately there's no direct ANSI sequence to query terminal dimensions. But we can hack around
2292
         * it: we position the cursor briefly at an absolute location very far down and very far to the
2293
         * right, and then read back where we actually ended up. Because cursor locations are capped at the
2294
         * terminal width/height we should then see the right values. In order to not risk integer overflows
2295
         * in terminal applications we'll use INT16_MAX-1 as location to jump to — hopefully a value that is
2296
         * large enough for any real-life terminals, but small enough to not overflow anything or be
2297
         * recognized as a "niche" value. (Note that the dimension fields in "struct winsize" are 16bit only,
2298
         * too). */
2299

2300
        if (terminal_is_dumb())
168✔
2301
                return -EOPNOTSUPP;
2302

UNCOV
2303
        r = terminal_verify_same(input_fd, output_fd);
×
UNCOV
2304
        if (r < 0)
×
UNCOV
2305
                return log_debug_errno(r, "Called with distinct input/output fds: %m");
×
2306

UNCOV
2307
        struct termios old_termios;
×
UNCOV
2308
        if (tcgetattr(input_fd, &old_termios) < 0)
×
UNCOV
2309
                return log_debug_errno(errno, "Failed to to get terminal settings: %m");
×
2310

2311
        struct termios new_termios = old_termios;
×
2312
        termios_disable_echo(&new_termios);
×
2313

2314
        if (tcsetattr(input_fd, TCSADRAIN, &new_termios) < 0)
×
2315
                return log_debug_errno(errno, "Failed to to set new terminal settings: %m");
×
2316

UNCOV
2317
        unsigned saved_row = 0, saved_column = 0;
×
2318

2319
        r = loop_write(output_fd,
×
2320
                       "\x1B[6n"           /* Request cursor position (DSR/CPR) */
2321
                       "\x1B[32766;32766H" /* Position cursor really far to the right and to the bottom, but let's stay within the 16bit signed range */
2322
                       "\x1B[6n",          /* Request cursor position again */
2323
                       SIZE_MAX);
2324
        if (r < 0)
×
UNCOV
2325
                goto finish;
×
2326

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

UNCOV
2330
        nonblock_input_fd = r = fd_reopen(input_fd, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
×
2331
        if (r < 0)
×
2332
                goto finish;
×
2333

UNCOV
2334
        usec_t end = usec_add(now(CLOCK_MONOTONIC), 333 * USEC_PER_MSEC);
×
UNCOV
2335
        char buf[STRLEN("\x1B[1;1R")]; /* The shortest valid reply possible */
×
UNCOV
2336
        size_t buf_full = 0;
×
2337
        CursorPositionContext context = {};
×
2338

2339
        for (bool first = true;; first = false) {
×
UNCOV
2340
                if (buf_full == 0) {
×
2341
                        usec_t n = now(CLOCK_MONOTONIC);
×
2342
                        if (n >= end) {
×
2343
                                r = -EOPNOTSUPP;
×
2344
                                goto finish;
×
2345
                        }
2346

2347
                        r = fd_wait_for_event(nonblock_input_fd, POLLIN, usec_sub_unsigned(end, n));
×
2348
                        if (r < 0)
×
2349
                                goto finish;
×
2350
                        if (r == 0) {
×
2351
                                r = -EOPNOTSUPP;
×
UNCOV
2352
                                goto finish;
×
2353
                        }
2354

2355
                        /* On the first try, read multiple characters, i.e. the shortest valid
2356
                         * reply. Afterwards read byte-wise, since we don't want to read too much, and
2357
                         * unnecessarily drop too many characters from the input queue. */
2358
                        ssize_t l = read(nonblock_input_fd, buf, first ? sizeof(buf) : 1);
×
2359
                        if (l < 0) {
×
UNCOV
2360
                                if (errno == EAGAIN)
×
UNCOV
2361
                                        continue;
×
2362

UNCOV
2363
                                r = -errno;
×
UNCOV
2364
                                goto finish;
×
2365
                        }
2366

2367
                        assert((size_t) l <= sizeof(buf));
×
2368
                        buf_full = l;
2369
                }
2370

2371
                size_t processed;
×
UNCOV
2372
                r = scan_cursor_position_response(&context, buf, buf_full, &processed);
×
UNCOV
2373
                if (r < 0)
×
2374
                        goto finish;
×
2375

UNCOV
2376
                assert(processed <= buf_full);
×
UNCOV
2377
                buf_full -= processed;
×
2378
                memmove(buf, buf + processed, buf_full);
×
2379

2380
                if (r > 0) {
×
2381
                        if (saved_row == 0) {
×
UNCOV
2382
                                assert(saved_column == 0);
×
2383

2384
                                /* First sequence, this is the cursor position before we set it somewhere
2385
                                 * into the void at the bottom right. Let's save where we are so that we can
2386
                                 * return later. */
2387

2388
                                /* Superficial validity checks */
2389
                                if (context.row <= 0 || context.column <= 0 || context.row >= 32766 || context.column >= 32766) {
×
UNCOV
2390
                                        r = -ENODATA;
×
UNCOV
2391
                                        goto finish;
×
2392
                                }
2393

UNCOV
2394
                                saved_row = context.row;
×
UNCOV
2395
                                saved_column = context.column;
×
2396

2397
                                /* Reset state */
2398
                                context = (CursorPositionContext) {};
×
2399
                        } else {
2400
                                /* Second sequence, this is the cursor position after we set it somewhere
2401
                                 * into the void at the bottom right. */
2402

2403
                                /* Superficial validity checks (no particular reason to check for < 4, it's
2404
                                 * just a way to look for unreasonably small values) */
2405
                                if (context.row < 4 || context.column < 4 || context.row >= 32766 || context.column >= 32766) {
×
UNCOV
2406
                                        r = -ENODATA;
×
UNCOV
2407
                                        goto finish;
×
2408
                                }
2409

UNCOV
2410
                                if (ret_rows)
×
UNCOV
2411
                                        *ret_rows = context.row;
×
2412
                                if (ret_columns)
×
2413
                                        *ret_columns = context.column;
×
2414

UNCOV
2415
                                r = 0;
×
UNCOV
2416
                                goto finish;
×
2417
                        }
2418
                }
2419
        }
2420

UNCOV
2421
finish:
×
2422
        /* Restore cursor position */
2423
        if (saved_row > 0 && saved_column > 0)
×
UNCOV
2424
                RET_GATHER(r, terminal_set_cursor_position(output_fd, saved_row, saved_column));
×
2425

UNCOV
2426
        RET_GATHER(r, RET_NERRNO(tcsetattr(input_fd, TCSADRAIN, &old_termios)));
×
2427
        return r;
2428
}
2429

2430
int terminal_fix_size(int input_fd, int output_fd) {
1✔
2431
        unsigned rows, columns;
1✔
2432
        int r;
1✔
2433

2434
        /* Tries to update the current terminal dimensions to the ones reported via ANSI sequences */
2435

2436
        r = terminal_verify_same(input_fd, output_fd);
1✔
2437
        if (r < 0)
1✔
2438
                return r;
1✔
2439

UNCOV
2440
        struct winsize ws = {};
×
UNCOV
2441
        if (ioctl(output_fd, TIOCGWINSZ, &ws) < 0)
×
UNCOV
2442
                return log_debug_errno(errno, "Failed to query terminal dimensions, ignoring: %m");
×
2443

UNCOV
2444
        r = terminal_get_size_by_dsr(input_fd, output_fd, &rows, &columns);
×
UNCOV
2445
        if (r < 0)
×
UNCOV
2446
                return log_debug_errno(r, "Failed to acquire terminal dimensions via ANSI sequences, not adjusting terminal dimensions: %m");
×
2447

2448
        if (ws.ws_row == rows && ws.ws_col == columns) {
×
2449
                log_debug("Terminal dimensions reported via ANSI sequences match currently set terminal dimensions, not changing.");
×
UNCOV
2450
                return 0;
×
2451
        }
2452

2453
        ws.ws_col = columns;
×
UNCOV
2454
        ws.ws_row = rows;
×
2455

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

UNCOV
2459
        log_debug("Fixed terminal dimensions to %ux%u based on ANSI sequence information.", columns, rows);
×
2460
        return 1;
2461
}
2462

2463
int terminal_is_pty_fd(int fd) {
3✔
2464
        int r;
3✔
2465

2466
        assert(fd >= 0);
3✔
2467

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

2470
        if (!isatty_safe(fd))
3✔
2471
                return false;
3✔
2472

2473
        r = is_fs_type_at(fd, NULL, DEVPTS_SUPER_MAGIC);
2✔
2474
        if (r != 0)
2✔
2475
                return r;
2476

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

UNCOV
2480
        int v;
×
UNCOV
2481
        if (ioctl(fd, TIOCGPKT, &v) < 0) {
×
UNCOV
2482
                if (ERRNO_IS_NOT_SUPPORTED(errno))
×
2483
                        return false;
2484

UNCOV
2485
                return -errno;
×
2486
        }
2487

2488
        return true;
2489
}
2490

2491
int pty_open_peer(int fd, int mode) {
20✔
2492
        assert(fd >= 0);
20✔
2493

2494
        /* Opens the peer PTY using the new race-free TIOCGPTPEER ioctl() (kernel 4.13).
2495
         *
2496
         * This is safe to be called on TTYs from other namespaces. */
2497

2498
        assert((mode & (O_CREAT|O_PATH|O_DIRECTORY|O_TMPFILE)) == 0);
20✔
2499

2500
        /* This replicates the EIO retry logic of open_terminal() in a modified way. */
UNCOV
2501
        for (unsigned c = 0;; c++) {
×
2502
                int peer_fd = ioctl(fd, TIOCGPTPEER, mode);
20✔
2503
                if (peer_fd >= 0)
20✔
2504
                        return peer_fd;
2505

UNCOV
2506
                if (errno != EIO)
×
UNCOV
2507
                        return -errno;
×
2508

2509
                /* Max 1s in total */
UNCOV
2510
                if (c >= 20)
×
2511
                        return -EIO;
2512

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

© 2026 Coveralls, Inc