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

systemd / systemd / 14630481637

23 Apr 2025 07:04PM UTC coverage: 72.178% (-0.002%) from 72.18%
14630481637

push

github

DaanDeMeyer
mkosi: Run clangd within the tools tree instead of the build container

Running within the build sandbox has a number of disadvantages:
- We have a separate clangd cache for each distribution/release combo
- It requires to build the full image before clangd can be used
- It breaks every time the image becomes out of date and requires a
  rebuild
- We can't look at system headers as we don't have the knowledge to map
  them from inside the build sandbox to the corresponding path on the host

Instead, let's have mkosi.clangd run clangd within the tools tree. We
already require building systemd for both the host and the target anyway,
and all the dependencies to build systemd are installed in the tools tree
already for that, as well as clangd since it's installed together with the
other clang tooling we install in the tools tree. Unlike the previous approach,
this approach only requires the mkosi tools tree to be built upfront, which has
a much higher chance of not invalidating its cache. We can also trivially map
system header lookups from within the sandbox to the path within mkosi.tools
on the host so that starts working as well.

297054 of 411557 relevant lines covered (72.18%)

686269.58 hits per line

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

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

3
#include <errno.h>
4
#include <fcntl.h>
5
#include <limits.h>
6
#include <linux/kd.h>
7
#include <linux/tiocl.h>
8
#include <linux/vt.h>
9
#include <poll.h>
10
#include <signal.h>
11
#include <stdarg.h>
12
#include <stddef.h>
13
#include <stdlib.h>
14
#include <sys/inotify.h>
15
#include <sys/ioctl.h>
16
#include <sys/sysmacros.h>
17
#include <sys/time.h>
18
#include <sys/types.h>
19
#include <sys/utsname.h>
20
#include <termios.h>
21
#include <unistd.h>
22

23
#include "alloc-util.h"
24
#include "ansi-color.h"
25
#include "chase.h"
26
#include "constants.h"
27
#include "devnum-util.h"
28
#include "env-util.h"
29
#include "errno-list.h"
30
#include "fd-util.h"
31
#include "fileio.h"
32
#include "fs-util.h"
33
#include "glyph-util.h"
34
#include "hexdecoct.h"
35
#include "inotify-util.h"
36
#include "io-util.h"
37
#include "log.h"
38
#include "macro.h"
39
#include "missing_magic.h"
40
#include "namespace-util.h"
41
#include "parse-util.h"
42
#include "path-util.h"
43
#include "proc-cmdline.h"
44
#include "process-util.h"
45
#include "signal-util.h"
46
#include "socket-util.h"
47
#include "stat-util.h"
48
#include "stdio-util.h"
49
#include "string-table.h"
50
#include "string-util.h"
51
#include "strv.h"
52
#include "terminal-util.h"
53
#include "time-util.h"
54
#include "user-util.h"
55

56
static volatile unsigned cached_columns = 0;
57
static volatile unsigned cached_lines = 0;
58

59
static volatile int cached_on_tty = -1;
60
static volatile int cached_on_dev_null = -1;
61
static volatile int cached_color_mode = _COLOR_MODE_INVALID;
62
static volatile int cached_underline_enabled = -1;
63

64
bool isatty_safe(int fd) {
5,228,736✔
65
        assert(fd >= 0);
5,228,736✔
66

67
        if (isatty(fd))
5,228,736✔
68
                return true;
69

70
        /* Linux/glibc returns EIO for hung up TTY on isatty(). Which is wrong, the thing doesn't stop being
71
         * a TTY after all, just because it is temporarily hung up. Let's work around this here, until this
72
         * is fixed in glibc. See: https://sourceware.org/bugzilla/show_bug.cgi?id=32103 */
73
        if (errno == EIO)
5,179,426✔
74
                return true;
75

76
        /* Be resilient if we're working on stdio, since they're set up by parent process. */
77
        assert(errno != EBADF || IN_SET(fd, STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO));
5,179,417✔
78

79
        return false;
80
}
81

82
int chvt(int vt) {
×
83
        _cleanup_close_ int fd = -EBADF;
×
84

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

88
        fd = open_terminal("/dev/tty0", O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK);
×
89
        if (fd < 0)
×
90
                return fd;
91

92
        if (vt <= 0) {
×
93
                int tiocl[2] = {
×
94
                        TIOCL_GETKMSGREDIRECT,
95
                        0
96
                };
97

98
                if (ioctl(fd, TIOCLINUX, tiocl) < 0)
×
99
                        return -errno;
×
100

101
                vt = tiocl[0] <= 0 ? 1 : tiocl[0];
×
102
        }
103

104
        return RET_NERRNO(ioctl(fd, VT_ACTIVATE, vt));
×
105
}
106

107
int read_one_char(FILE *f, char *ret, usec_t t, bool echo, bool *need_nl) {
10✔
108
        _cleanup_free_ char *line = NULL;
10✔
109
        struct termios old_termios;
10✔
110
        int r, fd;
10✔
111

112
        assert(ret);
10✔
113

114
        if (!f)
10✔
115
                f = stdin;
×
116

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

125
                new_termios.c_lflag &= ~(ICANON|(echo ? 0 : ECHO));
×
126
                new_termios.c_cc[VMIN] = 1;
×
127
                new_termios.c_cc[VTIME] = 0;
×
128

129
                if (tcsetattr(fd, TCSADRAIN, &new_termios) >= 0) {
×
130
                        char c;
×
131

132
                        if (t != USEC_INFINITY) {
×
133
                                if (fd_wait_for_event(fd, POLLIN, t) <= 0) {
×
134
                                        (void) tcsetattr(fd, TCSADRAIN, &old_termios);
×
135
                                        return -ETIMEDOUT;
×
136
                                }
137
                        }
138

139
                        r = safe_fgetc(f, &c);
×
140
                        (void) tcsetattr(fd, TCSADRAIN, &old_termios);
×
141
                        if (r < 0)
×
142
                                return r;
143
                        if (r == 0)
×
144
                                return -EIO;
145

146
                        if (need_nl)
×
147
                                *need_nl = c != '\n';
×
148

149
                        *ret = c;
×
150
                        return 0;
×
151
                }
152
        }
153

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

160
                if (fd_wait_for_event(fd, POLLIN, t) <= 0)
4✔
161
                        return -ETIMEDOUT;
162
        }
163

164
        /* If this is not a terminal, then read a full line instead */
165

166
        r = read_line(f, 16, &line); /* longer than necessary, to eat up UTF-8 chars/vt100 key sequences */
10✔
167
        if (r < 0)
10✔
168
                return r;
169
        if (r == 0)
10✔
170
                return -EIO;
171

172
        if (strlen(line) != 1)
8✔
173
                return -EBADMSG;
174

175
        if (need_nl)
1✔
176
                *need_nl = false;
1✔
177

178
        *ret = line[0];
1✔
179
        return 0;
1✔
180
}
181

182
#define DEFAULT_ASK_REFRESH_USEC (2*USEC_PER_SEC)
183

184
int ask_char(char *ret, const char *replies, const char *fmt, ...) {
×
185
        int r;
×
186

187
        assert(ret);
×
188
        assert(replies);
×
189
        assert(fmt);
×
190

191
        for (;;) {
×
192
                va_list ap;
×
193
                char c;
×
194
                bool need_nl = true;
×
195

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

198
                putchar('\r');
×
199

200
                va_start(ap, fmt);
×
201
                vprintf(fmt, ap);
×
202
                va_end(ap);
×
203

204
                fputs(ansi_normal(), stdout);
×
205

206
                fflush(stdout);
×
207

208
                r = read_one_char(stdin, &c, DEFAULT_ASK_REFRESH_USEC, /* echo= */ true, &need_nl);
×
209
                if (r < 0) {
×
210

211
                        if (r == -ETIMEDOUT)
×
212
                                continue;
×
213

214
                        if (r == -EBADMSG) {
×
215
                                puts("Bad input, please try again.");
×
216
                                continue;
×
217
                        }
218

219
                        putchar('\n');
×
220
                        return r;
×
221
                }
222

223
                if (need_nl)
×
224
                        putchar('\n');
×
225

226
                if (strchr(replies, c)) {
×
227
                        *ret = c;
×
228
                        return 0;
×
229
                }
230

231
                puts("Read unexpected character, please try again.");
×
232
        }
233
}
234

235
typedef enum CompletionResult{
236
        COMPLETION_ALREADY,       /* the input string is already complete */
237
        COMPLETION_FULL,          /* completed the input string to be complete now */
238
        COMPLETION_PARTIAL,       /* completed the input string so that is still incomplete */
239
        COMPLETION_NONE,          /* found no matching completion */
240
        _COMPLETION_RESULT_MAX,
241
        _COMPLETION_RESULT_INVALID = -EINVAL,
242
        _COMPLETION_RESULT_ERRNO_MAX = -ERRNO_MAX,
243
} CompletionResult;
244

245
static CompletionResult pick_completion(const char *string, char *const*completions, char **ret) {
×
246
        _cleanup_free_ char *found = NULL;
×
247
        bool partial = false;
×
248

249
        string = strempty(string);
×
250

251
        STRV_FOREACH(c, completions) {
×
252

253
                /* Ignore entries that are not actually completions */
254
                if (!startswith(*c, string))
×
255
                        continue;
×
256

257
                /* Store first completion that matches */
258
                if (!found) {
×
259
                        found = strdup(*c);
×
260
                        if (!found)
×
261
                                return -ENOMEM;
262

263
                        continue;
×
264
                }
265

266
                /* If there's another completion that works truncate the one we already found by common
267
                 * prefix */
268
                size_t n = str_common_prefix(found, *c);
×
269
                if (n == SIZE_MAX)
×
270
                        continue;
×
271

272
                found[n] = 0;
×
273
                partial = true;
×
274
        }
275

276
        *ret = TAKE_PTR(found);
×
277

278
        if (!*ret)
×
279
                return COMPLETION_NONE;
280
        if (partial)
×
281
                return COMPLETION_PARTIAL;
282

283
        return streq(string, *ret) ? COMPLETION_ALREADY : COMPLETION_FULL;
×
284
}
285

286
static void clear_by_backspace(size_t n) {
×
287
        /* Erase the specified number of character cells backwards on the terminal */
288
        for (size_t i = 0; i < n; i++)
×
289
                fputs("\b \b", stdout);
×
290
}
×
291

292
int ask_string_full(
7✔
293
                char **ret,
294
                GetCompletionsCallback get_completions,
295
                void *userdata,
296
                const char *text, ...) {
297

298
        va_list ap;
7✔
299
        int r;
7✔
300

301
        assert(ret);
7✔
302
        assert(text);
7✔
303

304
        /* Output the prompt */
305
        fputs(ansi_highlight(), stdout);
14✔
306
        va_start(ap, text);
7✔
307
        vprintf(text, ap);
7✔
308
        va_end(ap);
7✔
309
        fputs(ansi_normal(), stdout);
14✔
310
        fflush(stdout);
7✔
311

312
        _cleanup_free_ char *string = NULL;
7✔
313
        size_t n = 0;
7✔
314

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

323
        /* Try to disable echo, which also tells us if this even is a terminal */
324
        struct termios old_termios;
×
325
        if (tcgetattr(fd_input, &old_termios) < 0)
×
326
                goto fallback;
×
327

328
        struct termios new_termios = old_termios;
×
329
        termios_disable_echo(&new_termios);
×
330
        if (tcsetattr(fd_input, TCSADRAIN, &new_termios) < 0)
×
331
                return -errno;
×
332

333
        for (;;) {
×
334
                int c = fgetc(stdin);
×
335

336
                /* On EOF or NUL, end the request, don't output anything anymore */
337
                if (IN_SET(c, EOF, 0))
×
338
                        break;
339

340
                /* On Return also end the request, but make this visible */
341
                if (IN_SET(c, '\n', '\r')) {
×
342
                        fputc('\n', stdout);
×
343
                        break;
344
                }
345

346
                if (c == '\t') {
×
347
                        /* Tab */
348

349
                        _cleanup_strv_free_ char **completions = NULL;
×
350
                        if (get_completions) {
×
351
                                r = get_completions(string, &completions, userdata);
×
352
                                if (r < 0)
×
353
                                        goto fail;
×
354
                        }
355

356
                        _cleanup_free_ char *new_string = NULL;
×
357
                        CompletionResult cr = pick_completion(string, completions, &new_string);
×
358
                        if (cr < 0) {
×
359
                                r = cr;
×
360
                                goto fail;
×
361
                        }
362
                        if (IN_SET(cr, COMPLETION_PARTIAL, COMPLETION_FULL)) {
×
363
                                /* Output the new suffix we learned */
364
                                fputs(ASSERT_PTR(startswith(new_string, strempty(string))), stdout);
×
365

366
                                /* And update the whole string */
367
                                free_and_replace(string, new_string);
×
368
                                n = strlen(string);
×
369
                        }
370
                        if (cr == COMPLETION_NONE)
×
371
                                fputc('\a', stdout); /* BEL */
×
372

373
                        if (IN_SET(cr, COMPLETION_PARTIAL, COMPLETION_ALREADY)) {
×
374
                                /* If this worked only partially, or if the user hit TAB even though we were
375
                                 * complete already, then show the remaining options (in the latter case just
376
                                 * the one). */
377
                                fputc('\n', stdout);
×
378

379
                                _cleanup_strv_free_ char **filtered = strv_filter_prefix(completions, string);
×
380
                                if (!filtered) {
×
381
                                        r = -ENOMEM;
×
382
                                        goto fail;
×
383
                                }
384

385
                                r = show_menu(filtered,
×
386
                                              /* n_columns= */ SIZE_MAX,
387
                                              /* column_width= */ SIZE_MAX,
388
                                              /* ellipsize_percentage= */ 0,
389
                                              /* grey_prefix=*/ string,
390
                                              /* with_numbers= */ false);
391
                                if (r < 0)
×
392
                                        goto fail;
×
393

394
                                /* Show the prompt again */
395
                                fputs(ansi_highlight(), stdout);
×
396
                                va_start(ap, text);
×
397
                                vprintf(text, ap);
×
398
                                va_end(ap);
×
399
                                fputs(ansi_normal(), stdout);
×
400
                                fputs(string, stdout);
×
401
                        }
402

403
                } else if (IN_SET(c, '\b', 127)) {
×
404
                        /* Backspace */
405

406
                        if (n == 0)
×
407
                                fputc('\a', stdout); /* BEL */
×
408
                        else {
409
                                size_t m = utf8_last_length(string, n);
×
410

411
                                char *e = string + n - m;
×
412
                                clear_by_backspace(utf8_console_width(e));
×
413

414
                                *e = 0;
×
415
                                n -= m;
×
416
                        }
417

418
                } else if (c == 21) {
×
419
                        /* Ctrl-u → erase all input */
420

421
                        clear_by_backspace(utf8_console_width(string));
×
422
                        if (string)
×
423
                                string[n = 0] = 0;
×
424
                        else
425
                                assert(n == 0);
×
426

427
                } else if (c == 4) {
×
428
                        /* Ctrl-d → cancel this field input */
429

430
                        r = -ECANCELED;
×
431
                        goto fail;
×
432

433
                } else if (char_is_cc(c) || n >= LINE_MAX)
×
434
                        /* refuse control characters and too long strings */
435
                        fputc('\a', stdout); /* BEL */
×
436
                else {
437
                        /* Regular char */
438

439
                        if (!GREEDY_REALLOC(string, n+2)) {
×
440
                                r = -ENOMEM;
×
441
                                goto fail;
×
442
                        }
443

444
                        string[n++] = (char) c;
×
445
                        string[n] = 0;
×
446

447
                        fputc(c, stdout);
×
448
                }
449

450
                fflush(stdout);
×
451
        }
452

453
        if (tcsetattr(fd_input, TCSADRAIN, &old_termios) < 0)
×
454
                return -errno;
×
455

456
        if (!string) {
×
457
                string = strdup("");
×
458
                if (!string)
×
459
                        return -ENOMEM;
460
        }
461

462
        *ret = TAKE_PTR(string);
×
463
        return 0;
×
464

465
fail:
×
466
        (void) tcsetattr(fd_input, TCSADRAIN, &old_termios);
×
467
        return r;
×
468

469
fallback:
7✔
470
        /* A simple fallback without TTY magic */
471
        r = read_line(stdin, LONG_LINE_MAX, &string);
7✔
472
        if (r < 0)
7✔
473
                return r;
474
        if (r == 0)
7✔
475
                return -EIO;
476

477
        *ret = TAKE_PTR(string);
7✔
478
        return 0;
7✔
479
}
480

481
bool any_key_to_proceed(void) {
6✔
482

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

487
        fputc('\n', stdout);
6✔
488
        fputs(ansi_highlight_magenta(), stdout);
12✔
489
        fputs("-- Press any key to proceed --", stdout);
6✔
490
        fputs(ansi_normal(), stdout);
12✔
491
        fputc('\n', stdout);
6✔
492
        fflush(stdout);
6✔
493

494
        char key = 0;
6✔
495
        (void) read_one_char(stdin, &key, USEC_INFINITY, /* echo= */ false, /* need_nl= */ NULL);
6✔
496

497
        fputc('\n', stdout);
6✔
498
        fflush(stdout);
6✔
499

500
        return key != 'q';
6✔
501
}
502

503
static size_t widest_list_element(char *const*l) {
×
504
        size_t w = 0;
×
505

506
        /* Returns the largest console width of all elements in 'l' */
507

508
        STRV_FOREACH(i, l)
×
509
                w = MAX(w, utf8_console_width(*i));
×
510

511
        return w;
×
512
}
513

514
int show_menu(char **x,
×
515
              size_t n_columns,
516
              size_t column_width,
517
              unsigned ellipsize_percentage,
518
              const char *grey_prefix,
519
              bool with_numbers) {
520

521
        assert(n_columns > 0);
×
522

523
        if (n_columns == SIZE_MAX)
×
524
                n_columns = 3;
×
525

526
        if (column_width == SIZE_MAX) {
×
527
                size_t widest = widest_list_element(x);
×
528

529
                /* If not specified, derive column width from screen width */
530
                size_t column_max = (columns()-1) / n_columns;
×
531

532
                /* Subtract room for numbers */
533
                if (with_numbers && column_max > 6)
×
534
                        column_max -= 6;
×
535

536
                /* If columns would get too tight let's make this a linear list instead. */
537
                if (column_max < 10 && widest > 10) {
×
538
                        n_columns = 1;
×
539
                        column_max = columns()-1;
×
540

541
                        if (with_numbers && column_max > 6)
×
542
                                column_max -= 6;
×
543
                }
544

545
                column_width = CLAMP(widest+1, 10U, column_max);
×
546
        }
547

548
        size_t n = strv_length(x);
×
549
        size_t per_column = DIV_ROUND_UP(n, n_columns);
×
550

551
        size_t break_lines = lines();
×
552
        if (break_lines > 2)
×
553
                break_lines--;
×
554

555
        /* The first page gets two extra lines, since we want to show
556
         * a title */
557
        size_t break_modulo = break_lines;
×
558
        if (break_modulo > 3)
×
559
                break_modulo -= 3;
×
560

561
        for (size_t i = 0; i < per_column; i++) {
×
562

563
                for (size_t j = 0; j < n_columns; j++) {
×
564
                        _cleanup_free_ char *e = NULL;
×
565

566
                        if (j * per_column + i >= n)
×
567
                                break;
568

569
                        e = ellipsize(x[j * per_column + i], column_width, ellipsize_percentage);
×
570
                        if (!e)
×
571
                                return -ENOMEM;
×
572

573
                        if (with_numbers)
×
574
                                printf("%s%4zu)%s ",
×
575
                                       ansi_grey(),
576
                                       j * per_column + i + 1,
577
                                       ansi_normal());
578

579
                        if (grey_prefix && startswith(e, grey_prefix)) {
×
580
                                size_t k = MIN(strlen(grey_prefix), column_width);
×
581
                                printf("%s%.*s%s",
×
582
                                       ansi_grey(),
583
                                       (int) k, e,
584
                                       ansi_normal());
585
                                printf("%-*s",
×
586
                                       (int) (column_width - k), e+k);
×
587
                        } else
588
                                printf("%-*s", (int) column_width, e);
×
589
                }
590

591
                putchar('\n');
×
592

593
                /* on the first screen we reserve 2 extra lines for the title */
594
                if (i % break_lines == break_modulo)
×
595
                        if (!any_key_to_proceed())
×
596
                                return 0;
597
        }
598

599
        return 0;
600
}
601

602
int open_terminal(const char *name, int mode) {
55,018✔
603
        _cleanup_close_ int fd = -EBADF;
55,018✔
604

605
        /*
606
         * If a TTY is in the process of being closed opening it might cause EIO. This is horribly awful, but
607
         * unlikely to be changed in the kernel. Hence we work around this problem by retrying a couple of
608
         * times.
609
         *
610
         * https://bugs.launchpad.net/ubuntu/+source/linux/+bug/554172/comments/245
611
         */
612

613
        assert((mode & (O_CREAT|O_PATH|O_DIRECTORY|O_TMPFILE)) == 0);
55,018✔
614

615
        for (unsigned c = 0;; c++) {
×
616
                fd = open(name, mode, 0);
55,018✔
617
                if (fd >= 0)
55,018✔
618
                        break;
619

620
                if (errno != EIO)
7,488✔
621
                        return -errno;
7,488✔
622

623
                /* Max 1s in total */
624
                if (c >= 20)
×
625
                        return -EIO;
626

627
                (void) usleep_safe(50 * USEC_PER_MSEC);
×
628
        }
629

630
        if (!isatty_safe(fd))
47,530✔
631
                return -ENOTTY;
×
632

633
        return TAKE_FD(fd);
634
}
635

636
int acquire_terminal(
343✔
637
                const char *name,
638
                AcquireTerminalFlags flags,
639
                usec_t timeout) {
640

641
        _cleanup_close_ int notify = -EBADF, fd = -EBADF;
343✔
642
        usec_t ts = USEC_INFINITY;
343✔
643
        int r, wd = -1;
343✔
644

645
        assert(name);
343✔
646

647
        AcquireTerminalFlags mode = flags & _ACQUIRE_TERMINAL_MODE_MASK;
343✔
648
        assert(IN_SET(mode, ACQUIRE_TERMINAL_TRY, ACQUIRE_TERMINAL_FORCE, ACQUIRE_TERMINAL_WAIT));
343✔
649
        assert(mode == ACQUIRE_TERMINAL_WAIT || !FLAGS_SET(flags, ACQUIRE_TERMINAL_WATCH_SIGTERM));
343✔
650

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

660
        if (mode == ACQUIRE_TERMINAL_WAIT) {
661
                notify = inotify_init1(IN_CLOEXEC | IN_NONBLOCK);
343✔
662
                if (notify < 0)
343✔
663
                        return -errno;
×
664

665
                wd = inotify_add_watch(notify, name, IN_CLOSE);
343✔
666
                if (wd < 0)
343✔
667
                        return -errno;
×
668

669
                if (timeout != USEC_INFINITY)
343✔
670
                        ts = now(CLOCK_MONOTONIC);
×
671
        }
672

673
        /* If we are called with ACQUIRE_TERMINAL_WATCH_SIGTERM we'll unblock SIGTERM during ppoll() temporarily */
674
        sigset_t poll_ss;
343✔
675
        assert_se(sigprocmask(SIG_SETMASK, /* newset= */ NULL, &poll_ss) >= 0);
343✔
676
        if (flags & ACQUIRE_TERMINAL_WATCH_SIGTERM) {
343✔
677
                assert_se(sigismember(&poll_ss, SIGTERM) > 0);
×
678
                assert_se(sigdelset(&poll_ss, SIGTERM) >= 0);
×
679
        }
680

681
        for (;;) {
343✔
682
                if (notify >= 0) {
343✔
683
                        r = flush_fd(notify);
343✔
684
                        if (r < 0)
343✔
685
                                return r;
×
686
                }
687

688
                /* We pass here O_NOCTTY only so that we can check the return value TIOCSCTTY and have a reliable way
689
                 * to figure out if we successfully became the controlling process of the tty */
690
                fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC);
343✔
691
                if (fd < 0)
343✔
692
                        return fd;
693

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

698
                /* First, try to get the tty */
699
                r = RET_NERRNO(ioctl(fd, TIOCSCTTY, mode == ACQUIRE_TERMINAL_FORCE));
343✔
700

701
                /* Reset signal handler to old value */
702
                assert_se(sigaction(SIGHUP, &sa_old, NULL) >= 0);
343✔
703

704
                /* Success? Exit the loop now! */
705
                if (r >= 0)
343✔
706
                        break;
707

708
                /* Any failure besides -EPERM? Fail, regardless of the mode. */
709
                if (r != -EPERM)
×
710
                        return r;
711

712
                if (flags & ACQUIRE_TERMINAL_PERMISSIVE) /* If we are in permissive mode, then EPERM is fine, turn this
×
713
                                                          * into a success. Note that EPERM is also returned if we
714
                                                          * already are the owner of the TTY. */
715
                        break;
716

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

720
                assert(notify >= 0);
×
721
                assert(wd >= 0);
×
722

723
                for (;;) {
×
724
                        usec_t left;
×
725
                        if (timeout == USEC_INFINITY)
×
726
                                left = USEC_INFINITY;
727
                        else {
728
                                assert(ts != USEC_INFINITY);
×
729

730
                                usec_t n = usec_sub_unsigned(now(CLOCK_MONOTONIC), ts);
×
731
                                if (n >= timeout)
×
732
                                        return -ETIMEDOUT;
×
733

734
                                left = timeout - n;
×
735
                        }
736

737
                        r = ppoll_usec_full(
×
738
                                        &(struct pollfd) {
×
739
                                                .fd = notify,
740
                                                .events = POLLIN,
741
                                        },
742
                                        /* n_pollfds = */ 1,
743
                                        left,
744
                                        &poll_ss);
745
                        if (r < 0)
×
746
                                return r;
747
                        if (r == 0)
×
748
                                return -ETIMEDOUT;
749

750
                        union inotify_event_buffer buffer;
×
751
                        ssize_t l;
×
752
                        l = read(notify, &buffer, sizeof(buffer));
×
753
                        if (l < 0) {
×
754
                                if (ERRNO_IS_TRANSIENT(errno))
×
755
                                        continue;
×
756

757
                                return -errno;
×
758
                        }
759

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

764
                                if (e->wd != wd || !(e->mask & IN_CLOSE)) /* Safety checks */
×
765
                                        return -EIO;
×
766
                        }
767

768
                        break;
×
769
                }
770

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

776
        return TAKE_FD(fd);
343✔
777
}
778

779
int release_terminal(void) {
63✔
780
        _cleanup_close_ int fd = -EBADF;
63✔
781
        int r;
63✔
782

783
        fd = open("/dev/tty", O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK);
63✔
784
        if (fd < 0)
63✔
785
                return -errno;
43✔
786

787
        /* Temporarily ignore SIGHUP, so that we don't get SIGHUP'ed
788
         * by our own TIOCNOTTY */
789
        struct sigaction sa_old;
20✔
790
        assert_se(sigaction(SIGHUP, &sigaction_ignore, &sa_old) >= 0);
20✔
791

792
        r = RET_NERRNO(ioctl(fd, TIOCNOTTY));
20✔
793

794
        assert_se(sigaction(SIGHUP, &sa_old, NULL) >= 0);
20✔
795

796
        return r;
797
}
798

799
int terminal_new_session(void) {
5✔
800

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

808
        if (!isatty_safe(STDIN_FILENO))
5✔
809
                return -ENXIO;
810

811
        (void) setsid();
4✔
812
        return RET_NERRNO(ioctl(STDIN_FILENO, TIOCSCTTY, 0));
4✔
813
}
814

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

820
int terminal_vhangup(const char *tty) {
×
821
        _cleanup_close_ int fd = -EBADF;
×
822

823
        assert(tty);
×
824

825
        fd = open_terminal(tty, O_RDWR|O_NOCTTY|O_CLOEXEC);
×
826
        if (fd < 0)
×
827
                return fd;
828

829
        return terminal_vhangup_fd(fd);
×
830
}
831

832
int vt_disallocate(const char *tty_path) {
86✔
833
        assert(tty_path);
86✔
834

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

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

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

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

854
        _cleanup_close_ int fd2 = open_terminal(tty_path, O_WRONLY|O_NOCTTY|O_CLOEXEC|O_NONBLOCK);
172✔
855
        if (fd2 < 0)
86✔
856
                return fd2;
857

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

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

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

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

877
        return parse_boolean(b);
388✔
878
}
879

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

959
        return r;
343✔
960
}
961

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

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

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

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

974
        k = loop_write_full(fd,
×
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);
982
        if (k < 0)
×
983
                log_debug_errno(k, "Failed to reset terminal through ANSI sequences: %m");
×
984

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

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✔
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✔
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✔
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✔
1014
                        log_warning_errno(r, "Failed to set configured terminal size on /dev/console, ignoring: %m");
×
1015
        } else
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) {
920✔
1024
        _cleanup_close_ int fd = -EBADF;
920✔
1025
        int r;
920✔
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);
920✔
1030
        if (fd < 0)
920✔
1031
                return fd;
1032

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

1037
        return TAKE_FD(fd);
1038
}
1039

1040
int make_console_stdio(void) {
×
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) {
×
1049
                log_warning_errno(fd, "Failed to acquire terminal, using /dev/null stdin/stdout/stderr instead: %m");
×
1050

1051
                r = make_null_stdio();
×
1052
                if (r < 0)
×
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();
×
1064
        return 0;
×
1065
}
1066

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

1070
        tty = skip_dev_prefix(tty);
317✔
1071

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

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

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

1083
        assert(tty);
198✔
1084

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

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

1094
bool tty_is_vc(const char *tty) {
119✔
1095
        assert(tty);
119✔
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;
119✔
1103
}
1104

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

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

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

1114
        assert(ret);
301✔
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;
301✔
1122
        r = chase("/dev/console", /* root= */ NULL, /* chase_flags= */ 0,  &chased, /* ret_fd= */ NULL);
301✔
1123
        if (r < 0)
301✔
1124
                return r;
1125
        if (!path_equal(chased, "/dev/console")) {
301✔
1126
                *ret = TAKE_PTR(chased);
148✔
1127
                return 0;
148✔
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✔
1144
                tty++;
×
1145
        else
1146
                tty = active;
1147

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

1151
                /* Get the active VC (e.g. tty1) */
1152
                r = read_one_line_file("/sys/class/tty/tty0/active", &active);
×
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

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

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);
×
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;
1190
                if (r == 0)
×
1191
                        break;
1192

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

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

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

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

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

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

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

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

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

1234
        assert(tty);
52✔
1235

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

1240
                tty = resolved;
2✔
1241
        }
1242

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

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

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

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

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

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

1262
        return ws.ws_col;
×
1263
}
1264

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

1268
        const char *e = getenv("COLUMNS");
1,399✔
1269
        if (!e)
1,399✔
1270
                return -ENXIO;
1,399✔
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) {
200,202✔
1281

1282
        if (cached_columns > 0)
200,202✔
1283
                return cached_columns;
198,977✔
1284

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

1292
        assert(c > 0);
1✔
1293

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

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

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

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

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

1310
        return ws.ws_row;
×
1311
}
1312

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

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

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

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

1331
        cached_lines = l;
155✔
1332
        return cached_lines;
155✔
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)
890✔
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)
890✔
1352
                rows = ws.ws_row;
×
1353
        else if (rows > USHRT_MAX)
890✔
1354
                rows = USHRT_MAX;
×
1355

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

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

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

1367
        if (ioctl(fd, TIOCSWINSZ, &ws) < 0)
366✔
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✔
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✔
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);
890✔
1406
                if (r < 0)
890✔
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);
890✔
1412
                if (r < 0)
890✔
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 */
1425
void columns_lines_cache_reset(int signum) {
×
1426
        cached_columns = 0;
×
1427
        cached_lines = 0;
×
1428
}
×
1429

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

1434
        cached_color_mode = _COLOR_MODE_INVALID;
14✔
1435
        cached_underline_enabled = -1;
14✔
1436
        cached_on_tty = -1;
14✔
1437
        cached_on_dev_null = -1;
14✔
1438
}
14✔
1439

1440
bool on_tty(void) {
7,368,610✔
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,368,610✔
1449
                cached_on_tty =
44,561✔
1450
                        isatty_safe(STDOUT_FILENO) &&
44,576✔
1451
                        isatty_safe(STDERR_FILENO);
15✔
1452

1453
        return cached_on_tty;
7,368,610✔
1454
}
1455

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

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

1463
        r = ttyname_r(fd, path, sizeof path); /* positive error */
409✔
1464
        assert(r >= 0);
409✔
1465
        if (r == ERANGE)
409✔
1466
                return -ENAMETOOLONG;
409✔
1467
        if (r > 0)
409✔
1468
                return -r;
402✔
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

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

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

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

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

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

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

1505
        p++;
3,107✔
1506

1507
        if (sscanf(p, " "
3,107✔
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,107✔
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✔
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✔
1552
                        if (errno != ENOENT)
×
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. */
1561
                        r = device_path_make_major_minor(S_IFCHR, devnr, &buf);
×
1562
                        if (r < 0)
×
1563
                                return r;
1564

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✔
1581
                *ret_devnr = devnr;
×
1582

1583
        return 0;
1584
}
1585

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

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

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

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

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

1608
                free(c);
×
1609

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

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

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

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

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

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

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

1638
        if (ret_peer_path)
120✔
1639
                *ret_peer_path = TAKE_PTR(p);
116✔
1640

1641
        return TAKE_FD(fd);
1642
}
1643

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

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

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

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

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

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

1662
        return 0;
1663
}
1664

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

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

1674
        r = pidref_namespace_open(pidref, &pidnsfd, &mntnsfd, /* ret_netns_fd = */ NULL, &usernsfd, &rootfd);
×
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);
1693
        if (r < 0)
×
1694
                return r;
1695
        if (r == 0) {
×
1696
                pair[0] = safe_close(pair[0]);
×
1697

1698
                fd = openpt_allocate(flags, /* ret_peer_path= */ NULL);
×
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

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

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

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

1720
        return TAKE_FD(fd);
1721
}
1722

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

1726
        if (cached_on_dev_null >= 0)
54,534✔
1727
                return cached_on_dev_null;
10,043✔
1728

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

1734
        return cached_on_dev_null;
44,491✔
1735
}
1736

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

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

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

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

1751
        return getenv_terminal_is_dumb();
200✔
1752
}
1753

1754
static const char* const color_mode_table[_COLOR_MODE_MAX] = {
1755
        [COLOR_OFF]   = "off",
1756
        [COLOR_16]    = "16",
1757
        [COLOR_256]   = "256",
1758
        [COLOR_24BIT] = "24bit",
1759
};
1760

1761
DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(color_mode, ColorMode, COLOR_24BIT);
27✔
1762

1763
static ColorMode parse_systemd_colors(void) {
13,764✔
1764
        const char *e;
13,764✔
1765

1766
        e = getenv("SYSTEMD_COLORS");
13,764✔
1767
        if (!e)
13,764✔
1768
                return _COLOR_MODE_INVALID;
1769

1770
        ColorMode m = color_mode_from_string(e);
13✔
1771
        if (m < 0)
13✔
1772
                return log_debug_errno(m, "Failed to parse $SYSTEMD_COLORS value '%s', ignoring: %m", e);
2✔
1773

1774
        return m;
1775
}
1776

1777
static ColorMode get_color_mode_impl(void) {
13,764✔
1778
        /* Returns the mode used to choose output colors. The possible modes are COLOR_OFF for no colors,
1779
         * COLOR_16 for only the base 16 ANSI colors, COLOR_256 for more colors, and COLOR_24BIT for
1780
         * unrestricted color output. */
1781

1782
        /* First, we check $SYSTEMD_COLORS, which is the explicit way to change the mode. */
1783
        ColorMode m = parse_systemd_colors();
13,764✔
1784
        if (m >= 0)
13,764✔
1785
                return m;
1786

1787
        /* Next, check for the presence of $NO_COLOR; value is ignored. */
1788
        if (getenv("NO_COLOR"))
13,753✔
1789
                return COLOR_OFF;
1790

1791
        /* If the above didn't work, we turn colors off unless we are on a TTY. And if we are on a TTY we
1792
         * turn it off if $TERM is set to "dumb". There's one special tweak though: if we are PID 1 then we
1793
         * do not check whether we are connected to a TTY, because we don't keep /dev/console open
1794
         * continuously due to fear of SAK, and hence things are a bit weird. */
1795
        if (getpid_cached() == 1 ? getenv_terminal_is_dumb() : terminal_is_dumb())
13,751✔
1796
                return COLOR_OFF;
1797

1798
        /* We failed to figure out any reason to *disable* colors. Let's see how many colors we shall use. */
1799
        if (STRPTR_IN_SET(getenv("COLORTERM"),
×
1800
                          "truecolor",
1801
                          "24bit"))
1802
                return COLOR_24BIT;
×
1803

1804
        /* Note that the Linux console can only display 16 colors. We still enable 256 color mode
1805
         * even for PID1 output though (which typically goes to the Linux console), since the Linux
1806
         * console is able to parse the 256 color sequences and automatically map them to the closest
1807
         * color in the 16 color palette (since kernel 3.16). Doing 256 colors is nice for people who
1808
         * invoke systemd in a container or via a serial link or such, and use a true 256 color
1809
         * terminal to do so. */
1810
        return COLOR_256;
1811
}
1812

1813
ColorMode get_color_mode(void) {
1,163,788✔
1814
        if (cached_color_mode < 0)
1,163,788✔
1815
                cached_color_mode = get_color_mode_impl();
13,764✔
1816

1817
        return cached_color_mode;
1,163,788✔
1818
}
1819

1820
bool dev_console_colors_enabled(void) {
×
1821
        _cleanup_free_ char *s = NULL;
×
1822
        ColorMode m;
×
1823

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

1831
        m = parse_systemd_colors();
×
1832
        if (m >= 0)
×
1833
                return m;
×
1834

1835
        if (getenv("NO_COLOR"))
×
1836
                return false;
1837

1838
        if (getenv_for_pid(1, "TERM", &s) <= 0)
×
1839
                (void) proc_cmdline_get_key("TERM", 0, &s);
×
1840

1841
        return !streq_ptr(s, "dumb");
×
1842
}
1843

1844
bool underline_enabled(void) {
443,553✔
1845

1846
        if (cached_underline_enabled < 0) {
443,553✔
1847

1848
                /* The Linux console doesn't support underlining, turn it off, but only there. */
1849

1850
                if (colors_enabled())
2,702✔
1851
                        cached_underline_enabled = !streq_ptr(getenv("TERM"), "linux");
1✔
1852
                else
1853
                        cached_underline_enabled = false;
2,701✔
1854
        }
1855

1856
        return cached_underline_enabled;
443,553✔
1857
}
1858

1859
int vt_restore(int fd) {
×
1860

1861
        static const struct vt_mode mode = {
×
1862
                .mode = VT_AUTO,
1863
        };
1864

1865
        int r, ret = 0;
×
1866

1867
        assert(fd >= 0);
×
1868

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

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

1875
        r = vt_reset_keyboard(fd);
×
1876
        if (r < 0)
×
1877
                RET_GATHER(ret, log_debug_errno(r, "Failed to reset keyboard mode, ignoring: %m"));
×
1878

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

1882
        r = fchmod_and_chown(fd, TTY_MODE, 0, GID_INVALID);
×
1883
        if (r < 0)
×
1884
                RET_GATHER(ret, log_debug_errno(r, "Failed to chmod()/chown() VT, ignoring: %m"));
×
1885

1886
        return ret;
1887
}
1888

1889
int vt_release(int fd, bool restore) {
×
1890
        assert(fd >= 0);
×
1891

1892
        /* This function releases the VT by acknowledging the VT-switch signal
1893
         * sent by the kernel and optionally reset the VT in text and auto
1894
         * VT-switching modes. */
1895

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

1899
        if (ioctl(fd, VT_RELDISP, 1) < 0)
×
1900
                return -errno;
×
1901

1902
        if (restore)
×
1903
                return vt_restore(fd);
×
1904

1905
        return 0;
1906
}
1907

1908
void get_log_colors(int priority, const char **on, const char **off, const char **highlight) {
175,712✔
1909
        /* Note that this will initialize output variables only when there's something to output.
1910
         * The caller must pre-initialize to "" or NULL as appropriate. */
1911

1912
        if (priority <= LOG_ERR) {
175,712✔
1913
                if (on)
6,349✔
1914
                        *on = ansi_highlight_red();
12,698✔
1915
                if (off)
6,349✔
1916
                        *off = ansi_normal();
12,698✔
1917
                if (highlight)
6,349✔
1918
                        *highlight = ansi_highlight();
×
1919

1920
        } else if (priority <= LOG_WARNING) {
169,363✔
1921
                if (on)
918✔
1922
                        *on = ansi_highlight_yellow();
918✔
1923
                if (off)
918✔
1924
                        *off = ansi_normal();
1,836✔
1925
                if (highlight)
918✔
1926
                        *highlight = ansi_highlight();
×
1927

1928
        } else if (priority <= LOG_NOTICE) {
168,445✔
1929
                if (on)
871✔
1930
                        *on = ansi_highlight();
1,742✔
1931
                if (off)
871✔
1932
                        *off = ansi_normal();
1,742✔
1933
                if (highlight)
871✔
1934
                        *highlight = ansi_highlight_red();
×
1935

1936
        } else if (priority >= LOG_DEBUG) {
167,574✔
1937
                if (on)
126,030✔
1938
                        *on = ansi_grey();
126,030✔
1939
                if (off)
126,030✔
1940
                        *off = ansi_normal();
252,060✔
1941
                if (highlight)
126,030✔
1942
                        *highlight = ansi_highlight_red();
×
1943
        }
1944
}
175,712✔
1945

1946
int terminal_set_cursor_position(int fd, unsigned row, unsigned column) {
×
1947
        assert(fd >= 0);
×
1948

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

1952
        return loop_write(fd, cursor_position, SIZE_MAX);
×
1953
}
1954

1955
int terminal_reset_defensive(int fd, TerminalResetFlags flags) {
317✔
1956
        int r = 0;
317✔
1957

1958
        assert(fd >= 0);
317✔
1959
        assert(!FLAGS_SET(flags, TERMINAL_RESET_AVOID_ANSI_SEQ|TERMINAL_RESET_FORCE_ANSI_SEQ));
317✔
1960

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

1964
        if (!isatty_safe(fd))
317✔
1965
                return -ENOTTY;
317✔
1966

1967
        RET_GATHER(r, terminal_reset_ioctl(fd, FLAGS_SET(flags, TERMINAL_RESET_SWITCH_TO_TEXT)));
310✔
1968

1969
        if (!FLAGS_SET(flags, TERMINAL_RESET_AVOID_ANSI_SEQ) &&
310✔
1970
            (FLAGS_SET(flags, TERMINAL_RESET_FORCE_ANSI_SEQ) || !getenv_terminal_is_dumb()))
304✔
1971
                RET_GATHER(r, terminal_reset_ansi_seq(fd));
304✔
1972

1973
        return r;
1974
}
1975

1976
int terminal_reset_defensive_locked(int fd, TerminalResetFlags flags) {
6✔
1977
        assert(fd >= 0);
6✔
1978

1979
        _cleanup_close_ int lock_fd = lock_dev_console();
6✔
1980
        if (lock_fd < 0)
6✔
1981
                log_debug_errno(lock_fd, "Failed to acquire lock for /dev/console, ignoring: %m");
×
1982

1983
        return terminal_reset_defensive(fd, flags);
6✔
1984
}
1985

1986
void termios_disable_echo(struct termios *termios) {
×
1987
        assert(termios);
×
1988

1989
        termios->c_lflag &= ~(ICANON|ECHO);
×
1990
        termios->c_cc[VMIN] = 1;
×
1991
        termios->c_cc[VTIME] = 0;
×
1992
}
×
1993

1994
static int terminal_verify_same(int input_fd, int output_fd) {
1✔
1995
        assert(input_fd >= 0);
1✔
1996
        assert(output_fd >= 0);
1✔
1997

1998
        /* Validates that the specified fds reference the same TTY */
1999

2000
        if (input_fd != output_fd) {
1✔
2001
                struct stat sti;
1✔
2002
                if (fstat(input_fd, &sti) < 0)
1✔
2003
                        return -errno;
1✔
2004

2005
                if (!S_ISCHR(sti.st_mode)) /* TTYs are character devices */
1✔
2006
                        return -ENOTTY;
2007

2008
                struct stat sto;
1✔
2009
                if (fstat(output_fd, &sto) < 0)
1✔
2010
                        return -errno;
×
2011

2012
                if (!S_ISCHR(sto.st_mode))
1✔
2013
                        return -ENOTTY;
2014

2015
                if (sti.st_rdev != sto.st_rdev)
×
2016
                        return -ENOLINK;
2017
        }
2018

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

2022
        return 0;
2023
}
2024

2025
typedef enum BackgroundColorState {
2026
        BACKGROUND_TEXT,
2027
        BACKGROUND_ESCAPE,
2028
        BACKGROUND_BRACKET,
2029
        BACKGROUND_FIRST_ONE,
2030
        BACKGROUND_SECOND_ONE,
2031
        BACKGROUND_SEMICOLON,
2032
        BACKGROUND_R,
2033
        BACKGROUND_G,
2034
        BACKGROUND_B,
2035
        BACKGROUND_RED,
2036
        BACKGROUND_GREEN,
2037
        BACKGROUND_BLUE,
2038
        BACKGROUND_STRING_TERMINATOR,
2039
} BackgroundColorState;
2040

2041
typedef struct BackgroundColorContext {
2042
        BackgroundColorState state;
2043
        uint32_t red, green, blue;
2044
        unsigned red_bits, green_bits, blue_bits;
2045
} BackgroundColorContext;
2046

2047
static int scan_background_color_response(
×
2048
                BackgroundColorContext *context,
2049
                const char *buf,
2050
                size_t size,
2051
                size_t *ret_processed) {
2052

2053
        assert(context);
×
2054
        assert(buf || size == 0);
×
2055

2056
        for (size_t i = 0; i < size; i++) {
×
2057
                char c = buf[i];
×
2058

2059
                switch (context->state) {
×
2060

2061
                case BACKGROUND_TEXT:
×
2062
                        context->state = c == '\x1B' ? BACKGROUND_ESCAPE : BACKGROUND_TEXT;
×
2063
                        break;
×
2064

2065
                case BACKGROUND_ESCAPE:
×
2066
                        context->state = c == ']' ? BACKGROUND_BRACKET : BACKGROUND_TEXT;
×
2067
                        break;
×
2068

2069
                case BACKGROUND_BRACKET:
×
2070
                        context->state = c == '1' ? BACKGROUND_FIRST_ONE : BACKGROUND_TEXT;
×
2071
                        break;
×
2072

2073
                case BACKGROUND_FIRST_ONE:
×
2074
                        context->state = c == '1' ? BACKGROUND_SECOND_ONE : BACKGROUND_TEXT;
×
2075
                        break;
×
2076

2077
                case BACKGROUND_SECOND_ONE:
×
2078
                        context->state = c == ';' ? BACKGROUND_SEMICOLON : BACKGROUND_TEXT;
×
2079
                        break;
×
2080

2081
                case BACKGROUND_SEMICOLON:
×
2082
                        context->state = c == 'r' ? BACKGROUND_R : BACKGROUND_TEXT;
×
2083
                        break;
×
2084

2085
                case BACKGROUND_R:
×
2086
                        context->state = c == 'g' ? BACKGROUND_G : BACKGROUND_TEXT;
×
2087
                        break;
×
2088

2089
                case BACKGROUND_G:
×
2090
                        context->state = c == 'b' ? BACKGROUND_B : BACKGROUND_TEXT;
×
2091
                        break;
×
2092

2093
                case BACKGROUND_B:
×
2094
                        context->state = c == ':' ? BACKGROUND_RED : BACKGROUND_TEXT;
×
2095
                        break;
×
2096

2097
                case BACKGROUND_RED:
×
2098
                        if (c == '/')
×
2099
                                context->state = context->red_bits > 0 ? BACKGROUND_GREEN : BACKGROUND_TEXT;
×
2100
                        else {
2101
                                int d = unhexchar(c);
×
2102
                                if (d < 0 || context->red_bits >= sizeof(context->red)*8)
×
2103
                                        context->state = BACKGROUND_TEXT;
×
2104
                                else {
2105
                                        context->red = (context->red << 4) | d;
×
2106
                                        context->red_bits += 4;
×
2107
                                }
2108
                        }
2109
                        break;
2110

2111
                case BACKGROUND_GREEN:
×
2112
                        if (c == '/')
×
2113
                                context->state = context->green_bits > 0 ? BACKGROUND_BLUE : BACKGROUND_TEXT;
×
2114
                        else {
2115
                                int d = unhexchar(c);
×
2116
                                if (d < 0 || context->green_bits >= sizeof(context->green)*8)
×
2117
                                        context->state = BACKGROUND_TEXT;
×
2118
                                else {
2119
                                        context->green = (context->green << 4) | d;
×
2120
                                        context->green_bits += 4;
×
2121
                                }
2122
                        }
2123
                        break;
2124

2125
                case BACKGROUND_BLUE:
×
2126
                        if (c == '\x07') {
×
2127
                                if (context->blue_bits > 0) {
×
2128
                                        if (ret_processed)
×
2129
                                                *ret_processed = i + 1;
×
2130

2131
                                        return 1; /* success! */
×
2132
                                }
2133

2134
                                context->state = BACKGROUND_TEXT;
×
2135
                        } else if (c == '\x1b')
×
2136
                                context->state = context->blue_bits > 0 ? BACKGROUND_STRING_TERMINATOR : BACKGROUND_TEXT;
×
2137
                        else {
2138
                                int d = unhexchar(c);
×
2139
                                if (d < 0 || context->blue_bits >= sizeof(context->blue)*8)
×
2140
                                        context->state = BACKGROUND_TEXT;
×
2141
                                else {
2142
                                        context->blue = (context->blue << 4) | d;
×
2143
                                        context->blue_bits += 4;
×
2144
                                }
2145
                        }
2146
                        break;
2147

2148
                case BACKGROUND_STRING_TERMINATOR:
×
2149
                        if (c == '\\') {
×
2150
                                if (ret_processed)
×
2151
                                        *ret_processed = i + 1;
×
2152

2153
                                return 1; /* success! */
×
2154
                        }
2155

2156
                        context->state = c == ']' ? BACKGROUND_ESCAPE : BACKGROUND_TEXT;
×
2157
                        break;
×
2158

2159
                }
2160

2161
                /* Reset any colors we might have picked up */
2162
                if (IN_SET(context->state, BACKGROUND_TEXT, BACKGROUND_ESCAPE)) {
×
2163
                        /* reset color */
2164
                        context->red = context->green = context->blue = 0;
×
2165
                        context->red_bits = context->green_bits = context->blue_bits = 0;
×
2166
                }
2167
        }
2168

2169
        if (ret_processed)
×
2170
                *ret_processed = size;
×
2171

2172
        return 0; /* all good, but not enough data yet */
2173
}
2174

2175
int get_default_background_color(double *ret_red, double *ret_green, double *ret_blue) {
124✔
2176
        _cleanup_close_ int nonblock_input_fd = -EBADF;
124✔
2177
        int r;
124✔
2178

2179
        assert(ret_red);
124✔
2180
        assert(ret_green);
124✔
2181
        assert(ret_blue);
124✔
2182

2183
        if (!colors_enabled())
124✔
2184
                return -EOPNOTSUPP;
2185

2186
        r = terminal_verify_same(STDIN_FILENO, STDOUT_FILENO);
×
2187
        if (r < 0)
×
2188
                return r;
2189

2190
        if (streq_ptr(getenv("TERM"), "linux")) {
×
2191
                /* Linux console is black */
2192
                *ret_red = *ret_green = *ret_blue = 0.0;
×
2193
                return 0;
×
2194
        }
2195

2196
        struct termios old_termios;
×
2197
        if (tcgetattr(STDIN_FILENO, &old_termios) < 0)
×
2198
                return -errno;
×
2199

2200
        struct termios new_termios = old_termios;
×
2201
        termios_disable_echo(&new_termios);
×
2202

2203
        if (tcsetattr(STDIN_FILENO, TCSADRAIN, &new_termios) < 0)
×
2204
                return -errno;
×
2205

2206
        r = loop_write(STDOUT_FILENO, ANSI_OSC "11;?" ANSI_ST, SIZE_MAX);
×
2207
        if (r < 0)
×
2208
                goto finish;
×
2209

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

2213
        nonblock_input_fd = fd_reopen(STDIN_FILENO, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
×
2214
        if (nonblock_input_fd < 0)
×
2215
                return nonblock_input_fd;
2216

2217
        usec_t end = usec_add(now(CLOCK_MONOTONIC), 333 * USEC_PER_MSEC);
×
2218
        char buf[STRLEN(ANSI_OSC "11;rgb:0/0/0" ANSI_ST)]; /* shortest possible reply */
×
2219
        size_t buf_full = 0;
×
2220
        BackgroundColorContext context = {};
×
2221

2222
        for (bool first = true;; first = false) {
×
2223
                if (buf_full == 0) {
×
2224
                        usec_t n = now(CLOCK_MONOTONIC);
×
2225
                        if (n >= end) {
×
2226
                                r = -EOPNOTSUPP;
×
2227
                                goto finish;
×
2228
                        }
2229

2230
                        r = fd_wait_for_event(nonblock_input_fd, POLLIN, usec_sub_unsigned(end, n));
×
2231
                        if (r < 0)
×
2232
                                goto finish;
×
2233
                        if (r == 0) {
×
2234
                                r = -EOPNOTSUPP;
×
2235
                                goto finish;
×
2236
                        }
2237

2238
                        /* On the first try, read multiple characters, i.e. the shortest valid
2239
                         * reply. Afterwards read byte-wise, since we don't want to read too much, and
2240
                         * unnecessarily drop too many characters from the input queue. */
2241
                        ssize_t l = read(nonblock_input_fd, buf, first ? sizeof(buf) : 1);
×
2242
                        if (l < 0) {
×
2243
                                if (errno == EAGAIN)
×
2244
                                        continue;
×
2245
                                r = -errno;
×
2246
                                goto finish;
×
2247
                        }
2248

2249
                        assert((size_t) l <= sizeof(buf));
×
2250
                        buf_full = l;
2251
                }
2252

2253
                size_t processed;
×
2254
                r = scan_background_color_response(&context, buf, buf_full, &processed);
×
2255
                if (r < 0)
×
2256
                        goto finish;
×
2257

2258
                assert(processed <= buf_full);
×
2259
                buf_full -= processed;
×
2260
                memmove(buf, buf + processed, buf_full);
×
2261

2262
                if (r > 0) {
×
2263
                        assert(context.red_bits > 0);
×
2264
                        *ret_red = (double) context.red / ((UINT64_C(1) << context.red_bits) - 1);
×
2265
                        assert(context.green_bits > 0);
×
2266
                        *ret_green = (double) context.green / ((UINT64_C(1) << context.green_bits) - 1);
×
2267
                        assert(context.blue_bits > 0);
×
2268
                        *ret_blue = (double) context.blue / ((UINT64_C(1) << context.blue_bits) - 1);
×
2269
                        r = 0;
×
2270
                        goto finish;
×
2271
                }
2272
        }
2273

2274
finish:
×
2275
        RET_GATHER(r, RET_NERRNO(tcsetattr(STDIN_FILENO, TCSADRAIN, &old_termios)));
×
2276
        return r;
2277
}
2278

2279
typedef enum CursorPositionState {
2280
        CURSOR_TEXT,
2281
        CURSOR_ESCAPE,
2282
        CURSOR_ROW,
2283
        CURSOR_COLUMN,
2284
} CursorPositionState;
2285

2286
typedef struct CursorPositionContext {
2287
        CursorPositionState state;
2288
        unsigned row, column;
2289
} CursorPositionContext;
2290

2291
static int scan_cursor_position_response(
×
2292
                CursorPositionContext *context,
2293
                const char *buf,
2294
                size_t size,
2295
                size_t *ret_processed) {
2296

2297
        assert(context);
×
2298
        assert(buf || size == 0);
×
2299

2300
        for (size_t i = 0; i < size; i++) {
×
2301
                char c = buf[i];
×
2302

2303
                switch (context->state) {
×
2304

2305
                case CURSOR_TEXT:
×
2306
                        context->state = c == '\x1B' ? CURSOR_ESCAPE : CURSOR_TEXT;
×
2307
                        break;
×
2308

2309
                case CURSOR_ESCAPE:
×
2310
                        context->state = c == '[' ? CURSOR_ROW : CURSOR_TEXT;
×
2311
                        break;
×
2312

2313
                case CURSOR_ROW:
×
2314
                        if (c == ';')
×
2315
                                context->state = context->row > 0 ? CURSOR_COLUMN : CURSOR_TEXT;
×
2316
                        else {
2317
                                int d = undecchar(c);
×
2318

2319
                                /* We read a decimal character, let's suffix it to the number we so far read,
2320
                                 * but let's do an overflow check first. */
2321
                                if (d < 0 || context->row > (UINT_MAX-d)/10)
×
2322
                                        context->state = CURSOR_TEXT;
×
2323
                                else
2324
                                        context->row = context->row * 10 + d;
×
2325
                        }
2326
                        break;
2327

2328
                case CURSOR_COLUMN:
×
2329
                        if (c == 'R') {
×
2330
                                if (context->column > 0) {
×
2331
                                        if (ret_processed)
×
2332
                                                *ret_processed = i + 1;
×
2333

2334
                                        return 1; /* success! */
×
2335
                                }
2336

2337
                                context->state = CURSOR_TEXT;
×
2338
                        } else {
2339
                                int d = undecchar(c);
×
2340

2341
                                /* As above, add the decimal character to our column number */
2342
                                if (d < 0 || context->column > (UINT_MAX-d)/10)
×
2343
                                        context->state = CURSOR_TEXT;
×
2344
                                else
2345
                                        context->column = context->column * 10 + d;
×
2346
                        }
2347

2348
                        break;
2349
                }
2350

2351
                /* Reset any positions we might have picked up */
2352
                if (IN_SET(context->state, CURSOR_TEXT, CURSOR_ESCAPE))
×
2353
                        context->row = context->column = 0;
×
2354
        }
2355

2356
        if (ret_processed)
×
2357
                *ret_processed = size;
×
2358

2359
        return 0; /* all good, but not enough data yet */
2360
}
2361

2362
int terminal_get_size_by_dsr(
174✔
2363
                int input_fd,
2364
                int output_fd,
2365
                unsigned *ret_rows,
2366
                unsigned *ret_columns) {
2367

2368
        _cleanup_close_ int nonblock_input_fd = -EBADF;
174✔
2369

2370
        assert(input_fd >= 0);
174✔
2371
        assert(output_fd >= 0);
174✔
2372

2373
        int r;
174✔
2374

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

2390
        if (terminal_is_dumb())
174✔
2391
                return -EOPNOTSUPP;
2392

2393
        r = terminal_verify_same(input_fd, output_fd);
×
2394
        if (r < 0)
×
2395
                return log_debug_errno(r, "Called with distinct input/output fds: %m");
×
2396

2397
        struct termios old_termios;
×
2398
        if (tcgetattr(input_fd, &old_termios) < 0)
×
2399
                return log_debug_errno(errno, "Failed to to get terminal settings: %m");
×
2400

2401
        struct termios new_termios = old_termios;
×
2402
        termios_disable_echo(&new_termios);
×
2403

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

2407
        unsigned saved_row = 0, saved_column = 0;
×
2408

2409
        r = loop_write(output_fd,
×
2410
                       "\x1B[6n"           /* Request cursor position (DSR/CPR) */
2411
                       "\x1B[32766;32766H" /* Position cursor really far to the right and to the bottom, but let's stay within the 16bit signed range */
2412
                       "\x1B[6n",          /* Request cursor position again */
2413
                       SIZE_MAX);
2414
        if (r < 0)
×
2415
                goto finish;
×
2416

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

2420
        nonblock_input_fd = fd_reopen(input_fd, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
×
2421
        if (nonblock_input_fd < 0)
×
2422
                return nonblock_input_fd;
2423

2424
        usec_t end = usec_add(now(CLOCK_MONOTONIC), 333 * USEC_PER_MSEC);
×
2425
        char buf[STRLEN("\x1B[1;1R")]; /* The shortest valid reply possible */
×
2426
        size_t buf_full = 0;
×
2427
        CursorPositionContext context = {};
×
2428

2429
        for (bool first = true;; first = false) {
×
2430
                if (buf_full == 0) {
×
2431
                        usec_t n = now(CLOCK_MONOTONIC);
×
2432
                        if (n >= end) {
×
2433
                                r = -EOPNOTSUPP;
×
2434
                                goto finish;
×
2435
                        }
2436

2437
                        r = fd_wait_for_event(nonblock_input_fd, POLLIN, usec_sub_unsigned(end, n));
×
2438
                        if (r < 0)
×
2439
                                goto finish;
×
2440
                        if (r == 0) {
×
2441
                                r = -EOPNOTSUPP;
×
2442
                                goto finish;
×
2443
                        }
2444

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

2453
                                r = -errno;
×
2454
                                goto finish;
×
2455
                        }
2456

2457
                        assert((size_t) l <= sizeof(buf));
×
2458
                        buf_full = l;
2459
                }
2460

2461
                size_t processed;
×
2462
                r = scan_cursor_position_response(&context, buf, buf_full, &processed);
×
2463
                if (r < 0)
×
2464
                        goto finish;
×
2465

2466
                assert(processed <= buf_full);
×
2467
                buf_full -= processed;
×
2468
                memmove(buf, buf + processed, buf_full);
×
2469

2470
                if (r > 0) {
×
2471
                        if (saved_row == 0) {
×
2472
                                assert(saved_column == 0);
×
2473

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

2478
                                /* Superficial validity checks */
2479
                                if (context.row <= 0 || context.column <= 0 || context.row >= 32766 || context.column >= 32766) {
×
2480
                                        r = -ENODATA;
×
2481
                                        goto finish;
×
2482
                                }
2483

2484
                                saved_row = context.row;
×
2485
                                saved_column = context.column;
×
2486

2487
                                /* Reset state */
2488
                                context = (CursorPositionContext) {};
×
2489
                        } else {
2490
                                /* Second sequence, this is the cursor position after we set it somewhere
2491
                                 * into the void at the bottom right. */
2492

2493
                                /* Superficial validity checks (no particular reason to check for < 4, it's
2494
                                 * just a way to look for unreasonably small values) */
2495
                                if (context.row < 4 || context.column < 4 || context.row >= 32766 || context.column >= 32766) {
×
2496
                                        r = -ENODATA;
×
2497
                                        goto finish;
×
2498
                                }
2499

2500
                                if (ret_rows)
×
2501
                                        *ret_rows = context.row;
×
2502
                                if (ret_columns)
×
2503
                                        *ret_columns = context.column;
×
2504

2505
                                r = 0;
×
2506
                                goto finish;
×
2507
                        }
2508
                }
2509
        }
2510

2511
finish:
×
2512
        /* Restore cursor position */
2513
        if (saved_row > 0 && saved_column > 0)
×
2514
                RET_GATHER(r, terminal_set_cursor_position(output_fd, saved_row, saved_column));
×
2515

2516
        RET_GATHER(r, RET_NERRNO(tcsetattr(input_fd, TCSADRAIN, &old_termios)));
×
2517
        return r;
2518
}
2519

2520
int terminal_fix_size(int input_fd, int output_fd) {
1✔
2521
        unsigned rows, columns;
1✔
2522
        int r;
1✔
2523

2524
        /* Tries to update the current terminal dimensions to the ones reported via ANSI sequences */
2525

2526
        r = terminal_verify_same(input_fd, output_fd);
1✔
2527
        if (r < 0)
1✔
2528
                return r;
1✔
2529

2530
        struct winsize ws = {};
×
2531
        if (ioctl(output_fd, TIOCGWINSZ, &ws) < 0)
×
2532
                return log_debug_errno(errno, "Failed to query terminal dimensions, ignoring: %m");
×
2533

2534
        r = terminal_get_size_by_dsr(input_fd, output_fd, &rows, &columns);
×
2535
        if (r < 0)
×
2536
                return log_debug_errno(r, "Failed to acquire terminal dimensions via ANSI sequences, not adjusting terminal dimensions: %m");
×
2537

2538
        if (ws.ws_row == rows && ws.ws_col == columns) {
×
2539
                log_debug("Terminal dimensions reported via ANSI sequences match currently set terminal dimensions, not changing.");
×
2540
                return 0;
×
2541
        }
2542

2543
        ws.ws_col = columns;
×
2544
        ws.ws_row = rows;
×
2545

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

2549
        log_debug("Fixed terminal dimensions to %ux%u based on ANSI sequence information.", columns, rows);
×
2550
        return 1;
2551
}
2552

2553
int terminal_is_pty_fd(int fd) {
3✔
2554
        int r;
3✔
2555

2556
        assert(fd >= 0);
3✔
2557

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

2560
        if (!isatty_safe(fd))
3✔
2561
                return false;
3✔
2562

2563
        r = is_fs_type_at(fd, NULL, DEVPTS_SUPER_MAGIC);
2✔
2564
        if (r != 0)
2✔
2565
                return r;
2566

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

2570
        int v;
×
2571
        if (ioctl(fd, TIOCGPKT, &v) < 0) {
×
2572
                if (ERRNO_IS_NOT_SUPPORTED(errno))
×
2573
                        return false;
2574

2575
                return -errno;
×
2576
        }
2577

2578
        return true;
2579
}
2580

2581
int pty_open_peer(int fd, int mode) {
20✔
2582
        assert(fd >= 0);
20✔
2583

2584
        /* Opens the peer PTY using the new race-free TIOCGPTPEER ioctl() (kernel 4.13).
2585
         *
2586
         * This is safe to be called on TTYs from other namespaces. */
2587

2588
        assert((mode & (O_CREAT|O_PATH|O_DIRECTORY|O_TMPFILE)) == 0);
20✔
2589

2590
        /* This replicates the EIO retry logic of open_terminal() in a modified way. */
2591
        for (unsigned c = 0;; c++) {
×
2592
                int peer_fd = ioctl(fd, TIOCGPTPEER, mode);
20✔
2593
                if (peer_fd >= 0)
20✔
2594
                        return peer_fd;
2595

2596
                if (errno != EIO)
×
2597
                        return -errno;
×
2598

2599
                /* Max 1s in total */
2600
                if (c >= 20)
×
2601
                        return -EIO;
2602

2603
                (void) usleep_safe(50 * USEC_PER_MSEC);
×
2604
        }
2605
}
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