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

systemd / systemd / 20447389715

21 Dec 2025 07:31PM UTC coverage: 72.37% (-0.1%) from 72.5%
20447389715

push

github

DaanDeMeyer
mkosi: Use initrd as exitrd

Let's speed up image builds by avoiding building
an exitrd and instead reusing the initrd image for
the same purpose.

308584 of 426400 relevant lines covered (72.37%)

1134231.7 hits per line

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

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

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

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

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

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

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

55
bool isatty_safe(int fd) {
5,152,744✔
56
        assert(fd >= 0);
5,152,744✔
57

58
        if (isatty(fd))
5,152,744✔
59
                return true;
60

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

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

70
        return false;
71
}
72

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

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

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

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

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

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

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

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

103
        assert(ret);
4✔
104

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

173
#define DEFAULT_ASK_REFRESH_USEC (2*USEC_PER_SEC)
174

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

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

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

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

189
                putchar('\r');
×
190

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

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

197
                fflush(stdout);
×
198

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

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

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

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

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

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

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

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

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

240
        string = strempty(string);
×
241

242
        STRV_FOREACH(c, completions) {
×
243

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

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

254
                        continue;
×
255
                }
256

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

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

267
        *ret = TAKE_PTR(found);
×
268

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

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

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

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

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

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

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

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

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

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

319
        struct termios new_termios = old_termios;
×
320
        termios_disable_echo(&new_termios);
×
321
        if (tcsetattr(fd_input, TCSANOW, &new_termios) < 0)
×
322
                return -errno;
×
323

324
        for (;;) {
×
325
                int c = fgetc(stdin);
×
326

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

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

337
                if (c == '\t') {
×
338
                        /* Tab */
339

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

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

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

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

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

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

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

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

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

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

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

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

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

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

421
                        r = -ECANCELED;
×
422
                        goto fail;
×
423

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

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

435
                        string[n++] = (char) c;
×
436
                        string[n] = 0;
×
437

438
                        fputc(c, stdout);
×
439
                }
440

441
                fflush(stdout);
×
442
        }
443

444
        if (tcsetattr(fd_input, TCSANOW, &old_termios) < 0)
×
445
                return -errno;
×
446

447
        if (!string) {
×
448
                string = strdup("");
×
449
                if (!string)
×
450
                        return -ENOMEM;
451
        }
452

453
        *ret = TAKE_PTR(string);
×
454
        return 0;
×
455

456
fail:
×
457
        (void) tcsetattr(fd_input, TCSANOW, &old_termios);
×
458
        return r;
×
459

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

468
        *ret = TAKE_PTR(string);
7✔
469
        return 0;
7✔
470
}
471

472
bool any_key_to_proceed(void) {
×
473

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

478
        fputc('\n', stdout);
×
479
        fputs(ansi_highlight_magenta(), stdout);
×
480
        fputs("-- Press any key to proceed --", stdout);
×
481
        fputs(ansi_normal(), stdout);
×
482
        fputc('\n', stdout);
×
483
        fflush(stdout);
×
484

485
        char key = 0;
×
486
        (void) read_one_char(stdin, &key, USEC_INFINITY, /* echo= */ false, /* need_nl= */ NULL);
×
487

488
        fputc('\n', stdout);
×
489
        fflush(stdout);
×
490

491
        return key != 'q';
×
492
}
493

494
static size_t widest_list_element(char *const*l) {
×
495
        size_t w = 0;
×
496

497
        /* Returns the largest console width of all elements in 'l' */
498

499
        STRV_FOREACH(i, l)
×
500
                w = MAX(w, utf8_console_width(*i));
×
501

502
        return w;
×
503
}
504

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

512
        assert(n_columns > 0);
×
513

514
        if (n_columns == SIZE_MAX)
×
515
                n_columns = 3;
×
516

517
        if (column_width == SIZE_MAX) {
×
518
                size_t widest = widest_list_element(x);
×
519

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

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

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

532
                        if (with_numbers && column_max > 6)
×
533
                                column_max -= 6;
×
534
                }
535

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

539
        size_t n = strv_length(x);
×
540
        size_t per_column = DIV_ROUND_UP(n, n_columns);
×
541

542
        size_t break_lines = lines();
×
543
        if (break_lines > 2)
×
544
                break_lines--;
×
545

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

552
        for (size_t i = 0; i < per_column; i++) {
×
553

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

557
                        if (j * per_column + i >= n)
×
558
                                break;
559

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

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

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

582
                putchar('\n');
×
583

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

590
        return 0;
591
}
592

593
int open_terminal(const char *name, int mode) {
46,456✔
594
        _cleanup_close_ int fd = -EBADF;
46,456✔
595

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

604
        assert((mode & (O_CREAT|O_PATH|O_DIRECTORY|O_TMPFILE)) == 0);
46,456✔
605

606
        for (unsigned c = 0;; c++) {
×
607
                fd = open(name, mode, 0);
46,456✔
608
                if (fd >= 0)
46,456✔
609
                        break;
610

611
                if (errno != EIO)
1,299✔
612
                        return -errno;
1,299✔
613

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

618
                (void) usleep_safe(50 * USEC_PER_MSEC);
×
619
        }
620

621
        if (!isatty_safe(fd))
45,157✔
622
                return -ENOTTY;
1✔
623

624
        return TAKE_FD(fd);
625
}
626

627
int acquire_terminal(
187✔
628
                const char *name,
629
                AcquireTerminalFlags flags,
630
                usec_t timeout) {
631

632
        _cleanup_close_ int notify = -EBADF, fd = -EBADF;
187✔
633
        usec_t ts = USEC_INFINITY;
187✔
634
        int r, wd = -1;
187✔
635

636
        assert(name);
187✔
637

638
        AcquireTerminalFlags mode = flags & _ACQUIRE_TERMINAL_MODE_MASK;
187✔
639
        assert(IN_SET(mode, ACQUIRE_TERMINAL_TRY, ACQUIRE_TERMINAL_FORCE, ACQUIRE_TERMINAL_WAIT));
187✔
640
        assert(mode == ACQUIRE_TERMINAL_WAIT || !FLAGS_SET(flags, ACQUIRE_TERMINAL_WATCH_SIGTERM));
187✔
641

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

651
        if (mode == ACQUIRE_TERMINAL_WAIT) {
×
652
                notify = inotify_init1(IN_CLOEXEC | IN_NONBLOCK);
187✔
653
                if (notify < 0)
187✔
654
                        return -errno;
×
655

656
                wd = inotify_add_watch(notify, name, IN_CLOSE);
187✔
657
                if (wd < 0)
187✔
658
                        return -errno;
×
659

660
                if (timeout != USEC_INFINITY)
187✔
661
                        ts = now(CLOCK_MONOTONIC);
×
662
        }
663

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

672
        for (;;) {
187✔
673
                if (notify >= 0) {
187✔
674
                        r = flush_fd(notify);
187✔
675
                        if (r < 0)
187✔
676
                                return r;
×
677
                }
678

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

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

689
                /* First, try to get the tty */
690
                r = RET_NERRNO(ioctl(fd, TIOCSCTTY, mode == ACQUIRE_TERMINAL_FORCE));
187✔
691

692
                /* Reset signal handler to old value */
693
                assert_se(sigaction(SIGHUP, &sa_old, NULL) >= 0);
187✔
694

695
                /* Success? Exit the loop now! */
696
                if (r >= 0)
187✔
697
                        break;
698

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

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

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

711
                assert(notify >= 0);
×
712
                assert(wd >= 0);
×
713

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

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

725
                                left = timeout - n;
×
726
                        }
727

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

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

748
                                return -errno;
×
749
                        }
750

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

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

759
                        break;
×
760
                }
761

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

767
        return TAKE_FD(fd);
187✔
768
}
769

770
int release_terminal(void) {
55✔
771
        _cleanup_close_ int fd = -EBADF;
55✔
772
        int r;
55✔
773

774
        fd = open("/dev/tty", O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK);
55✔
775
        if (fd < 0)
55✔
776
                return -errno;
34✔
777

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

783
        r = RET_NERRNO(ioctl(fd, TIOCNOTTY));
21✔
784

785
        assert_se(sigaction(SIGHUP, &sa_old, NULL) >= 0);
21✔
786

787
        return r;
788
}
789

790
int terminal_new_session(void) {
5✔
791

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

799
        if (!isatty_safe(STDIN_FILENO))
5✔
800
                return -ENXIO;
801

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

806
void terminal_detach_session(void) {
55✔
807
        (void) setsid();
55✔
808
        (void) release_terminal();
55✔
809
}
55✔
810

811
int terminal_vhangup_fd(int fd) {
97✔
812
        assert(fd >= 0);
97✔
813
        return RET_NERRNO(ioctl(fd, TIOCVHANGUP));
97✔
814
}
815

816
int terminal_vhangup(const char *tty) {
×
817
        _cleanup_close_ int fd = -EBADF;
×
818

819
        assert(tty);
×
820

821
        fd = open_terminal(tty, O_RDWR|O_NOCTTY|O_CLOEXEC);
×
822
        if (fd < 0)
×
823
                return fd;
824

825
        return terminal_vhangup_fd(fd);
×
826
}
827

828
int vt_disallocate(const char *tty_path) {
49✔
829
        assert(tty_path);
49✔
830

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

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

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

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

850
        _cleanup_close_ int fd2 = open_terminal(tty_path, O_WRONLY|O_NOCTTY|O_CLOEXEC|O_NONBLOCK);
98✔
851
        if (fd2 < 0)
49✔
852
                return fd2;
853

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

863
static int vt_default_utf8(void) {
882✔
864
        _cleanup_free_ char *b = NULL;
882✔
865
        int r;
882✔
866

867
        /* Read the default VT UTF8 setting from the kernel */
868

869
        r = read_one_line_file("/sys/module/vt/parameters/default_utf8", &b);
882✔
870
        if (r < 0)
882✔
871
                return r;
872

873
        return parse_boolean(b);
488✔
874
}
875

876
static int vt_reset_keyboard(int fd) {
441✔
877
        int r, kb;
441✔
878

879
        assert(fd >= 0);
441✔
880

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

886
        kb = vt_default_utf8() != 0 ? K_UNICODE : K_XLATE;
441✔
887
        return RET_NERRNO(ioctl(fd, KDSKBMODE, kb));
441✔
888
}
889

890
static int terminal_reset_ioctl(int fd, bool switch_to_text) {
441✔
891
        struct termios termios;
441✔
892
        int r;
441✔
893

894
        /* Set terminal to some sane defaults */
895

896
        assert(fd >= 0);
441✔
897

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

901
        /* Disable exclusive mode, just in case */
902
        if (ioctl(fd, TIOCNXCL) < 0)
441✔
903
                log_debug_errno(errno, "TIOCNXCL ioctl failed on TTY, ignoring: %m");
2✔
904

905
        /* Switch to text mode */
906
        if (switch_to_text)
441✔
907
                if (ioctl(fd, KDSETMODE, KD_TEXT) < 0)
213✔
908
                        log_debug_errno(errno, "KDSETMODE ioctl for switching to text mode failed on TTY, ignoring: %m");
164✔
909

910
        /* Set default keyboard mode */
911
        r = vt_reset_keyboard(fd);
441✔
912
        if (r < 0)
441✔
913
                log_debug_errno(r, "Failed to reset VT keyboard, ignoring: %m");
346✔
914

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

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

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

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

944
        termios.c_cc[VTIME]  = 0;
439✔
945
        termios.c_cc[VMIN]   = 1;
439✔
946

947
        r = RET_NERRNO(tcsetattr(fd, TCSANOW, &termios));
439✔
948
        if (r < 0)
×
949
                log_debug_errno(r, "Failed to set terminal parameters: %m");
×
950

951
finish:
×
952
        /* Just in case, flush all crap out */
953
        (void) tcflush(fd, TCIOFLUSH);
441✔
954

955
        return r;
441✔
956
}
957

958
int terminal_reset_ansi_seq(int fd) {
435✔
959
        int r, k;
435✔
960

961
        assert(fd >= 0);
435✔
962

963
        if (getenv_terminal_is_dumb())
435✔
964
                return 0;
965

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

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

981
        if (r > 0) {
×
982
                r = fd_nonblock(fd, false);
×
983
                if (r < 0)
×
984
                        log_debug_errno(r, "Failed to set terminal back to blocking mode: %m");
×
985
        }
986

987
        return k < 0 ? k : r;
×
988
}
989

990
void reset_dev_console_fd(int fd, bool switch_to_text) {
36✔
991
        int r;
36✔
992

993
        assert(fd >= 0);
36✔
994

995
        _cleanup_close_ int lock_fd = lock_dev_console();
36✔
996
        if (lock_fd < 0)
36✔
997
                log_debug_errno(lock_fd, "Failed to lock /dev/console, ignoring: %m");
×
998

999
        r = terminal_reset_ioctl(fd, switch_to_text);
36✔
1000
        if (r < 0)
36✔
1001
                log_warning_errno(r, "Failed to reset /dev/console, ignoring: %m");
×
1002

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

1014
        r = terminal_reset_ansi_seq(fd);
36✔
1015
        if (r < 0)
36✔
1016
                log_warning_errno(r, "Failed to reset /dev/console using ANSI sequences, ignoring: %m");
36✔
1017
}
36✔
1018

1019
int lock_dev_console(void) {
572✔
1020
        _cleanup_close_ int fd = -EBADF;
572✔
1021
        int r;
572✔
1022

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

1029
        r = lock_generic(fd, LOCK_BSD, LOCK_EX);
572✔
1030
        if (r < 0)
572✔
1031
                return r;
×
1032

1033
        return TAKE_FD(fd);
1034
}
1035

1036
int make_console_stdio(void) {
×
1037
        int fd, r;
×
1038

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

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

1047
                r = make_null_stdio();
×
1048
                if (r < 0)
×
1049
                        return log_error_errno(r, "Failed to make /dev/null stdin/stdout/stderr: %m");
×
1050

1051
        } else {
1052
                reset_dev_console_fd(fd, /* switch_to_text= */ true);
×
1053

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

1059
        reset_terminal_feature_caches();
×
1060
        return 0;
×
1061
}
1062

1063
static int vtnr_from_tty_raw(const char *tty, unsigned *ret) {
300✔
1064
        assert(tty);
300✔
1065

1066
        tty = skip_dev_prefix(tty);
300✔
1067

1068
        const char *e = startswith(tty, "tty");
300✔
1069
        if (!e)
300✔
1070
                return -EINVAL;
1071

1072
        return safe_atou(e, ret);
260✔
1073
}
1074

1075
int vtnr_from_tty(const char *tty) {
176✔
1076
        unsigned u;
176✔
1077
        int r;
176✔
1078

1079
        assert(tty);
176✔
1080

1081
        r = vtnr_from_tty_raw(tty, &u);
176✔
1082
        if (r < 0)
176✔
1083
                return r;
176✔
1084
        if (!vtnr_is_valid(u))
176✔
1085
                return -ERANGE;
1086

1087
        return (int) u;
176✔
1088
}
1089

1090
bool tty_is_vc(const char *tty) {
124✔
1091
        assert(tty);
124✔
1092

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

1098
        return vtnr_from_tty_raw(tty, /* ret= */ NULL) >= 0;
124✔
1099
}
1100

1101
bool tty_is_console(const char *tty) {
443✔
1102
        assert(tty);
443✔
1103

1104
        return streq(skip_dev_prefix(tty), "console");
443✔
1105
}
1106

1107
int resolve_dev_console(char **ret) {
181✔
1108
        int r;
181✔
1109

1110
        assert(ret);
181✔
1111

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

1117
        _cleanup_free_ char *chased = NULL;
181✔
1118
        r = chase("/dev/console", /* root= */ NULL, /* flags= */ 0,  &chased, /* ret_fd= */ NULL);
181✔
1119
        if (r < 0)
181✔
1120
                return r;
1121
        if (!path_equal(chased, "/dev/console")) {
181✔
1122
                *ret = TAKE_PTR(chased);
82✔
1123
                return 0;
82✔
1124
        }
1125

1126
        r = path_is_read_only_fs("/sys");
99✔
1127
        if (r < 0)
99✔
1128
                return r;
1129
        if (r > 0)
99✔
1130
                return -ENOMEDIUM;
1131

1132
        _cleanup_free_ char *active = NULL;
99✔
1133
        r = read_one_line_file("/sys/class/tty/console/active", &active);
99✔
1134
        if (r < 0)
99✔
1135
                return r;
1136

1137
        /* If multiple log outputs are configured the last one is what /dev/console points to */
1138
        const char *tty = strrchr(active, ' ');
99✔
1139
        if (tty)
99✔
1140
                tty++;
×
1141
        else
1142
                tty = active;
1143

1144
        if (streq(tty, "tty0")) {
99✔
1145
                active = mfree(active);
×
1146

1147
                /* Get the active VC (e.g. tty1) */
1148
                r = read_one_line_file("/sys/class/tty/tty0/active", &active);
×
1149
                if (r < 0)
×
1150
                        return r;
1151

1152
                tty = active;
×
1153
        }
1154

1155
        _cleanup_free_ char *path = NULL;
99✔
1156
        path = path_join("/dev", tty);
99✔
1157
        if (!path)
99✔
1158
                return -ENOMEM;
1159

1160
        *ret = TAKE_PTR(path);
99✔
1161
        return 0;
99✔
1162
}
1163

1164
int get_kernel_consoles(char ***ret) {
×
1165
        _cleanup_strv_free_ char **l = NULL;
×
1166
        _cleanup_free_ char *line = NULL;
×
1167
        int r;
×
1168

1169
        assert(ret);
×
1170

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

1176
        r = read_one_line_file("/sys/class/tty/console/active", &line);
×
1177
        if (r < 0)
×
1178
                return r;
1179

1180
        for (const char *p = line;;) {
×
1181
                _cleanup_free_ char *tty = NULL, *path = NULL;
×
1182

1183
                r = extract_first_word(&p, &tty, NULL, 0);
×
1184
                if (r < 0)
×
1185
                        return r;
1186
                if (r == 0)
×
1187
                        break;
1188

1189
                if (streq(tty, "tty0")) {
×
1190
                        tty = mfree(tty);
×
1191
                        r = read_one_line_file("/sys/class/tty/tty0/active", &tty);
×
1192
                        if (r < 0)
×
1193
                                return r;
1194
                }
1195

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

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

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

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

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

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

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

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

1230
        assert(tty);
49✔
1231

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

1236
                tty = resolved;
1✔
1237
        }
1238

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

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

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

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

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

1254
        return ws.ws_col;
×
1255
}
1256

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

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

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

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

1272
unsigned columns(void) {
263,561✔
1273

1274
        if (cached_columns > 0)
263,561✔
1275
                return cached_columns;
262,199✔
1276

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

1284
        assert(c > 0);
1✔
1285

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

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

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

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

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

1302
        return ws.ws_row;
×
1303
}
1304

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

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

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

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

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

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

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

1332
        if (!ident)
550✔
1333
                ident = "TTY";
78✔
1334

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

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

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

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

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

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

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

1362
        return 0;
1363
}
1364

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

1370
        assert(tty);
508✔
1371

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

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

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

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

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

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

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

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

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

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

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

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

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

1432
bool on_tty(void) {
7,453,558✔
1433

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

1440
        if (cached_on_tty < 0)
7,453,558✔
1441
                cached_on_tty =
31,984✔
1442
                        isatty_safe(STDOUT_FILENO) &&
32,012✔
1443
                        isatty_safe(STDERR_FILENO);
28✔
1444

1445
        return cached_on_tty;
7,453,558✔
1446
}
1447

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

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

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

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

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

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

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

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

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

1486
        assert(pid >= 0);
4,477✔
1487

1488
        p = procfs_file_alloca(pid, "stat");
22,045✔
1489
        r = read_one_line_file(p, &line);
4,477✔
1490
        if (r < 0)
4,477✔
1491
                return r;
1492

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

1497
        p++;
4,477✔
1498

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

1508
        if (devnum_is_zero(ttynr))
4,477✔
1509
                return -ENXIO;
1510

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

1514
        return 0;
1515
}
1516

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

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

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

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

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

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

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

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

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

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

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

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

1575
        return 0;
1576
}
1577

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

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

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

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

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

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

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

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

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

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

1622
        if (ret_peer_path)
138✔
1623
                *ret_peer_path = TAKE_PTR(p);
134✔
1624

1625
        return TAKE_FD(fd);
1626
}
1627

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

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

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

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

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

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

1646
        return 0;
1647
}
1648

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

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

1658
        r = pidref_namespace_open(pidref, &pidnsfd, &mntnsfd, /* ret_netns_fd= */ NULL, &usernsfd, &rootfd);
×
1659
        if (r < 0)
×
1660
                return r;
1661

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

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

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

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

1687
                _exit(EXIT_SUCCESS);
×
1688
        }
1689

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

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

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

1702
        return TAKE_FD(fd);
1703
}
1704

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

1708
        if (cached_on_dev_null >= 0)
38,827✔
1709
                return cached_on_dev_null;
6,953✔
1710

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

1716
        return cached_on_dev_null;
31,874✔
1717
}
1718

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

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

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

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

1733
        return getenv_terminal_is_dumb();
137✔
1734
}
1735

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

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

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

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

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

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

1760
int vt_restore(int fd) {
×
1761

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

1766
        int r, ret = 0;
×
1767

1768
        assert(fd >= 0);
×
1769

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

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

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

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

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

1787
        return ret;
1788
}
1789

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

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

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

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

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

1806
        return 0;
1807
}
1808

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

1813
        if (priority <= LOG_ERR) {
201,744✔
1814
                if (on)
6,495✔
1815
                        *on = ansi_highlight_red();
12,990✔
1816
                if (off)
6,495✔
1817
                        *off = ansi_normal();
12,990✔
1818
                if (highlight)
6,495✔
1819
                        *highlight = ansi_highlight();
×
1820

1821
        } else if (priority <= LOG_WARNING) {
195,249✔
1822
                if (on)
911✔
1823
                        *on = ansi_highlight_yellow();
911✔
1824
                if (off)
911✔
1825
                        *off = ansi_normal();
1,822✔
1826
                if (highlight)
911✔
1827
                        *highlight = ansi_highlight();
×
1828

1829
        } else if (priority <= LOG_NOTICE) {
194,338✔
1830
                if (on)
894✔
1831
                        *on = ansi_highlight();
1,788✔
1832
                if (off)
894✔
1833
                        *off = ansi_normal();
1,788✔
1834
                if (highlight)
894✔
1835
                        *highlight = ansi_highlight_red();
×
1836

1837
        } else if (priority >= LOG_DEBUG) {
193,444✔
1838
                if (on)
153,061✔
1839
                        *on = ansi_grey();
153,061✔
1840
                if (off)
153,061✔
1841
                        *off = ansi_normal();
306,122✔
1842
                if (highlight)
153,061✔
1843
                        *highlight = ansi_highlight_red();
×
1844
        }
1845
}
201,744✔
1846

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

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

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

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

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

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

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

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

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

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

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

1884
        return 0;
1885
}
1886

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

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

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

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

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

1912
                switch (context->state) {
×
1913

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

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

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

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

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

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

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

1955
                        break;
1956
                }
1957

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

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

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

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

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

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

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

1986
        struct termios old_termios;
×
1987
        if (tcgetattr(input_fd, &old_termios) < 0)
×
1988
                return log_debug_errno(errno, "Failed to get terminal settings: %m");
×
1989

1990
        struct termios new_termios = old_termios;
×
1991
        termios_disable_echo(&new_termios);
×
1992

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

1996
        /* Request cursor position (DSR/CPR) */
1997
        r = loop_write(output_fd, "\x1B[6n", SIZE_MAX);
×
1998
        if (r < 0)
×
1999
                goto finish;
×
2000

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

2004
        nonblock_input_fd = r = fd_reopen(input_fd, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
×
2005
        if (r < 0)
×
2006
                goto finish;
×
2007

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

2013
        for (bool first = true;; first = false) {
×
2014
                if (buf_full == 0) {
×
2015
                        usec_t n = now(CLOCK_MONOTONIC);
×
2016
                        if (n >= end) {
×
2017
                                r = -EOPNOTSUPP;
×
2018
                                goto finish;
×
2019
                        }
2020

2021
                        r = fd_wait_for_event(nonblock_input_fd, POLLIN, usec_sub_unsigned(end, n));
×
2022
                        if (r < 0)
×
2023
                                goto finish;
×
2024
                        if (r == 0) {
×
2025
                                r = -EOPNOTSUPP;
×
2026
                                goto finish;
×
2027
                        }
2028

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

2037
                                r = -errno;
×
2038
                                goto finish;
×
2039
                        }
2040

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

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

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

2054
                if (r > 0) {
×
2055
                        /* Superficial validity check */
2056
                        if (context.row >= 32766 || context.column >= 32766) {
×
2057
                                r = -ENODATA;
×
2058
                                goto finish;
×
2059
                        }
2060

2061
                        if (ret_row)
×
2062
                                *ret_row = context.row;
×
2063
                        if (ret_column)
×
2064
                                *ret_column = context.column;
×
2065

2066
                        r = 0;
×
2067
                        goto finish;
×
2068
                }
2069
        }
2070

2071
finish:
×
2072
        /* We ignore failure here and in similar cases below. We already got a reply and if cleanup fails,
2073
         * this doesn't change the validity of the result. */
2074
        (void) tcsetattr(input_fd, TCSANOW, &old_termios);
×
2075
        return r;
×
2076
}
2077

2078
int terminal_reset_defensive(int fd, TerminalResetFlags flags) {
412✔
2079
        int r = 0;
412✔
2080

2081
        assert(fd >= 0);
412✔
2082
        assert(!FLAGS_SET(flags, TERMINAL_RESET_AVOID_ANSI_SEQ|TERMINAL_RESET_FORCE_ANSI_SEQ));
412✔
2083

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

2087
        if (!isatty_safe(fd))
412✔
2088
                return -ENOTTY;
412✔
2089

2090
        RET_GATHER(r, terminal_reset_ioctl(fd, FLAGS_SET(flags, TERMINAL_RESET_SWITCH_TO_TEXT)));
405✔
2091

2092
        if (!FLAGS_SET(flags, TERMINAL_RESET_AVOID_ANSI_SEQ) &&
405✔
2093
            (FLAGS_SET(flags, TERMINAL_RESET_FORCE_ANSI_SEQ) || !getenv_terminal_is_dumb()))
399✔
2094
                RET_GATHER(r, terminal_reset_ansi_seq(fd));
399✔
2095

2096
        return r;
2097
}
2098

2099
int terminal_reset_defensive_locked(int fd, TerminalResetFlags flags) {
6✔
2100
        assert(fd >= 0);
6✔
2101

2102
        _cleanup_close_ int lock_fd = lock_dev_console();
6✔
2103
        if (lock_fd < 0)
6✔
2104
                log_debug_errno(lock_fd, "Failed to acquire lock for /dev/console, ignoring: %m");
×
2105

2106
        return terminal_reset_defensive(fd, flags);
6✔
2107
}
2108

2109
void termios_disable_echo(struct termios *termios) {
1✔
2110
        assert(termios);
1✔
2111

2112
        termios->c_lflag &= ~(ICANON|ECHO);
1✔
2113
        termios->c_cc[VMIN] = 1;
1✔
2114
        termios->c_cc[VTIME] = 0;
1✔
2115
}
1✔
2116

2117
typedef enum BackgroundColorState {
2118
        BACKGROUND_TEXT,
2119
        BACKGROUND_ESCAPE,
2120
        BACKGROUND_BRACKET,
2121
        BACKGROUND_FIRST_ONE,
2122
        BACKGROUND_SECOND_ONE,
2123
        BACKGROUND_SEMICOLON,
2124
        BACKGROUND_R,
2125
        BACKGROUND_G,
2126
        BACKGROUND_B,
2127
        BACKGROUND_RED,
2128
        BACKGROUND_GREEN,
2129
        BACKGROUND_BLUE,
2130
        BACKGROUND_STRING_TERMINATOR,
2131
} BackgroundColorState;
2132

2133
typedef struct BackgroundColorContext {
2134
        BackgroundColorState state;
2135
        uint32_t red, green, blue;
2136
        unsigned red_bits, green_bits, blue_bits;
2137
} BackgroundColorContext;
2138

2139
static int scan_background_color_response(
×
2140
                BackgroundColorContext *context,
2141
                const char *buf,
2142
                size_t size,
2143
                size_t *ret_processed) {
2144

2145
        assert(context);
×
2146
        assert(buf);
×
2147
        assert(ret_processed);
×
2148

2149
        for (size_t i = 0; i < size; i++) {
×
2150
                char c = buf[i];
×
2151

2152
                switch (context->state) {
×
2153

2154
                case BACKGROUND_TEXT:
×
2155
                        context->state = c == '\x1B' ? BACKGROUND_ESCAPE : BACKGROUND_TEXT;
×
2156
                        break;
×
2157

2158
                case BACKGROUND_ESCAPE:
×
2159
                        context->state = c == ']' ? BACKGROUND_BRACKET : BACKGROUND_TEXT;
×
2160
                        break;
×
2161

2162
                case BACKGROUND_BRACKET:
×
2163
                        context->state = c == '1' ? BACKGROUND_FIRST_ONE : BACKGROUND_TEXT;
×
2164
                        break;
×
2165

2166
                case BACKGROUND_FIRST_ONE:
×
2167
                        context->state = c == '1' ? BACKGROUND_SECOND_ONE : BACKGROUND_TEXT;
×
2168
                        break;
×
2169

2170
                case BACKGROUND_SECOND_ONE:
×
2171
                        context->state = c == ';' ? BACKGROUND_SEMICOLON : BACKGROUND_TEXT;
×
2172
                        break;
×
2173

2174
                case BACKGROUND_SEMICOLON:
×
2175
                        context->state = c == 'r' ? BACKGROUND_R : BACKGROUND_TEXT;
×
2176
                        break;
×
2177

2178
                case BACKGROUND_R:
×
2179
                        context->state = c == 'g' ? BACKGROUND_G : BACKGROUND_TEXT;
×
2180
                        break;
×
2181

2182
                case BACKGROUND_G:
×
2183
                        context->state = c == 'b' ? BACKGROUND_B : BACKGROUND_TEXT;
×
2184
                        break;
×
2185

2186
                case BACKGROUND_B:
×
2187
                        context->state = c == ':' ? BACKGROUND_RED : BACKGROUND_TEXT;
×
2188
                        break;
×
2189

2190
                case BACKGROUND_RED:
×
2191
                        if (c == '/')
×
2192
                                context->state = context->red_bits > 0 ? BACKGROUND_GREEN : BACKGROUND_TEXT;
×
2193
                        else {
2194
                                int d = unhexchar(c);
×
2195
                                if (d < 0 || context->red_bits >= sizeof(context->red)*8)
×
2196
                                        context->state = BACKGROUND_TEXT;
×
2197
                                else {
2198
                                        context->red = (context->red << 4) | d;
×
2199
                                        context->red_bits += 4;
×
2200
                                }
2201
                        }
2202
                        break;
2203

2204
                case BACKGROUND_GREEN:
×
2205
                        if (c == '/')
×
2206
                                context->state = context->green_bits > 0 ? BACKGROUND_BLUE : BACKGROUND_TEXT;
×
2207
                        else {
2208
                                int d = unhexchar(c);
×
2209
                                if (d < 0 || context->green_bits >= sizeof(context->green)*8)
×
2210
                                        context->state = BACKGROUND_TEXT;
×
2211
                                else {
2212
                                        context->green = (context->green << 4) | d;
×
2213
                                        context->green_bits += 4;
×
2214
                                }
2215
                        }
2216
                        break;
2217

2218
                case BACKGROUND_BLUE:
×
2219
                        if (c == '\x07') {
×
2220
                                if (context->blue_bits > 0) {
×
2221
                                        *ret_processed = i + 1;
×
2222
                                        return 1; /* success! */
×
2223
                                }
2224

2225
                                context->state = BACKGROUND_TEXT;
×
2226
                        } else if (c == '\x1b')
×
2227
                                context->state = context->blue_bits > 0 ? BACKGROUND_STRING_TERMINATOR : BACKGROUND_TEXT;
×
2228
                        else {
2229
                                int d = unhexchar(c);
×
2230
                                if (d < 0 || context->blue_bits >= sizeof(context->blue)*8)
×
2231
                                        context->state = BACKGROUND_TEXT;
×
2232
                                else {
2233
                                        context->blue = (context->blue << 4) | d;
×
2234
                                        context->blue_bits += 4;
×
2235
                                }
2236
                        }
2237
                        break;
2238

2239
                case BACKGROUND_STRING_TERMINATOR:
×
2240
                        if (c == '\\') {
×
2241
                                *ret_processed = i + 1;
×
2242
                                return 1; /* success! */
×
2243
                        }
2244

2245
                        context->state = c == ']' ? BACKGROUND_ESCAPE : BACKGROUND_TEXT;
×
2246
                        break;
×
2247

2248
                }
2249

2250
                /* Reset any colors we might have picked up */
2251
                if (IN_SET(context->state, BACKGROUND_TEXT, BACKGROUND_ESCAPE)) {
×
2252
                        /* reset color */
2253
                        context->red = context->green = context->blue = 0;
×
2254
                        context->red_bits = context->green_bits = context->blue_bits = 0;
×
2255
                }
2256
        }
2257

2258
        *ret_processed = size;
×
2259
        return 0; /* all good, but not enough data yet */
×
2260
}
2261

2262
int get_default_background_color(double *ret_red, double *ret_green, double *ret_blue) {
155✔
2263
        int r;
155✔
2264

2265
        assert(ret_red);
155✔
2266
        assert(ret_green);
155✔
2267
        assert(ret_blue);
155✔
2268

2269
        if (!colors_enabled())
155✔
2270
                return -EOPNOTSUPP;
155✔
2271

2272
        r = terminal_verify_same(STDIN_FILENO, STDOUT_FILENO);
×
2273
        if (r < 0)
×
2274
                return r;
2275

2276
        if (streq_ptr(getenv("TERM"), "linux")) {
×
2277
                /* Linux console is black */
2278
                *ret_red = *ret_green = *ret_blue = 0.0;
×
2279
                return 0;
×
2280
        }
2281

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

2288
        struct termios old_termios;
×
2289
        if (tcgetattr(nonblock_input_fd, &old_termios) < 0)
×
2290
                return -errno;
×
2291

2292
        struct termios new_termios = old_termios;
×
2293
        termios_disable_echo(&new_termios);
×
2294

2295
        if (tcsetattr(nonblock_input_fd, TCSANOW, &new_termios) < 0)
×
2296
                return -errno;
×
2297

2298
        r = loop_write(STDOUT_FILENO, ANSI_OSC "11;?" ANSI_ST, SIZE_MAX);
×
2299
        if (r < 0)
×
2300
                goto finish;
×
2301

2302
        usec_t end = usec_add(now(CLOCK_MONOTONIC), CONSOLE_REPLY_WAIT_USEC);
×
2303
        char buf[STRLEN(ANSI_OSC "11;rgb:0/0/0" ANSI_ST)]; /* shortest possible reply */
×
2304
        size_t buf_full = 0;
×
2305
        BackgroundColorContext context = {};
×
2306

2307
        for (bool first = true;; first = false) {
×
2308
                if (buf_full == 0) {
×
2309
                        usec_t n = now(CLOCK_MONOTONIC);
×
2310
                        if (n >= end) {
×
2311
                                r = -EOPNOTSUPP;
×
2312
                                goto finish;
×
2313
                        }
2314

2315
                        r = fd_wait_for_event(nonblock_input_fd, POLLIN, usec_sub_unsigned(end, n));
×
2316
                        if (r < 0)
×
2317
                                goto finish;
×
2318
                        if (r == 0) {
×
2319
                                r = -EOPNOTSUPP;
×
2320
                                goto finish;
×
2321
                        }
2322

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

2334
                        assert((size_t) l <= sizeof(buf));
×
2335
                        buf_full = l;
2336
                }
2337

2338
                size_t processed;
×
2339
                r = scan_background_color_response(&context, buf, buf_full, &processed);
×
2340
                if (r < 0)
×
2341
                        goto finish;
×
2342

2343
                assert(processed <= buf_full);
×
2344
                buf_full -= processed;
×
2345
                memmove(buf, buf + processed, buf_full);
×
2346

2347
                if (r > 0) {
×
2348
                        assert(context.red_bits > 0);
×
2349
                        *ret_red = (double) context.red / ((UINT64_C(1) << context.red_bits) - 1);
×
2350
                        assert(context.green_bits > 0);
×
2351
                        *ret_green = (double) context.green / ((UINT64_C(1) << context.green_bits) - 1);
×
2352
                        assert(context.blue_bits > 0);
×
2353
                        *ret_blue = (double) context.blue / ((UINT64_C(1) << context.blue_bits) - 1);
×
2354
                        r = 0;
×
2355
                        goto finish;
×
2356
                }
2357
        }
2358

2359
finish:
×
2360
        (void) tcsetattr(nonblock_input_fd, TCSANOW, &old_termios);
×
2361
        return r;
×
2362
}
2363

2364
int terminal_get_size_by_dsr(
100✔
2365
                int input_fd,
2366
                int output_fd,
2367
                unsigned *ret_rows,
2368
                unsigned *ret_columns) {
2369

2370
        int r;
100✔
2371

2372
        assert(input_fd >= 0);
100✔
2373
        assert(output_fd >= 0);
100✔
2374

2375
        /* Tries to determine the terminal dimension by means of ANSI sequences.
2376
         *
2377
         * We position the cursor briefly at an absolute location very far down and very far to the right,
2378
         * and then read back where we actually ended up. Because cursor locations are capped at the terminal
2379
         * width/height we should then see the right values. In order to not risk integer overflows in
2380
         * terminal applications we'll use INT16_MAX-1 as location to jump to — hopefully a value that is
2381
         * large enough for any real-life terminals, but small enough to not overflow anything or be
2382
         * recognized as a "niche" value. (Note that the dimension fields in "struct winsize" are 16bit only,
2383
         * too). */
2384

2385
        if (terminal_is_dumb())
100✔
2386
                return -EOPNOTSUPP;
100✔
2387

2388
        r = terminal_verify_same(input_fd, output_fd);
×
2389
        if (r < 0)
×
2390
                return log_debug_errno(r, "Called with distinct input/output fds: %m");
×
2391

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

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

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

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

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

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

2418
        usec_t end = usec_add(now(CLOCK_MONOTONIC), CONSOLE_REPLY_WAIT_USEC);
×
2419
        char buf[STRLEN("\x1B[1;1R")]; /* The shortest valid reply possible */
×
2420
        size_t buf_full = 0;
×
2421
        CursorPositionContext context = {};
×
2422

2423
        for (bool first = true;; first = false) {
×
2424
                if (buf_full == 0) {
×
2425
                        usec_t n = now(CLOCK_MONOTONIC);
×
2426
                        if (n >= end) {
×
2427
                                r = -EOPNOTSUPP;
×
2428
                                goto finish;
×
2429
                        }
2430

2431
                        r = fd_wait_for_event(nonblock_input_fd, POLLIN, usec_sub_unsigned(end, n));
×
2432
                        if (r < 0)
×
2433
                                goto finish;
×
2434
                        if (r == 0) {
×
2435
                                r = -EOPNOTSUPP;
×
2436
                                goto finish;
×
2437
                        }
2438

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

2447
                                r = -errno;
×
2448
                                goto finish;
×
2449
                        }
2450

2451
                        assert((size_t) l <= sizeof(buf));
×
2452
                        buf_full = l;
2453
                }
2454

2455
                size_t processed;
×
2456
                r = scan_cursor_position_response(&context, buf, buf_full, &processed);
×
2457
                if (r < 0)
×
2458
                        goto finish;
×
2459

2460
                assert(processed <= buf_full);
×
2461
                buf_full -= processed;
×
2462
                memmove(buf, buf + processed, buf_full);
×
2463

2464
                if (r > 0) {
×
2465
                        if (saved_row == 0) {
×
2466
                                assert(saved_column == 0);
×
2467

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

2472
                                /* Superficial validity checks */
2473
                                if (context.row <= 0 || context.column <= 0 || context.row >= 32766 || context.column >= 32766) {
×
2474
                                        r = -ENODATA;
×
2475
                                        goto finish;
×
2476
                                }
2477

2478
                                saved_row = context.row;
×
2479
                                saved_column = context.column;
×
2480

2481
                                /* Reset state */
2482
                                context = (CursorPositionContext) {};
×
2483
                        } else {
2484
                                /* Second sequence, this is the cursor position after we set it somewhere
2485
                                 * into the void at the bottom right. */
2486

2487
                                /* Superficial validity checks (no particular reason to check for < 4, it's
2488
                                 * just a way to look for unreasonably small values) */
2489
                                if (context.row < 4 || context.column < 4 || context.row >= 32766 || context.column >= 32766) {
×
2490
                                        r = -ENODATA;
×
2491
                                        goto finish;
×
2492
                                }
2493

2494
                                if (ret_rows)
×
2495
                                        *ret_rows = context.row;
×
2496
                                if (ret_columns)
×
2497
                                        *ret_columns = context.column;
×
2498

2499
                                r = 0;
×
2500
                                goto finish;
×
2501
                        }
2502
                }
2503
        }
2504

2505
finish:
×
2506
        /* Restore cursor position */
2507
        if (saved_row > 0 && saved_column > 0)
×
2508
                (void) terminal_set_cursor_position(output_fd, saved_row, saved_column);
×
2509
        (void) tcsetattr(nonblock_input_fd, TCSANOW, &old_termios);
×
2510

2511
        return r;
×
2512
}
2513

2514
/*
2515
 * See https://terminalguide.namepad.de/seq/csi_st-18/,
2516
 * https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Functions-using-CSI-_-ordered-by-the-final-character_s_.
2517
 */
2518
#define CSI18_Q  "\x1B[18t"               /* Report the size of the text area in characters */
2519
#define CSI18_Rp "\x1B[8;"                /* Reply prefix */
2520
#define CSI18_R0 CSI18_Rp "1;1t"          /* Shortest reply */
2521
#define CSI18_R1 CSI18_Rp "32766;32766t"  /* Longest reply */
2522

2523
static int scan_text_area_size_response(
×
2524
                const char *buf,
2525
                size_t size,
2526
                unsigned *ret_rows,
2527
                unsigned *ret_columns) {
2528

2529
        assert(buf);
×
2530
        assert(ret_rows);
×
2531
        assert(ret_columns);
×
2532

2533
        /* Check if we have enough space for the shortest possible answer. */
2534
        if (size < STRLEN(CSI18_R0))
×
2535
                return -EAGAIN;
×
2536

2537
        /* Check if the terminating sequence is present */
2538
        if (buf[size - 1] != 't')
×
2539
                return -EAGAIN;
2540

2541
        unsigned short rows, columns;
×
2542
        if (sscanf(buf, CSI18_Rp "%hu;%hut", &rows, &columns) != 2)
×
2543
                return -EINVAL;
2544

2545
        *ret_rows = rows;
×
2546
        *ret_columns = columns;
×
2547
        return 0;
×
2548
}
2549

2550
int terminal_get_size_by_csi18(
1✔
2551
                int input_fd,
2552
                int output_fd,
2553
                unsigned *ret_rows,
2554
                unsigned *ret_columns) {
2555
        int r;
1✔
2556

2557
        assert(input_fd >= 0);
1✔
2558
        assert(output_fd >= 0);
1✔
2559

2560
        /* Tries to determine the terminal dimension by means of an ANSI sequence CSI 18. */
2561

2562
        if (terminal_is_dumb())
1✔
2563
                return -EOPNOTSUPP;
1✔
2564

2565
        r = terminal_verify_same(input_fd, output_fd);
×
2566
        if (r < 0)
×
2567
                return log_debug_errno(r, "Called with distinct input/output fds: %m");
×
2568

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

2575
        struct termios old_termios;
×
2576
        if (tcgetattr(nonblock_input_fd, &old_termios) < 0)
×
2577
                return log_debug_errno(errno, "Failed to get terminal settings: %m");
×
2578

2579
        struct termios new_termios = old_termios;
×
2580
        termios_disable_echo(&new_termios);
×
2581

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

2585
        r = loop_write(output_fd, CSI18_Q, SIZE_MAX);
×
2586
        if (r < 0)
×
2587
                goto finish;
×
2588

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

2593
        for (;;) {
×
2594
                usec_t n = now(CLOCK_MONOTONIC);
×
2595
                if (n >= end) {
×
2596
                        r = -EOPNOTSUPP;
2597
                        break;
2598
                }
2599

2600
                r = fd_wait_for_event(nonblock_input_fd, POLLIN, usec_sub_unsigned(end, n));
×
2601
                if (r < 0)
×
2602
                        break;
2603
                if (r == 0) {
×
2604
                        r = -EOPNOTSUPP;
2605
                        break;
2606
                }
2607

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

2619
                assert((size_t) l <= sizeof(buf) - bytes);
×
2620
                bytes += l;
×
2621

2622
                r = scan_text_area_size_response(buf, bytes, ret_rows, ret_columns);
×
2623
                if (r != -EAGAIN)
×
2624
                        break;
2625

2626
                if (bytes == sizeof(buf)) {
×
2627
                        r = -EOPNOTSUPP; /* The response has the right prefix, but we didn't find a valid
2628
                                          * answer with a terminator in the allotted space. Something is
2629
                                          * wrong, possibly some unrelated bytes got injected into the
2630
                                          * answer. */
2631
                        break;
2632
                }
2633
        }
2634

2635
finish:
×
2636
        (void) tcsetattr(nonblock_input_fd, TCSANOW, &old_termios);
×
2637
        return r;
×
2638
}
2639

2640
int terminal_fix_size(int input_fd, int output_fd) {
1✔
2641
        unsigned rows, columns;
1✔
2642
        int r;
1✔
2643

2644
        /* Tries to update the current terminal dimensions to the ones reported via ANSI sequences.
2645
         *
2646
         * Why bother with this? The ioctl() information is often incorrect on serial terminals (since
2647
         * there's no handshake or protocol to determine the right dimensions in RS232), but since the ANSI
2648
         * sequences are interpreted by the final terminal instead of an intermediary tty driver they should
2649
         * be more accurate.
2650
         */
2651
        r = terminal_verify_same(input_fd, output_fd);
1✔
2652
        if (r < 0)
1✔
2653
                return r;
1✔
2654

2655
        struct winsize ws = {};
×
2656
        if (ioctl(output_fd, TIOCGWINSZ, &ws) < 0)
×
2657
                return log_debug_errno(errno, "Failed to query terminal dimensions, ignoring: %m");
×
2658

2659
        r = terminal_get_size_by_csi18(input_fd, output_fd, &rows, &columns);
×
2660
        if (IN_SET(r, -EOPNOTSUPP, -EINVAL))
×
2661
                /* We get -EOPNOTSUPP if the query fails and -EINVAL when the received answer is invalid.
2662
                 * Try the fallback method. It is more involved and moves the cursor, but seems to have wider
2663
                 * support. */
2664
                r = terminal_get_size_by_dsr(input_fd, output_fd, &rows, &columns);
×
2665
        if (r < 0)
×
2666
                return log_debug_errno(r, "Failed to acquire terminal dimensions via ANSI sequences, not adjusting terminal dimensions: %m");
×
2667

2668
        if (ws.ws_row == rows && ws.ws_col == columns) {
×
2669
                log_debug("Terminal dimensions reported via ANSI sequences match currently set terminal dimensions, not changing.");
×
2670
                return 0;
×
2671
        }
2672

2673
        ws.ws_col = columns;
×
2674
        ws.ws_row = rows;
×
2675

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

2679
        log_debug("Fixed terminal dimensions to %ux%u based on ANSI sequence information.", columns, rows);
×
2680
        return 1;
2681
}
2682

2683
#define MAX_TERMINFO_LENGTH 64
2684
/* python -c 'print("".join(hex(ord(i))[2:] for i in "name").upper())' */
2685
#define DCS_TERMINFO_Q ANSI_DCS "+q" "6E616D65" ANSI_ST
2686
/* The answer is either 0+r… (invalid) or 1+r… (OK). */
2687
#define DCS_TERMINFO_R0 ANSI_DCS "0+r" ANSI_ST
2688
#define DCS_TERMINFO_R1 ANSI_DCS "1+r" "6E616D65" "=" /* This is followed by Pt ST. */
2689
assert_cc(STRLEN(DCS_TERMINFO_R0) <= STRLEN(DCS_TERMINFO_R1 ANSI_ST));
2690

2691
static int scan_terminfo_response(
×
2692
                const char *buf,
2693
                size_t size,
2694
                char **ret_name) {
2695
        int r;
×
2696

2697
        assert(buf);
×
2698
        assert(ret_name);
×
2699

2700
        /* Check if we have enough space for the shortest possible answer. */
2701
        if (size < STRLEN(DCS_TERMINFO_R0))
×
2702
                return -EAGAIN;
×
2703

2704
        /* Check if the terminating sequence is present */
2705
        if (memcmp(buf + size - STRLEN(ANSI_ST), ANSI_ST, STRLEN(ANSI_ST)) != 0)
×
2706
                return -EAGAIN;
2707

2708
        if (size <= STRLEN(DCS_TERMINFO_R1 ANSI_ST))
×
2709
                return -EINVAL;  /* The answer is invalid or empty */
2710

2711
        if (memcmp(buf, DCS_TERMINFO_R1, STRLEN(DCS_TERMINFO_R1)) != 0)
×
2712
                return -EINVAL;  /* The answer is not valid */
2713

2714
        _cleanup_free_ void *dec = NULL;
×
2715
        size_t dec_size;
×
2716
        r = unhexmem_full(buf + STRLEN(DCS_TERMINFO_R1), size - STRLEN(DCS_TERMINFO_R1 ANSI_ST),
×
2717
                          /* secure= */ false,
2718
                          &dec, &dec_size);
2719
        if (r < 0)
×
2720
                return r;
2721

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

2726
        *ret_name = TAKE_PTR(dec);
×
2727
        return 0;
×
2728
}
2729

2730
int terminal_get_terminfo_by_dcs(int fd, char **ret_name) {
2✔
2731
        int r;
2✔
2732

2733
        assert(fd >= 0);
2✔
2734
        assert(ret_name);
2✔
2735

2736
        /* Note: fd must be in non-blocking read-write mode! */
2737

2738
        struct termios old_termios;
2✔
2739
        if (tcgetattr(fd, &old_termios) < 0)
2✔
2740
                return -errno;
1✔
2741

2742
        struct termios new_termios = old_termios;
1✔
2743
        termios_disable_echo(&new_termios);
1✔
2744

2745
        if (tcsetattr(fd, TCSANOW, &new_termios) < 0)
1✔
2746
                return -errno;
×
2747

2748
        r = loop_write(fd, DCS_TERMINFO_Q, SIZE_MAX);
1✔
2749
        if (r < 0)
1✔
2750
                goto finish;
×
2751

2752
        usec_t end = usec_add(now(CLOCK_MONOTONIC), CONSOLE_REPLY_WAIT_USEC);
1✔
2753
        char buf[STRLEN(DCS_TERMINFO_R1) + MAX_TERMINFO_LENGTH + STRLEN(ANSI_ST)];
1✔
2754
        size_t bytes = 0;
1✔
2755

2756
        for (;;) {
1✔
2757
                usec_t n = now(CLOCK_MONOTONIC);
1✔
2758
                if (n >= end) {
1✔
2759
                        r = -EOPNOTSUPP;
2760
                        break;
2761
                }
2762

2763
                r = fd_wait_for_event(fd, POLLIN, usec_sub_unsigned(end, n));
2✔
2764
                if (r < 0)
1✔
2765
                        break;
2766
                if (r == 0) {
1✔
2767
                        r = -EOPNOTSUPP;
2768
                        break;
2769
                }
2770

2771
                /* On the first read, read multiple characters, i.e. the shortest valid reply. Afterwards
2772
                 * read byte by byte, since we don't want to read too much and drop characters from the input
2773
                 * queue. */
2774
                ssize_t l = read(fd, buf + bytes, bytes == 0 ? STRLEN(DCS_TERMINFO_R0) : 1);
×
2775
                if (l < 0) {
×
2776
                        if (errno == EAGAIN)
×
2777
                                continue;
×
2778
                        r = -errno;
×
2779
                        break;
×
2780
                }
2781

2782
                assert((size_t) l <= sizeof(buf) - bytes);
×
2783
                bytes += l;
×
2784

2785
                r = scan_terminfo_response(buf, bytes, ret_name);
×
2786
                if (r != -EAGAIN)
×
2787
                        break;
2788

2789
                if (bytes == sizeof(buf)) {
×
2790
                        r = -EOPNOTSUPP; /* The response has the right prefix, but we didn't find a valid
2791
                                          * answer with a terminator in the allotted space. Something is
2792
                                          * wrong, possibly some unrelated bytes got injected into the
2793
                                          * answer. */
2794
                        break;
2795
                }
2796
        }
2797

2798
finish:
×
2799
        (void) tcsetattr(fd, TCSANOW, &old_termios);
1✔
2800
        return r;
1✔
2801
}
2802

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

2808
        assert(filename_is_valid(name));
5✔
2809

2810
        _cleanup_free_ char *p = path_join("/usr/share/terminfo", CHAR_TO_STR(name[0]), name);
10✔
2811
        if (!p)
5✔
2812
                return log_oom_debug();
×
2813

2814
        r = RET_NERRNO(access(p, F_OK));
5✔
2815
        if (r == -ENOENT)
1✔
2816
                return false;
2817
        if (r < 0)
4✔
2818
                return r;
×
2819
        return true;
2820
}
2821

2822
int query_term_for_tty(const char *tty, char **ret_term) {
49✔
2823
        _cleanup_free_ char *dcs_term = NULL;
49✔
2824
        int r;
49✔
2825

2826
        assert(tty);
49✔
2827
        assert(ret_term);
49✔
2828

2829
        if (tty_is_vc_resolve(tty))
49✔
2830
                return strdup_to(ret_term, "linux");
46✔
2831

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

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

2839
        r = terminal_get_terminfo_by_dcs(tty_fd, &dcs_term);
1✔
2840
        if (r < 0)
1✔
2841
                return log_debug_errno(r, "Failed to query %s for terminfo: %m", tty);
1✔
2842

2843
        r = have_terminfo_file(dcs_term);
×
2844
        if (r < 0)
×
2845
                return log_debug_errno(r, "Failed to look for terminfo %s: %m", dcs_term);
×
2846
        if (r == 0)
×
2847
                return log_info_errno(SYNTHETIC_ERRNO(ENODATA),
×
2848
                                      "Terminfo %s not found for %s.", dcs_term, tty);
2849

2850
        *ret_term = TAKE_PTR(dcs_term);
×
2851
        return 0;
×
2852
}
2853

2854
int terminal_is_pty_fd(int fd) {
3✔
2855
        int r;
3✔
2856

2857
        assert(fd >= 0);
3✔
2858

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

2861
        if (!isatty_safe(fd))
3✔
2862
                return false;
3✔
2863

2864
        r = is_fs_type_at(fd, NULL, DEVPTS_SUPER_MAGIC);
2✔
2865
        if (r != 0)
2✔
2866
                return r;
2867

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

2871
        int v;
×
2872
        if (ioctl(fd, TIOCGPKT, &v) < 0) {
×
2873
                if (ERRNO_IS_NOT_SUPPORTED(errno))
×
2874
                        return false;
2875

2876
                return -errno;
×
2877
        }
2878

2879
        return true;
2880
}
2881

2882
int pty_open_peer(int fd, int mode) {
25✔
2883
        assert(fd >= 0);
25✔
2884

2885
        /* Opens the peer PTY using the new race-free TIOCGPTPEER ioctl() (kernel 4.13).
2886
         *
2887
         * This is safe to be called on TTYs from other namespaces. */
2888

2889
        assert((mode & (O_CREAT|O_PATH|O_DIRECTORY|O_TMPFILE)) == 0);
25✔
2890

2891
        /* This replicates the EIO retry logic of open_terminal() in a modified way. */
2892
        for (unsigned c = 0;; c++) {
×
2893
                int peer_fd = ioctl(fd, TIOCGPTPEER, mode);
25✔
2894
                if (peer_fd >= 0)
25✔
2895
                        return peer_fd;
2896

2897
                if (errno != EIO)
×
2898
                        return -errno;
×
2899

2900
                /* Max 1s in total */
2901
                if (c >= 20)
×
2902
                        return -EIO;
2903

2904
                (void) usleep_safe(50 * USEC_PER_MSEC);
×
2905
        }
2906
}
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