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

systemd / systemd / 21553233292

31 Jan 2026 06:30AM UTC coverage: 72.813% (+0.02%) from 72.798%
21553233292

push

github

yuwata
quirks: keyboard: d330 keyboard rework

change matches to generic input devices one as name can't be used
because these keyboards have multiple names and also they are used in
multiple devices. This way will be possible to define more detachable
keyboards in an easy way for multiple devices.

Known USB ids for D330 series are: 17EF:60C3, 17EF:60C6 and 17EF:60C8.

Also mark that the tablet part keys and the keyboard doesn't have any
LED to let interfaces decide to expose on-screen indicators.

311506 of 427817 relevant lines covered (72.81%)

1225363.7 hits per line

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

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

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

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

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

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

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

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

58
        if (isatty(fd))
5,292,250✔
59
                return true;
60

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

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

70
        return false;
71
}
72

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

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

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

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

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

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

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

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

103
        assert(ret);
4✔
104

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

173
#define DEFAULT_ASK_REFRESH_USEC (2*USEC_PER_SEC)
174

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

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

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

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

189
                putchar('\r');
×
190

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

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

197
                fflush(stdout);
×
198

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

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

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

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

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

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

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

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

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

240
        string = strempty(string);
×
241

242
        STRV_FOREACH(c, completions) {
×
243

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

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

254
                        continue;
×
255
                }
256

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

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

267
        *ret = TAKE_PTR(found);
×
268

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

441
                fflush(stdout);
×
442
        }
443

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

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

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

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

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

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

472
bool any_key_to_proceed(void) {
×
473

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

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

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

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

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

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

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

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

502
        return w;
×
503
}
504

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

512
        assert(n_columns > 0);
×
513

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

582
                putchar('\n');
×
583

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

590
        return 0;
591
}
592

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

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

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

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

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

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

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

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

624
        return TAKE_FD(fd);
625
}
626

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

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

636
        assert(name);
187✔
637

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

725
                                left = timeout - n;
×
726
                        }
727

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

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

748
                                return -errno;
×
749
                        }
750

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

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

759
                        break;
×
760
                }
761

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

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

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

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

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

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

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

787
        return r;
788
}
789

790
int terminal_new_session(void) {
5✔
791

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

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

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

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

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

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

819
        assert(tty);
×
820

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

825
        return terminal_vhangup_fd(fd);
×
826
}
827

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

955
        return r;
442✔
956
}
957

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1034
        return TAKE_FD(fd);
1035
}
1036

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

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

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

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

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

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

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

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

1067
        tty = skip_dev_prefix(tty);
298✔
1068

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

1073
        return safe_atou(e, ret);
258✔
1074
}
1075

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

1080
        assert(tty);
175✔
1081

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

1088
        return (int) u;
175✔
1089
}
1090

1091
bool tty_is_vc(const char *tty) {
123✔
1092
        assert(tty);
123✔
1093

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

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

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

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

1108
int resolve_dev_console(char **ret) {
182✔
1109
        int r;
182✔
1110

1111
        assert(ret);
182✔
1112

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

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

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

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

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

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

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

1153
                tty = active;
×
1154
        }
1155

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

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

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

1170
        assert(ret);
×
1171

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

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

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

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

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

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

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

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

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

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

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

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

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

1231
        assert(tty);
48✔
1232

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

1237
                tty = resolved;
1✔
1238
        }
1239

1240
        return tty_is_vc(tty);
48✔
1241
}
1242

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

1246
        if (fd < 0)
1,371✔
1247
                return -EBADF;
1248

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

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

1255
        return ws.ws_col;
×
1256
}
1257

1258
int getenv_columns(void) {
1,633✔
1259
        int r;
1,633✔
1260

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

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

1270
        return (int) c;
2✔
1271
}
1272

1273
unsigned columns(void) {
261,773✔
1274

1275
        if (cached_columns > 0)
261,773✔
1276
                return cached_columns;
260,400✔
1277

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

1285
        assert(c > 0);
2✔
1286

1287
        cached_columns = c;
1,373✔
1288
        return cached_columns;
1,373✔
1289
}
1290

1291
int fd_lines(int fd) {
193✔
1292
        struct winsize ws = {};
193✔
1293

1294
        if (fd < 0)
193✔
1295
                return -EBADF;
1296

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

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

1303
        return ws.ws_row;
×
1304
}
1305

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

1310
        if (cached_lines > 0)
193✔
1311
                return cached_lines;
×
1312

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

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

1324
        cached_lines = l;
193✔
1325
        return cached_lines;
193✔
1326
}
1327

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

1331
        assert(fd >= 0);
559✔
1332

1333
        if (!ident)
559✔
1334
                ident = "TTY";
86✔
1335

1336
        if (rows == UINT_MAX && cols == UINT_MAX)
559✔
1337
                return 0;
559✔
1338

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

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

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

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

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

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

1363
        return 0;
1364
}
1365

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

1371
        assert(tty);
509✔
1372

1373
        if (!ret_rows && !ret_cols)
509✔
1374
                return 0;
1375

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

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

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

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

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

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

1409
        if (ret_rows)
509✔
1410
                *ret_rows = rows;
509✔
1411
        if (ret_cols)
509✔
1412
                *ret_cols = cols;
509✔
1413

1414
        return rows != UINT_MAX || cols != UINT_MAX;
509✔
1415
}
1416

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

1423
void reset_terminal_feature_caches(void) {
24✔
1424
        cached_columns = 0;
24✔
1425
        cached_lines = 0;
24✔
1426

1427
        cached_on_tty = -1;
24✔
1428
        cached_on_dev_null = -1;
24✔
1429

1430
        reset_ansi_feature_caches();
24✔
1431
}
24✔
1432

1433
bool on_tty(void) {
7,457,749✔
1434

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

1441
        if (cached_on_tty < 0)
7,457,749✔
1442
                cached_on_tty =
32,229✔
1443
                        isatty_safe(STDOUT_FILENO) &&
32,266✔
1444
                        isatty_safe(STDERR_FILENO);
37✔
1445

1446
        return cached_on_tty;
7,457,749✔
1447
}
1448

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

1453
        assert(fd >= 0);
536✔
1454
        assert(ret);
536✔
1455

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

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

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

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

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

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

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

1487
        assert(pid >= 0);
4,525✔
1488

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

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

1498
        p++;
4,525✔
1499

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

1509
        if (devnum_is_zero(ttynr))
4,525✔
1510
                return -ENXIO;
1511

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

1515
        return 0;
1516
}
1517

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

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

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

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

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

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

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

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

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

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

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

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

1576
        return 0;
1577
}
1578

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

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

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

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

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

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

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

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

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

1620
        if (unlockpt(fd) < 0)
140✔
1621
                return -errno;
×
1622

1623
        if (ret_peer_path)
140✔
1624
                *ret_peer_path = TAKE_PTR(p);
136✔
1625

1626
        return TAKE_FD(fd);
1627
}
1628

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

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

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

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

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

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

1647
        return 0;
1648
}
1649

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

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

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

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

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

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

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

1688
                _exit(EXIT_SUCCESS);
×
1689
        }
1690

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

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

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

1703
        return TAKE_FD(fd);
1704
}
1705

1706
static bool on_dev_null(void) {
39,012✔
1707
        struct stat dst, ost, est;
39,012✔
1708

1709
        if (cached_on_dev_null >= 0)
39,012✔
1710
                return cached_on_dev_null;
6,904✔
1711

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

1717
        return cached_on_dev_null;
32,108✔
1718
}
1719

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

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

1727
        return streq(e, "dumb");
1,294✔
1728
}
1729

1730
bool terminal_is_dumb(void) {
39,049✔
1731
        if (!on_tty() && !on_dev_null())
39,049✔
1732
                return true;
1733

1734
        return getenv_terminal_is_dumb();
156✔
1735
}
1736

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

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

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

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

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

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

1761
int vt_restore(int fd) {
×
1762

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

1767
        int r, ret = 0;
×
1768

1769
        assert(fd >= 0);
×
1770

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

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

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

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

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

1788
        return ret;
1789
}
1790

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

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

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

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

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

1807
        return 0;
1808
}
1809

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

1814
        if (priority <= LOG_ERR) {
206,746✔
1815
                if (on)
6,451✔
1816
                        *on = ansi_highlight_red();
12,902✔
1817
                if (off)
6,451✔
1818
                        *off = ansi_normal();
12,902✔
1819
                if (highlight)
6,451✔
1820
                        *highlight = ansi_highlight();
×
1821

1822
        } else if (priority <= LOG_WARNING) {
200,295✔
1823
                if (on)
908✔
1824
                        *on = ansi_highlight_yellow();
908✔
1825
                if (off)
908✔
1826
                        *off = ansi_normal();
1,816✔
1827
                if (highlight)
908✔
1828
                        *highlight = ansi_highlight();
×
1829

1830
        } else if (priority <= LOG_NOTICE) {
199,387✔
1831
                if (on)
893✔
1832
                        *on = ansi_highlight();
1,786✔
1833
                if (off)
893✔
1834
                        *off = ansi_normal();
1,786✔
1835
                if (highlight)
893✔
1836
                        *highlight = ansi_highlight_red();
×
1837

1838
        } else if (priority >= LOG_DEBUG) {
198,494✔
1839
                if (on)
157,398✔
1840
                        *on = ansi_grey();
157,398✔
1841
                if (off)
157,398✔
1842
                        *off = ansi_normal();
314,796✔
1843
                if (highlight)
157,398✔
1844
                        *highlight = ansi_highlight_red();
×
1845
        }
1846
}
206,746✔
1847

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

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

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

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

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

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

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

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

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

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

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

1885
        return 0;
1886
}
1887

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

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

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

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

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

1913
                switch (context->state) {
×
1914

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

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

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

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

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

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

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

1956
                        break;
1957
                }
1958

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2088
        if (!isatty_safe(fd))
413✔
2089
                return -ENOTTY;
413✔
2090

2091
        RET_GATHER(r, terminal_reset_ioctl(fd, FLAGS_SET(flags, TERMINAL_RESET_SWITCH_TO_TEXT)));
406✔
2092

2093
        if (!FLAGS_SET(flags, TERMINAL_RESET_AVOID_ANSI_SEQ) &&
406✔
2094
            (FLAGS_SET(flags, TERMINAL_RESET_FORCE_ANSI_SEQ) || !getenv_terminal_is_dumb()))
400✔
2095
                RET_GATHER(r, terminal_reset_ansi_seq(fd));
400✔
2096

2097
        return r;
2098
}
2099

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

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

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

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

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

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

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

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

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

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

2153
                switch (context->state) {
×
2154

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2249
                }
2250

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2371
        int r;
98✔
2372

2373
        assert(input_fd >= 0);
98✔
2374
        assert(output_fd >= 0);
98✔
2375

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

2386
        if (terminal_is_dumb())
98✔
2387
                return -EOPNOTSUPP;
98✔
2388

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2512
        return r;
×
2513
}
2514

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2790
                if (bytes == sizeof(buf)) {
×
2791
                        r = -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
                        break;
2796
                }
2797
        }
2798

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

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

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

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

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

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

2827
        assert(tty);
48✔
2828
        assert(ret_term);
48✔
2829

2830
        if (tty_is_vc_resolve(tty))
48✔
2831
                return strdup_to(ret_term, "linux");
45✔
2832

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

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

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

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

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

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

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

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

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

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

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

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

2877
                return -errno;
×
2878
        }
2879

2880
        return true;
2881
}
2882

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

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

2890
        assert((mode & (O_CREAT|O_PATH|O_DIRECTORY|O_TMPFILE)) == 0);
27✔
2891

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

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

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

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