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

systemd / systemd / 16791678039

06 Aug 2025 11:10PM UTC coverage: 72.181% (-0.04%) from 72.223%
16791678039

push

github

yuwata
logging: Improve logging messages related to NFTSet.

The 'NFTSet' directive in various units adds and removes entries in nftables
sets, it does not add or remove entire sets. The logging messages should
indicate that an entry was added or removed, not that a set was added or
removed.

2 of 6 new or added lines in 3 files covered. (33.33%)

496 existing lines in 52 files now uncovered.

302228 of 418708 relevant lines covered (72.18%)

647735.83 hits per line

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

43.72
/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
#define ANSI_RESET_CURSOR                          \
47
        "\033?25h"     /* turn on cursor */        \
48
        "\033?12l"     /* reset cursor blinking */ \
49
        "\033 1q"      /* reset cursor style */
50

51
/* How much to wait for a reply to a terminal sequence */
52
#define CONSOLE_REPLY_WAIT_USEC  (333 * USEC_PER_MSEC)
53

54
static volatile unsigned cached_columns = 0;
55
static volatile unsigned cached_lines = 0;
56

57
static volatile int cached_on_tty = -1;
58
static volatile int cached_on_dev_null = -1;
59

60
bool isatty_safe(int fd) {
5,361,849✔
61
        assert(fd >= 0);
5,361,849✔
62

63
        if (isatty(fd))
5,361,849✔
64
                return true;
65

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

72
        /* Be resilient if we're working on stdio, since they're set up by parent process. */
73
        assert(errno != EBADF || IN_SET(fd, STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO));
5,311,857✔
74

75
        return false;
76
}
77

78
int chvt(int vt) {
×
79
        _cleanup_close_ int fd = -EBADF;
×
80

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

84
        fd = open_terminal("/dev/tty0", O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK);
×
85
        if (fd < 0)
×
86
                return fd;
87

88
        if (vt <= 0) {
×
89
                int tiocl[2] = {
×
90
                        TIOCL_GETKMSGREDIRECT,
91
                        0
92
                };
93

94
                if (ioctl(fd, TIOCLINUX, tiocl) < 0)
×
95
                        return -errno;
×
96

97
                vt = tiocl[0] <= 0 ? 1 : tiocl[0];
×
98
        }
99

100
        return RET_NERRNO(ioctl(fd, VT_ACTIVATE, vt));
×
101
}
102

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

108
        assert(ret);
10✔
109

110
        if (!f)
10✔
111
                f = stdin;
×
112

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

121
                new_termios.c_lflag &= ~(ICANON|(echo ? 0 : ECHO));
×
122
                new_termios.c_cc[VMIN] = 1;
×
123
                new_termios.c_cc[VTIME] = 0;
×
124

125
                if (tcsetattr(fd, TCSANOW, &new_termios) >= 0) {
×
126
                        char c;
×
127

128
                        if (t != USEC_INFINITY) {
×
129
                                if (fd_wait_for_event(fd, POLLIN, t) <= 0) {
×
130
                                        (void) tcsetattr(fd, TCSANOW, &old_termios);
×
131
                                        return -ETIMEDOUT;
×
132
                                }
133
                        }
134

135
                        r = safe_fgetc(f, &c);
×
136
                        (void) tcsetattr(fd, TCSANOW, &old_termios);
×
137
                        if (r < 0)
×
138
                                return r;
139
                        if (r == 0)
×
140
                                return -EIO;
141

142
                        if (need_nl)
×
143
                                *need_nl = c != '\n';
×
144

145
                        *ret = c;
×
146
                        return 0;
×
147
                }
148
        }
149

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

156
                if (fd_wait_for_event(fd, POLLIN, t) <= 0)
4✔
157
                        return -ETIMEDOUT;
158
        }
159

160
        /* If this is not a terminal, then read a full line instead */
161

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

168
        if (strlen(line) != 1)
8✔
169
                return -EBADMSG;
170

171
        if (need_nl)
1✔
172
                *need_nl = false;
1✔
173

174
        *ret = line[0];
1✔
175
        return 0;
1✔
176
}
177

178
#define DEFAULT_ASK_REFRESH_USEC (2*USEC_PER_SEC)
179

180
int ask_char(char *ret, const char *replies, const char *fmt, ...) {
×
181
        int r;
×
182

183
        assert(ret);
×
184
        assert(replies);
×
185
        assert(fmt);
×
186

187
        for (;;) {
×
188
                va_list ap;
×
189
                char c;
×
190
                bool need_nl = true;
×
191

192
                fputs(ansi_highlight(), stdout);
×
193

194
                putchar('\r');
×
195

196
                va_start(ap, fmt);
×
197
                vprintf(fmt, ap);
×
198
                va_end(ap);
×
199

200
                fputs(ansi_normal(), stdout);
×
201

202
                fflush(stdout);
×
203

204
                r = read_one_char(stdin, &c, DEFAULT_ASK_REFRESH_USEC, /* echo= */ true, &need_nl);
×
205
                if (r < 0) {
×
206

207
                        if (r == -ETIMEDOUT)
×
208
                                continue;
×
209

210
                        if (r == -EBADMSG) {
×
211
                                puts("Bad input, please try again.");
×
212
                                continue;
×
213
                        }
214

215
                        putchar('\n');
×
216
                        return r;
×
217
                }
218

219
                if (need_nl)
×
220
                        putchar('\n');
×
221

222
                if (strchr(replies, c)) {
×
223
                        *ret = c;
×
224
                        return 0;
×
225
                }
226

227
                puts("Read unexpected character, please try again.");
×
228
        }
229
}
230

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

241
static CompletionResult pick_completion(const char *string, char *const*completions, char **ret) {
×
242
        _cleanup_free_ char *found = NULL;
×
243
        bool partial = false;
×
244

245
        string = strempty(string);
×
246

247
        STRV_FOREACH(c, completions) {
×
248

249
                /* Ignore entries that are not actually completions */
250
                if (!startswith(*c, string))
×
251
                        continue;
×
252

253
                /* Store first completion that matches */
254
                if (!found) {
×
255
                        found = strdup(*c);
×
256
                        if (!found)
×
257
                                return -ENOMEM;
258

259
                        continue;
×
260
                }
261

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

268
                found[n] = 0;
×
269
                partial = true;
×
270
        }
271

272
        *ret = TAKE_PTR(found);
×
273

274
        if (!*ret)
×
275
                return COMPLETION_NONE;
276
        if (partial)
×
277
                return COMPLETION_PARTIAL;
278

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

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

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

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

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

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

308
        _cleanup_free_ char *string = NULL;
7✔
309
        size_t n = 0;
7✔
310

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

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

324
        struct termios new_termios = old_termios;
×
325
        termios_disable_echo(&new_termios);
×
326
        if (tcsetattr(fd_input, TCSANOW, &new_termios) < 0)
×
327
                return -errno;
×
328

329
        for (;;) {
×
330
                int c = fgetc(stdin);
×
331

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

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

342
                if (c == '\t') {
×
343
                        /* Tab */
344

345
                        _cleanup_strv_free_ char **completions = NULL;
×
346
                        if (get_completions) {
×
347
                                r = get_completions(string, &completions, userdata);
×
348
                                if (r < 0)
×
349
                                        goto fail;
×
350
                        }
351

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

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

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

375
                                _cleanup_strv_free_ char **filtered = strv_filter_prefix(completions, string);
×
376
                                if (!filtered) {
×
377
                                        r = -ENOMEM;
×
378
                                        goto fail;
×
379
                                }
380

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

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

399
                } else if (IN_SET(c, '\b', 127)) {
×
400
                        /* Backspace */
401

402
                        if (n == 0)
×
403
                                fputc('\a', stdout); /* BEL */
×
404
                        else {
405
                                size_t m = utf8_last_length(string, n);
×
406

407
                                char *e = string + n - m;
×
408
                                clear_by_backspace(utf8_console_width(e));
×
409

410
                                *e = 0;
×
411
                                n -= m;
×
412
                        }
413

414
                } else if (c == 21) {
×
415
                        /* Ctrl-u → erase all input */
416

417
                        clear_by_backspace(utf8_console_width(string));
×
418
                        if (string)
×
419
                                string[n = 0] = 0;
×
420
                        else
421
                                assert(n == 0);
×
422

423
                } else if (c == 4) {
×
424
                        /* Ctrl-d → cancel this field input */
425

426
                        r = -ECANCELED;
×
427
                        goto fail;
×
428

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

435
                        if (!GREEDY_REALLOC(string, n+2)) {
×
436
                                r = -ENOMEM;
×
437
                                goto fail;
×
438
                        }
439

440
                        string[n++] = (char) c;
×
441
                        string[n] = 0;
×
442

443
                        fputc(c, stdout);
×
444
                }
445

446
                fflush(stdout);
×
447
        }
448

449
        if (tcsetattr(fd_input, TCSANOW, &old_termios) < 0)
×
450
                return -errno;
×
451

452
        if (!string) {
×
453
                string = strdup("");
×
454
                if (!string)
×
455
                        return -ENOMEM;
456
        }
457

458
        *ret = TAKE_PTR(string);
×
459
        return 0;
×
460

461
fail:
×
462
        (void) tcsetattr(fd_input, TCSANOW, &old_termios);
×
463
        return r;
×
464

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

473
        *ret = TAKE_PTR(string);
7✔
474
        return 0;
7✔
475
}
476

477
bool any_key_to_proceed(void) {
6✔
478

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

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

490
        char key = 0;
6✔
491
        (void) read_one_char(stdin, &key, USEC_INFINITY, /* echo= */ false, /* need_nl= */ NULL);
6✔
492

493
        fputc('\n', stdout);
6✔
494
        fflush(stdout);
6✔
495

496
        return key != 'q';
6✔
497
}
498

499
static size_t widest_list_element(char *const*l) {
×
500
        size_t w = 0;
×
501

502
        /* Returns the largest console width of all elements in 'l' */
503

504
        STRV_FOREACH(i, l)
×
505
                w = MAX(w, utf8_console_width(*i));
×
506

507
        return w;
×
508
}
509

510
int show_menu(char **x,
×
511
              size_t n_columns,
512
              size_t column_width,
513
              unsigned ellipsize_percentage,
514
              const char *grey_prefix,
515
              bool with_numbers) {
516

517
        assert(n_columns > 0);
×
518

519
        if (n_columns == SIZE_MAX)
×
520
                n_columns = 3;
×
521

522
        if (column_width == SIZE_MAX) {
×
523
                size_t widest = widest_list_element(x);
×
524

525
                /* If not specified, derive column width from screen width */
526
                size_t column_max = (columns()-1) / n_columns;
×
527

528
                /* Subtract room for numbers */
529
                if (with_numbers && column_max > 6)
×
530
                        column_max -= 6;
×
531

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

537
                        if (with_numbers && column_max > 6)
×
538
                                column_max -= 6;
×
539
                }
540

541
                column_width = CLAMP(widest+1, 10U, column_max);
×
542
        }
543

544
        size_t n = strv_length(x);
×
545
        size_t per_column = DIV_ROUND_UP(n, n_columns);
×
546

547
        size_t break_lines = lines();
×
548
        if (break_lines > 2)
×
549
                break_lines--;
×
550

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

557
        for (size_t i = 0; i < per_column; i++) {
×
558

559
                for (size_t j = 0; j < n_columns; j++) {
×
560
                        _cleanup_free_ char *e = NULL;
×
561

562
                        if (j * per_column + i >= n)
×
563
                                break;
564

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

569
                        if (with_numbers)
×
570
                                printf("%s%4zu)%s ",
×
571
                                       ansi_grey(),
572
                                       j * per_column + i + 1,
573
                                       ansi_normal());
574

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

587
                putchar('\n');
×
588

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

595
        return 0;
596
}
597

598
int open_terminal(const char *name, int mode) {
49,378✔
599
        _cleanup_close_ int fd = -EBADF;
49,378✔
600

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

609
        assert((mode & (O_CREAT|O_PATH|O_DIRECTORY|O_TMPFILE)) == 0);
49,378✔
610

611
        for (unsigned c = 0;; c++) {
×
612
                fd = open(name, mode, 0);
49,378✔
613
                if (fd >= 0)
49,378✔
614
                        break;
615

616
                if (errno != EIO)
1,354✔
617
                        return -errno;
1,354✔
618

619
                /* Max 1s in total */
620
                if (c >= 20)
×
621
                        return -EIO;
622

623
                (void) usleep_safe(50 * USEC_PER_MSEC);
×
624
        }
625

626
        if (!isatty_safe(fd))
48,024✔
627
                return -ENOTTY;
1✔
628

629
        return TAKE_FD(fd);
630
}
631

632
int acquire_terminal(
395✔
633
                const char *name,
634
                AcquireTerminalFlags flags,
635
                usec_t timeout) {
636

637
        _cleanup_close_ int notify = -EBADF, fd = -EBADF;
395✔
638
        usec_t ts = USEC_INFINITY;
395✔
639
        int r, wd = -1;
395✔
640

641
        assert(name);
395✔
642

643
        AcquireTerminalFlags mode = flags & _ACQUIRE_TERMINAL_MODE_MASK;
395✔
644
        assert(IN_SET(mode, ACQUIRE_TERMINAL_TRY, ACQUIRE_TERMINAL_FORCE, ACQUIRE_TERMINAL_WAIT));
395✔
645
        assert(mode == ACQUIRE_TERMINAL_WAIT || !FLAGS_SET(flags, ACQUIRE_TERMINAL_WATCH_SIGTERM));
395✔
646

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

656
        if (mode == ACQUIRE_TERMINAL_WAIT) {
×
657
                notify = inotify_init1(IN_CLOEXEC | IN_NONBLOCK);
395✔
658
                if (notify < 0)
395✔
659
                        return -errno;
×
660

661
                wd = inotify_add_watch(notify, name, IN_CLOSE);
395✔
662
                if (wd < 0)
395✔
663
                        return -errno;
26✔
664

665
                if (timeout != USEC_INFINITY)
369✔
666
                        ts = now(CLOCK_MONOTONIC);
×
667
        }
668

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

677
        for (;;) {
369✔
678
                if (notify >= 0) {
369✔
679
                        r = flush_fd(notify);
369✔
680
                        if (r < 0)
369✔
681
                                return r;
×
682
                }
683

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

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

694
                /* First, try to get the tty */
695
                r = RET_NERRNO(ioctl(fd, TIOCSCTTY, mode == ACQUIRE_TERMINAL_FORCE));
369✔
696

697
                /* Reset signal handler to old value */
698
                assert_se(sigaction(SIGHUP, &sa_old, NULL) >= 0);
369✔
699

700
                /* Success? Exit the loop now! */
701
                if (r >= 0)
369✔
702
                        break;
703

704
                /* Any failure besides -EPERM? Fail, regardless of the mode. */
705
                if (r != -EPERM)
×
706
                        return r;
707

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

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

716
                assert(notify >= 0);
×
717
                assert(wd >= 0);
×
718

719
                for (;;) {
×
720
                        usec_t left;
×
721
                        if (timeout == USEC_INFINITY)
×
722
                                left = USEC_INFINITY;
723
                        else {
724
                                assert(ts != USEC_INFINITY);
×
725

726
                                usec_t n = usec_sub_unsigned(now(CLOCK_MONOTONIC), ts);
×
727
                                if (n >= timeout)
×
728
                                        return -ETIMEDOUT;
×
729

730
                                left = timeout - n;
×
731
                        }
732

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

746
                        union inotify_event_buffer buffer;
×
747
                        ssize_t l;
×
748
                        l = read(notify, &buffer, sizeof(buffer));
×
749
                        if (l < 0) {
×
750
                                if (ERRNO_IS_TRANSIENT(errno))
×
751
                                        continue;
×
752

753
                                return -errno;
×
754
                        }
755

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

760
                                if (e->wd != wd || !(e->mask & IN_CLOSE)) /* Safety checks */
×
761
                                        return -EIO;
×
762
                        }
763

764
                        break;
×
765
                }
766

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

772
        return TAKE_FD(fd);
369✔
773
}
774

775
int release_terminal(void) {
53✔
776
        _cleanup_close_ int fd = -EBADF;
53✔
777
        int r;
53✔
778

779
        fd = open("/dev/tty", O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK);
53✔
780
        if (fd < 0)
53✔
781
                return -errno;
33✔
782

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

788
        r = RET_NERRNO(ioctl(fd, TIOCNOTTY));
20✔
789

790
        assert_se(sigaction(SIGHUP, &sa_old, NULL) >= 0);
20✔
791

792
        return r;
793
}
794

795
int terminal_new_session(void) {
5✔
796

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

804
        if (!isatty_safe(STDIN_FILENO))
5✔
805
                return -ENXIO;
806

807
        (void) setsid();
4✔
808
        return RET_NERRNO(ioctl(STDIN_FILENO, TIOCSCTTY, 0));
4✔
809
}
810

811
void terminal_detach_session(void) {
53✔
812
        (void) setsid();
53✔
813
        (void) release_terminal();
53✔
814
}
53✔
815

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

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

824
        assert(tty);
×
825

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

830
        return terminal_vhangup_fd(fd);
×
831
}
832

833
int vt_disallocate(const char *tty_path) {
97✔
834
        assert(tty_path);
97✔
835

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

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

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

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

855
        _cleanup_close_ int fd2 = open_terminal(tty_path, O_WRONLY|O_NOCTTY|O_CLOEXEC|O_NONBLOCK);
194✔
856
        if (fd2 < 0)
97✔
857
                return fd2;
858

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

869
static int vt_default_utf8(void) {
760✔
870
        _cleanup_free_ char *b = NULL;
760✔
871
        int r;
760✔
872

873
        /* Read the default VT UTF8 setting from the kernel */
874

875
        r = read_one_line_file("/sys/module/vt/parameters/default_utf8", &b);
760✔
876
        if (r < 0)
760✔
877
                return r;
878

879
        return parse_boolean(b);
434✔
880
}
881

882
static int vt_reset_keyboard(int fd) {
380✔
883
        int r, kb;
380✔
884

885
        assert(fd >= 0);
380✔
886

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

892
        kb = vt_default_utf8() != 0 ? K_UNICODE : K_XLATE;
380✔
893
        return RET_NERRNO(ioctl(fd, KDSKBMODE, kb));
380✔
894
}
895

896
static int terminal_reset_ioctl(int fd, bool switch_to_text) {
380✔
897
        struct termios termios;
380✔
898
        int r;
380✔
899

900
        /* Set terminal to some sane defaults */
901

902
        assert(fd >= 0);
380✔
903

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

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

911
        /* Switch to text mode */
912
        if (switch_to_text)
380✔
913
                if (ioctl(fd, KDSETMODE, KD_TEXT) < 0)
180✔
914
                        log_debug_errno(errno, "KDSETMODE ioctl for switching to text mode failed on TTY, ignoring: %m");
83✔
915

916
        /* Set default keyboard mode */
917
        r = vt_reset_keyboard(fd);
380✔
918
        if (r < 0)
380✔
919
                log_debug_errno(r, "Failed to reset VT keyboard, ignoring: %m");
189✔
920

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

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

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

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

950
        termios.c_cc[VTIME]  = 0;
378✔
951
        termios.c_cc[VMIN]   = 1;
378✔
952

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

957
finish:
×
958
        /* Just in case, flush all crap out */
959
        (void) tcflush(fd, TCIOFLUSH);
380✔
960

961
        return r;
380✔
962
}
963

964
static int terminal_reset_ansi_seq(int fd) {
374✔
965
        int r, k;
374✔
966

967
        assert(fd >= 0);
374✔
968

969
        if (getenv_terminal_is_dumb())
374✔
970
                return 0;
971

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

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

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

994
        return k < 0 ? k : r;
×
995
}
996

997
void reset_dev_console_fd(int fd, bool switch_to_text) {
34✔
998
        int r;
34✔
999

1000
        assert(fd >= 0);
34✔
1001

1002
        _cleanup_close_ int lock_fd = lock_dev_console();
34✔
1003
        if (lock_fd < 0)
34✔
1004
                log_debug_errno(lock_fd, "Failed to lock /dev/console, ignoring: %m");
×
1005

1006
        r = terminal_reset_ioctl(fd, switch_to_text);
34✔
1007
        if (r < 0)
34✔
1008
                log_warning_errno(r, "Failed to reset /dev/console, ignoring: %m");
×
1009

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

1021
        r = terminal_reset_ansi_seq(fd);
34✔
1022
        if (r < 0)
34✔
1023
                log_warning_errno(r, "Failed to reset /dev/console using ANSI sequences, ignoring: %m");
34✔
1024
}
34✔
1025

1026
int lock_dev_console(void) {
970✔
1027
        _cleanup_close_ int fd = -EBADF;
970✔
1028
        int r;
970✔
1029

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

1036
        r = lock_generic(fd, LOCK_BSD, LOCK_EX);
970✔
1037
        if (r < 0)
970✔
1038
                return r;
×
1039

1040
        return TAKE_FD(fd);
1041
}
1042

1043
int make_console_stdio(void) {
×
1044
        int fd, r;
×
1045

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

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

1054
                r = make_null_stdio();
×
1055
                if (r < 0)
×
1056
                        return log_error_errno(r, "Failed to make /dev/null stdin/stdout/stderr: %m");
×
1057

1058
        } else {
1059
                reset_dev_console_fd(fd, /* switch_to_text= */ true);
×
1060

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

1066
        reset_terminal_feature_caches();
×
1067
        return 0;
×
1068
}
1069

1070
static int vtnr_from_tty_raw(const char *tty, unsigned *ret) {
346✔
1071
        assert(tty);
346✔
1072

1073
        tty = skip_dev_prefix(tty);
346✔
1074

1075
        const char *e = startswith(tty, "tty");
346✔
1076
        if (!e)
346✔
1077
                return -EINVAL;
1078

1079
        return safe_atou(e, ret);
307✔
1080
}
1081

1082
int vtnr_from_tty(const char *tty) {
223✔
1083
        unsigned u;
223✔
1084
        int r;
223✔
1085

1086
        assert(tty);
223✔
1087

1088
        r = vtnr_from_tty_raw(tty, &u);
223✔
1089
        if (r < 0)
223✔
1090
                return r;
223✔
1091
        if (!vtnr_is_valid(u))
223✔
1092
                return -ERANGE;
1093

1094
        return (int) u;
223✔
1095
}
1096

1097
bool tty_is_vc(const char *tty) {
123✔
1098
        assert(tty);
123✔
1099

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

1105
        return vtnr_from_tty_raw(tty, /* ret = */ NULL) >= 0;
123✔
1106
}
1107

1108
bool tty_is_console(const char *tty) {
880✔
1109
        assert(tty);
880✔
1110

1111
        return streq(skip_dev_prefix(tty), "console");
880✔
1112
}
1113

1114
int resolve_dev_console(char **ret) {
315✔
1115
        int r;
315✔
1116

1117
        assert(ret);
315✔
1118

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

1124
        _cleanup_free_ char *chased = NULL;
315✔
1125
        r = chase("/dev/console", /* root= */ NULL, /* flags= */ 0,  &chased, /* ret_fd= */ NULL);
315✔
1126
        if (r < 0)
315✔
1127
                return r;
1128
        if (!path_equal(chased, "/dev/console")) {
315✔
1129
                *ret = TAKE_PTR(chased);
158✔
1130
                return 0;
158✔
1131
        }
1132

1133
        r = path_is_read_only_fs("/sys");
157✔
1134
        if (r < 0)
157✔
1135
                return r;
1136
        if (r > 0)
157✔
1137
                return -ENOMEDIUM;
1138

1139
        _cleanup_free_ char *active = NULL;
157✔
1140
        r = read_one_line_file("/sys/class/tty/console/active", &active);
157✔
1141
        if (r < 0)
157✔
1142
                return r;
1143

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

1151
        if (streq(tty, "tty0")) {
157✔
1152
                active = mfree(active);
×
1153

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

1159
                tty = active;
×
1160
        }
1161

1162
        _cleanup_free_ char *path = NULL;
157✔
1163
        path = path_join("/dev", tty);
157✔
1164
        if (!path)
157✔
1165
                return -ENOMEM;
1166

1167
        *ret = TAKE_PTR(path);
157✔
1168
        return 0;
157✔
1169
}
1170

UNCOV
1171
int get_kernel_consoles(char ***ret) {
×
1172
        _cleanup_strv_free_ char **l = NULL;
×
UNCOV
1173
        _cleanup_free_ char *line = NULL;
×
UNCOV
1174
        int r;
×
1175

UNCOV
1176
        assert(ret);
×
1177

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

1183
        r = read_one_line_file("/sys/class/tty/console/active", &line);
×
1184
        if (r < 0)
×
1185
                return r;
1186

1187
        for (const char *p = line;;) {
×
1188
                _cleanup_free_ char *tty = NULL, *path = NULL;
×
1189

1190
                r = extract_first_word(&p, &tty, NULL, 0);
×
1191
                if (r < 0)
×
1192
                        return r;
1193
                if (r == 0)
×
1194
                        break;
1195

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

1203
                path = path_join("/dev", tty);
×
1204
                if (!path)
×
1205
                        return -ENOMEM;
1206

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

1212
                r = strv_consume(&l, TAKE_PTR(path));
×
1213
                if (r < 0)
×
1214
                        return r;
1215
        }
1216

1217
        if (strv_isempty(l)) {
×
1218
                log_debug("No devices found for system console");
×
1219
                goto fallback;
×
1220
        }
1221

1222
        *ret = TAKE_PTR(l);
×
1223
        return strv_length(*ret);
×
1224

UNCOV
1225
fallback:
×
UNCOV
1226
        r = strv_extend(&l, "/dev/console");
×
UNCOV
1227
        if (r < 0)
×
1228
                return r;
1229

UNCOV
1230
        *ret = TAKE_PTR(l);
×
UNCOV
1231
        return 0;
×
1232
}
1233

1234
bool tty_is_vc_resolve(const char *tty) {
49✔
1235
        _cleanup_free_ char *resolved = NULL;
49✔
1236

1237
        assert(tty);
49✔
1238

1239
        if (streq(skip_dev_prefix(tty), "console")) {
49✔
1240
                if (resolve_dev_console(&resolved) < 0)
1✔
1241
                        return false;
1242

1243
                tty = resolved;
1✔
1244
        }
1245

1246
        return tty_is_vc(tty);
49✔
1247
}
1248

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

1252
        if (fd < 0)
1,290✔
1253
                return -EBADF;
1254

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

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

1261
        return ws.ws_col;
×
1262
}
1263

1264
int getenv_columns(void) {
1,515✔
1265
        int r;
1,515✔
1266

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

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

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

1279
unsigned columns(void) {
215,448✔
1280

1281
        if (cached_columns > 0)
215,448✔
1282
                return cached_columns;
214,157✔
1283

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

1291
        assert(c > 0);
1✔
1292

1293
        cached_columns = c;
1,291✔
1294
        return cached_columns;
1,291✔
1295
}
1296

1297
int fd_lines(int fd) {
177✔
1298
        struct winsize ws = {};
177✔
1299

1300
        if (fd < 0)
177✔
1301
                return -EBADF;
1302

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

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

1309
        return ws.ws_row;
×
1310
}
1311

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

1316
        if (cached_lines > 0)
177✔
1317
                return cached_lines;
×
1318

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

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

1330
        cached_lines = l;
177✔
1331
        return cached_lines;
177✔
1332
}
1333

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

1337
        assert(fd >= 0);
1,152✔
1338

1339
        if (!ident)
1,152✔
1340
                ident = "TTY";
66✔
1341

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

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

1350
        if (rows == UINT_MAX)
927✔
1351
                rows = ws.ws_row;
×
1352
        else if (rows > USHRT_MAX)
927✔
1353
                rows = USHRT_MAX;
×
1354

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

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

1363
        ws.ws_row = rows;
379✔
1364
        ws.ws_col = cols;
379✔
1365

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

1369
        return 0;
1370
}
1371

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

1377
        assert(tty);
1,120✔
1378

1379
        if (!ret_rows && !ret_cols)
1,120✔
1380
                return 0;
1381

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

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

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

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

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

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

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

1420
        return rows != UINT_MAX || cols != UINT_MAX;
1,118✔
1421
}
1422

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

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

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

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

1439
bool on_tty(void) {
7,406,624✔
1440

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

1447
        if (cached_on_tty < 0)
7,406,624✔
1448
                cached_on_tty =
31,114✔
1449
                        isatty_safe(STDOUT_FILENO) &&
31,131✔
1450
                        isatty_safe(STDERR_FILENO);
17✔
1451

1452
        return cached_on_tty;
7,406,624✔
1453
}
1454

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

1459
        assert(fd >= 0);
452✔
1460
        assert(ret);
452✔
1461

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

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

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

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

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

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

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

1493
        assert(pid >= 0);
3,762✔
1494

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

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

1504
        p++;
3,762✔
1505

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

1515
        if (devnum_is_zero(ttynr))
3,762✔
1516
                return -ENXIO;
1517

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

1521
        return 0;
1522
}
1523

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

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

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

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

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

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

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

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

1564
                        fn = buf;
×
1565
                }
1566
        } else
1567
                fn = buf;
×
1568

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

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

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

1582
        return 0;
1583
}
1584

1585
int ptsname_malloc(int fd, char **ret) {
124✔
1586
        assert(fd >= 0);
124✔
1587
        assert(ret);
124✔
1588

1589
        for (size_t l = 50;;) {
×
1590
                _cleanup_free_ char *c = NULL;
×
1591

1592
                c = new(char, l);
124✔
1593
                if (!c)
124✔
1594
                        return -ENOMEM;
1595

1596
                if (ptsname_r(fd, c, l) >= 0) {
124✔
1597
                        *ret = TAKE_PTR(c);
124✔
1598
                        return 0;
124✔
1599
                }
1600
                if (errno != ERANGE)
×
1601
                        return -errno;
×
1602

1603
                if (!MUL_ASSIGN_SAFE(&l, 2))
×
1604
                        return -ENOMEM;
1605
        }
1606
}
1607

1608
int openpt_allocate(int flags, char **ret_peer_path) {
128✔
1609
        _cleanup_close_ int fd = -EBADF;
128✔
1610
        int r;
128✔
1611

1612
        fd = posix_openpt(flags|O_NOCTTY|O_CLOEXEC);
128✔
1613
        if (fd < 0)
128✔
1614
                return -errno;
×
1615

1616
        _cleanup_free_ char *p = NULL;
128✔
1617
        if (ret_peer_path) {
128✔
1618
                r = ptsname_malloc(fd, &p);
124✔
1619
                if (r < 0)
124✔
1620
                        return r;
1621

1622
                if (!path_startswith(p, "/dev/pts/"))
124✔
1623
                        return -EINVAL;
1624
        }
1625

1626
        if (unlockpt(fd) < 0)
128✔
1627
                return -errno;
×
1628

1629
        if (ret_peer_path)
128✔
1630
                *ret_peer_path = TAKE_PTR(p);
124✔
1631

1632
        return TAKE_FD(fd);
1633
}
1634

1635
static int ptsname_namespace(int pty, char **ret) {
×
1636
        int no = -1;
×
1637

1638
        assert(pty >= 0);
×
1639
        assert(ret);
×
1640

1641
        /* Like ptsname(), but doesn't assume that the path is
1642
         * accessible in the local namespace. */
1643

1644
        if (ioctl(pty, TIOCGPTN, &no) < 0)
×
1645
                return -errno;
×
1646

1647
        if (no < 0)
×
1648
                return -EIO;
1649

1650
        if (asprintf(ret, "/dev/pts/%i", no) < 0)
×
1651
                return -ENOMEM;
×
1652

1653
        return 0;
1654
}
1655

1656
int openpt_allocate_in_namespace(
×
1657
                const PidRef *pidref,
1658
                int flags,
1659
                char **ret_peer_path) {
1660

1661
        _cleanup_close_ int pidnsfd = -EBADF, mntnsfd = -EBADF, usernsfd = -EBADF, rootfd = -EBADF, fd = -EBADF;
×
1662
        _cleanup_close_pair_ int pair[2] = EBADF_PAIR;
×
1663
        int r;
×
1664

1665
        r = pidref_namespace_open(pidref, &pidnsfd, &mntnsfd, /* ret_netns_fd = */ NULL, &usernsfd, &rootfd);
×
1666
        if (r < 0)
×
1667
                return r;
1668

1669
        if (socketpair(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0, pair) < 0)
×
1670
                return -errno;
×
1671

1672
        r = namespace_fork(
×
1673
                        "(sd-openptns)",
1674
                        "(sd-openpt)",
1675
                        /* except_fds= */ NULL,
1676
                        /* n_except_fds= */ 0,
1677
                        FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGKILL|FORK_WAIT,
1678
                        pidnsfd,
1679
                        mntnsfd,
1680
                        /* netns_fd= */ -EBADF,
1681
                        usernsfd,
1682
                        rootfd,
1683
                        /* ret_pid= */ NULL);
1684
        if (r < 0)
×
1685
                return r;
1686
        if (r == 0) {
×
1687
                pair[0] = safe_close(pair[0]);
×
1688

1689
                fd = openpt_allocate(flags, /* ret_peer_path= */ NULL);
×
1690
                if (fd < 0)
×
1691
                        _exit(EXIT_FAILURE);
×
1692

1693
                if (send_one_fd(pair[1], fd, 0) < 0)
×
1694
                        _exit(EXIT_FAILURE);
×
1695

1696
                _exit(EXIT_SUCCESS);
×
1697
        }
1698

1699
        pair[1] = safe_close(pair[1]);
×
1700

1701
        fd = receive_one_fd(pair[0], 0);
×
1702
        if (fd < 0)
×
1703
                return fd;
1704

1705
        if (ret_peer_path) {
×
1706
                r = ptsname_namespace(fd, ret_peer_path);
×
1707
                if (r < 0)
×
1708
                        return r;
×
1709
        }
1710

1711
        return TAKE_FD(fd);
1712
}
1713

1714
static bool on_dev_null(void) {
39,345✔
1715
        struct stat dst, ost, est;
39,345✔
1716

1717
        if (cached_on_dev_null >= 0)
39,345✔
1718
                return cached_on_dev_null;
8,302✔
1719

1720
        if (stat("/dev/null", &dst) < 0 || fstat(STDOUT_FILENO, &ost) < 0 || fstat(STDERR_FILENO, &est) < 0)
31,043✔
1721
                cached_on_dev_null = false;
1✔
1722
        else
1723
                cached_on_dev_null = stat_inode_same(&dst, &ost) && stat_inode_same(&dst, &est);
34,871✔
1724

1725
        return cached_on_dev_null;
31,043✔
1726
}
1727

1728
bool getenv_terminal_is_dumb(void) {
13,066✔
1729
        const char *e;
13,066✔
1730

1731
        e = getenv("TERM");
13,066✔
1732
        if (!e)
13,066✔
1733
                return true;
1734

1735
        return streq(e, "dumb");
11,941✔
1736
}
1737

1738
bool terminal_is_dumb(void) {
39,354✔
1739
        if (!on_tty() && !on_dev_null())
39,354✔
1740
                return true;
1741

1742
        return getenv_terminal_is_dumb();
224✔
1743
}
1744

1745
bool dev_console_colors_enabled(void) {
×
1746
        _cleanup_free_ char *s = NULL;
×
1747
        ColorMode m;
×
1748

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

1756
        m = parse_systemd_colors();
×
1757
        if (m >= 0)
×
1758
                return m;
×
1759

1760
        if (getenv("NO_COLOR"))
×
1761
                return false;
1762

1763
        if (getenv_for_pid(1, "TERM", &s) <= 0)
×
1764
                (void) proc_cmdline_get_key("TERM", 0, &s);
×
1765

1766
        return !streq_ptr(s, "dumb");
×
1767
}
1768

1769
int vt_restore(int fd) {
×
1770

1771
        static const struct vt_mode mode = {
×
1772
                .mode = VT_AUTO,
1773
        };
1774

1775
        int r, ret = 0;
×
1776

1777
        assert(fd >= 0);
×
1778

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

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

1785
        r = vt_reset_keyboard(fd);
×
1786
        if (r < 0)
×
1787
                RET_GATHER(ret, log_debug_errno(r, "Failed to reset keyboard mode, ignoring: %m"));
×
1788

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

1792
        r = fchmod_and_chown(fd, TTY_MODE, 0, GID_INVALID);
×
1793
        if (r < 0)
×
1794
                RET_GATHER(ret, log_debug_errno(r, "Failed to chmod()/chown() VT, ignoring: %m"));
×
1795

1796
        return ret;
1797
}
1798

1799
int vt_release(int fd, bool restore) {
×
1800
        assert(fd >= 0);
×
1801

1802
        /* This function releases the VT by acknowledging the VT-switch signal
1803
         * sent by the kernel and optionally reset the VT in text and auto
1804
         * VT-switching modes. */
1805

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

1809
        if (ioctl(fd, VT_RELDISP, 1) < 0)
×
1810
                return -errno;
×
1811

1812
        if (restore)
×
1813
                return vt_restore(fd);
×
1814

1815
        return 0;
1816
}
1817

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

1822
        if (priority <= LOG_ERR) {
178,674✔
1823
                if (on)
6,421✔
1824
                        *on = ansi_highlight_red();
12,842✔
1825
                if (off)
6,421✔
1826
                        *off = ansi_normal();
12,842✔
1827
                if (highlight)
6,421✔
1828
                        *highlight = ansi_highlight();
×
1829

1830
        } else if (priority <= LOG_WARNING) {
172,253✔
1831
                if (on)
914✔
1832
                        *on = ansi_highlight_yellow();
914✔
1833
                if (off)
914✔
1834
                        *off = ansi_normal();
1,828✔
1835
                if (highlight)
914✔
1836
                        *highlight = ansi_highlight();
×
1837

1838
        } else if (priority <= LOG_NOTICE) {
171,339✔
1839
                if (on)
870✔
1840
                        *on = ansi_highlight();
1,740✔
1841
                if (off)
870✔
1842
                        *off = ansi_normal();
1,740✔
1843
                if (highlight)
870✔
1844
                        *highlight = ansi_highlight_red();
×
1845

1846
        } else if (priority >= LOG_DEBUG) {
170,469✔
1847
                if (on)
130,853✔
1848
                        *on = ansi_grey();
130,853✔
1849
                if (off)
130,853✔
1850
                        *off = ansi_normal();
261,706✔
1851
                if (highlight)
130,853✔
1852
                        *highlight = ansi_highlight_red();
×
1853
        }
1854
}
178,674✔
1855

1856
int terminal_set_cursor_position(int fd, unsigned row, unsigned column) {
×
1857
        assert(fd >= 0);
×
1858

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

1862
        return loop_write(fd, cursor_position, SIZE_MAX);
×
1863
}
1864

1865
int terminal_reset_defensive(int fd, TerminalResetFlags flags) {
353✔
1866
        int r = 0;
353✔
1867

1868
        assert(fd >= 0);
353✔
1869
        assert(!FLAGS_SET(flags, TERMINAL_RESET_AVOID_ANSI_SEQ|TERMINAL_RESET_FORCE_ANSI_SEQ));
353✔
1870

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

1874
        if (!isatty_safe(fd))
353✔
1875
                return -ENOTTY;
353✔
1876

1877
        RET_GATHER(r, terminal_reset_ioctl(fd, FLAGS_SET(flags, TERMINAL_RESET_SWITCH_TO_TEXT)));
346✔
1878

1879
        if (!FLAGS_SET(flags, TERMINAL_RESET_AVOID_ANSI_SEQ) &&
346✔
1880
            (FLAGS_SET(flags, TERMINAL_RESET_FORCE_ANSI_SEQ) || !getenv_terminal_is_dumb()))
340✔
1881
                RET_GATHER(r, terminal_reset_ansi_seq(fd));
340✔
1882

1883
        return r;
1884
}
1885

1886
int terminal_reset_defensive_locked(int fd, TerminalResetFlags flags) {
6✔
1887
        assert(fd >= 0);
6✔
1888

1889
        _cleanup_close_ int lock_fd = lock_dev_console();
6✔
1890
        if (lock_fd < 0)
6✔
1891
                log_debug_errno(lock_fd, "Failed to acquire lock for /dev/console, ignoring: %m");
×
1892

1893
        return terminal_reset_defensive(fd, flags);
6✔
1894
}
1895

1896
void termios_disable_echo(struct termios *termios) {
1✔
1897
        assert(termios);
1✔
1898

1899
        termios->c_lflag &= ~(ICANON|ECHO);
1✔
1900
        termios->c_cc[VMIN] = 1;
1✔
1901
        termios->c_cc[VTIME] = 0;
1✔
1902
}
1✔
1903

1904
static int terminal_verify_same(int input_fd, int output_fd) {
1✔
1905
        assert(input_fd >= 0);
1✔
1906
        assert(output_fd >= 0);
1✔
1907

1908
        /* Validates that the specified fds reference the same TTY */
1909

1910
        if (input_fd != output_fd) {
1✔
1911
                struct stat sti;
1✔
1912
                if (fstat(input_fd, &sti) < 0)
1✔
1913
                        return -errno;
1✔
1914

1915
                if (!S_ISCHR(sti.st_mode)) /* TTYs are character devices */
1✔
1916
                        return -ENOTTY;
1917

1918
                struct stat sto;
1✔
1919
                if (fstat(output_fd, &sto) < 0)
1✔
1920
                        return -errno;
×
1921

1922
                if (!S_ISCHR(sto.st_mode))
1✔
1923
                        return -ENOTTY;
1924

1925
                if (sti.st_rdev != sto.st_rdev)
×
1926
                        return -ENOLINK;
1927
        }
1928

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

1932
        return 0;
1933
}
1934

1935
typedef enum BackgroundColorState {
1936
        BACKGROUND_TEXT,
1937
        BACKGROUND_ESCAPE,
1938
        BACKGROUND_BRACKET,
1939
        BACKGROUND_FIRST_ONE,
1940
        BACKGROUND_SECOND_ONE,
1941
        BACKGROUND_SEMICOLON,
1942
        BACKGROUND_R,
1943
        BACKGROUND_G,
1944
        BACKGROUND_B,
1945
        BACKGROUND_RED,
1946
        BACKGROUND_GREEN,
1947
        BACKGROUND_BLUE,
1948
        BACKGROUND_STRING_TERMINATOR,
1949
} BackgroundColorState;
1950

1951
typedef struct BackgroundColorContext {
1952
        BackgroundColorState state;
1953
        uint32_t red, green, blue;
1954
        unsigned red_bits, green_bits, blue_bits;
1955
} BackgroundColorContext;
1956

1957
static int scan_background_color_response(
×
1958
                BackgroundColorContext *context,
1959
                const char *buf,
1960
                size_t size,
1961
                size_t *ret_processed) {
1962

1963
        assert(context);
×
1964
        assert(buf);
×
1965
        assert(ret_processed);
×
1966

1967
        for (size_t i = 0; i < size; i++) {
×
1968
                char c = buf[i];
×
1969

1970
                switch (context->state) {
×
1971

1972
                case BACKGROUND_TEXT:
×
1973
                        context->state = c == '\x1B' ? BACKGROUND_ESCAPE : BACKGROUND_TEXT;
×
1974
                        break;
×
1975

1976
                case BACKGROUND_ESCAPE:
×
1977
                        context->state = c == ']' ? BACKGROUND_BRACKET : BACKGROUND_TEXT;
×
1978
                        break;
×
1979

1980
                case BACKGROUND_BRACKET:
×
1981
                        context->state = c == '1' ? BACKGROUND_FIRST_ONE : BACKGROUND_TEXT;
×
1982
                        break;
×
1983

1984
                case BACKGROUND_FIRST_ONE:
×
1985
                        context->state = c == '1' ? BACKGROUND_SECOND_ONE : BACKGROUND_TEXT;
×
1986
                        break;
×
1987

1988
                case BACKGROUND_SECOND_ONE:
×
1989
                        context->state = c == ';' ? BACKGROUND_SEMICOLON : BACKGROUND_TEXT;
×
1990
                        break;
×
1991

1992
                case BACKGROUND_SEMICOLON:
×
1993
                        context->state = c == 'r' ? BACKGROUND_R : BACKGROUND_TEXT;
×
1994
                        break;
×
1995

1996
                case BACKGROUND_R:
×
1997
                        context->state = c == 'g' ? BACKGROUND_G : BACKGROUND_TEXT;
×
1998
                        break;
×
1999

2000
                case BACKGROUND_G:
×
2001
                        context->state = c == 'b' ? BACKGROUND_B : BACKGROUND_TEXT;
×
2002
                        break;
×
2003

2004
                case BACKGROUND_B:
×
2005
                        context->state = c == ':' ? BACKGROUND_RED : BACKGROUND_TEXT;
×
2006
                        break;
×
2007

2008
                case BACKGROUND_RED:
×
2009
                        if (c == '/')
×
2010
                                context->state = context->red_bits > 0 ? BACKGROUND_GREEN : BACKGROUND_TEXT;
×
2011
                        else {
2012
                                int d = unhexchar(c);
×
2013
                                if (d < 0 || context->red_bits >= sizeof(context->red)*8)
×
2014
                                        context->state = BACKGROUND_TEXT;
×
2015
                                else {
2016
                                        context->red = (context->red << 4) | d;
×
2017
                                        context->red_bits += 4;
×
2018
                                }
2019
                        }
2020
                        break;
2021

2022
                case BACKGROUND_GREEN:
×
2023
                        if (c == '/')
×
2024
                                context->state = context->green_bits > 0 ? BACKGROUND_BLUE : BACKGROUND_TEXT;
×
2025
                        else {
2026
                                int d = unhexchar(c);
×
2027
                                if (d < 0 || context->green_bits >= sizeof(context->green)*8)
×
2028
                                        context->state = BACKGROUND_TEXT;
×
2029
                                else {
2030
                                        context->green = (context->green << 4) | d;
×
2031
                                        context->green_bits += 4;
×
2032
                                }
2033
                        }
2034
                        break;
2035

2036
                case BACKGROUND_BLUE:
×
2037
                        if (c == '\x07') {
×
2038
                                if (context->blue_bits > 0) {
×
2039
                                        *ret_processed = i + 1;
×
2040
                                        return 1; /* success! */
×
2041
                                }
2042

2043
                                context->state = BACKGROUND_TEXT;
×
2044
                        } else if (c == '\x1b')
×
2045
                                context->state = context->blue_bits > 0 ? BACKGROUND_STRING_TERMINATOR : BACKGROUND_TEXT;
×
2046
                        else {
2047
                                int d = unhexchar(c);
×
2048
                                if (d < 0 || context->blue_bits >= sizeof(context->blue)*8)
×
2049
                                        context->state = BACKGROUND_TEXT;
×
2050
                                else {
2051
                                        context->blue = (context->blue << 4) | d;
×
2052
                                        context->blue_bits += 4;
×
2053
                                }
2054
                        }
2055
                        break;
2056

2057
                case BACKGROUND_STRING_TERMINATOR:
×
2058
                        if (c == '\\') {
×
2059
                                *ret_processed = i + 1;
×
2060
                                return 1; /* success! */
×
2061
                        }
2062

2063
                        context->state = c == ']' ? BACKGROUND_ESCAPE : BACKGROUND_TEXT;
×
2064
                        break;
×
2065

2066
                }
2067

2068
                /* Reset any colors we might have picked up */
2069
                if (IN_SET(context->state, BACKGROUND_TEXT, BACKGROUND_ESCAPE)) {
×
2070
                        /* reset color */
2071
                        context->red = context->green = context->blue = 0;
×
2072
                        context->red_bits = context->green_bits = context->blue_bits = 0;
×
2073
                }
2074
        }
2075

2076
        *ret_processed = size;
×
2077
        return 0; /* all good, but not enough data yet */
×
2078
}
2079

2080
int get_default_background_color(double *ret_red, double *ret_green, double *ret_blue) {
146✔
2081
        _cleanup_close_ int nonblock_input_fd = -EBADF;
146✔
2082
        int r;
146✔
2083

2084
        assert(ret_red);
146✔
2085
        assert(ret_green);
146✔
2086
        assert(ret_blue);
146✔
2087

2088
        if (!colors_enabled())
146✔
2089
                return -EOPNOTSUPP;
2090

2091
        r = terminal_verify_same(STDIN_FILENO, STDOUT_FILENO);
×
2092
        if (r < 0)
×
2093
                return r;
2094

2095
        if (streq_ptr(getenv("TERM"), "linux")) {
×
2096
                /* Linux console is black */
2097
                *ret_red = *ret_green = *ret_blue = 0.0;
×
2098
                return 0;
×
2099
        }
2100

2101
        struct termios old_termios;
×
2102
        if (tcgetattr(STDIN_FILENO, &old_termios) < 0)
×
2103
                return -errno;
×
2104

2105
        struct termios new_termios = old_termios;
×
2106
        termios_disable_echo(&new_termios);
×
2107

2108
        if (tcsetattr(STDIN_FILENO, TCSANOW, &new_termios) < 0)
×
2109
                return -errno;
×
2110

2111
        r = loop_write(STDOUT_FILENO, ANSI_OSC "11;?" ANSI_ST, SIZE_MAX);
×
2112
        if (r < 0)
×
2113
                goto finish;
×
2114

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

2118
        nonblock_input_fd = r = fd_reopen(STDIN_FILENO, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
×
2119
        if (r < 0)
×
2120
                goto finish;
×
2121

2122
        usec_t end = usec_add(now(CLOCK_MONOTONIC), CONSOLE_REPLY_WAIT_USEC);
×
2123
        char buf[STRLEN(ANSI_OSC "11;rgb:0/0/0" ANSI_ST)]; /* shortest possible reply */
×
2124
        size_t buf_full = 0;
×
2125
        BackgroundColorContext context = {};
×
2126

2127
        for (bool first = true;; first = false) {
×
2128
                if (buf_full == 0) {
×
2129
                        usec_t n = now(CLOCK_MONOTONIC);
×
2130
                        if (n >= end) {
×
2131
                                r = -EOPNOTSUPP;
×
2132
                                goto finish;
×
2133
                        }
2134

2135
                        r = fd_wait_for_event(nonblock_input_fd, POLLIN, usec_sub_unsigned(end, n));
×
2136
                        if (r < 0)
×
2137
                                goto finish;
×
2138
                        if (r == 0) {
×
2139
                                r = -EOPNOTSUPP;
×
2140
                                goto finish;
×
2141
                        }
2142

2143
                        /* On the first try, read multiple characters, i.e. the shortest valid
2144
                         * reply. Afterwards read byte-wise, since we don't want to read too much, and
2145
                         * unnecessarily drop too many characters from the input queue. */
2146
                        ssize_t l = read(nonblock_input_fd, buf, first ? sizeof(buf) : 1);
×
2147
                        if (l < 0) {
×
2148
                                if (errno == EAGAIN)
×
2149
                                        continue;
×
2150
                                r = -errno;
×
2151
                                goto finish;
×
2152
                        }
2153

2154
                        assert((size_t) l <= sizeof(buf));
×
2155
                        buf_full = l;
2156
                }
2157

2158
                size_t processed;
×
2159
                r = scan_background_color_response(&context, buf, buf_full, &processed);
×
2160
                if (r < 0)
×
2161
                        goto finish;
×
2162

2163
                assert(processed <= buf_full);
×
2164
                buf_full -= processed;
×
2165
                memmove(buf, buf + processed, buf_full);
×
2166

2167
                if (r > 0) {
×
2168
                        assert(context.red_bits > 0);
×
2169
                        *ret_red = (double) context.red / ((UINT64_C(1) << context.red_bits) - 1);
×
2170
                        assert(context.green_bits > 0);
×
2171
                        *ret_green = (double) context.green / ((UINT64_C(1) << context.green_bits) - 1);
×
2172
                        assert(context.blue_bits > 0);
×
2173
                        *ret_blue = (double) context.blue / ((UINT64_C(1) << context.blue_bits) - 1);
×
2174
                        r = 0;
×
2175
                        goto finish;
×
2176
                }
2177
        }
2178

2179
finish:
×
2180
        RET_GATHER(r, RET_NERRNO(tcsetattr(STDIN_FILENO, TCSANOW, &old_termios)));
×
2181
        return r;
2182
}
2183

2184
typedef enum CursorPositionState {
2185
        CURSOR_TEXT,
2186
        CURSOR_ESCAPE,
2187
        CURSOR_ROW,
2188
        CURSOR_COLUMN,
2189
} CursorPositionState;
2190

2191
typedef struct CursorPositionContext {
2192
        CursorPositionState state;
2193
        unsigned row, column;
2194
} CursorPositionContext;
2195

2196
static int scan_cursor_position_response(
×
2197
                CursorPositionContext *context,
2198
                const char *buf,
2199
                size_t size,
2200
                size_t *ret_processed) {
2201

2202
        assert(context);
×
2203
        assert(buf);
×
2204
        assert(ret_processed);
×
2205

2206
        for (size_t i = 0; i < size; i++) {
×
2207
                char c = buf[i];
×
2208

2209
                switch (context->state) {
×
2210

2211
                case CURSOR_TEXT:
×
2212
                        context->state = c == '\x1B' ? CURSOR_ESCAPE : CURSOR_TEXT;
×
2213
                        break;
×
2214

2215
                case CURSOR_ESCAPE:
×
2216
                        context->state = c == '[' ? CURSOR_ROW : CURSOR_TEXT;
×
2217
                        break;
×
2218

2219
                case CURSOR_ROW:
×
2220
                        if (c == ';')
×
2221
                                context->state = context->row > 0 ? CURSOR_COLUMN : CURSOR_TEXT;
×
2222
                        else {
2223
                                int d = undecchar(c);
×
2224

2225
                                /* We read a decimal character, let's suffix it to the number we so far read,
2226
                                 * but let's do an overflow check first. */
2227
                                if (d < 0 || context->row > (UINT_MAX-d)/10)
×
2228
                                        context->state = CURSOR_TEXT;
×
2229
                                else
2230
                                        context->row = context->row * 10 + d;
×
2231
                        }
2232
                        break;
2233

2234
                case CURSOR_COLUMN:
×
2235
                        if (c == 'R') {
×
2236
                                if (context->column > 0) {
×
2237
                                        *ret_processed = i + 1;
×
2238
                                        return 1; /* success! */
×
2239
                                }
2240

2241
                                context->state = CURSOR_TEXT;
×
2242
                        } else {
2243
                                int d = undecchar(c);
×
2244

2245
                                /* As above, add the decimal character to our column number */
2246
                                if (d < 0 || context->column > (UINT_MAX-d)/10)
×
2247
                                        context->state = CURSOR_TEXT;
×
2248
                                else
2249
                                        context->column = context->column * 10 + d;
×
2250
                        }
2251

2252
                        break;
2253
                }
2254

2255
                /* Reset any positions we might have picked up */
2256
                if (IN_SET(context->state, CURSOR_TEXT, CURSOR_ESCAPE))
×
2257
                        context->row = context->column = 0;
×
2258
        }
2259

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

2264
int terminal_get_size_by_dsr(
198✔
2265
                int input_fd,
2266
                int output_fd,
2267
                unsigned *ret_rows,
2268
                unsigned *ret_columns) {
2269

2270
        _cleanup_close_ int nonblock_input_fd = -EBADF;
198✔
2271
        int r;
198✔
2272

2273
        assert(input_fd >= 0);
198✔
2274
        assert(output_fd >= 0);
198✔
2275

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

2291
        if (terminal_is_dumb())
198✔
2292
                return -EOPNOTSUPP;
2293

2294
        r = terminal_verify_same(input_fd, output_fd);
×
2295
        if (r < 0)
×
2296
                return log_debug_errno(r, "Called with distinct input/output fds: %m");
×
2297

2298
        struct termios old_termios;
×
2299
        if (tcgetattr(input_fd, &old_termios) < 0)
×
2300
                return log_debug_errno(errno, "Failed to get terminal settings: %m");
×
2301

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

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

2308
        unsigned saved_row = 0, saved_column = 0;
×
2309

2310
        r = loop_write(output_fd,
×
2311
                       "\x1B[6n"           /* Request cursor position (DSR/CPR) */
2312
                       "\x1B[32766;32766H" /* Position cursor really far to the right and to the bottom, but let's stay within the 16bit signed range */
2313
                       "\x1B[6n",          /* Request cursor position again */
2314
                       SIZE_MAX);
2315
        if (r < 0)
×
2316
                goto finish;
×
2317

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

2321
        nonblock_input_fd = r = fd_reopen(input_fd, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
×
2322
        if (r < 0)
×
2323
                goto finish;
×
2324

2325
        usec_t end = usec_add(now(CLOCK_MONOTONIC), CONSOLE_REPLY_WAIT_USEC);
×
2326
        char buf[STRLEN("\x1B[1;1R")]; /* The shortest valid reply possible */
×
2327
        size_t buf_full = 0;
×
2328
        CursorPositionContext context = {};
×
2329

2330
        for (bool first = true;; first = false) {
×
2331
                if (buf_full == 0) {
×
2332
                        usec_t n = now(CLOCK_MONOTONIC);
×
2333
                        if (n >= end) {
×
2334
                                r = -EOPNOTSUPP;
×
2335
                                goto finish;
×
2336
                        }
2337

2338
                        r = fd_wait_for_event(nonblock_input_fd, POLLIN, usec_sub_unsigned(end, n));
×
2339
                        if (r < 0)
×
2340
                                goto finish;
×
2341
                        if (r == 0) {
×
2342
                                r = -EOPNOTSUPP;
×
2343
                                goto finish;
×
2344
                        }
2345

2346
                        /* On the first try, read multiple characters, i.e. the shortest valid
2347
                         * reply. Afterwards read byte-wise, since we don't want to read too much, and
2348
                         * unnecessarily drop too many characters from the input queue. */
2349
                        ssize_t l = read(nonblock_input_fd, buf, first ? sizeof(buf) : 1);
×
2350
                        if (l < 0) {
×
2351
                                if (errno == EAGAIN)
×
2352
                                        continue;
×
2353

2354
                                r = -errno;
×
2355
                                goto finish;
×
2356
                        }
2357

2358
                        assert((size_t) l <= sizeof(buf));
×
2359
                        buf_full = l;
2360
                }
2361

2362
                size_t processed;
×
2363
                r = scan_cursor_position_response(&context, buf, buf_full, &processed);
×
2364
                if (r < 0)
×
2365
                        goto finish;
×
2366

2367
                assert(processed <= buf_full);
×
2368
                buf_full -= processed;
×
2369
                memmove(buf, buf + processed, buf_full);
×
2370

2371
                if (r > 0) {
×
2372
                        if (saved_row == 0) {
×
2373
                                assert(saved_column == 0);
×
2374

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

2379
                                /* Superficial validity checks */
2380
                                if (context.row <= 0 || context.column <= 0 || context.row >= 32766 || context.column >= 32766) {
×
2381
                                        r = -ENODATA;
×
2382
                                        goto finish;
×
2383
                                }
2384

2385
                                saved_row = context.row;
×
2386
                                saved_column = context.column;
×
2387

2388
                                /* Reset state */
2389
                                context = (CursorPositionContext) {};
×
2390
                        } else {
2391
                                /* Second sequence, this is the cursor position after we set it somewhere
2392
                                 * into the void at the bottom right. */
2393

2394
                                /* Superficial validity checks (no particular reason to check for < 4, it's
2395
                                 * just a way to look for unreasonably small values) */
2396
                                if (context.row < 4 || context.column < 4 || context.row >= 32766 || context.column >= 32766) {
×
2397
                                        r = -ENODATA;
×
2398
                                        goto finish;
×
2399
                                }
2400

2401
                                if (ret_rows)
×
2402
                                        *ret_rows = context.row;
×
2403
                                if (ret_columns)
×
2404
                                        *ret_columns = context.column;
×
2405

2406
                                r = 0;
×
2407
                                goto finish;
×
2408
                        }
2409
                }
2410
        }
2411

2412
finish:
×
2413
        /* Restore cursor position */
2414
        if (saved_row > 0 && saved_column > 0)
×
2415
                RET_GATHER(r, terminal_set_cursor_position(output_fd, saved_row, saved_column));
×
2416

2417
        RET_GATHER(r, RET_NERRNO(tcsetattr(input_fd, TCSANOW, &old_termios)));
×
2418
        return r;
2419
}
2420

2421
int terminal_fix_size(int input_fd, int output_fd) {
1✔
2422
        unsigned rows, columns;
1✔
2423
        int r;
1✔
2424

2425
        /* Tries to update the current terminal dimensions to the ones reported via ANSI sequences */
2426

2427
        r = terminal_verify_same(input_fd, output_fd);
1✔
2428
        if (r < 0)
1✔
2429
                return r;
1✔
2430

2431
        struct winsize ws = {};
×
2432
        if (ioctl(output_fd, TIOCGWINSZ, &ws) < 0)
×
2433
                return log_debug_errno(errno, "Failed to query terminal dimensions, ignoring: %m");
×
2434

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

2439
        if (ws.ws_row == rows && ws.ws_col == columns) {
×
2440
                log_debug("Terminal dimensions reported via ANSI sequences match currently set terminal dimensions, not changing.");
×
2441
                return 0;
×
2442
        }
2443

2444
        ws.ws_col = columns;
×
2445
        ws.ws_row = rows;
×
2446

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

2450
        log_debug("Fixed terminal dimensions to %ux%u based on ANSI sequence information.", columns, rows);
×
2451
        return 1;
2452
}
2453

2454
#define MAX_TERMINFO_LENGTH 64
2455
/* python -c 'print("".join(hex(ord(i))[2:] for i in "name").upper())' */
2456
#define DCS_TERMINFO_Q ANSI_DCS "+q" "6E616D65" ANSI_ST
2457
/* The answer is either 0+r… (invalid) or 1+r… (OK). */
2458
#define DCS_TERMINFO_R0 ANSI_DCS "0+r" ANSI_ST
2459
#define DCS_TERMINFO_R1 ANSI_DCS "1+r" "6E616D65" "=" /* This is followed by Pt ST. */
2460
assert_cc(STRLEN(DCS_TERMINFO_R0) <= STRLEN(DCS_TERMINFO_R1 ANSI_ST));
2461

2462
static int scan_terminfo_response(
×
2463
                const char *buf,
2464
                size_t size,
2465
                char **ret_name) {
2466
        int r;
×
2467

2468
        assert(buf);
×
2469
        assert(ret_name);
×
2470

2471
        /* Check if we have enough space for the shortest possible answer. */
2472
        if (size < STRLEN(DCS_TERMINFO_R0))
×
2473
                return -EAGAIN;
×
2474

2475
        /* Check if the terminating sequence is present */
2476
        if (memcmp(buf + size - STRLEN(ANSI_ST), ANSI_ST, STRLEN(ANSI_ST)) != 0)
×
2477
                return -EAGAIN;
2478

2479
        if (size <= STRLEN(DCS_TERMINFO_R1 ANSI_ST))
×
2480
                return -EINVAL;  /* The answer is invalid or empty */
2481

2482
        if (memcmp(buf, DCS_TERMINFO_R1, STRLEN(DCS_TERMINFO_R1)) != 0)
×
2483
                return -EINVAL;  /* The answer is not valid */
2484

2485
        _cleanup_free_ void *dec = NULL;
×
2486
        size_t dec_size;
×
2487
        r = unhexmem_full(buf + STRLEN(DCS_TERMINFO_R1), size - STRLEN(DCS_TERMINFO_R1 ANSI_ST),
×
2488
                          /* secure= */ false,
2489
                          &dec, &dec_size);
2490
        if (r < 0)
×
2491
                return r;
2492

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

2497
        *ret_name = TAKE_PTR(dec);
×
2498
        return 0;
×
2499
}
2500

2501
int terminal_get_terminfo_by_dcs(int fd, char **ret_name) {
2✔
2502
        int r;
2✔
2503

2504
        assert(fd >= 0);
2✔
2505
        assert(ret_name);
2✔
2506

2507
        /* Note: fd must be in non-blocking read-write mode! */
2508

2509
        struct termios old_termios;
2✔
2510
        if (tcgetattr(fd, &old_termios) < 0)
2✔
2511
                return -errno;
1✔
2512

2513
        struct termios new_termios = old_termios;
1✔
2514
        termios_disable_echo(&new_termios);
1✔
2515

2516
        if (tcsetattr(fd, TCSANOW, &new_termios) < 0)
1✔
2517
                return -errno;
×
2518

2519
        r = loop_write(fd, DCS_TERMINFO_Q, SIZE_MAX);
1✔
2520
        if (r < 0)
1✔
2521
                goto finish;
×
2522

2523
        usec_t end = usec_add(now(CLOCK_MONOTONIC), CONSOLE_REPLY_WAIT_USEC);
1✔
2524
        char buf[STRLEN(DCS_TERMINFO_R1) + MAX_TERMINFO_LENGTH + STRLEN(ANSI_ST)];
1✔
2525
        size_t bytes = 0;
1✔
2526

2527
        for (;;) {
1✔
2528
                usec_t n = now(CLOCK_MONOTONIC);
1✔
2529
                if (n >= end) {
1✔
2530
                        r = -EOPNOTSUPP;
2531
                        break;
2532
                }
2533

2534
                r = fd_wait_for_event(fd, POLLIN, usec_sub_unsigned(end, n));
2✔
2535
                if (r < 0)
1✔
2536
                        break;
2537
                if (r == 0) {
1✔
2538
                        r = -EOPNOTSUPP;
2539
                        break;
2540
                }
2541

2542
                /* On the first read, read multiple characters, i.e. the shortest valid reply. Afterwards
2543
                 * read byte by byte, since we don't want to read too much and drop characters from the input
2544
                 * queue. */
2545
                ssize_t l = read(fd, buf + bytes, bytes == 0 ? STRLEN(DCS_TERMINFO_R0) : 1);
×
2546
                if (l < 0) {
×
2547
                        if (errno == EAGAIN)
×
2548
                                continue;
×
2549
                        r = -errno;
×
2550
                        break;
×
2551
                }
2552

2553
                assert((size_t) l <= sizeof(buf) - bytes);
×
2554
                bytes += l;
×
2555

2556
                r = scan_terminfo_response(buf, bytes, ret_name);
×
2557
                if (r != -EAGAIN)
×
2558
                        break;
2559

2560
                if (bytes == sizeof(buf)) {
×
2561
                        r = -EOPNOTSUPP; /* The response has the right prefix, but we didn't find a valid
2562
                                          * answer with a terminator in the allotted space. Something is
2563
                                          * wrong, possibly some unrelated bytes got injected into the
2564
                                          * answer. */
2565
                        break;
2566
                }
2567
        }
2568

2569
finish:
×
2570
        /* We ignore failure here. We already got a reply and if cleanup fails, we can't help that. */
2571
        (void) tcsetattr(fd, TCSANOW, &old_termios);
1✔
2572
        return r;
1✔
2573
}
2574

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

2580
        assert(filename_is_valid(name));
5✔
2581

2582
        _cleanup_free_ char *p = path_join("/usr/share/terminfo", CHAR_TO_STR(name[0]), name);
10✔
2583
        if (!p)
5✔
2584
                return log_oom_debug();
×
2585

2586
        r = RET_NERRNO(access(p, F_OK));
5✔
2587
        if (r == -ENOENT)
1✔
2588
                return false;
2589
        if (r < 0)
4✔
2590
                return r;
×
2591
        return true;
2592
}
2593

2594
int query_term_for_tty(const char *tty, char **ret_term) {
49✔
2595
        _cleanup_free_ char *dcs_term = NULL;
49✔
2596
        int r;
49✔
2597

2598
        assert(tty);
49✔
2599
        assert(ret_term);
49✔
2600

2601
        if (tty_is_vc_resolve(tty))
49✔
2602
                return strdup_to(ret_term, "linux");
46✔
2603

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

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

2611
        r = terminal_get_terminfo_by_dcs(tty_fd, &dcs_term);
1✔
2612
        if (r < 0)
1✔
2613
                return log_debug_errno(r, "Failed to query %s for terminfo: %m", tty);
1✔
2614

2615
        r = have_terminfo_file(dcs_term);
×
2616
        if (r < 0)
×
2617
                return log_debug_errno(r, "Failed to look for terminfo %s: %m", dcs_term);
×
2618
        if (r == 0)
×
2619
                return log_info_errno(SYNTHETIC_ERRNO(ENODATA),
×
2620
                                      "Terminfo %s not found for %s.", dcs_term, tty);
2621

2622
        *ret_term = TAKE_PTR(dcs_term);
×
2623
        return 0;
×
2624
}
2625

2626
int terminal_is_pty_fd(int fd) {
3✔
2627
        int r;
3✔
2628

2629
        assert(fd >= 0);
3✔
2630

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

2633
        if (!isatty_safe(fd))
3✔
2634
                return false;
3✔
2635

2636
        r = is_fs_type_at(fd, NULL, DEVPTS_SUPER_MAGIC);
2✔
2637
        if (r != 0)
2✔
2638
                return r;
2639

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

2643
        int v;
×
2644
        if (ioctl(fd, TIOCGPKT, &v) < 0) {
×
2645
                if (ERRNO_IS_NOT_SUPPORTED(errno))
×
2646
                        return false;
2647

2648
                return -errno;
×
2649
        }
2650

2651
        return true;
2652
}
2653

2654
int pty_open_peer(int fd, int mode) {
20✔
2655
        assert(fd >= 0);
20✔
2656

2657
        /* Opens the peer PTY using the new race-free TIOCGPTPEER ioctl() (kernel 4.13).
2658
         *
2659
         * This is safe to be called on TTYs from other namespaces. */
2660

2661
        assert((mode & (O_CREAT|O_PATH|O_DIRECTORY|O_TMPFILE)) == 0);
20✔
2662

2663
        /* This replicates the EIO retry logic of open_terminal() in a modified way. */
2664
        for (unsigned c = 0;; c++) {
×
2665
                int peer_fd = ioctl(fd, TIOCGPTPEER, mode);
20✔
2666
                if (peer_fd >= 0)
20✔
2667
                        return peer_fd;
2668

2669
                if (errno != EIO)
×
2670
                        return -errno;
×
2671

2672
                /* Max 1s in total */
2673
                if (c >= 20)
×
2674
                        return -EIO;
2675

2676
                (void) usleep_safe(50 * USEC_PER_MSEC);
×
2677
        }
2678
}
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