Line data Source code
1 : /* SPDX-License-Identifier: LGPL-2.1+ */
2 :
3 : #include <errno.h>
4 : #include <fcntl.h>
5 : #include <limits.h>
6 : #include <linux/kd.h>
7 : #include <linux/tiocl.h>
8 : #include <linux/vt.h>
9 : #include <poll.h>
10 : #include <signal.h>
11 : #include <stdarg.h>
12 : #include <stddef.h>
13 : #include <stdlib.h>
14 : #include <string.h>
15 : #include <sys/inotify.h>
16 : #include <sys/ioctl.h>
17 : #include <sys/socket.h>
18 : #include <sys/sysmacros.h>
19 : #include <sys/time.h>
20 : #include <sys/types.h>
21 : #include <sys/utsname.h>
22 : #include <termios.h>
23 : #include <unistd.h>
24 :
25 : #include "alloc-util.h"
26 : #include "copy.h"
27 : #include "def.h"
28 : #include "env-util.h"
29 : #include "fd-util.h"
30 : #include "fileio.h"
31 : #include "fs-util.h"
32 : #include "io-util.h"
33 : #include "log.h"
34 : #include "macro.h"
35 : #include "namespace-util.h"
36 : #include "parse-util.h"
37 : #include "path-util.h"
38 : #include "proc-cmdline.h"
39 : #include "process-util.h"
40 : #include "socket-util.h"
41 : #include "stat-util.h"
42 : #include "string-util.h"
43 : #include "strv.h"
44 : #include "terminal-util.h"
45 : #include "time-util.h"
46 : #include "util.h"
47 :
48 : static volatile unsigned cached_columns = 0;
49 : static volatile unsigned cached_lines = 0;
50 :
51 : static volatile int cached_on_tty = -1;
52 : static volatile int cached_colors_enabled = -1;
53 : static volatile int cached_underline_enabled = -1;
54 :
55 0 : int chvt(int vt) {
56 0 : _cleanup_close_ int fd;
57 :
58 : /* Switch to the specified vt number. If the VT is specified <= 0 switch to the VT the kernel log messages go,
59 : * if that's configured. */
60 :
61 0 : fd = open_terminal("/dev/tty0", O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK);
62 0 : if (fd < 0)
63 0 : return -errno;
64 :
65 0 : if (vt <= 0) {
66 0 : int tiocl[2] = {
67 : TIOCL_GETKMSGREDIRECT,
68 : 0
69 : };
70 :
71 0 : if (ioctl(fd, TIOCLINUX, tiocl) < 0)
72 0 : return -errno;
73 :
74 0 : vt = tiocl[0] <= 0 ? 1 : tiocl[0];
75 : }
76 :
77 0 : if (ioctl(fd, VT_ACTIVATE, vt) < 0)
78 0 : return -errno;
79 :
80 0 : return 0;
81 : }
82 :
83 4 : int read_one_char(FILE *f, char *ret, usec_t t, bool *need_nl) {
84 4 : _cleanup_free_ char *line = NULL;
85 : struct termios old_termios;
86 : int r;
87 :
88 4 : assert(f);
89 4 : assert(ret);
90 :
91 : /* If this is a terminal, then switch canonical mode off, so that we can read a single character */
92 4 : if (tcgetattr(fileno(f), &old_termios) >= 0) {
93 0 : struct termios new_termios = old_termios;
94 :
95 0 : new_termios.c_lflag &= ~ICANON;
96 0 : new_termios.c_cc[VMIN] = 1;
97 0 : new_termios.c_cc[VTIME] = 0;
98 :
99 0 : if (tcsetattr(fileno(f), TCSADRAIN, &new_termios) >= 0) {
100 : char c;
101 :
102 0 : if (t != USEC_INFINITY) {
103 0 : if (fd_wait_for_event(fileno(f), POLLIN, t) <= 0) {
104 0 : (void) tcsetattr(fileno(f), TCSADRAIN, &old_termios);
105 0 : return -ETIMEDOUT;
106 : }
107 : }
108 :
109 0 : r = safe_fgetc(f, &c);
110 0 : (void) tcsetattr(fileno(f), TCSADRAIN, &old_termios);
111 0 : if (r < 0)
112 0 : return r;
113 0 : if (r == 0)
114 0 : return -EIO;
115 :
116 0 : if (need_nl)
117 0 : *need_nl = c != '\n';
118 :
119 0 : *ret = c;
120 0 : return 0;
121 : }
122 : }
123 :
124 4 : if (t != USEC_INFINITY) {
125 4 : if (fd_wait_for_event(fileno(f), POLLIN, t) <= 0)
126 0 : return -ETIMEDOUT;
127 : }
128 :
129 : /* If this is not a terminal, then read a full line instead */
130 :
131 4 : r = read_line(f, 16, &line); /* longer than necessary, to eat up UTF-8 chars/vt100 key sequences */
132 4 : if (r < 0)
133 0 : return r;
134 4 : if (r == 0)
135 1 : return -EIO;
136 :
137 3 : if (strlen(line) != 1)
138 2 : return -EBADMSG;
139 :
140 1 : if (need_nl)
141 1 : *need_nl = false;
142 :
143 1 : *ret = line[0];
144 1 : return 0;
145 : }
146 :
147 : #define DEFAULT_ASK_REFRESH_USEC (2*USEC_PER_SEC)
148 :
149 0 : int ask_char(char *ret, const char *replies, const char *fmt, ...) {
150 : int r;
151 :
152 0 : assert(ret);
153 0 : assert(replies);
154 0 : assert(fmt);
155 :
156 0 : for (;;) {
157 : va_list ap;
158 : char c;
159 0 : bool need_nl = true;
160 :
161 0 : if (colors_enabled())
162 0 : fputs(ANSI_HIGHLIGHT, stdout);
163 :
164 0 : putchar('\r');
165 :
166 0 : va_start(ap, fmt);
167 0 : vprintf(fmt, ap);
168 0 : va_end(ap);
169 :
170 0 : if (colors_enabled())
171 0 : fputs(ANSI_NORMAL, stdout);
172 :
173 0 : fflush(stdout);
174 :
175 0 : r = read_one_char(stdin, &c, DEFAULT_ASK_REFRESH_USEC, &need_nl);
176 0 : if (r < 0) {
177 :
178 0 : if (r == -ETIMEDOUT)
179 0 : continue;
180 :
181 0 : if (r == -EBADMSG) {
182 0 : puts("Bad input, please try again.");
183 0 : continue;
184 : }
185 :
186 0 : putchar('\n');
187 0 : return r;
188 : }
189 :
190 0 : if (need_nl)
191 0 : putchar('\n');
192 :
193 0 : if (strchr(replies, c)) {
194 0 : *ret = c;
195 0 : return 0;
196 : }
197 :
198 0 : puts("Read unexpected character, please try again.");
199 : }
200 : }
201 :
202 0 : int ask_string(char **ret, const char *text, ...) {
203 0 : _cleanup_free_ char *line = NULL;
204 : va_list ap;
205 : int r;
206 :
207 0 : assert(ret);
208 0 : assert(text);
209 :
210 0 : if (colors_enabled())
211 0 : fputs(ANSI_HIGHLIGHT, stdout);
212 :
213 0 : va_start(ap, text);
214 0 : vprintf(text, ap);
215 0 : va_end(ap);
216 :
217 0 : if (colors_enabled())
218 0 : fputs(ANSI_NORMAL, stdout);
219 :
220 0 : fflush(stdout);
221 :
222 0 : r = read_line(stdin, LONG_LINE_MAX, &line);
223 0 : if (r < 0)
224 0 : return r;
225 0 : if (r == 0)
226 0 : return -EIO;
227 :
228 0 : *ret = TAKE_PTR(line);
229 0 : return 0;
230 : }
231 :
232 0 : int reset_terminal_fd(int fd, bool switch_to_text) {
233 : struct termios termios;
234 0 : int r = 0;
235 :
236 : /* Set terminal to some sane defaults */
237 :
238 0 : assert(fd >= 0);
239 :
240 : /* We leave locked terminal attributes untouched, so that
241 : * Plymouth may set whatever it wants to set, and we don't
242 : * interfere with that. */
243 :
244 : /* Disable exclusive mode, just in case */
245 0 : (void) ioctl(fd, TIOCNXCL);
246 :
247 : /* Switch to text mode */
248 0 : if (switch_to_text)
249 0 : (void) ioctl(fd, KDSETMODE, KD_TEXT);
250 :
251 : /* Set default keyboard mode */
252 0 : (void) vt_reset_keyboard(fd);
253 :
254 0 : if (tcgetattr(fd, &termios) < 0) {
255 0 : r = -errno;
256 0 : goto finish;
257 : }
258 :
259 : /* We only reset the stuff that matters to the software. How
260 : * hardware is set up we don't touch assuming that somebody
261 : * else will do that for us */
262 :
263 0 : termios.c_iflag &= ~(IGNBRK | BRKINT | ISTRIP | INLCR | IGNCR | IUCLC);
264 0 : termios.c_iflag |= ICRNL | IMAXBEL | IUTF8;
265 0 : termios.c_oflag |= ONLCR;
266 0 : termios.c_cflag |= CREAD;
267 0 : termios.c_lflag = ISIG | ICANON | IEXTEN | ECHO | ECHOE | ECHOK | ECHOCTL | ECHOPRT | ECHOKE;
268 :
269 0 : termios.c_cc[VINTR] = 03; /* ^C */
270 0 : termios.c_cc[VQUIT] = 034; /* ^\ */
271 0 : termios.c_cc[VERASE] = 0177;
272 0 : termios.c_cc[VKILL] = 025; /* ^X */
273 0 : termios.c_cc[VEOF] = 04; /* ^D */
274 0 : termios.c_cc[VSTART] = 021; /* ^Q */
275 0 : termios.c_cc[VSTOP] = 023; /* ^S */
276 0 : termios.c_cc[VSUSP] = 032; /* ^Z */
277 0 : termios.c_cc[VLNEXT] = 026; /* ^V */
278 0 : termios.c_cc[VWERASE] = 027; /* ^W */
279 0 : termios.c_cc[VREPRINT] = 022; /* ^R */
280 0 : termios.c_cc[VEOL] = 0;
281 0 : termios.c_cc[VEOL2] = 0;
282 :
283 0 : termios.c_cc[VTIME] = 0;
284 0 : termios.c_cc[VMIN] = 1;
285 :
286 0 : if (tcsetattr(fd, TCSANOW, &termios) < 0)
287 0 : r = -errno;
288 :
289 0 : finish:
290 : /* Just in case, flush all crap out */
291 0 : (void) tcflush(fd, TCIOFLUSH);
292 :
293 0 : return r;
294 : }
295 :
296 0 : int reset_terminal(const char *name) {
297 0 : _cleanup_close_ int fd = -1;
298 :
299 : /* We open the terminal with O_NONBLOCK here, to ensure we
300 : * don't block on carrier if this is a terminal with carrier
301 : * configured. */
302 :
303 0 : fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK);
304 0 : if (fd < 0)
305 0 : return fd;
306 :
307 0 : return reset_terminal_fd(fd, true);
308 : }
309 :
310 0 : int open_terminal(const char *name, int mode) {
311 0 : unsigned c = 0;
312 : int fd;
313 :
314 : /*
315 : * If a TTY is in the process of being closed opening it might
316 : * cause EIO. This is horribly awful, but unlikely to be
317 : * changed in the kernel. Hence we work around this problem by
318 : * retrying a couple of times.
319 : *
320 : * https://bugs.launchpad.net/ubuntu/+source/linux/+bug/554172/comments/245
321 : */
322 :
323 0 : if (mode & O_CREAT)
324 0 : return -EINVAL;
325 :
326 : for (;;) {
327 0 : fd = open(name, mode, 0);
328 0 : if (fd >= 0)
329 0 : break;
330 :
331 0 : if (errno != EIO)
332 0 : return -errno;
333 :
334 : /* Max 1s in total */
335 0 : if (c >= 20)
336 0 : return -errno;
337 :
338 0 : usleep(50 * USEC_PER_MSEC);
339 0 : c++;
340 : }
341 :
342 0 : if (isatty(fd) <= 0) {
343 0 : safe_close(fd);
344 0 : return -ENOTTY;
345 : }
346 :
347 0 : return fd;
348 : }
349 :
350 0 : int acquire_terminal(
351 : const char *name,
352 : AcquireTerminalFlags flags,
353 : usec_t timeout) {
354 :
355 0 : _cleanup_close_ int notify = -1, fd = -1;
356 0 : usec_t ts = USEC_INFINITY;
357 0 : int r, wd = -1;
358 :
359 0 : assert(name);
360 0 : assert(IN_SET(flags & ~ACQUIRE_TERMINAL_PERMISSIVE, ACQUIRE_TERMINAL_TRY, ACQUIRE_TERMINAL_FORCE, ACQUIRE_TERMINAL_WAIT));
361 :
362 : /* We use inotify to be notified when the tty is closed. We create the watch before checking if we can actually
363 : * acquire it, so that we don't lose any event.
364 : *
365 : * Note: strictly speaking this actually watches for the device being closed, it does *not* really watch
366 : * whether a tty loses its controlling process. However, unless some rogue process uses TIOCNOTTY on /dev/tty
367 : * *after* closing its tty otherwise this will not become a problem. As long as the administrator makes sure to
368 : * not configure any service on the same tty as an untrusted user this should not be a problem. (Which they
369 : * probably should not do anyway.) */
370 :
371 0 : if ((flags & ~ACQUIRE_TERMINAL_PERMISSIVE) == ACQUIRE_TERMINAL_WAIT) {
372 0 : notify = inotify_init1(IN_CLOEXEC | (timeout != USEC_INFINITY ? IN_NONBLOCK : 0));
373 0 : if (notify < 0)
374 0 : return -errno;
375 :
376 0 : wd = inotify_add_watch(notify, name, IN_CLOSE);
377 0 : if (wd < 0)
378 0 : return -errno;
379 :
380 0 : if (timeout != USEC_INFINITY)
381 0 : ts = now(CLOCK_MONOTONIC);
382 : }
383 :
384 0 : for (;;) {
385 0 : struct sigaction sa_old, sa_new = {
386 : .sa_handler = SIG_IGN,
387 : .sa_flags = SA_RESTART,
388 : };
389 :
390 0 : if (notify >= 0) {
391 0 : r = flush_fd(notify);
392 0 : if (r < 0)
393 0 : return r;
394 : }
395 :
396 : /* We pass here O_NOCTTY only so that we can check the return value TIOCSCTTY and have a reliable way
397 : * to figure out if we successfully became the controlling process of the tty */
398 0 : fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC);
399 0 : if (fd < 0)
400 0 : return fd;
401 :
402 : /* Temporarily ignore SIGHUP, so that we don't get SIGHUP'ed if we already own the tty. */
403 0 : assert_se(sigaction(SIGHUP, &sa_new, &sa_old) == 0);
404 :
405 : /* First, try to get the tty */
406 0 : r = ioctl(fd, TIOCSCTTY,
407 0 : (flags & ~ACQUIRE_TERMINAL_PERMISSIVE) == ACQUIRE_TERMINAL_FORCE) < 0 ? -errno : 0;
408 :
409 : /* Reset signal handler to old value */
410 0 : assert_se(sigaction(SIGHUP, &sa_old, NULL) == 0);
411 :
412 : /* Success? Exit the loop now! */
413 0 : if (r >= 0)
414 0 : break;
415 :
416 : /* Any failure besides -EPERM? Fail, regardless of the mode. */
417 0 : if (r != -EPERM)
418 0 : return r;
419 :
420 0 : if (flags & ACQUIRE_TERMINAL_PERMISSIVE) /* If we are in permissive mode, then EPERM is fine, turn this
421 : * into a success. Note that EPERM is also returned if we
422 : * already are the owner of the TTY. */
423 0 : break;
424 :
425 0 : if (flags != ACQUIRE_TERMINAL_WAIT) /* If we are in TRY or FORCE mode, then propagate EPERM as EPERM */
426 0 : return r;
427 :
428 0 : assert(notify >= 0);
429 0 : assert(wd >= 0);
430 :
431 0 : for (;;) {
432 : union inotify_event_buffer buffer;
433 : struct inotify_event *e;
434 : ssize_t l;
435 :
436 0 : if (timeout != USEC_INFINITY) {
437 : usec_t n;
438 :
439 0 : assert(ts != USEC_INFINITY);
440 :
441 0 : n = now(CLOCK_MONOTONIC);
442 0 : if (ts + timeout < n)
443 0 : return -ETIMEDOUT;
444 :
445 0 : r = fd_wait_for_event(notify, POLLIN, ts + timeout - n);
446 0 : if (r < 0)
447 0 : return r;
448 0 : if (r == 0)
449 0 : return -ETIMEDOUT;
450 : }
451 :
452 0 : l = read(notify, &buffer, sizeof(buffer));
453 0 : if (l < 0) {
454 0 : if (IN_SET(errno, EINTR, EAGAIN))
455 0 : continue;
456 :
457 0 : return -errno;
458 : }
459 :
460 0 : FOREACH_INOTIFY_EVENT(e, buffer, l) {
461 0 : if (e->mask & IN_Q_OVERFLOW) /* If we hit an inotify queue overflow, simply check if the terminal is up for grabs now. */
462 0 : break;
463 :
464 0 : if (e->wd != wd || !(e->mask & IN_CLOSE)) /* Safety checks */
465 0 : return -EIO;
466 : }
467 :
468 0 : break;
469 : }
470 :
471 : /* We close the tty fd here since if the old session ended our handle will be dead. It's important that
472 : * we do this after sleeping, so that we don't enter an endless loop. */
473 0 : fd = safe_close(fd);
474 : }
475 :
476 0 : return TAKE_FD(fd);
477 : }
478 :
479 0 : int release_terminal(void) {
480 : static const struct sigaction sa_new = {
481 : .sa_handler = SIG_IGN,
482 : .sa_flags = SA_RESTART,
483 : };
484 :
485 0 : _cleanup_close_ int fd = -1;
486 : struct sigaction sa_old;
487 : int r;
488 :
489 0 : fd = open("/dev/tty", O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK);
490 0 : if (fd < 0)
491 0 : return -errno;
492 :
493 : /* Temporarily ignore SIGHUP, so that we don't get SIGHUP'ed
494 : * by our own TIOCNOTTY */
495 0 : assert_se(sigaction(SIGHUP, &sa_new, &sa_old) == 0);
496 :
497 0 : r = ioctl(fd, TIOCNOTTY) < 0 ? -errno : 0;
498 :
499 0 : assert_se(sigaction(SIGHUP, &sa_old, NULL) == 0);
500 :
501 0 : return r;
502 : }
503 :
504 0 : int terminal_vhangup_fd(int fd) {
505 0 : assert(fd >= 0);
506 :
507 0 : if (ioctl(fd, TIOCVHANGUP) < 0)
508 0 : return -errno;
509 :
510 0 : return 0;
511 : }
512 :
513 0 : int terminal_vhangup(const char *name) {
514 0 : _cleanup_close_ int fd;
515 :
516 0 : fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK);
517 0 : if (fd < 0)
518 0 : return fd;
519 :
520 0 : return terminal_vhangup_fd(fd);
521 : }
522 :
523 0 : int vt_disallocate(const char *name) {
524 : const char *e;
525 : int r;
526 :
527 : /* Deallocate the VT if possible. If not possible
528 : * (i.e. because it is the active one), at least clear it
529 : * entirely (including the scrollback buffer). */
530 :
531 0 : e = path_startswith(name, "/dev/");
532 0 : if (!e)
533 0 : return -EINVAL;
534 :
535 0 : if (tty_is_vc(name)) {
536 0 : _cleanup_close_ int fd = -1;
537 : unsigned u;
538 : const char *n;
539 :
540 0 : n = startswith(e, "tty");
541 0 : if (!n)
542 0 : return -EINVAL;
543 :
544 0 : r = safe_atou(n, &u);
545 0 : if (r < 0)
546 0 : return r;
547 :
548 0 : if (u <= 0)
549 0 : return -EINVAL;
550 :
551 : /* Try to deallocate */
552 0 : fd = open_terminal("/dev/tty0", O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK);
553 0 : if (fd < 0)
554 0 : return fd;
555 :
556 0 : r = ioctl(fd, VT_DISALLOCATE, u);
557 0 : if (r >= 0)
558 0 : return 0;
559 0 : if (errno != EBUSY)
560 0 : return -errno;
561 : }
562 :
563 : /* So this is not a VT (in which case we cannot deallocate it),
564 : * or we failed to deallocate. Let's at least clear the screen. */
565 :
566 0 : _cleanup_close_ int fd2 = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC);
567 0 : if (fd2 < 0)
568 0 : return fd2;
569 :
570 0 : (void) loop_write(fd2,
571 : "\033[r" /* clear scrolling region */
572 : "\033[H" /* move home */
573 : "\033[3J", /* clear screen including scrollback, requires Linux 2.6.40 */
574 : 10, false);
575 0 : return 0;
576 : }
577 :
578 0 : int make_console_stdio(void) {
579 : int fd, r;
580 :
581 : /* Make /dev/console the controlling terminal and stdin/stdout/stderr, if we can. If we can't use
582 : * /dev/null instead. This is particularly useful if /dev/console is turned off, e.g. if console=null
583 : * is specified on the kernel command line. */
584 :
585 0 : fd = acquire_terminal("/dev/console", ACQUIRE_TERMINAL_FORCE|ACQUIRE_TERMINAL_PERMISSIVE, USEC_INFINITY);
586 0 : if (fd < 0) {
587 0 : log_warning_errno(fd, "Failed to acquire terminal, using /dev/null stdin/stdout/stderr instead: %m");
588 :
589 0 : r = make_null_stdio();
590 0 : if (r < 0)
591 0 : return log_error_errno(r, "Failed to make /dev/null stdin/stdout/stderr: %m");
592 :
593 : } else {
594 0 : r = reset_terminal_fd(fd, true);
595 0 : if (r < 0)
596 0 : log_warning_errno(r, "Failed to reset terminal, ignoring: %m");
597 :
598 0 : r = rearrange_stdio(fd, fd, fd); /* This invalidates 'fd' both on success and on failure. */
599 0 : if (r < 0)
600 0 : return log_error_errno(r, "Failed to make terminal stdin/stdout/stderr: %m");
601 : }
602 :
603 0 : reset_terminal_feature_caches();
604 0 : return 0;
605 : }
606 :
607 12 : bool tty_is_vc(const char *tty) {
608 12 : assert(tty);
609 :
610 12 : return vtnr_from_tty(tty) >= 0;
611 : }
612 :
613 0 : bool tty_is_console(const char *tty) {
614 0 : assert(tty);
615 :
616 0 : return streq(skip_dev_prefix(tty), "console");
617 : }
618 :
619 12 : int vtnr_from_tty(const char *tty) {
620 : int i, r;
621 :
622 12 : assert(tty);
623 :
624 12 : tty = skip_dev_prefix(tty);
625 :
626 12 : if (!startswith(tty, "tty") )
627 4 : return -EINVAL;
628 :
629 8 : if (tty[3] < '0' || tty[3] > '9')
630 2 : return -EINVAL;
631 :
632 6 : r = safe_atoi(tty+3, &i);
633 6 : if (r < 0)
634 0 : return r;
635 :
636 6 : if (i < 0 || i > 63)
637 0 : return -EINVAL;
638 :
639 6 : return i;
640 : }
641 :
642 2 : int resolve_dev_console(char **ret) {
643 2 : _cleanup_free_ char *active = NULL;
644 : char *tty;
645 : int r;
646 :
647 2 : assert(ret);
648 :
649 : /* Resolve where /dev/console is pointing to, if /sys is actually ours (i.e. not read-only-mounted which is a
650 : * sign for container setups) */
651 :
652 2 : if (path_is_read_only_fs("/sys") > 0)
653 0 : return -ENOMEDIUM;
654 :
655 2 : r = read_one_line_file("/sys/class/tty/console/active", &active);
656 2 : if (r < 0)
657 0 : return r;
658 :
659 : /* If multiple log outputs are configured the last one is what /dev/console points to */
660 2 : tty = strrchr(active, ' ');
661 2 : if (tty)
662 0 : tty++;
663 : else
664 2 : tty = active;
665 :
666 2 : if (streq(tty, "tty0")) {
667 2 : active = mfree(active);
668 :
669 : /* Get the active VC (e.g. tty1) */
670 2 : r = read_one_line_file("/sys/class/tty/tty0/active", &active);
671 2 : if (r < 0)
672 0 : return r;
673 :
674 2 : tty = active;
675 : }
676 :
677 2 : if (tty == active)
678 2 : *ret = TAKE_PTR(active);
679 : else {
680 : char *tmp;
681 :
682 0 : tmp = strdup(tty);
683 0 : if (!tmp)
684 0 : return -ENOMEM;
685 :
686 0 : *ret = tmp;
687 : }
688 :
689 2 : return 0;
690 : }
691 :
692 0 : int get_kernel_consoles(char ***ret) {
693 0 : _cleanup_strv_free_ char **l = NULL;
694 0 : _cleanup_free_ char *line = NULL;
695 : const char *p;
696 : int r;
697 :
698 0 : assert(ret);
699 :
700 : /* If /sys is mounted read-only this means we are running in some kind of container environment. In that
701 : * case /sys would reflect the host system, not us, hence ignore the data we can read from it. */
702 0 : if (path_is_read_only_fs("/sys") > 0)
703 0 : goto fallback;
704 :
705 0 : r = read_one_line_file("/sys/class/tty/console/active", &line);
706 0 : if (r < 0)
707 0 : return r;
708 :
709 0 : p = line;
710 0 : for (;;) {
711 0 : _cleanup_free_ char *tty = NULL, *path = NULL;
712 :
713 0 : r = extract_first_word(&p, &tty, NULL, 0);
714 0 : if (r < 0)
715 0 : return r;
716 0 : if (r == 0)
717 0 : break;
718 :
719 0 : if (streq(tty, "tty0")) {
720 0 : tty = mfree(tty);
721 0 : r = read_one_line_file("/sys/class/tty/tty0/active", &tty);
722 0 : if (r < 0)
723 0 : return r;
724 : }
725 :
726 0 : path = path_join("/dev", tty);
727 0 : if (!path)
728 0 : return -ENOMEM;
729 :
730 0 : if (access(path, F_OK) < 0) {
731 0 : log_debug_errno(errno, "Console device %s is not accessible, skipping: %m", path);
732 0 : continue;
733 : }
734 :
735 0 : r = strv_consume(&l, TAKE_PTR(path));
736 0 : if (r < 0)
737 0 : return r;
738 : }
739 :
740 0 : if (strv_isempty(l)) {
741 0 : log_debug("No devices found for system console");
742 0 : goto fallback;
743 : }
744 :
745 0 : *ret = TAKE_PTR(l);
746 :
747 0 : return 0;
748 :
749 0 : fallback:
750 0 : r = strv_extend(&l, "/dev/console");
751 0 : if (r < 0)
752 0 : return r;
753 :
754 0 : *ret = TAKE_PTR(l);
755 :
756 0 : return 0;
757 : }
758 :
759 12 : bool tty_is_vc_resolve(const char *tty) {
760 12 : _cleanup_free_ char *resolved = NULL;
761 :
762 12 : assert(tty);
763 :
764 12 : tty = skip_dev_prefix(tty);
765 :
766 12 : if (streq(tty, "console")) {
767 2 : if (resolve_dev_console(&resolved) < 0)
768 0 : return false;
769 :
770 2 : tty = resolved;
771 : }
772 :
773 12 : return tty_is_vc(tty);
774 : }
775 :
776 12 : const char *default_term_for_tty(const char *tty) {
777 12 : return tty && tty_is_vc_resolve(tty) ? "linux" : "vt220";
778 : }
779 :
780 3 : int fd_columns(int fd) {
781 3 : struct winsize ws = {};
782 :
783 3 : if (ioctl(fd, TIOCGWINSZ, &ws) < 0)
784 3 : return -errno;
785 :
786 0 : if (ws.ws_col <= 0)
787 0 : return -EIO;
788 :
789 0 : return ws.ws_col;
790 : }
791 :
792 91 : unsigned columns(void) {
793 : const char *e;
794 : int c;
795 :
796 91 : if (cached_columns > 0)
797 87 : return cached_columns;
798 :
799 4 : c = 0;
800 4 : e = getenv("COLUMNS");
801 4 : if (e)
802 1 : (void) safe_atoi(e, &c);
803 :
804 4 : if (c <= 0 || c > USHRT_MAX) {
805 3 : c = fd_columns(STDOUT_FILENO);
806 3 : if (c <= 0)
807 3 : c = 80;
808 : }
809 :
810 4 : cached_columns = c;
811 4 : return cached_columns;
812 : }
813 :
814 0 : int fd_lines(int fd) {
815 0 : struct winsize ws = {};
816 :
817 0 : if (ioctl(fd, TIOCGWINSZ, &ws) < 0)
818 0 : return -errno;
819 :
820 0 : if (ws.ws_row <= 0)
821 0 : return -EIO;
822 :
823 0 : return ws.ws_row;
824 : }
825 :
826 0 : unsigned lines(void) {
827 : const char *e;
828 : int l;
829 :
830 0 : if (cached_lines > 0)
831 0 : return cached_lines;
832 :
833 0 : l = 0;
834 0 : e = getenv("LINES");
835 0 : if (e)
836 0 : (void) safe_atoi(e, &l);
837 :
838 0 : if (l <= 0 || l > USHRT_MAX) {
839 0 : l = fd_lines(STDOUT_FILENO);
840 0 : if (l <= 0)
841 0 : l = 24;
842 : }
843 :
844 0 : cached_lines = l;
845 0 : return cached_lines;
846 : }
847 :
848 : /* intended to be used as a SIGWINCH sighandler */
849 0 : void columns_lines_cache_reset(int signum) {
850 0 : cached_columns = 0;
851 0 : cached_lines = 0;
852 0 : }
853 :
854 0 : void reset_terminal_feature_caches(void) {
855 0 : cached_columns = 0;
856 0 : cached_lines = 0;
857 :
858 0 : cached_colors_enabled = -1;
859 0 : cached_underline_enabled = -1;
860 0 : cached_on_tty = -1;
861 0 : }
862 :
863 8602 : bool on_tty(void) {
864 :
865 : /* We check both stdout and stderr, so that situations where pipes on the shell are used are reliably
866 : * recognized, regardless if only the output or the errors are piped to some place. Since on_tty() is generally
867 : * used to default to a safer, non-interactive, non-color mode of operation it's probably good to be defensive
868 : * here, and check for both. Note that we don't check for STDIN_FILENO, because it should fine to use fancy
869 : * terminal functionality when outputting stuff, even if the input is piped to us. */
870 :
871 8602 : if (cached_on_tty < 0)
872 130 : cached_on_tty =
873 130 : isatty(STDOUT_FILENO) > 0 &&
874 0 : isatty(STDERR_FILENO) > 0;
875 :
876 8602 : return cached_on_tty;
877 : }
878 :
879 1 : int getttyname_malloc(int fd, char **ret) {
880 : char path[PATH_MAX], *c; /* PATH_MAX is counted *with* the trailing NUL byte */
881 : int r;
882 :
883 1 : assert(fd >= 0);
884 1 : assert(ret);
885 :
886 1 : r = ttyname_r(fd, path, sizeof path); /* positive error */
887 1 : assert(r >= 0);
888 1 : if (r == ERANGE)
889 0 : return -ENAMETOOLONG;
890 1 : if (r > 0)
891 0 : return -r;
892 :
893 1 : c = strdup(skip_dev_prefix(path));
894 1 : if (!c)
895 0 : return -ENOMEM;
896 :
897 1 : *ret = c;
898 1 : return 0;
899 : }
900 :
901 0 : int getttyname_harder(int fd, char **ret) {
902 0 : _cleanup_free_ char *s = NULL;
903 : int r;
904 :
905 0 : r = getttyname_malloc(fd, &s);
906 0 : if (r < 0)
907 0 : return r;
908 :
909 0 : if (streq(s, "tty"))
910 0 : return get_ctty(0, NULL, ret);
911 :
912 0 : *ret = TAKE_PTR(s);
913 0 : return 0;
914 : }
915 :
916 406 : int get_ctty_devnr(pid_t pid, dev_t *d) {
917 : int r;
918 406 : _cleanup_free_ char *line = NULL;
919 : const char *p;
920 : unsigned long ttynr;
921 :
922 406 : assert(pid >= 0);
923 :
924 406 : p = procfs_file_alloca(pid, "stat");
925 406 : r = read_one_line_file(p, &line);
926 406 : if (r < 0)
927 0 : return r;
928 :
929 406 : p = strrchr(line, ')');
930 406 : if (!p)
931 0 : return -EIO;
932 :
933 406 : p++;
934 :
935 406 : if (sscanf(p, " "
936 : "%*c " /* state */
937 : "%*d " /* ppid */
938 : "%*d " /* pgrp */
939 : "%*d " /* session */
940 : "%lu ", /* ttynr */
941 : &ttynr) != 1)
942 0 : return -EIO;
943 :
944 406 : if (major(ttynr) == 0 && minor(ttynr) == 0)
945 406 : return -ENXIO;
946 :
947 0 : if (d)
948 0 : *d = (dev_t) ttynr;
949 :
950 0 : return 0;
951 : }
952 :
953 2 : int get_ctty(pid_t pid, dev_t *ret_devnr, char **ret) {
954 2 : _cleanup_free_ char *fn = NULL, *b = NULL;
955 : dev_t devnr;
956 : int r;
957 :
958 2 : r = get_ctty_devnr(pid, &devnr);
959 2 : if (r < 0)
960 2 : return r;
961 :
962 0 : r = device_path_make_canonical(S_IFCHR, devnr, &fn);
963 0 : if (r < 0) {
964 0 : if (r != -ENOENT) /* No symlink for this in /dev/char/? */
965 0 : return r;
966 :
967 0 : if (major(devnr) == 136) {
968 : /* This is an ugly hack: PTY devices are not listed in /dev/char/, as they don't follow the
969 : * Linux device model. This means we have no nice way to match them up against their actual
970 : * device node. Let's hence do the check by the fixed, assigned major number. Normally we try
971 : * to avoid such fixed major/minor matches, but there appears to nother nice way to handle
972 : * this. */
973 :
974 0 : if (asprintf(&b, "pts/%u", minor(devnr)) < 0)
975 0 : return -ENOMEM;
976 : } else {
977 : /* Probably something similar to the ptys which have no symlink in /dev/char/. Let's return
978 : * something vaguely useful. */
979 :
980 0 : r = device_path_make_major_minor(S_IFCHR, devnr, &fn);
981 0 : if (r < 0)
982 0 : return r;
983 : }
984 : }
985 :
986 0 : if (!b) {
987 : const char *w;
988 :
989 0 : w = path_startswith(fn, "/dev/");
990 0 : if (w) {
991 0 : b = strdup(w);
992 0 : if (!b)
993 0 : return -ENOMEM;
994 : } else
995 0 : b = TAKE_PTR(fn);
996 : }
997 :
998 0 : if (ret)
999 0 : *ret = TAKE_PTR(b);
1000 :
1001 0 : if (ret_devnr)
1002 0 : *ret_devnr = devnr;
1003 :
1004 0 : return 0;
1005 : }
1006 :
1007 0 : int ptsname_malloc(int fd, char **ret) {
1008 0 : size_t l = 100;
1009 :
1010 0 : assert(fd >= 0);
1011 0 : assert(ret);
1012 :
1013 0 : for (;;) {
1014 : char *c;
1015 :
1016 0 : c = new(char, l);
1017 0 : if (!c)
1018 0 : return -ENOMEM;
1019 :
1020 0 : if (ptsname_r(fd, c, l) == 0) {
1021 0 : *ret = c;
1022 0 : return 0;
1023 : }
1024 0 : if (errno != ERANGE) {
1025 0 : free(c);
1026 0 : return -errno;
1027 : }
1028 :
1029 0 : free(c);
1030 :
1031 0 : if (l > SIZE_MAX / 2)
1032 0 : return -ENOMEM;
1033 :
1034 0 : l *= 2;
1035 : }
1036 : }
1037 :
1038 0 : int openpt_allocate(int flags, char **ret_slave) {
1039 0 : _cleanup_close_ int fd = -1;
1040 0 : _cleanup_free_ char *p = NULL;
1041 : int r;
1042 :
1043 0 : fd = posix_openpt(flags|O_NOCTTY|O_CLOEXEC);
1044 0 : if (fd < 0)
1045 0 : return -errno;
1046 :
1047 0 : if (ret_slave) {
1048 0 : r = ptsname_malloc(fd, &p);
1049 0 : if (r < 0)
1050 0 : return r;
1051 :
1052 0 : if (!path_startswith(p, "/dev/pts/"))
1053 0 : return -EINVAL;
1054 : }
1055 :
1056 0 : if (unlockpt(fd) < 0)
1057 0 : return -errno;
1058 :
1059 0 : if (ret_slave)
1060 0 : *ret_slave = TAKE_PTR(p);
1061 :
1062 0 : return TAKE_FD(fd);
1063 : }
1064 :
1065 0 : static int ptsname_namespace(int pty, char **ret) {
1066 0 : int no = -1, r;
1067 :
1068 : /* Like ptsname(), but doesn't assume that the path is
1069 : * accessible in the local namespace. */
1070 :
1071 0 : r = ioctl(pty, TIOCGPTN, &no);
1072 0 : if (r < 0)
1073 0 : return -errno;
1074 :
1075 0 : if (no < 0)
1076 0 : return -EIO;
1077 :
1078 0 : if (asprintf(ret, "/dev/pts/%i", no) < 0)
1079 0 : return -ENOMEM;
1080 :
1081 0 : return 0;
1082 : }
1083 :
1084 0 : int openpt_allocate_in_namespace(pid_t pid, int flags, char **ret_slave) {
1085 0 : _cleanup_close_ int pidnsfd = -1, mntnsfd = -1, usernsfd = -1, rootfd = -1, fd = -1;
1086 0 : _cleanup_close_pair_ int pair[2] = { -1, -1 };
1087 : pid_t child;
1088 : int r;
1089 :
1090 0 : assert(pid > 0);
1091 :
1092 0 : r = namespace_open(pid, &pidnsfd, &mntnsfd, NULL, &usernsfd, &rootfd);
1093 0 : if (r < 0)
1094 0 : return r;
1095 :
1096 0 : if (socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) < 0)
1097 0 : return -errno;
1098 :
1099 0 : r = namespace_fork("(sd-openptns)", "(sd-openpt)", NULL, 0, FORK_RESET_SIGNALS|FORK_DEATHSIG,
1100 : pidnsfd, mntnsfd, -1, usernsfd, rootfd, &child);
1101 0 : if (r < 0)
1102 0 : return r;
1103 0 : if (r == 0) {
1104 0 : pair[0] = safe_close(pair[0]);
1105 :
1106 0 : fd = openpt_allocate(flags, NULL);
1107 0 : if (fd < 0)
1108 0 : _exit(EXIT_FAILURE);
1109 :
1110 0 : if (send_one_fd(pair[1], fd, 0) < 0)
1111 0 : _exit(EXIT_FAILURE);
1112 :
1113 0 : _exit(EXIT_SUCCESS);
1114 : }
1115 :
1116 0 : pair[1] = safe_close(pair[1]);
1117 :
1118 0 : r = wait_for_terminate_and_check("(sd-openptns)", child, 0);
1119 0 : if (r < 0)
1120 0 : return r;
1121 0 : if (r != EXIT_SUCCESS)
1122 0 : return -EIO;
1123 :
1124 0 : fd = receive_one_fd(pair[0], 0);
1125 0 : if (fd < 0)
1126 0 : return fd;
1127 :
1128 0 : if (ret_slave) {
1129 0 : r = ptsname_namespace(fd, ret_slave);
1130 0 : if (r < 0)
1131 0 : return r;
1132 : }
1133 :
1134 0 : return TAKE_FD(fd);
1135 : }
1136 :
1137 0 : int open_terminal_in_namespace(pid_t pid, const char *name, int mode) {
1138 0 : _cleanup_close_ int pidnsfd = -1, mntnsfd = -1, usernsfd = -1, rootfd = -1;
1139 0 : _cleanup_close_pair_ int pair[2] = { -1, -1 };
1140 : pid_t child;
1141 : int r;
1142 :
1143 0 : r = namespace_open(pid, &pidnsfd, &mntnsfd, NULL, &usernsfd, &rootfd);
1144 0 : if (r < 0)
1145 0 : return r;
1146 :
1147 0 : if (socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) < 0)
1148 0 : return -errno;
1149 :
1150 0 : r = namespace_fork("(sd-terminalns)", "(sd-terminal)", NULL, 0, FORK_RESET_SIGNALS|FORK_DEATHSIG,
1151 : pidnsfd, mntnsfd, -1, usernsfd, rootfd, &child);
1152 0 : if (r < 0)
1153 0 : return r;
1154 0 : if (r == 0) {
1155 : int master;
1156 :
1157 0 : pair[0] = safe_close(pair[0]);
1158 :
1159 0 : master = open_terminal(name, mode|O_NOCTTY|O_CLOEXEC);
1160 0 : if (master < 0)
1161 0 : _exit(EXIT_FAILURE);
1162 :
1163 0 : if (send_one_fd(pair[1], master, 0) < 0)
1164 0 : _exit(EXIT_FAILURE);
1165 :
1166 0 : _exit(EXIT_SUCCESS);
1167 : }
1168 :
1169 0 : pair[1] = safe_close(pair[1]);
1170 :
1171 0 : r = wait_for_terminate_and_check("(sd-terminalns)", child, 0);
1172 0 : if (r < 0)
1173 0 : return r;
1174 0 : if (r != EXIT_SUCCESS)
1175 0 : return -EIO;
1176 :
1177 0 : return receive_one_fd(pair[0], 0);
1178 : }
1179 :
1180 0 : static bool getenv_terminal_is_dumb(void) {
1181 : const char *e;
1182 :
1183 0 : e = getenv("TERM");
1184 0 : if (!e)
1185 0 : return true;
1186 :
1187 0 : return streq(e, "dumb");
1188 : }
1189 :
1190 150 : bool terminal_is_dumb(void) {
1191 150 : if (!on_tty())
1192 150 : return true;
1193 :
1194 0 : return getenv_terminal_is_dumb();
1195 : }
1196 :
1197 1122 : bool colors_enabled(void) {
1198 :
1199 : /* Returns true if colors are considered supported on our stdout. For that we check $SYSTEMD_COLORS first
1200 : * (which is the explicit way to turn colors on/off). If that didn't work we turn colors off unless we are on a
1201 : * TTY. And if we are on a TTY we turn it off if $TERM is set to "dumb". There's one special tweak though: if
1202 : * we are PID 1 then we do not check whether we are connected to a TTY, because we don't keep /dev/console open
1203 : * continuously due to fear of SAK, and hence things are a bit weird. */
1204 :
1205 1122 : if (cached_colors_enabled < 0) {
1206 : int val;
1207 :
1208 130 : val = getenv_bool("SYSTEMD_COLORS");
1209 130 : if (val >= 0)
1210 1 : cached_colors_enabled = val;
1211 129 : else if (getpid_cached() == 1)
1212 : /* PID1 outputs to the console without holding it open all the time */
1213 0 : cached_colors_enabled = !getenv_terminal_is_dumb();
1214 : else
1215 129 : cached_colors_enabled = !terminal_is_dumb();
1216 : }
1217 :
1218 1122 : return cached_colors_enabled;
1219 : }
1220 :
1221 0 : bool dev_console_colors_enabled(void) {
1222 0 : _cleanup_free_ char *s = NULL;
1223 : int b;
1224 :
1225 : /* Returns true if we assume that color is supported on /dev/console.
1226 : *
1227 : * For that we first check if we explicitly got told to use colors or not, by checking $SYSTEMD_COLORS. If that
1228 : * isn't set we check whether PID 1 has $TERM set, and if not, whether TERM is set on the kernel command
1229 : * line. If we find $TERM set we assume color if it's not set to "dumb", similarly to how regular
1230 : * colors_enabled() operates. */
1231 :
1232 0 : b = getenv_bool("SYSTEMD_COLORS");
1233 0 : if (b >= 0)
1234 0 : return b;
1235 :
1236 0 : if (getenv_for_pid(1, "TERM", &s) <= 0)
1237 0 : (void) proc_cmdline_get_key("TERM", 0, &s);
1238 :
1239 0 : return !streq_ptr(s, "dumb");
1240 : }
1241 :
1242 39 : bool underline_enabled(void) {
1243 :
1244 39 : if (cached_underline_enabled < 0) {
1245 :
1246 : /* The Linux console doesn't support underlining, turn it off, but only there. */
1247 :
1248 9 : if (colors_enabled())
1249 0 : cached_underline_enabled = !streq_ptr(getenv("TERM"), "linux");
1250 : else
1251 9 : cached_underline_enabled = false;
1252 : }
1253 :
1254 39 : return cached_underline_enabled;
1255 : }
1256 :
1257 0 : int vt_default_utf8(void) {
1258 0 : _cleanup_free_ char *b = NULL;
1259 : int r;
1260 :
1261 : /* Read the default VT UTF8 setting from the kernel */
1262 :
1263 0 : r = read_one_line_file("/sys/module/vt/parameters/default_utf8", &b);
1264 0 : if (r < 0)
1265 0 : return r;
1266 :
1267 0 : return parse_boolean(b);
1268 : }
1269 :
1270 0 : int vt_reset_keyboard(int fd) {
1271 : int kb;
1272 :
1273 : /* If we can't read the default, then default to unicode. It's 2017 after all. */
1274 0 : kb = vt_default_utf8() != 0 ? K_UNICODE : K_XLATE;
1275 :
1276 0 : if (ioctl(fd, KDSKBMODE, kb) < 0)
1277 0 : return -errno;
1278 :
1279 0 : return 0;
1280 : }
1281 :
1282 0 : int vt_restore(int fd) {
1283 : static const struct vt_mode mode = {
1284 : .mode = VT_AUTO,
1285 : };
1286 0 : int r, q = 0;
1287 :
1288 0 : if (ioctl(fd, KDSETMODE, KD_TEXT) < 0)
1289 0 : q = log_debug_errno(errno, "Failed to set VT in text mode, ignoring: %m");
1290 :
1291 0 : r = vt_reset_keyboard(fd);
1292 0 : if (r < 0) {
1293 0 : log_debug_errno(r, "Failed to reset keyboard mode, ignoring: %m");
1294 0 : if (q >= 0)
1295 0 : q = r;
1296 : }
1297 :
1298 0 : if (ioctl(fd, VT_SETMODE, &mode) < 0) {
1299 0 : log_debug_errno(errno, "Failed to set VT_AUTO mode, ignoring: %m");
1300 0 : if (q >= 0)
1301 0 : q = -errno;
1302 : }
1303 :
1304 0 : r = fchmod_and_chown(fd, TTY_MODE, 0, (gid_t) -1);
1305 0 : if (r < 0) {
1306 0 : log_debug_errno(r, "Failed to chmod()/chown() VT, ignoring: %m");
1307 0 : if (q >= 0)
1308 0 : q = r;
1309 : }
1310 :
1311 0 : return q;
1312 : }
1313 :
1314 0 : int vt_release(int fd, bool restore) {
1315 0 : assert(fd >= 0);
1316 :
1317 : /* This function releases the VT by acknowledging the VT-switch signal
1318 : * sent by the kernel and optionally reset the VT in text and auto
1319 : * VT-switching modes. */
1320 :
1321 0 : if (ioctl(fd, VT_RELDISP, 1) < 0)
1322 0 : return -errno;
1323 :
1324 0 : if (restore)
1325 0 : return vt_restore(fd);
1326 :
1327 0 : return 0;
1328 : }
1329 :
1330 1 : void get_log_colors(int priority, const char **on, const char **off, const char **highlight) {
1331 : /* Note that this will initialize output variables only when there's something to output.
1332 : * The caller must pre-initalize to "" or NULL as appropriate. */
1333 :
1334 1 : if (priority <= LOG_ERR) {
1335 1 : if (on)
1336 1 : *on = ANSI_HIGHLIGHT_RED;
1337 1 : if (off)
1338 1 : *off = ANSI_NORMAL;
1339 1 : if (highlight)
1340 0 : *highlight = ANSI_HIGHLIGHT;
1341 :
1342 0 : } else if (priority <= LOG_WARNING) {
1343 0 : if (on)
1344 0 : *on = ANSI_HIGHLIGHT_YELLOW;
1345 0 : if (off)
1346 0 : *off = ANSI_NORMAL;
1347 0 : if (highlight)
1348 0 : *highlight = ANSI_HIGHLIGHT;
1349 :
1350 0 : } else if (priority <= LOG_NOTICE) {
1351 0 : if (on)
1352 0 : *on = ANSI_HIGHLIGHT;
1353 0 : if (off)
1354 0 : *off = ANSI_NORMAL;
1355 0 : if (highlight)
1356 0 : *highlight = ANSI_HIGHLIGHT_RED;
1357 :
1358 0 : } else if (priority >= LOG_DEBUG) {
1359 0 : if (on)
1360 0 : *on = ANSI_GREY;
1361 0 : if (off)
1362 0 : *off = ANSI_NORMAL;
1363 0 : if (highlight)
1364 0 : *highlight = ANSI_HIGHLIGHT_RED;
1365 : }
1366 1 : }
|