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

systemd / systemd / 23927985597

02 Apr 2026 07:45PM UTC coverage: 72.362% (+0.02%) from 72.343%
23927985597

push

github

daandemeyer
ci: Drop base64 encoding in claude review workflow

Doesn't seem to work nearly as good as the previous solution which
just told claude not to escape stuff.

319121 of 441004 relevant lines covered (72.36%)

1167673.48 hits per line

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

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

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

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

47
/* How much to wait when reading/writing ANSI sequences from/to the console */
48
#define CONSOLE_ANSI_SEQUENCE_TIMEOUT_USEC (333 * USEC_PER_MSEC)
49

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

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

56
bool isatty_safe(int fd) {
5,494,305✔
57
        assert(fd >= 0);
5,494,305✔
58

59
        if (isatty(fd))
5,494,305✔
60
                return true;
61

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

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

71
        return false;
72
}
73

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

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

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

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

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

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

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

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

104
        assert(ret);
4✔
105

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

174
#define DEFAULT_ASK_REFRESH_USEC (2*USEC_PER_SEC)
175

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

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

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

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

190
                putchar('\r');
×
191

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

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

198
                fflush(stdout);
×
199

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

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

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

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

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

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

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

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

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

241
        string = strempty(string);
×
242

243
        STRV_FOREACH(c, completions) {
×
244

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

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

255
                        continue;
×
256
                }
257

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

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

268
        *ret = TAKE_PTR(found);
×
269

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

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

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

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

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

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

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

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

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

315
        if (fd_input < 0 || fd_output < 0 || same_fd(fd_input, fd_output) <= 0)
7✔
316
                goto fallback;
7✔
317

318
        /* Try to disable echo, which also tells us if this even is a terminal */
319
        if (tcgetattr(fd_input, &old_termios) < 0) {
×
320
                old_termios = TERMIOS_NULL;
×
321
                goto fallback;
×
322
        }
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
                                        return r;
350
                        }
351

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

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

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

373
                                _cleanup_strv_free_ char **filtered = strv_filter_prefix(completions, string);
×
374
                                if (!filtered)
×
375
                                        return -ENOMEM;
376

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

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

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

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

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

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

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

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

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

422
                        return -ECANCELED;
423

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

430
                        if (!GREEDY_REALLOC(string, n+2))
×
431
                                return -ENOMEM;
432

433
                        string[n++] = (char) c;
×
434
                        string[n] = 0;
×
435

436
                        fputc(c, stdout);
×
437
                }
438

439
                fflush(stdout);
×
440
        }
441

442
        if (!string) {
×
443
                string = strdup("");
×
444
                if (!string)
×
445
                        return -ENOMEM;
446
        }
447

448
        *ret = TAKE_PTR(string);
×
449
        return 0;
×
450

451
fallback:
7✔
452
        /* A simple fallback without TTY magic */
453
        r = read_line(stdin, LONG_LINE_MAX, &string);
7✔
454
        if (r < 0)
7✔
455
                return r;
456
        if (r == 0)
7✔
457
                return -EIO;
458

459
        *ret = TAKE_PTR(string);
7✔
460
        return 0;
7✔
461
}
462

463
bool any_key_to_proceed(void) {
×
464

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

469
        fputc('\n', stdout);
×
470
        fputs(ansi_highlight_magenta(), stdout);
×
471
        fputs("-- Press any key to proceed --", stdout);
×
472
        fputs(ansi_normal(), stdout);
×
473
        fputc('\n', stdout);
×
474
        fflush(stdout);
×
475

476
        char key = 0;
×
477
        (void) read_one_char(stdin, &key, USEC_INFINITY, /* echo= */ false, /* need_nl= */ NULL);
×
478

479
        fputc('\n', stdout);
×
480
        fflush(stdout);
×
481

482
        return key != 'q';
×
483
}
484

485
static size_t widest_list_element(char *const*l) {
×
486
        size_t w = 0;
×
487

488
        /* Returns the largest console width of all elements in 'l' */
489

490
        STRV_FOREACH(i, l)
×
491
                w = MAX(w, utf8_console_width(*i));
×
492

493
        return w;
×
494
}
495

496
int show_menu(char **x,
×
497
              size_t n_columns,
498
              size_t column_width,
499
              unsigned ellipsize_percentage,
500
              const char *grey_prefix,
501
              bool with_numbers) {
502

503
        assert(n_columns > 0);
×
504

505
        if (n_columns == SIZE_MAX)
×
506
                n_columns = 3;
×
507

508
        if (column_width == SIZE_MAX) {
×
509
                size_t widest = widest_list_element(x);
×
510

511
                /* If not specified, derive column width from screen width */
512
                size_t column_max = (columns()-1) / n_columns;
×
513

514
                /* Subtract room for numbers */
515
                if (with_numbers && column_max > 6)
×
516
                        column_max -= 6;
×
517

518
                /* If columns would get too tight let's make this a linear list instead. */
519
                if (column_max < 10 && widest > 10) {
×
520
                        n_columns = 1;
×
521
                        column_max = columns()-1;
×
522

523
                        if (with_numbers && column_max > 6)
×
524
                                column_max -= 6;
×
525
                }
526

527
                column_width = CLAMP(widest+1, 10U, column_max);
×
528
        }
529

530
        size_t n = strv_length(x);
×
531
        size_t per_column = DIV_ROUND_UP(n, n_columns);
×
532

533
        size_t break_lines = lines();
×
534
        if (break_lines > 2)
×
535
                break_lines--;
×
536

537
        /* The first page gets two extra lines, since we want to show
538
         * a title */
539
        size_t break_modulo = break_lines;
×
540
        if (break_modulo > 3)
×
541
                break_modulo -= 3;
×
542

543
        for (size_t i = 0; i < per_column; i++) {
×
544

545
                for (size_t j = 0; j < n_columns; j++) {
×
546
                        _cleanup_free_ char *e = NULL;
×
547

548
                        if (j * per_column + i >= n)
×
549
                                break;
550

551
                        e = ellipsize(x[j * per_column + i], column_width, ellipsize_percentage);
×
552
                        if (!e)
×
553
                                return -ENOMEM;
×
554

555
                        if (with_numbers)
×
556
                                printf("%s%4zu)%s ",
×
557
                                       ansi_grey(),
558
                                       j * per_column + i + 1,
559
                                       ansi_normal());
560

561
                        if (grey_prefix && startswith(e, grey_prefix)) {
×
562
                                size_t k = MIN(strlen(grey_prefix), column_width);
×
563
                                printf("%s%.*s%s",
×
564
                                       ansi_grey(),
565
                                       (int) k, e,
566
                                       ansi_normal());
567
                                printf("%-*s",
×
568
                                       (int) (column_width - k), e+k);
×
569
                        } else
570
                                printf("%-*s", (int) column_width, e);
×
571
                }
572

573
                putchar('\n');
×
574

575
                /* on the first screen we reserve 2 extra lines for the title */
576
                if (i % break_lines == break_modulo)
×
577
                        if (!any_key_to_proceed())
×
578
                                return 0;
579
        }
580

581
        return 0;
582
}
583

584
int open_terminal(const char *name, int mode) {
50,540✔
585
        _cleanup_close_ int fd = -EBADF;
50,540✔
586

587
        /*
588
         * If a TTY is in the process of being closed opening it might cause EIO. This is horribly awful, but
589
         * unlikely to be changed in the kernel. Hence we work around this problem by retrying a couple of
590
         * times.
591
         *
592
         * https://bugs.launchpad.net/ubuntu/+source/linux/+bug/554172/comments/245
593
         */
594

595
        assert((mode & (O_CREAT|O_PATH|O_DIRECTORY|O_TMPFILE)) == 0);
50,540✔
596

597
        for (unsigned c = 0;; c++) {
×
598
                fd = open(name, mode, 0);
50,540✔
599
                if (fd >= 0)
50,540✔
600
                        break;
601

602
                if (errno != EIO)
1,252✔
603
                        return -errno;
1,252✔
604

605
                /* Max 1s in total */
606
                if (c >= 20)
×
607
                        return -EIO;
608

609
                (void) usleep_safe(50 * USEC_PER_MSEC);
×
610
        }
611

612
        if (!isatty_safe(fd))
49,288✔
613
                return -ENOTTY;
1✔
614

615
        return TAKE_FD(fd);
616
}
617

618
int acquire_terminal(
188✔
619
                const char *name,
620
                AcquireTerminalFlags flags,
621
                usec_t timeout) {
622

623
        _cleanup_close_ int notify = -EBADF, fd = -EBADF;
188✔
624
        usec_t ts = USEC_INFINITY;
188✔
625
        int r, wd = -1;
188✔
626

627
        assert(name);
188✔
628

629
        AcquireTerminalFlags mode = flags & _ACQUIRE_TERMINAL_MODE_MASK;
188✔
630
        assert(IN_SET(mode, ACQUIRE_TERMINAL_TRY, ACQUIRE_TERMINAL_FORCE, ACQUIRE_TERMINAL_WAIT));
188✔
631
        assert(mode == ACQUIRE_TERMINAL_WAIT || !FLAGS_SET(flags, ACQUIRE_TERMINAL_WATCH_SIGTERM));
188✔
632

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

642
        if (mode == ACQUIRE_TERMINAL_WAIT) {
×
643
                notify = inotify_init1(IN_CLOEXEC | IN_NONBLOCK);
188✔
644
                if (notify < 0)
188✔
645
                        return -errno;
×
646

647
                wd = inotify_add_watch(notify, name, IN_CLOSE);
188✔
648
                if (wd < 0)
188✔
649
                        return -errno;
×
650

651
                if (timeout != USEC_INFINITY)
188✔
652
                        ts = now(CLOCK_MONOTONIC);
×
653
        }
654

655
        /* If we are called with ACQUIRE_TERMINAL_WATCH_SIGTERM we'll unblock SIGTERM during ppoll() temporarily */
656
        sigset_t poll_ss;
188✔
657
        assert_se(sigprocmask(SIG_SETMASK, /* newset= */ NULL, &poll_ss) >= 0);
188✔
658
        if (flags & ACQUIRE_TERMINAL_WATCH_SIGTERM) {
188✔
659
                assert_se(sigismember(&poll_ss, SIGTERM) > 0);
×
660
                assert_se(sigdelset(&poll_ss, SIGTERM) >= 0);
×
661
        }
662

663
        for (;;) {
188✔
664
                if (notify >= 0) {
188✔
665
                        r = flush_fd(notify);
188✔
666
                        if (r < 0)
188✔
667
                                return r;
×
668
                }
669

670
                /* We pass here O_NOCTTY only so that we can check the return value TIOCSCTTY and have a reliable way
671
                 * to figure out if we successfully became the controlling process of the tty */
672
                fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC);
188✔
673
                if (fd < 0)
188✔
674
                        return fd;
675

676
                /* Temporarily ignore SIGHUP, so that we don't get SIGHUP'ed if we already own the tty. */
677
                struct sigaction sa_old;
188✔
678
                assert_se(sigaction(SIGHUP, &sigaction_ignore, &sa_old) >= 0);
188✔
679

680
                /* First, try to get the tty */
681
                r = RET_NERRNO(ioctl(fd, TIOCSCTTY, mode == ACQUIRE_TERMINAL_FORCE));
188✔
682

683
                /* Reset signal handler to old value */
684
                assert_se(sigaction(SIGHUP, &sa_old, NULL) >= 0);
188✔
685

686
                /* Success? Exit the loop now! */
687
                if (r >= 0)
188✔
688
                        break;
689

690
                /* Any failure besides -EPERM? Fail, regardless of the mode. */
691
                if (r != -EPERM)
×
692
                        return r;
693

694
                if (flags & ACQUIRE_TERMINAL_PERMISSIVE) /* If we are in permissive mode, then EPERM is fine, turn this
×
695
                                                          * into a success. Note that EPERM is also returned if we
696
                                                          * already are the owner of the TTY. */
697
                        break;
698

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

702
                assert(notify >= 0);
×
703
                assert(wd >= 0);
×
704

705
                for (;;) {
×
706
                        usec_t left;
×
707
                        if (timeout == USEC_INFINITY)
×
708
                                left = USEC_INFINITY;
709
                        else {
710
                                assert(ts != USEC_INFINITY);
×
711

712
                                usec_t n = usec_sub_unsigned(now(CLOCK_MONOTONIC), ts);
×
713
                                if (n >= timeout)
×
714
                                        return -ETIMEDOUT;
×
715

716
                                left = timeout - n;
×
717
                        }
718

719
                        r = ppoll_usec_full(
×
720
                                        &(struct pollfd) {
×
721
                                                .fd = notify,
722
                                                .events = POLLIN,
723
                                        },
724
                                        /* n_fds= */ 1,
725
                                        left,
726
                                        &poll_ss);
727
                        if (r < 0)
×
728
                                return r;
729
                        if (r == 0)
×
730
                                return -ETIMEDOUT;
731

732
                        union inotify_event_buffer buffer;
×
733
                        ssize_t l;
×
734
                        l = read(notify, &buffer, sizeof(buffer));
×
735
                        if (l < 0) {
×
736
                                if (ERRNO_IS_TRANSIENT(errno))
×
737
                                        continue;
×
738

739
                                return -errno;
×
740
                        }
741

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

746
                                if (e->wd != wd || !(e->mask & IN_CLOSE)) /* Safety checks */
×
747
                                        return -EIO;
×
748
                        }
749

750
                        break;
×
751
                }
752

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

758
        return TAKE_FD(fd);
188✔
759
}
760

761
int release_terminal(void) {
56✔
762
        _cleanup_close_ int fd = -EBADF;
56✔
763
        int r;
56✔
764

765
        fd = open("/dev/tty", O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK);
56✔
766
        if (fd < 0)
56✔
767
                return -errno;
35✔
768

769
        /* Temporarily ignore SIGHUP, so that we don't get SIGHUP'ed
770
         * by our own TIOCNOTTY */
771
        struct sigaction sa_old;
21✔
772
        assert_se(sigaction(SIGHUP, &sigaction_ignore, &sa_old) >= 0);
21✔
773

774
        r = RET_NERRNO(ioctl(fd, TIOCNOTTY));
21✔
775

776
        assert_se(sigaction(SIGHUP, &sa_old, NULL) >= 0);
21✔
777

778
        return r;
779
}
780

781
int terminal_new_session(void) {
5✔
782

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

790
        if (!isatty_safe(STDIN_FILENO))
5✔
791
                return -ENXIO;
792

793
        (void) setsid();
4✔
794
        return RET_NERRNO(ioctl(STDIN_FILENO, TIOCSCTTY, 0));
4✔
795
}
796

797
void terminal_detach_session(void) {
56✔
798
        (void) setsid();
56✔
799
        (void) release_terminal();
56✔
800
}
56✔
801

802
int terminal_vhangup_fd(int fd) {
97✔
803
        assert(fd >= 0);
97✔
804
        return RET_NERRNO(ioctl(fd, TIOCVHANGUP));
97✔
805
}
806

807
int terminal_vhangup(const char *tty) {
×
808
        _cleanup_close_ int fd = -EBADF;
×
809

810
        assert(tty);
×
811

812
        fd = open_terminal(tty, O_RDWR|O_NOCTTY|O_CLOEXEC);
×
813
        if (fd < 0)
×
814
                return fd;
815

816
        return terminal_vhangup_fd(fd);
×
817
}
818

819
int vt_disallocate(const char *tty_path) {
50✔
820
        assert(tty_path);
50✔
821

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

825
        int ttynr = vtnr_from_tty(tty_path);
50✔
826
        if (ttynr > 0) {
50✔
827
                _cleanup_close_ int fd = open_terminal("/dev/tty0", O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK);
50✔
828
                if (fd < 0)
50✔
829
                        return fd;
830

831
                /* Try to deallocate */
832
                if (ioctl(fd, VT_DISALLOCATE, ttynr) >= 0)
50✔
833
                        return 0;
834
                if (errno != EBUSY)
50✔
835
                        return -errno;
×
836
        }
837

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

841
        _cleanup_close_ int fd2 = open_terminal(tty_path, O_WRONLY|O_NOCTTY|O_CLOEXEC|O_NONBLOCK);
100✔
842
        if (fd2 < 0)
50✔
843
                return fd2;
844

845
        return loop_write_full(fd2,
50✔
846
                               "\033[r"   /* clear scrolling region */
847
                               "\033[H"   /* move home */
848
                               "\033[3J"  /* clear screen including scrollback, requires Linux 2.6.40 */
849
                               "\033c",   /* reset to initial state */
850
                               SIZE_MAX,
851
                               CONSOLE_ANSI_SEQUENCE_TIMEOUT_USEC);
852
}
853

854
static int vt_default_utf8(void) {
886✔
855
        _cleanup_free_ char *b = NULL;
886✔
856
        int r;
886✔
857

858
        /* Read the default VT UTF8 setting from the kernel */
859

860
        r = read_one_line_file("/sys/module/vt/parameters/default_utf8", &b);
886✔
861
        if (r < 0)
886✔
862
                return r;
863

864
        return parse_boolean(b);
496✔
865
}
866

867
static int vt_reset_keyboard(int fd) {
443✔
868
        int r, kb;
443✔
869

870
        assert(fd >= 0);
443✔
871

872
        /* If we can't read the default, then default to Unicode. It's 2024 after all. */
873
        r = vt_default_utf8();
443✔
874
        if (r < 0)
443✔
875
                log_debug_errno(r, "Failed to determine kernel VT UTF-8 mode, assuming enabled: %m");
195✔
876

877
        kb = vt_default_utf8() != 0 ? K_UNICODE : K_XLATE;
443✔
878
        return RET_NERRNO(ioctl(fd, KDSKBMODE, kb));
443✔
879
}
880

881
static int terminal_reset_ioctl(int fd, bool switch_to_text) {
443✔
882
        struct termios termios;
443✔
883
        int r;
443✔
884

885
        /* Set terminal to some sane defaults */
886

887
        assert(fd >= 0);
443✔
888

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

892
        /* Disable exclusive mode, just in case */
893
        if (ioctl(fd, TIOCNXCL) < 0)
443✔
894
                log_debug_errno(errno, "TIOCNXCL ioctl failed on TTY, ignoring: %m");
2✔
895

896
        /* Switch to text mode */
897
        if (switch_to_text)
443✔
898
                if (ioctl(fd, KDSETMODE, KD_TEXT) < 0)
214✔
899
                        log_debug_errno(errno, "KDSETMODE ioctl for switching to text mode failed on TTY, ignoring: %m");
164✔
900

901
        /* Set default keyboard mode */
902
        r = vt_reset_keyboard(fd);
443✔
903
        if (r < 0)
443✔
904
                log_debug_errno(r, "Failed to reset VT keyboard, ignoring: %m");
346✔
905

906
        if (tcgetattr(fd, &termios) < 0) {
443✔
907
                r = log_debug_errno(errno, "Failed to get terminal parameters: %m");
2✔
908
                goto finish;
2✔
909
        }
910

911
        /* We only reset the stuff that matters to the software. How
912
         * hardware is set up we don't touch assuming that somebody
913
         * else will do that for us */
914

915
        termios.c_iflag &= ~(IGNBRK | BRKINT | ISTRIP | INLCR | IGNCR | IUCLC);
441✔
916
        termios.c_iflag |= ICRNL | IMAXBEL | IUTF8;
441✔
917
        termios.c_oflag |= ONLCR | OPOST;
441✔
918
        termios.c_cflag |= CREAD;
441✔
919
        termios.c_lflag = ISIG | ICANON | IEXTEN | ECHO | ECHOE | ECHOK | ECHOCTL | ECHOKE;
441✔
920

921
        termios.c_cc[VINTR]    =   03;  /* ^C */
441✔
922
        termios.c_cc[VQUIT]    =  034;  /* ^\ */
441✔
923
        termios.c_cc[VERASE]   = 0177;
441✔
924
        termios.c_cc[VKILL]    =  025;  /* ^X */
441✔
925
        termios.c_cc[VEOF]     =   04;  /* ^D */
441✔
926
        termios.c_cc[VSTART]   =  021;  /* ^Q */
441✔
927
        termios.c_cc[VSTOP]    =  023;  /* ^S */
441✔
928
        termios.c_cc[VSUSP]    =  032;  /* ^Z */
441✔
929
        termios.c_cc[VLNEXT]   =  026;  /* ^V */
441✔
930
        termios.c_cc[VWERASE]  =  027;  /* ^W */
441✔
931
        termios.c_cc[VREPRINT] =  022;  /* ^R */
441✔
932
        termios.c_cc[VEOL]     =    0;
441✔
933
        termios.c_cc[VEOL2]    =    0;
441✔
934

935
        termios.c_cc[VTIME]  = 0;
441✔
936
        termios.c_cc[VMIN]   = 1;
441✔
937

938
        r = RET_NERRNO(tcsetattr(fd, TCSANOW, &termios));
441✔
939
        if (r < 0)
×
940
                log_debug_errno(r, "Failed to set terminal parameters: %m");
×
941

942
finish:
×
943
        /* Just in case, flush all crap out */
944
        (void) tcflush(fd, TCIOFLUSH);
443✔
945

946
        return r;
443✔
947
}
948

949
int terminal_reset_ansi_seq(int fd) {
437✔
950
        _cleanup_(nonblock_resetp) int nonblock_reset = -EBADF;
×
951
        int r;
437✔
952

953
        assert(fd >= 0);
437✔
954

955
        if (getenv_terminal_is_dumb())
437✔
956
                return 0;
957

958
        r = fd_nonblock(fd, true);
×
959
        if (r < 0)
×
960
                return log_debug_errno(r, "Failed to set terminal to non-blocking mode: %m");
×
961
        if (r > 0)
×
962
                nonblock_reset = fd;
×
963

964
        r = loop_write_full(fd,
×
965
                            "\033[!p"              /* soft terminal reset */
966
                            ANSI_OSC "104" ANSI_ST /* reset color palette via OSC 104 */
967
                            ANSI_NORMAL            /* reset colors */
968
                            "\033[?7h"             /* enable line-wrapping */
969
                            "\033[1G"              /* place cursor at beginning of current line */
970
                            "\033[0J",             /* erase till end of screen */
971
                            SIZE_MAX,
972
                            CONSOLE_ANSI_SEQUENCE_TIMEOUT_USEC);
973
        if (r < 0)
×
974
                log_debug_errno(r, "Failed to reset terminal through ANSI sequences: %m");
437✔
975

976
        return r;
977
}
978

979
void reset_dev_console_fd(int fd, bool switch_to_text) {
36✔
980
        int r;
36✔
981

982
        assert(fd >= 0);
36✔
983

984
        _cleanup_close_ int lock_fd = lock_dev_console();
36✔
985
        if (lock_fd < 0)
36✔
986
                log_debug_errno(lock_fd, "Failed to lock /dev/console, ignoring: %m");
×
987

988
        r = terminal_reset_ioctl(fd, switch_to_text);
36✔
989
        if (r < 0)
36✔
990
                log_warning_errno(r, "Failed to reset /dev/console, ignoring: %m");
×
991

992
        unsigned rows, cols;
36✔
993
        r = proc_cmdline_tty_size("/dev/console", &rows, &cols);
36✔
994
        if (r < 0)
36✔
995
                log_warning_errno(r, "Failed to get /dev/console size, ignoring: %m");
×
996
        else if (r > 0) {
36✔
997
                r = terminal_set_size_fd(fd, NULL, rows, cols);
36✔
998
                if (r < 0)
36✔
999
                        log_warning_errno(r, "Failed to set configured terminal size on /dev/console, ignoring: %m");
×
1000
        } else
1001
                (void) terminal_fix_size(fd, fd);
×
1002

1003
        r = terminal_reset_ansi_seq(fd);
36✔
1004
        if (r < 0)
36✔
1005
                log_warning_errno(r, "Failed to reset /dev/console using ANSI sequences, ignoring: %m");
36✔
1006
}
36✔
1007

1008
int lock_dev_console(void) {
579✔
1009
        _cleanup_close_ int fd = -EBADF;
579✔
1010
        int r;
579✔
1011

1012
        /* NB: We do not use O_NOFOLLOW here, because some container managers might place a symlink to some
1013
         * pty in /dev/console, in which case it should be fine to lock the target TTY. */
1014
        fd = open_terminal("/dev/console", O_RDONLY|O_CLOEXEC|O_NOCTTY);
579✔
1015
        if (fd < 0)
579✔
1016
                return fd;
1017

1018
        r = lock_generic(fd, LOCK_BSD, LOCK_EX);
579✔
1019
        if (r < 0)
579✔
1020
                return r;
×
1021

1022
        return TAKE_FD(fd);
1023
}
1024

1025
int make_console_stdio(void) {
×
1026
        int fd, r;
×
1027

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

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

1036
                r = make_null_stdio();
×
1037
                if (r < 0)
×
1038
                        return log_error_errno(r, "Failed to make /dev/null stdin/stdout/stderr: %m");
×
1039

1040
        } else {
1041
                reset_dev_console_fd(fd, /* switch_to_text= */ true);
×
1042

1043
                r = rearrange_stdio(fd, fd, fd); /* This invalidates 'fd' both on success and on failure. */
×
1044
                if (r < 0)
×
1045
                        return log_error_errno(r, "Failed to make terminal stdin/stdout/stderr: %m");
×
1046
        }
1047

1048
        reset_terminal_feature_caches();
×
1049
        return 0;
×
1050
}
1051

1052
static int vtnr_from_tty_raw(const char *tty, unsigned *ret) {
304✔
1053
        assert(tty);
304✔
1054

1055
        tty = skip_dev_prefix(tty);
304✔
1056

1057
        const char *e = startswith(tty, "tty");
304✔
1058
        if (!e)
304✔
1059
                return -EINVAL;
1060

1061
        return safe_atou(e, ret);
263✔
1062
}
1063

1064
int vtnr_from_tty(const char *tty) {
178✔
1065
        unsigned u;
178✔
1066
        int r;
178✔
1067

1068
        assert(tty);
178✔
1069

1070
        r = vtnr_from_tty_raw(tty, &u);
178✔
1071
        if (r < 0)
178✔
1072
                return r;
178✔
1073
        if (!vtnr_is_valid(u))
178✔
1074
                return -ERANGE;
1075

1076
        return (int) u;
178✔
1077
}
1078

1079
bool tty_is_vc(const char *tty) {
126✔
1080
        assert(tty);
126✔
1081

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

1087
        return vtnr_from_tty_raw(tty, /* ret= */ NULL) >= 0;
126✔
1088
}
1089

1090
bool tty_is_console(const char *tty) {
446✔
1091
        assert(tty);
446✔
1092

1093
        return streq(skip_dev_prefix(tty), "console");
446✔
1094
}
1095

1096
int resolve_dev_console(char **ret) {
181✔
1097
        int r;
181✔
1098

1099
        assert(ret);
181✔
1100

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

1106
        _cleanup_free_ char *chased = NULL;
181✔
1107
        r = chase("/dev/console", /* root= */ NULL, /* flags= */ 0, &chased, /* ret_fd= */ NULL);
181✔
1108
        if (r < 0)
181✔
1109
                return r;
1110
        if (!path_equal(chased, "/dev/console")) {
181✔
1111
                *ret = TAKE_PTR(chased);
81✔
1112
                return 0;
81✔
1113
        }
1114

1115
        r = path_is_read_only_fs("/sys");
100✔
1116
        if (r < 0)
100✔
1117
                return r;
1118
        if (r > 0)
100✔
1119
                return -ENOMEDIUM;
1120

1121
        _cleanup_free_ char *active = NULL;
100✔
1122
        r = read_one_line_file("/sys/class/tty/console/active", &active);
100✔
1123
        if (r < 0)
100✔
1124
                return r;
1125
        if (r == 0)
100✔
1126
                return -ENXIO;
1127

1128
        /* If multiple log outputs are configured the last one is what /dev/console points to */
1129
        const char *tty = strrchr(active, ' ');
100✔
1130
        if (tty)
100✔
1131
                tty++;
×
1132
        else
1133
                tty = active;
1134

1135
        if (streq(tty, "tty0")) {
100✔
1136
                active = mfree(active);
×
1137

1138
                /* Get the active VC (e.g. tty1) */
1139
                r = read_one_line_file("/sys/class/tty/tty0/active", &active);
×
1140
                if (r < 0)
×
1141
                        return r;
1142
                if (r == 0)
×
1143
                        return -ENXIO;
1144

1145
                tty = active;
×
1146
        }
1147

1148
        _cleanup_free_ char *path = NULL;
100✔
1149
        path = path_join("/dev", tty);
100✔
1150
        if (!path)
100✔
1151
                return -ENOMEM;
1152

1153
        *ret = TAKE_PTR(path);
100✔
1154
        return 0;
100✔
1155
}
1156

1157
int get_kernel_consoles(char ***ret) {
1✔
1158
        _cleanup_strv_free_ char **l = NULL;
×
1159
        _cleanup_free_ char *line = NULL;
1✔
1160
        int r;
1✔
1161

1162
        assert(ret);
1✔
1163

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

1169
        r = read_one_line_file("/sys/class/tty/console/active", &line);
×
1170
        if (r < 0)
×
1171
                return r;
1172

1173
        for (const char *p = line;;) {
×
1174
                _cleanup_free_ char *tty = NULL, *path = NULL;
×
1175

1176
                r = extract_first_word(&p, &tty, NULL, 0);
×
1177
                if (r < 0)
×
1178
                        return r;
1179
                if (r == 0)
×
1180
                        break;
1181

1182
                if (streq(tty, "tty0")) {
×
1183
                        tty = mfree(tty);
×
1184
                        r = read_one_line_file("/sys/class/tty/tty0/active", &tty);
×
1185
                        if (r < 0)
×
1186
                                return r;
1187
                        if (r == 0) {
×
1188
                                log_debug("No VT active, skipping /dev/tty0.");
×
1189
                                continue;
×
1190
                        }
1191
                }
1192

1193
                path = path_join("/dev", tty);
×
1194
                if (!path)
×
1195
                        return -ENOMEM;
1196

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

1202
                r = strv_consume(&l, TAKE_PTR(path));
×
1203
                if (r < 0)
×
1204
                        return r;
1205
        }
1206

1207
        if (strv_isempty(l)) {
×
1208
                log_debug("No devices found for system console");
×
1209
                goto fallback;
×
1210
        }
1211

1212
        *ret = TAKE_PTR(l);
×
1213
        return strv_length(*ret);
×
1214

1215
fallback:
1✔
1216
        r = strv_extend(&l, "/dev/console");
1✔
1217
        if (r < 0)
1✔
1218
                return r;
1219

1220
        *ret = TAKE_PTR(l);
1✔
1221
        return 0;
1✔
1222
}
1223

1224
bool tty_is_vc_resolve(const char *tty) {
50✔
1225
        _cleanup_free_ char *resolved = NULL;
50✔
1226

1227
        assert(tty);
50✔
1228

1229
        if (streq(skip_dev_prefix(tty), "console")) {
50✔
1230
                if (resolve_dev_console(&resolved) < 0)
1✔
1231
                        return false;
1232

1233
                tty = resolved;
1✔
1234
        }
1235

1236
        return tty_is_vc(tty);
50✔
1237
}
1238

1239
int fd_columns(int fd) {
1,398✔
1240
        struct winsize ws = {};
1,398✔
1241

1242
        if (fd < 0)
1,398✔
1243
                return -EBADF;
1244

1245
        if (ioctl(fd, TIOCGWINSZ, &ws) < 0)
1,398✔
1246
                return -errno;
1,398✔
1247

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

1251
        return ws.ws_col;
×
1252
}
1253

1254
int getenv_columns(void) {
1,686✔
1255
        int r;
1,686✔
1256

1257
        const char *e = getenv("COLUMNS");
1,686✔
1258
        if (!e)
1,686✔
1259
                return -ENXIO;
1,686✔
1260

1261
        unsigned c;
2✔
1262
        r = safe_atou_bounded(e, 1, USHRT_MAX, &c);
2✔
1263
        if (r < 0)
2✔
1264
                return r;
1265

1266
        return (int) c;
2✔
1267
}
1268

1269
unsigned columns(void) {
274,890✔
1270

1271
        if (cached_columns > 0)
274,890✔
1272
                return cached_columns;
273,490✔
1273

1274
        int c = getenv_columns();
1,400✔
1275
        if (c < 0) {
1,400✔
1276
                c = fd_columns(STDOUT_FILENO);
1,398✔
1277
                if (c < 0)
1,398✔
1278
                        c = 80;
1279
        }
1280

1281
        assert(c > 0);
2✔
1282

1283
        cached_columns = c;
1,400✔
1284
        return cached_columns;
1,400✔
1285
}
1286

1287
int fd_lines(int fd) {
208✔
1288
        struct winsize ws = {};
208✔
1289

1290
        if (fd < 0)
208✔
1291
                return -EBADF;
1292

1293
        if (ioctl(fd, TIOCGWINSZ, &ws) < 0)
208✔
1294
                return -errno;
208✔
1295

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

1299
        return ws.ws_row;
×
1300
}
1301

1302
unsigned lines(void) {
208✔
1303
        const char *e;
208✔
1304
        int l;
208✔
1305

1306
        if (cached_lines > 0)
208✔
1307
                return cached_lines;
×
1308

1309
        l = 0;
208✔
1310
        e = getenv("LINES");
208✔
1311
        if (e)
208✔
1312
                (void) safe_atoi(e, &l);
×
1313

1314
        if (l <= 0 || l > USHRT_MAX) {
208✔
1315
                l = fd_lines(STDOUT_FILENO);
208✔
1316
                if (l <= 0)
208✔
1317
                        l = 24;
208✔
1318
        }
1319

1320
        cached_lines = l;
208✔
1321
        return cached_lines;
208✔
1322
}
1323

1324
int terminal_set_size_fd(int fd, const char *ident, unsigned rows, unsigned cols) {
560✔
1325
        struct winsize ws;
560✔
1326

1327
        assert(fd >= 0);
560✔
1328

1329
        if (!ident)
560✔
1330
                ident = "TTY";
86✔
1331

1332
        if (rows == UINT_MAX && cols == UINT_MAX)
560✔
1333
                return 0;
560✔
1334

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

1340
        if (rows == UINT_MAX)
413✔
1341
                rows = ws.ws_row;
×
1342
        else if (rows > USHRT_MAX)
413✔
1343
                rows = USHRT_MAX;
×
1344

1345
        if (cols == UINT_MAX)
413✔
1346
                cols = ws.ws_col;
×
1347
        else if (cols > USHRT_MAX)
413✔
1348
                cols = USHRT_MAX;
×
1349

1350
        if (rows == ws.ws_row && cols == ws.ws_col)
413✔
1351
                return 0;
1352

1353
        ws.ws_row = rows;
137✔
1354
        ws.ws_col = cols;
137✔
1355

1356
        if (ioctl(fd, TIOCSWINSZ, &ws) < 0)
137✔
1357
                return log_debug_errno(errno, "TIOCSWINSZ ioctl for setting %s size failed: %m", ident);
×
1358

1359
        return 0;
1360
}
1361

1362
int proc_cmdline_tty_size(const char *tty, unsigned *ret_rows, unsigned *ret_cols) {
510✔
1363
        _cleanup_free_ char *rowskey = NULL, *rowsvalue = NULL, *colskey = NULL, *colsvalue = NULL;
510✔
1364
        unsigned rows = UINT_MAX, cols = UINT_MAX;
510✔
1365
        int r;
510✔
1366

1367
        assert(tty);
510✔
1368

1369
        if (!ret_rows && !ret_cols)
510✔
1370
                return 0;
1371

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

1379
        rowskey = strjoin("systemd.tty.rows.", tty);
510✔
1380
        if (!rowskey)
510✔
1381
                return -ENOMEM;
1382

1383
        colskey = strjoin("systemd.tty.columns.", tty);
510✔
1384
        if (!colskey)
510✔
1385
                return -ENOMEM;
1386

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

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

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

1405
        if (ret_rows)
510✔
1406
                *ret_rows = rows;
510✔
1407
        if (ret_cols)
510✔
1408
                *ret_cols = cols;
510✔
1409

1410
        return rows != UINT_MAX || cols != UINT_MAX;
510✔
1411
}
1412

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

1419
void reset_terminal_feature_caches(void) {
24✔
1420
        cached_columns = 0;
24✔
1421
        cached_lines = 0;
24✔
1422

1423
        cached_on_tty = -1;
24✔
1424
        cached_on_dev_null = -1;
24✔
1425

1426
        reset_ansi_feature_caches();
24✔
1427
}
24✔
1428

1429
bool on_tty(void) {
7,475,774✔
1430

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

1437
        if (cached_on_tty < 0)
7,475,774✔
1438
                cached_on_tty =
33,397✔
1439
                        isatty_safe(STDOUT_FILENO) &&
33,434✔
1440
                        isatty_safe(STDERR_FILENO);
37✔
1441

1442
        return cached_on_tty;
7,475,774✔
1443
}
1444

1445
int getttyname_malloc(int fd, char **ret) {
574✔
1446
        char path[PATH_MAX]; /* PATH_MAX is counted *with* the trailing NUL byte */
574✔
1447
        int r;
574✔
1448

1449
        assert(fd >= 0);
574✔
1450
        assert(ret);
574✔
1451

1452
        r = ttyname_r(fd, path, sizeof path); /* positive error */
574✔
1453
        assert(r >= 0);
574✔
1454
        if (r == ERANGE)
574✔
1455
                return -ENAMETOOLONG;
574✔
1456
        if (r > 0)
574✔
1457
                return -r;
567✔
1458

1459
        return strdup_to(ret, skip_dev_prefix(path));
7✔
1460
}
1461

1462
int getttyname_harder(int fd, char **ret) {
24✔
1463
        _cleanup_free_ char *s = NULL;
24✔
1464
        int r;
24✔
1465

1466
        r = getttyname_malloc(fd, &s);
24✔
1467
        if (r < 0)
24✔
1468
                return r;
1469

1470
        if (streq(s, "tty"))
×
1471
                return get_ctty(0, NULL, ret);
×
1472

1473
        *ret = TAKE_PTR(s);
×
1474
        return 0;
×
1475
}
1476

1477
int get_ctty_devnr(pid_t pid, dev_t *ret) {
5,333✔
1478
        _cleanup_free_ char *line = NULL;
5,333✔
1479
        unsigned long ttynr;
5,333✔
1480
        const char *p;
5,333✔
1481
        int r;
5,333✔
1482

1483
        assert(pid >= 0);
5,333✔
1484

1485
        p = procfs_file_alloca(pid, "stat");
26,325✔
1486
        r = read_one_line_file(p, &line);
5,333✔
1487
        if (r < 0)
5,333✔
1488
                return r;
1489

1490
        p = strrchr(line, ')');
5,333✔
1491
        if (!p)
5,333✔
1492
                return -EIO;
1493

1494
        p++;
5,333✔
1495

1496
        if (sscanf(p, " "
5,333✔
1497
                   "%*c "  /* state */
1498
                   "%*d "  /* ppid */
1499
                   "%*d "  /* pgrp */
1500
                   "%*d "  /* session */
1501
                   "%lu ", /* ttynr */
1502
                   &ttynr) != 1)
1503
                return -EIO;
1504

1505
        if (devnum_is_zero(ttynr))
5,333✔
1506
                return -ENXIO;
1507

1508
        if (ret)
4✔
1509
                *ret = (dev_t) ttynr;
2✔
1510

1511
        return 0;
1512
}
1513

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

1521
        r = get_ctty_devnr(pid, &devnr);
31✔
1522
        if (r < 0)
31✔
1523
                return r;
1524

1525
        r = device_path_make_canonical(S_IFCHR, devnr, &buf);
2✔
1526
        if (r < 0) {
2✔
1527
                struct stat st;
2✔
1528

1529
                if (r != -ENOENT) /* No symlink for this in /dev/char/? */
2✔
1530
                        return r;
×
1531

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

1540
                if (stat(pty, &st) < 0) {
2✔
1541
                        if (errno != ENOENT)
×
1542
                                return -errno;
×
1543

1544
                } else if (S_ISCHR(st.st_mode) && devnr == st.st_rdev) /* Bingo! */
2✔
1545
                        fn = pty;
1546

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

1554
                        fn = buf;
×
1555
                }
1556
        } else
1557
                fn = buf;
×
1558

1559
        w = path_startswith(fn, "/dev/");
2✔
1560
        if (!w)
2✔
1561
                return -EINVAL;
1562

1563
        if (ret) {
2✔
1564
                r = strdup_to(ret, w);
2✔
1565
                if (r < 0)
2✔
1566
                        return r;
1567
        }
1568

1569
        if (ret_devnr)
2✔
1570
                *ret_devnr = devnr;
×
1571

1572
        return 0;
1573
}
1574

1575
int ptsname_malloc(int fd, char **ret) {
136✔
1576
        assert(fd >= 0);
136✔
1577
        assert(ret);
136✔
1578

1579
        for (size_t l = 50;;) {
×
1580
                _cleanup_free_ char *c = NULL;
×
1581

1582
                c = new(char, l);
136✔
1583
                if (!c)
136✔
1584
                        return -ENOMEM;
1585

1586
                if (ptsname_r(fd, c, l) >= 0) {
136✔
1587
                        *ret = TAKE_PTR(c);
136✔
1588
                        return 0;
136✔
1589
                }
1590
                if (errno != ERANGE)
×
1591
                        return -errno;
×
1592

1593
                if (!MUL_ASSIGN_SAFE(&l, 2))
×
1594
                        return -ENOMEM;
1595
        }
1596
}
1597

1598
int openpt_allocate(int flags, char **ret_peer_path) {
140✔
1599
        _cleanup_close_ int fd = -EBADF;
140✔
1600
        int r;
140✔
1601

1602
        fd = posix_openpt(flags|O_NOCTTY|O_CLOEXEC);
140✔
1603
        if (fd < 0)
140✔
1604
                return -errno;
×
1605

1606
        _cleanup_free_ char *p = NULL;
140✔
1607
        if (ret_peer_path) {
140✔
1608
                r = ptsname_malloc(fd, &p);
136✔
1609
                if (r < 0)
136✔
1610
                        return r;
1611

1612
                if (!path_startswith(p, "/dev/pts/"))
136✔
1613
                        return -EINVAL;
1614
        }
1615

1616
        if (unlockpt(fd) < 0)
140✔
1617
                return -errno;
×
1618

1619
        if (ret_peer_path)
140✔
1620
                *ret_peer_path = TAKE_PTR(p);
136✔
1621

1622
        return TAKE_FD(fd);
1623
}
1624

1625
static int ptsname_namespace(int pty, char **ret) {
×
1626
        int no = -1;
×
1627

1628
        assert(pty >= 0);
×
1629
        assert(ret);
×
1630

1631
        /* Like ptsname(), but doesn't assume that the path is
1632
         * accessible in the local namespace. */
1633

1634
        if (ioctl(pty, TIOCGPTN, &no) < 0)
×
1635
                return -errno;
×
1636

1637
        if (no < 0)
×
1638
                return -EIO;
1639

1640
        if (asprintf(ret, "/dev/pts/%i", no) < 0)
×
1641
                return -ENOMEM;
×
1642

1643
        return 0;
1644
}
1645

1646
int openpt_allocate_in_namespace(
×
1647
                const PidRef *pidref,
1648
                int flags,
1649
                char **ret_peer_path) {
1650

1651
        _cleanup_close_ int pidnsfd = -EBADF, mntnsfd = -EBADF, usernsfd = -EBADF, rootfd = -EBADF, fd = -EBADF;
×
1652
        _cleanup_close_pair_ int pair[2] = EBADF_PAIR;
×
1653
        int r;
×
1654

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

1659
        if (socketpair(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0, pair) < 0)
×
1660
                return -errno;
×
1661

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

1677
                fd = openpt_allocate(flags, /* ret_peer_path= */ NULL);
×
1678
                if (fd < 0)
×
1679
                        _exit(EXIT_FAILURE);
×
1680

1681
                if (send_one_fd(pair[1], fd, 0) < 0)
×
1682
                        _exit(EXIT_FAILURE);
×
1683

1684
                _exit(EXIT_SUCCESS);
×
1685
        }
1686

1687
        pair[1] = safe_close(pair[1]);
×
1688

1689
        fd = receive_one_fd(pair[0], 0);
×
1690
        if (fd < 0)
×
1691
                return fd;
1692

1693
        if (ret_peer_path) {
×
1694
                r = ptsname_namespace(fd, ret_peer_path);
×
1695
                if (r < 0)
×
1696
                        return r;
×
1697
        }
1698

1699
        return TAKE_FD(fd);
1700
}
1701

1702
static bool on_dev_null(void) {
40,768✔
1703
        struct stat dst, ost, est;
40,768✔
1704

1705
        if (cached_on_dev_null >= 0)
40,768✔
1706
                return cached_on_dev_null;
7,499✔
1707

1708
        if (stat("/dev/null", &dst) < 0 || fstat(STDOUT_FILENO, &ost) < 0 || fstat(STDERR_FILENO, &est) < 0)
33,269✔
1709
                cached_on_dev_null = false;
1✔
1710
        else
1711
                cached_on_dev_null = stat_inode_same(&dst, &ost) && stat_inode_same(&dst, &est);
37,103✔
1712

1713
        return cached_on_dev_null;
33,269✔
1714
}
1715

1716
bool term_env_valid(const char *term) {
×
1717
        /* Checks if the specified $TERM value is suitable for propagation, i.e. is not empty, not set to
1718
         * "unknown" (as is common in CI), and only contains characters valid in terminal type names.
1719
         * Valid $TERM values are things like "xterm-256color", "linux", "screen.xterm-256color", i.e.
1720
         * alphanumeric characters, hyphens, underscores, dots, and plus signs. */
1721
        return !isempty(term) &&
×
1722
                !streq(term, "unknown") &&
×
1723
                in_charset(term, ALPHANUMERICAL "-_+.");
×
1724
}
1725

1726
bool getenv_terminal_is_dumb(void) {
13,860✔
1727
        const char *e;
13,860✔
1728

1729
        e = getenv("TERM");
13,860✔
1730
        if (!e)
13,860✔
1731
                return true;
1732

1733
        return streq(e, "dumb");
1,305✔
1734
}
1735

1736
bool terminal_is_dumb(void) {
40,805✔
1737
        if (!on_tty() && !on_dev_null())
40,805✔
1738
                return true;
1739

1740
        return getenv_terminal_is_dumb();
59✔
1741
}
1742

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

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

1754
        m = parse_systemd_colors();
×
1755
        if (m >= 0)
×
1756
                return m;
×
1757

1758
        if (getenv("NO_COLOR"))
×
1759
                return false;
1760

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

1764
        return !streq_ptr(s, "dumb");
×
1765
}
1766

1767
int vt_restore(int fd) {
×
1768

1769
        static const struct vt_mode mode = {
×
1770
                .mode = VT_AUTO,
1771
        };
1772

1773
        int r, ret = 0;
×
1774

1775
        assert(fd >= 0);
×
1776

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

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

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

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

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

1794
        return ret;
1795
}
1796

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

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

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

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

1810
        if (restore)
×
1811
                return vt_restore(fd);
×
1812

1813
        return 0;
1814
}
1815

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

1820
        if (priority <= LOG_ERR) {
226,570✔
1821
                if (on)
6,754✔
1822
                        *on = ansi_highlight_red();
13,508✔
1823
                if (off)
6,754✔
1824
                        *off = ansi_normal();
13,508✔
1825
                if (highlight)
6,754✔
1826
                        *highlight = ansi_highlight();
×
1827

1828
        } else if (priority <= LOG_WARNING) {
219,816✔
1829
                if (on)
1,001✔
1830
                        *on = ansi_highlight_yellow();
1,001✔
1831
                if (off)
1,001✔
1832
                        *off = ansi_normal();
2,002✔
1833
                if (highlight)
1,001✔
1834
                        *highlight = ansi_highlight();
×
1835

1836
        } else if (priority <= LOG_NOTICE) {
218,815✔
1837
                if (on)
1,081✔
1838
                        *on = ansi_highlight();
2,162✔
1839
                if (off)
1,081✔
1840
                        *off = ansi_normal();
2,162✔
1841
                if (highlight)
1,081✔
1842
                        *highlight = ansi_highlight_red();
×
1843

1844
        } else if (priority >= LOG_DEBUG) {
217,734✔
1845
                if (on)
169,894✔
1846
                        *on = ansi_grey();
169,894✔
1847
                if (off)
169,894✔
1848
                        *off = ansi_normal();
339,788✔
1849
                if (highlight)
169,894✔
1850
                        *highlight = ansi_highlight_red();
×
1851
        }
1852
}
226,570✔
1853

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

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

1860
        return loop_write(fd, cursor_position, SIZE_MAX);
×
1861
}
1862

1863
static int terminal_verify_same(int input_fd, int output_fd) {
×
1864
        assert(input_fd >= 0);
×
1865
        assert(output_fd >= 0);
×
1866

1867
        /* Validates that the specified fds reference the same TTY */
1868

1869
        if (input_fd != output_fd) {
×
1870
                struct stat sti;
×
1871
                if (fstat(input_fd, &sti) < 0)
×
1872
                        return -errno;
×
1873

1874
                if (!S_ISCHR(sti.st_mode)) /* TTYs are character devices */
×
1875
                        return -ENOTTY;
1876

1877
                struct stat sto;
×
1878
                if (fstat(output_fd, &sto) < 0)
×
1879
                        return -errno;
×
1880

1881
                if (!S_ISCHR(sto.st_mode))
×
1882
                        return -ENOTTY;
1883

1884
                if (sti.st_rdev != sto.st_rdev)
×
1885
                        return -ENOLINK;
1886
        }
1887

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

1891
        return 0;
1892
}
1893

1894
typedef enum CursorPositionState {
1895
        CURSOR_TEXT,
1896
        CURSOR_ESCAPE,
1897
        CURSOR_ROW,
1898
        CURSOR_COLUMN,
1899
} CursorPositionState;
1900

1901
typedef struct CursorPositionContext {
1902
        CursorPositionState state;
1903
        unsigned row, column;
1904
} CursorPositionContext;
1905

1906
static int scan_cursor_position_response(
×
1907
                CursorPositionContext *context,
1908
                const char *buf,
1909
                size_t size,
1910
                size_t *ret_processed) {
1911

1912
        assert(context);
×
1913
        assert(buf);
×
1914
        assert(ret_processed);
×
1915

1916
        for (size_t i = 0; i < size; i++) {
×
1917
                char c = buf[i];
×
1918

1919
                switch (context->state) {
×
1920

1921
                case CURSOR_TEXT:
×
1922
                        context->state = c == '\x1B' ? CURSOR_ESCAPE : CURSOR_TEXT;
×
1923
                        break;
×
1924

1925
                case CURSOR_ESCAPE:
×
1926
                        context->state = c == '[' ? CURSOR_ROW : CURSOR_TEXT;
×
1927
                        break;
×
1928

1929
                case CURSOR_ROW:
×
1930
                        if (c == ';')
×
1931
                                context->state = context->row > 0 ? CURSOR_COLUMN : CURSOR_TEXT;
×
1932
                        else {
1933
                                int d = undecchar(c);
×
1934

1935
                                /* We read a decimal character, let's suffix it to the number we so far read,
1936
                                 * but let's do an overflow check first. */
1937
                                if (d < 0 || context->row > (UINT_MAX-d)/10)
×
1938
                                        context->state = CURSOR_TEXT;
×
1939
                                else
1940
                                        context->row = context->row * 10 + d;
×
1941
                        }
1942
                        break;
1943

1944
                case CURSOR_COLUMN:
×
1945
                        if (c == 'R') {
×
1946
                                if (context->column > 0) {
×
1947
                                        *ret_processed = i + 1;
×
1948
                                        return 1; /* success! */
×
1949
                                }
1950

1951
                                context->state = CURSOR_TEXT;
×
1952
                        } else {
1953
                                int d = undecchar(c);
×
1954

1955
                                /* As above, add the decimal character to our column number */
1956
                                if (d < 0 || context->column > (UINT_MAX-d)/10)
×
1957
                                        context->state = CURSOR_TEXT;
×
1958
                                else
1959
                                        context->column = context->column * 10 + d;
×
1960
                        }
1961

1962
                        break;
1963
                }
1964

1965
                /* Reset any positions we might have picked up */
1966
                if (IN_SET(context->state, CURSOR_TEXT, CURSOR_ESCAPE))
×
1967
                        context->row = context->column = 0;
×
1968
        }
1969

1970
        *ret_processed = size;
×
1971
        return 0; /* all good, but not enough data yet */
×
1972
}
1973

1974
int terminal_get_cursor_position(
×
1975
                int input_fd,
1976
                int output_fd,
1977
                unsigned *ret_row,
1978
                unsigned *ret_column) {
1979

1980
        _cleanup_close_ int nonblock_input_fd = -EBADF;
×
1981
        int r;
×
1982

1983
        assert(input_fd >= 0);
×
1984
        assert(output_fd >= 0);
×
1985

1986
        if (getenv_terminal_is_dumb())
×
1987
                return -EOPNOTSUPP;
1988

1989
        r = terminal_verify_same(input_fd, output_fd);
×
1990
        if (r < 0)
×
1991
                return log_debug_errno(r, "Called with distinct input/output fds: %m");
×
1992

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

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

2001
        struct termios new_termios = old_termios;
×
2002
        termios_disable_echo(&new_termios);
×
2003

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

2007
        /* Request cursor position (DSR/CPR) */
2008
        r = loop_write(output_fd, "\x1B[6n", SIZE_MAX);
×
2009
        if (r < 0)
×
2010
                return r;
2011

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

2015
        nonblock_input_fd = r = fd_reopen(input_fd, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
×
2016
        if (r < 0)
×
2017
                return r;
2018

2019
        usec_t end = usec_add(now(CLOCK_MONOTONIC), CONSOLE_ANSI_SEQUENCE_TIMEOUT_USEC);
×
2020
        char buf[STRLEN("\x1B[1;1R")]; /* The shortest valid reply possible */
×
2021
        size_t buf_full = 0;
×
2022
        CursorPositionContext context = {};
×
2023

2024
        for (bool first = true;; first = false) {
×
2025
                if (buf_full == 0) {
×
2026
                        usec_t n = now(CLOCK_MONOTONIC);
×
2027
                        if (n >= end)
×
2028
                                return -EOPNOTSUPP;
×
2029

2030
                        r = fd_wait_for_event(nonblock_input_fd, POLLIN, usec_sub_unsigned(end, n));
×
2031
                        if (r < 0)
×
2032
                                return r;
2033
                        if (r == 0)
×
2034
                                return -EOPNOTSUPP;
2035

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

2044
                                return -errno;
×
2045
                        }
2046

2047
                        assert((size_t) l <= sizeof(buf));
×
2048
                        buf_full = l;
2049
                }
2050

2051
                size_t processed;
×
2052
                r = scan_cursor_position_response(&context, buf, buf_full, &processed);
×
2053
                if (r < 0)
×
2054
                        return r;
2055

2056
                assert(processed <= buf_full);
×
2057
                buf_full -= processed;
×
2058
                memmove(buf, buf + processed, buf_full);
×
2059

2060
                if (r > 0) {
×
2061
                        /* Superficial validity check */
2062
                        if (context.row >= 32766 || context.column >= 32766)
×
2063
                                return -ENODATA;
2064

2065
                        if (ret_row)
×
2066
                                *ret_row = context.row;
×
2067
                        if (ret_column)
×
2068
                                *ret_column = context.column;
×
2069

2070
                        return 0;
×
2071
                }
2072
        }
2073
}
2074

2075
int terminal_reset_defensive(int fd, TerminalResetFlags flags) {
414✔
2076
        int r = 0;
414✔
2077

2078
        assert(fd >= 0);
414✔
2079
        assert(!FLAGS_SET(flags, TERMINAL_RESET_AVOID_ANSI_SEQ|TERMINAL_RESET_FORCE_ANSI_SEQ));
414✔
2080

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

2084
        if (!isatty_safe(fd))
414✔
2085
                return -ENOTTY;
414✔
2086

2087
        RET_GATHER(r, terminal_reset_ioctl(fd, FLAGS_SET(flags, TERMINAL_RESET_SWITCH_TO_TEXT)));
407✔
2088

2089
        if (!FLAGS_SET(flags, TERMINAL_RESET_AVOID_ANSI_SEQ) &&
407✔
2090
            (FLAGS_SET(flags, TERMINAL_RESET_FORCE_ANSI_SEQ) || !getenv_terminal_is_dumb()))
401✔
2091
                RET_GATHER(r, terminal_reset_ansi_seq(fd));
401✔
2092

2093
        return r;
2094
}
2095

2096
int terminal_reset_defensive_locked(int fd, TerminalResetFlags flags) {
6✔
2097
        assert(fd >= 0);
6✔
2098

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

2103
        return terminal_reset_defensive(fd, flags);
6✔
2104
}
2105

2106
void termios_disable_echo(struct termios *termios) {
1✔
2107
        assert(termios);
1✔
2108

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

2114
static bool termios_is_null(const struct termios *t) {
9✔
2115
        if (!t)
9✔
2116
                return true;
2117

2118
        return t->c_iflag == UINT_MAX &&
17✔
2119
               t->c_oflag == UINT_MAX &&
8✔
2120
               t->c_cflag == UINT_MAX &&
26✔
2121
               t->c_lflag == UINT_MAX;
8✔
2122
}
2123

2124
void termios_reset(const TermiosResetContext *c) {
112✔
2125
        assert(c);
112✔
2126

2127
        PROTECT_ERRNO;
112✔
2128

2129
        if (c->fd && *c->fd >= 0 && !termios_is_null(c->termios))
112✔
2130
                (void) tcsetattr(*c->fd, TCSANOW, c->termios);
1✔
2131
}
112✔
2132

2133
typedef enum BackgroundColorState {
2134
        BACKGROUND_TEXT,
2135
        BACKGROUND_ESCAPE,
2136
        BACKGROUND_BRACKET,
2137
        BACKGROUND_FIRST_ONE,
2138
        BACKGROUND_SECOND_ONE,
2139
        BACKGROUND_SEMICOLON,
2140
        BACKGROUND_R,
2141
        BACKGROUND_G,
2142
        BACKGROUND_B,
2143
        BACKGROUND_RED,
2144
        BACKGROUND_GREEN,
2145
        BACKGROUND_BLUE,
2146
        BACKGROUND_STRING_TERMINATOR,
2147
} BackgroundColorState;
2148

2149
typedef struct BackgroundColorContext {
2150
        BackgroundColorState state;
2151
        uint32_t red, green, blue;
2152
        unsigned red_bits, green_bits, blue_bits;
2153
} BackgroundColorContext;
2154

2155
static int scan_background_color_response(
×
2156
                BackgroundColorContext *context,
2157
                const char *buf,
2158
                size_t size,
2159
                size_t *ret_processed) {
2160

2161
        assert(context);
×
2162
        assert(buf);
×
2163
        assert(ret_processed);
×
2164

2165
        for (size_t i = 0; i < size; i++) {
×
2166
                char c = buf[i];
×
2167

2168
                switch (context->state) {
×
2169

2170
                case BACKGROUND_TEXT:
×
2171
                        context->state = c == '\x1B' ? BACKGROUND_ESCAPE : BACKGROUND_TEXT;
×
2172
                        break;
×
2173

2174
                case BACKGROUND_ESCAPE:
×
2175
                        context->state = c == ']' ? BACKGROUND_BRACKET : BACKGROUND_TEXT;
×
2176
                        break;
×
2177

2178
                case BACKGROUND_BRACKET:
×
2179
                        context->state = c == '1' ? BACKGROUND_FIRST_ONE : BACKGROUND_TEXT;
×
2180
                        break;
×
2181

2182
                case BACKGROUND_FIRST_ONE:
×
2183
                        context->state = c == '1' ? BACKGROUND_SECOND_ONE : BACKGROUND_TEXT;
×
2184
                        break;
×
2185

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

2190
                case BACKGROUND_SEMICOLON:
×
2191
                        context->state = c == 'r' ? BACKGROUND_R : BACKGROUND_TEXT;
×
2192
                        break;
×
2193

2194
                case BACKGROUND_R:
×
2195
                        context->state = c == 'g' ? BACKGROUND_G : BACKGROUND_TEXT;
×
2196
                        break;
×
2197

2198
                case BACKGROUND_G:
×
2199
                        context->state = c == 'b' ? BACKGROUND_B : BACKGROUND_TEXT;
×
2200
                        break;
×
2201

2202
                case BACKGROUND_B:
×
2203
                        context->state = c == ':' ? BACKGROUND_RED : BACKGROUND_TEXT;
×
2204
                        break;
×
2205

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

2220
                case BACKGROUND_GREEN:
×
2221
                        if (c == '/')
×
2222
                                context->state = context->green_bits > 0 ? BACKGROUND_BLUE : BACKGROUND_TEXT;
×
2223
                        else {
2224
                                int d = unhexchar(c);
×
2225
                                if (d < 0 || context->green_bits >= sizeof(context->green)*8)
×
2226
                                        context->state = BACKGROUND_TEXT;
×
2227
                                else {
2228
                                        context->green = (context->green << 4) | d;
×
2229
                                        context->green_bits += 4;
×
2230
                                }
2231
                        }
2232
                        break;
2233

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

2241
                                context->state = BACKGROUND_TEXT;
×
2242
                        } else if (c == '\x1b')
×
2243
                                context->state = context->blue_bits > 0 ? BACKGROUND_STRING_TERMINATOR : BACKGROUND_TEXT;
×
2244
                        else {
2245
                                int d = unhexchar(c);
×
2246
                                if (d < 0 || context->blue_bits >= sizeof(context->blue)*8)
×
2247
                                        context->state = BACKGROUND_TEXT;
×
2248
                                else {
2249
                                        context->blue = (context->blue << 4) | d;
×
2250
                                        context->blue_bits += 4;
×
2251
                                }
2252
                        }
2253
                        break;
2254

2255
                case BACKGROUND_STRING_TERMINATOR:
×
2256
                        if (c == '\\') {
×
2257
                                *ret_processed = i + 1;
×
2258
                                return 1; /* success! */
×
2259
                        }
2260

2261
                        context->state = c == ']' ? BACKGROUND_ESCAPE : BACKGROUND_TEXT;
×
2262
                        break;
×
2263

2264
                }
2265

2266
                /* Reset any colors we might have picked up */
2267
                if (IN_SET(context->state, BACKGROUND_TEXT, BACKGROUND_ESCAPE)) {
×
2268
                        /* reset color */
2269
                        context->red = context->green = context->blue = 0;
×
2270
                        context->red_bits = context->green_bits = context->blue_bits = 0;
×
2271
                }
2272
        }
2273

2274
        *ret_processed = size;
×
2275
        return 0; /* all good, but not enough data yet */
×
2276
}
2277

2278
int get_default_background_color(double *ret_red, double *ret_green, double *ret_blue) {
170✔
2279
        int r;
170✔
2280

2281
        assert(ret_red);
170✔
2282
        assert(ret_green);
170✔
2283
        assert(ret_blue);
170✔
2284

2285
        if (!colors_enabled())
170✔
2286
                return -EOPNOTSUPP;
170✔
2287

2288
        r = terminal_verify_same(STDIN_FILENO, STDOUT_FILENO);
×
2289
        if (r < 0)
×
2290
                return r;
2291

2292
        if (streq_ptr(getenv("TERM"), "linux")) {
×
2293
                /* Linux console is black */
2294
                *ret_red = *ret_green = *ret_blue = 0.0;
×
2295
                return 0;
×
2296
        }
2297

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

2304
        struct termios old_termios = TERMIOS_NULL;
×
2305
        CLEANUP_TERMIOS_RESET(nonblock_input_fd, old_termios);
×
2306

2307
        if (tcgetattr(nonblock_input_fd, &old_termios) < 0)
×
2308
                return -errno;
×
2309

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

2313
        if (tcsetattr(nonblock_input_fd, TCSANOW, &new_termios) < 0)
×
2314
                return -errno;
×
2315

2316
        r = loop_write(STDOUT_FILENO, ANSI_OSC "11;?" ANSI_ST, SIZE_MAX);
×
2317
        if (r < 0)
×
2318
                return r;
2319

2320
        usec_t end = usec_add(now(CLOCK_MONOTONIC), CONSOLE_ANSI_SEQUENCE_TIMEOUT_USEC);
×
2321
        char buf[STRLEN(ANSI_OSC "11;rgb:0/0/0" ANSI_ST)]; /* shortest possible reply */
×
2322
        size_t buf_full = 0;
×
2323
        BackgroundColorContext context = {};
×
2324

2325
        for (bool first = true;; first = false) {
×
2326
                if (buf_full == 0) {
×
2327
                        usec_t n = now(CLOCK_MONOTONIC);
×
2328
                        if (n >= end)
×
2329
                                return -EOPNOTSUPP;
×
2330

2331
                        r = fd_wait_for_event(nonblock_input_fd, POLLIN, usec_sub_unsigned(end, n));
×
2332
                        if (r < 0)
×
2333
                                return r;
2334
                        if (r == 0)
×
2335
                                return -EOPNOTSUPP;
2336

2337
                        /* On the first try, read multiple characters, i.e. the shortest valid
2338
                         * reply. Afterwards read byte-wise, since we don't want to read too much, and
2339
                         * unnecessarily drop too many characters from the input queue. */
2340
                        ssize_t l = read(nonblock_input_fd, buf, first ? sizeof(buf) : 1);
×
2341
                        if (l < 0) {
×
2342
                                if (errno == EAGAIN)
×
2343
                                        continue;
×
2344
                                return -errno;
×
2345
                        }
2346

2347
                        assert((size_t) l <= sizeof(buf));
×
2348
                        buf_full = l;
2349
                }
2350

2351
                size_t processed;
×
2352
                r = scan_background_color_response(&context, buf, buf_full, &processed);
×
2353
                if (r < 0)
×
2354
                        return r;
2355

2356
                assert(processed <= buf_full);
×
2357
                buf_full -= processed;
×
2358
                memmove(buf, buf + processed, buf_full);
×
2359

2360
                if (r > 0) {
×
2361
                        assert(context.red_bits > 0);
×
2362
                        *ret_red = (double) context.red / ((UINT64_C(1) << context.red_bits) - 1);
×
2363
                        assert(context.green_bits > 0);
×
2364
                        *ret_green = (double) context.green / ((UINT64_C(1) << context.green_bits) - 1);
×
2365
                        assert(context.blue_bits > 0);
×
2366
                        *ret_blue = (double) context.blue / ((UINT64_C(1) << context.blue_bits) - 1);
×
2367
                        return 0;
×
2368
                }
2369
        }
2370
}
2371

2372
/* Determine terminal dimensions by means of ANSI sequences: save the cursor via DECSC, position it far
2373
 * to the bottom right (clamped to actual terminal dimensions), read back via DSR where we ended up, and
2374
 * restore cursor via DECRC. Only needs a single DSR round-trip, and always restores the cursor regardless
2375
 * of whether the response is received.
2376
 *
2377
 * Caller must have already opened a non-blocking input fd and configured termios (echo/icanon off). */
2378
static int terminal_query_size_by_dsr(
×
2379
                int nonblock_input_fd,
2380
                int output_fd,
2381
                unsigned *ret_rows,
2382
                unsigned *ret_columns) {
2383

2384
        int r;
×
2385

2386
        assert(nonblock_input_fd >= 0);
×
2387
        assert(output_fd >= 0);
×
2388

2389
        /* Use DECSC/DECRC to save/restore cursor instead of querying position via DSR. This way the cursor
2390
         * is always restored — even on timeout — and we only need one DSR response instead of two. */
2391
        r = loop_write_full(output_fd,
×
2392
                            "\x1B" "7"              /* DECSC: save cursor position */
2393
                            "\x1B[32766;32766H"     /* CUP: position cursor far to the right and to the bottom, staying within 16bit signed range */
2394
                            "\x1B[6n"               /* DSR: request cursor position (CPR) */
2395
                            "\x1B" "8",             /* DECRC: restore cursor position */
2396
                            SIZE_MAX,
2397
                            CONSOLE_ANSI_SEQUENCE_TIMEOUT_USEC);
2398
        if (r < 0)
×
2399
                return r;
×
2400

2401
        usec_t end = usec_add(now(CLOCK_MONOTONIC), CONSOLE_ANSI_SEQUENCE_TIMEOUT_USEC);
×
2402
        char buf[STRLEN("\x1B[1;1R")]; /* The shortest valid reply possible */
×
2403
        size_t buf_full = 0;
×
2404
        CursorPositionContext context = {};
×
2405

2406
        for (bool first = true;; first = false) {
×
2407
                if (buf_full == 0) {
×
2408
                        usec_t n = now(CLOCK_MONOTONIC);
×
2409
                        if (n >= end)
×
2410
                                return -EOPNOTSUPP;
×
2411

2412
                        r = fd_wait_for_event(nonblock_input_fd, POLLIN, usec_sub_unsigned(end, n));
×
2413
                        if (r < 0)
×
2414
                                return r;
2415
                        if (r == 0)
×
2416
                                return -EOPNOTSUPP;
2417

2418
                        /* On the first try, read multiple characters, i.e. the shortest valid
2419
                         * reply. Afterwards read byte-wise, since we don't want to read too much, and
2420
                         * unnecessarily drop too many characters from the input queue. */
2421
                        ssize_t l = read(nonblock_input_fd, buf, first ? sizeof(buf) : 1);
×
2422
                        if (l < 0) {
×
2423
                                if (errno == EAGAIN)
×
2424
                                        continue;
×
2425

2426
                                return -errno;
×
2427
                        }
2428

2429
                        assert((size_t) l <= sizeof(buf));
×
2430
                        buf_full = l;
2431
                }
2432

2433
                size_t processed;
×
2434
                r = scan_cursor_position_response(&context, buf, buf_full, &processed);
×
2435
                if (r < 0)
×
2436
                        return r;
2437

2438
                assert(processed <= buf_full);
×
2439
                buf_full -= processed;
×
2440
                memmove(buf, buf + processed, buf_full);
×
2441

2442
                if (r > 0) {
×
2443
                        /* Superficial validity checks (no particular reason to check for < 4, it's
2444
                         * just a way to look for unreasonably small values) */
2445
                        if (context.row < 4 || context.column < 4 || context.row >= 32766 || context.column >= 32766)
×
2446
                                return -ENODATA;
2447

2448
                        if (ret_rows)
×
2449
                                *ret_rows = context.row;
×
2450
                        if (ret_columns)
×
2451
                                *ret_columns = context.column;
×
2452

2453
                        return 0;
×
2454
                }
2455
        }
2456
}
2457

2458
/* Common setup for ANSI terminal queries: validate the fds, open a non-blocking input fd, and configure
2459
 * termios with echo and canonical mode disabled. Caller must restore termios and close the fd when done. */
2460
static int terminal_prepare_query(
103✔
2461
                int input_fd,
2462
                int output_fd,
2463
                int *ret_nonblock_fd,
2464
                struct termios *ret_saved_termios) {
2465

2466
        int r;
103✔
2467

2468
        assert(input_fd >= 0);
103✔
2469
        assert(output_fd >= 0);
103✔
2470
        assert(ret_nonblock_fd);
103✔
2471
        assert(ret_saved_termios);
103✔
2472

2473
        /* Use getenv_terminal_is_dumb() instead of terminal_is_dumb() here since we operate on an
2474
         * explicitly passed fd, not on stdio. terminal_is_dumb() additionally checks on_tty() which
2475
         * tests whether *stderr* is a tty — that's irrelevant when we're querying a directly opened
2476
         * terminal such as /dev/console. */
2477
        if (getenv_terminal_is_dumb())
103✔
2478
                return -EOPNOTSUPP;
103✔
2479

2480
        r = terminal_verify_same(input_fd, output_fd);
×
2481
        if (r < 0)
×
2482
                return log_debug_errno(r, "Called with distinct input/output fds: %m");
×
2483

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

2490
        if (tcgetattr(nonblock_input_fd, ret_saved_termios) < 0)
×
2491
                return log_debug_errno(errno, "Failed to get terminal settings: %m");
×
2492

2493
        struct termios new_termios = *ret_saved_termios;
×
2494
        termios_disable_echo(&new_termios);
×
2495

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

2499
        *ret_nonblock_fd = TAKE_FD(nonblock_input_fd);
×
2500
        return 0;
×
2501
}
2502

2503
/*
2504
 * See https://terminalguide.namepad.de/seq/csi_st-18/,
2505
 * https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Functions-using-CSI-_-ordered-by-the-final-character_s_.
2506
 */
2507
#define CSI18_Q  "\x1B[18t"               /* Report the size of the text area in characters */
2508
#define CSI18_Rp "\x1B[8;"                /* Reply prefix */
2509
#define CSI18_R0 CSI18_Rp "1;1t"          /* Shortest reply */
2510
#define CSI18_R1 CSI18_Rp "32766;32766t"  /* Longest reply */
2511

2512
static int scan_text_area_size_response(
×
2513
                const char *buf,
2514
                size_t size,
2515
                unsigned *ret_rows,
2516
                unsigned *ret_columns) {
2517

2518
        assert(buf);
×
2519
        assert(ret_rows);
×
2520
        assert(ret_columns);
×
2521

2522
        /* Check if we have enough space for the shortest possible answer. */
2523
        if (size < STRLEN(CSI18_R0))
×
2524
                return -EAGAIN;
×
2525

2526
        /* Check if the terminating sequence is present */
2527
        if (buf[size - 1] != 't')
×
2528
                return -EAGAIN;
2529

2530
        unsigned short rows, columns;
×
2531
        if (sscanf(buf, CSI18_Rp "%hu;%hut", &rows, &columns) != 2)
×
2532
                return -EINVAL;
2533

2534
        *ret_rows = rows;
×
2535
        *ret_columns = columns;
×
2536
        return 0;
×
2537
}
2538

2539
/* Determine terminal dimensions by means of an ANSI CSI 18 sequence.
2540
 *
2541
 * Caller must have already opened a non-blocking input fd and configured termios (echo/icanon off). */
2542
static int terminal_query_size_by_csi18(
×
2543
                int nonblock_input_fd,
2544
                int output_fd,
2545
                unsigned *ret_rows,
2546
                unsigned *ret_columns) {
2547

2548
        int r;
×
2549

2550
        assert(nonblock_input_fd >= 0);
×
2551
        assert(output_fd >= 0);
×
2552

2553
        r = loop_write_full(output_fd, CSI18_Q, SIZE_MAX, CONSOLE_ANSI_SEQUENCE_TIMEOUT_USEC);
×
2554
        if (r < 0)
×
2555
                return r;
×
2556

2557
        usec_t end = usec_add(now(CLOCK_MONOTONIC), CONSOLE_ANSI_SEQUENCE_TIMEOUT_USEC);
×
2558
        char buf[STRLEN(CSI18_R1)];
×
2559
        size_t bytes = 0;
×
2560

2561
        for (;;) {
×
2562
                usec_t n = now(CLOCK_MONOTONIC);
×
2563
                if (n >= end)
×
2564
                        return -EOPNOTSUPP;
2565

2566
                r = fd_wait_for_event(nonblock_input_fd, POLLIN, usec_sub_unsigned(end, n));
×
2567
                if (r < 0)
×
2568
                        return r;
2569
                if (r == 0)
×
2570
                        return -EOPNOTSUPP;
2571

2572
                /* On the first read, read multiple characters, i.e. the shortest valid reply. Afterwards
2573
                 * read byte by byte, since we don't want to read too much and drop characters from the input
2574
                 * queue. */
2575
                ssize_t l = read(nonblock_input_fd, buf + bytes, bytes == 0 ? STRLEN(CSI18_R0) : 1);
×
2576
                if (l < 0) {
×
2577
                        if (errno == EAGAIN)
×
2578
                                continue;
×
2579
                        return -errno;
×
2580
                }
2581

2582
                assert((size_t) l <= sizeof(buf) - bytes);
×
2583
                bytes += l;
×
2584

2585
                r = scan_text_area_size_response(buf, bytes, ret_rows, ret_columns);
×
2586
                if (r != -EAGAIN)
×
2587
                        return r;
2588

2589
                if (bytes == sizeof(buf))
×
2590
                        return -EOPNOTSUPP; /* The response has the right prefix, but we didn't find a valid
2591
                                             * answer with a terminator in the allotted space. Something is
2592
                                             * wrong, possibly some unrelated bytes got injected into the
2593
                                             * answer. */
2594
        }
2595
}
2596

2597
int terminal_get_size(
103✔
2598
                int input_fd,
2599
                int output_fd,
2600
                unsigned *ret_rows,
2601
                unsigned *ret_columns,
2602
                bool try_dsr,
2603
                bool try_csi18) {
2604

2605
        _cleanup_close_ int nonblock_input_fd = -EBADF;
103✔
2606
        struct termios old_termios = TERMIOS_NULL;
103✔
2607
        CLEANUP_TERMIOS_RESET(nonblock_input_fd, old_termios);
103✔
2608
        _cleanup_(nonblock_resetp) int nonblock_reset = -EBADF;
103✔
2609
        int r;
103✔
2610

2611
        assert(try_dsr || try_csi18);
103✔
2612

2613
        r = terminal_prepare_query(input_fd, output_fd, &nonblock_input_fd, &old_termios);
103✔
2614
        if (r < 0)
103✔
2615
                return r;
2616

2617
        /* Put the output fd in non-blocking mode with a write timeout, to avoid blocking indefinitely on
2618
         * write if the terminal is not consuming data (e.g. serial console with flow control). */
2619
        r = fd_nonblock(output_fd, true);
×
2620
        if (r < 0)
×
2621
                return log_debug_errno(r, "Failed to set terminal to non-blocking mode: %m");
×
2622
        if (r > 0)
×
2623
                nonblock_reset = output_fd;
×
2624

2625
        /* Flush any stale input that might confuse the response parsers. */
2626
        (void) tcflush(nonblock_input_fd, TCIFLUSH);
×
2627

2628
        if (try_csi18) {
×
2629
                r = terminal_query_size_by_csi18(nonblock_input_fd, output_fd, ret_rows, ret_columns);
×
2630
                if (r >= 0)
×
2631
                        return r;
2632

2633
                /* Query failed. Flush any outstanding input. */
2634
                (void) tcflush(nonblock_input_fd, TCIFLUSH);
×
2635

2636
                if (!IN_SET(r, -EOPNOTSUPP, -EINVAL))
×
2637
                        return r;
2638
        }
2639

2640
        if (try_dsr) {
×
2641
                r = terminal_query_size_by_dsr(nonblock_input_fd, output_fd, ret_rows, ret_columns);
×
2642
                if (r >= 0)
×
2643
                        return r;
2644

2645
                /* Query failed. Flush any outstanding input. */
2646
                (void) tcflush(nonblock_input_fd, TCIFLUSH);
×
2647
        }
2648

2649
        return r;
2650
}
2651

2652
int terminal_fix_size(int input_fd, int output_fd) {
1✔
2653
        unsigned rows, columns;
1✔
2654
        int r;
1✔
2655

2656
        /* Tries to update the current terminal dimensions to the ones reported via ANSI sequences.
2657
         *
2658
         * Why bother with this? The ioctl() information is often incorrect on serial terminals (since
2659
         * there's no handshake or protocol to determine the right dimensions in RS232), but since the ANSI
2660
         * sequences are interpreted by the final terminal instead of an intermediary tty driver they should
2661
         * be more accurate.
2662
         */
2663

2664
        struct winsize ws = {};
1✔
2665
        if (ioctl(output_fd, TIOCGWINSZ, &ws) < 0)
1✔
2666
                return log_debug_errno(errno, "Failed to query terminal dimensions, ignoring: %m");
1✔
2667

2668
        r = terminal_get_size(input_fd, output_fd, &rows, &columns, /* try_dsr= */ true, /* try_csi18= */ true);
×
2669
        if (r < 0)
×
2670
                return log_debug_errno(r, "Failed to acquire terminal dimensions via ANSI sequences, not adjusting terminal dimensions: %m");
×
2671

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

2677
        ws.ws_col = columns;
×
2678
        ws.ws_row = rows;
×
2679

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

2683
        log_debug("Fixed terminal dimensions to %ux%u based on ANSI sequence information.", columns, rows);
×
2684
        return 1;
2685
}
2686

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

2695
static int scan_terminfo_response(
×
2696
                const char *buf,
2697
                size_t size,
2698
                char **ret_name) {
2699
        int r;
×
2700

2701
        assert(buf);
×
2702
        assert(ret_name);
×
2703

2704
        /* Check if we have enough space for the shortest possible answer. */
2705
        if (size < STRLEN(DCS_TERMINFO_R0))
×
2706
                return -EAGAIN;
×
2707

2708
        /* Check if the terminating sequence is present */
2709
        if (memcmp(buf + size - STRLEN(ANSI_ST), ANSI_ST, STRLEN(ANSI_ST)) != 0)
×
2710
                return -EAGAIN;
2711

2712
        if (size <= STRLEN(DCS_TERMINFO_R1 ANSI_ST))
×
2713
                return -EINVAL;  /* The answer is invalid or empty */
2714

2715
        if (memcmp(buf, DCS_TERMINFO_R1, STRLEN(DCS_TERMINFO_R1)) != 0)
×
2716
                return -EINVAL;  /* The answer is not valid */
2717

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

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

2730
        *ret_name = TAKE_PTR(dec);
×
2731
        return 0;
×
2732
}
2733

2734
int terminal_get_terminfo_by_dcs(int fd, char **ret_name) {
2✔
2735
        int r;
2✔
2736

2737
        assert(fd >= 0);
2✔
2738
        assert(ret_name);
2✔
2739

2740
        /* Note: fd must be in non-blocking read-write mode! */
2741

2742
        struct termios old_termios = TERMIOS_NULL;
2✔
2743
        CLEANUP_TERMIOS_RESET(fd, old_termios);
2✔
2744

2745
        if (tcgetattr(fd, &old_termios) < 0)
2✔
2746
                return -errno;
1✔
2747

2748
        struct termios new_termios = old_termios;
1✔
2749
        termios_disable_echo(&new_termios);
1✔
2750

2751
        if (tcsetattr(fd, TCSANOW, &new_termios) < 0)
1✔
2752
                return -errno;
×
2753

2754
        r = loop_write(fd, DCS_TERMINFO_Q, SIZE_MAX);
1✔
2755
        if (r < 0)
1✔
2756
                return r;
2757

2758
        usec_t end = usec_add(now(CLOCK_MONOTONIC), CONSOLE_ANSI_SEQUENCE_TIMEOUT_USEC);
1✔
2759
        char buf[STRLEN(DCS_TERMINFO_R1) + MAX_TERMINFO_LENGTH + STRLEN(ANSI_ST)];
1✔
2760
        size_t bytes = 0;
1✔
2761

2762
        for (;;) {
1✔
2763
                usec_t n = now(CLOCK_MONOTONIC);
1✔
2764
                if (n >= end)
1✔
2765
                        return -EOPNOTSUPP;
2766

2767
                r = fd_wait_for_event(fd, POLLIN, usec_sub_unsigned(end, n));
2✔
2768
                if (r < 0)
1✔
2769
                        return r;
2770
                if (r == 0)
1✔
2771
                        return -EOPNOTSUPP;
2772

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

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

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

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

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

2803
        assert(filename_is_valid(name));
5✔
2804

2805
        _cleanup_free_ char *p = path_join("/usr/share/terminfo", CHAR_TO_STR(name[0]), name);
10✔
2806
        if (!p)
5✔
2807
                return log_oom_debug();
×
2808

2809
        r = RET_NERRNO(access(p, F_OK));
5✔
2810
        if (r == -ENOENT)
1✔
2811
                return false;
2812
        if (r < 0)
4✔
2813
                return r;
×
2814
        return true;
2815
}
2816

2817
int query_term_for_tty(const char *tty, char **ret_term) {
50✔
2818
        _cleanup_free_ char *dcs_term = NULL;
50✔
2819
        int r;
50✔
2820

2821
        assert(tty);
50✔
2822
        assert(ret_term);
50✔
2823

2824
        if (tty_is_vc_resolve(tty))
50✔
2825
                return strdup_to(ret_term, "linux");
47✔
2826

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

2830
        _cleanup_close_ int tty_fd = open_terminal(tty, O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK);
53✔
2831
        if (tty_fd < 0)
3✔
2832
                return log_debug_errno(tty_fd, "Failed to open %s to query terminfo: %m", tty);
2✔
2833

2834
        r = terminal_get_terminfo_by_dcs(tty_fd, &dcs_term);
1✔
2835
        if (r < 0)
1✔
2836
                return log_debug_errno(r, "Failed to query %s for terminfo: %m", tty);
1✔
2837

2838
        r = have_terminfo_file(dcs_term);
×
2839
        if (r < 0)
×
2840
                return log_debug_errno(r, "Failed to look for terminfo %s: %m", dcs_term);
×
2841
        if (r == 0)
×
2842
                return log_info_errno(SYNTHETIC_ERRNO(ENODATA),
×
2843
                                      "Terminfo %s not found for %s.", dcs_term, tty);
2844

2845
        *ret_term = TAKE_PTR(dcs_term);
×
2846
        return 0;
×
2847
}
2848

2849
int terminal_is_pty_fd(int fd) {
3✔
2850
        int r;
3✔
2851

2852
        assert(fd >= 0);
3✔
2853

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

2856
        if (!isatty_safe(fd))
3✔
2857
                return false;
3✔
2858

2859
        r = is_fs_type_at(fd, NULL, DEVPTS_SUPER_MAGIC);
2✔
2860
        if (r != 0)
2✔
2861
                return r;
2862

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

2866
        int v;
×
2867
        if (ioctl(fd, TIOCGPKT, &v) < 0) {
×
2868
                if (ERRNO_IS_NOT_SUPPORTED(errno))
×
2869
                        return false;
2870

2871
                return -errno;
×
2872
        }
2873

2874
        return true;
2875
}
2876

2877
int pty_open_peer(int fd, int mode) {
27✔
2878
        assert(fd >= 0);
27✔
2879

2880
        /* Opens the peer PTY using the new race-free TIOCGPTPEER ioctl() (kernel 4.13).
2881
         *
2882
         * This is safe to be called on TTYs from other namespaces. */
2883

2884
        assert((mode & (O_CREAT|O_PATH|O_DIRECTORY|O_TMPFILE)) == 0);
27✔
2885

2886
        /* This replicates the EIO retry logic of open_terminal() in a modified way. */
2887
        for (unsigned c = 0;; c++) {
×
2888
                int peer_fd = ioctl(fd, TIOCGPTPEER, mode);
27✔
2889
                if (peer_fd >= 0)
27✔
2890
                        return peer_fd;
2891

2892
                if (errno != EIO)
×
2893
                        return -errno;
×
2894

2895
                /* Max 1s in total */
2896
                if (c >= 20)
×
2897
                        return -EIO;
2898

2899
                (void) usleep_safe(50 * USEC_PER_MSEC);
×
2900
        }
2901
}
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