Line data Source code
1 : /***
2 : This file is part of systemd.
3 :
4 : Copyright 2010 Lennart Poettering
5 :
6 : systemd is free software; you can redistribute it and/or modify it
7 : under the terms of the GNU Lesser General Public License as published by
8 : the Free Software Foundation; either version 2.1 of the License, or
9 : (at your option) any later version.
10 :
11 : systemd is distributed in the hope that it will be useful, but
12 : WITHOUT ANY WARRANTY; without even the implied warranty of
13 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 : Lesser General Public License for more details.
15 :
16 : You should have received a copy of the GNU Lesser General Public License
17 : along with systemd; If not, see <http://www.gnu.org/licenses/>.
18 : ***/
19 :
20 : #include <sys/ioctl.h>
21 : #include <sys/types.h>
22 : #include <sys/stat.h>
23 : #include <termios.h>
24 : #include <unistd.h>
25 : #include <fcntl.h>
26 : #include <signal.h>
27 : #include <time.h>
28 : #include <assert.h>
29 : #include <poll.h>
30 : #include <linux/vt.h>
31 : #include <linux/tiocl.h>
32 : #include <linux/kd.h>
33 :
34 : #include "terminal-util.h"
35 : #include "time-util.h"
36 : #include "process-util.h"
37 : #include "util.h"
38 : #include "fileio.h"
39 : #include "path-util.h"
40 :
41 : static volatile unsigned cached_columns = 0;
42 : static volatile unsigned cached_lines = 0;
43 :
44 0 : int chvt(int vt) {
45 0 : _cleanup_close_ int fd;
46 :
47 0 : fd = open_terminal("/dev/tty0", O_RDWR|O_NOCTTY|O_CLOEXEC);
48 0 : if (fd < 0)
49 0 : return -errno;
50 :
51 0 : if (vt < 0) {
52 0 : int tiocl[2] = {
53 : TIOCL_GETKMSGREDIRECT,
54 : 0
55 : };
56 :
57 0 : if (ioctl(fd, TIOCLINUX, tiocl) < 0)
58 0 : return -errno;
59 :
60 0 : vt = tiocl[0] <= 0 ? 1 : tiocl[0];
61 : }
62 :
63 0 : if (ioctl(fd, VT_ACTIVATE, vt) < 0)
64 0 : return -errno;
65 :
66 0 : return 0;
67 : }
68 :
69 4 : int read_one_char(FILE *f, char *ret, usec_t t, bool *need_nl) {
70 : struct termios old_termios, new_termios;
71 : char c, line[LINE_MAX];
72 :
73 4 : assert(f);
74 4 : assert(ret);
75 :
76 4 : if (tcgetattr(fileno(f), &old_termios) >= 0) {
77 0 : new_termios = old_termios;
78 :
79 0 : new_termios.c_lflag &= ~ICANON;
80 0 : new_termios.c_cc[VMIN] = 1;
81 0 : new_termios.c_cc[VTIME] = 0;
82 :
83 0 : if (tcsetattr(fileno(f), TCSADRAIN, &new_termios) >= 0) {
84 : size_t k;
85 :
86 0 : if (t != USEC_INFINITY) {
87 0 : if (fd_wait_for_event(fileno(f), POLLIN, t) <= 0) {
88 0 : tcsetattr(fileno(f), TCSADRAIN, &old_termios);
89 0 : return -ETIMEDOUT;
90 : }
91 : }
92 :
93 0 : k = fread(&c, 1, 1, f);
94 :
95 0 : tcsetattr(fileno(f), TCSADRAIN, &old_termios);
96 :
97 0 : if (k <= 0)
98 0 : return -EIO;
99 :
100 0 : if (need_nl)
101 0 : *need_nl = c != '\n';
102 :
103 0 : *ret = c;
104 0 : return 0;
105 : }
106 : }
107 :
108 4 : if (t != USEC_INFINITY) {
109 4 : if (fd_wait_for_event(fileno(f), POLLIN, t) <= 0)
110 0 : return -ETIMEDOUT;
111 : }
112 :
113 4 : errno = 0;
114 4 : if (!fgets(line, sizeof(line), f))
115 1 : return errno ? -errno : -EIO;
116 :
117 3 : truncate_nl(line);
118 :
119 3 : if (strlen(line) != 1)
120 2 : return -EBADMSG;
121 :
122 1 : if (need_nl)
123 1 : *need_nl = false;
124 :
125 1 : *ret = line[0];
126 1 : return 0;
127 : }
128 :
129 0 : int ask_char(char *ret, const char *replies, const char *text, ...) {
130 : int r;
131 :
132 0 : assert(ret);
133 0 : assert(replies);
134 0 : assert(text);
135 :
136 : for (;;) {
137 : va_list ap;
138 : char c;
139 0 : bool need_nl = true;
140 :
141 0 : if (on_tty())
142 0 : fputs(ANSI_HIGHLIGHT_ON, stdout);
143 :
144 0 : va_start(ap, text);
145 0 : vprintf(text, ap);
146 0 : va_end(ap);
147 :
148 0 : if (on_tty())
149 0 : fputs(ANSI_HIGHLIGHT_OFF, stdout);
150 :
151 0 : fflush(stdout);
152 :
153 0 : r = read_one_char(stdin, &c, USEC_INFINITY, &need_nl);
154 0 : if (r < 0) {
155 :
156 0 : if (r == -EBADMSG) {
157 0 : puts("Bad input, please try again.");
158 0 : continue;
159 : }
160 :
161 0 : putchar('\n');
162 0 : return r;
163 : }
164 :
165 0 : if (need_nl)
166 0 : putchar('\n');
167 :
168 0 : if (strchr(replies, c)) {
169 0 : *ret = c;
170 0 : return 0;
171 : }
172 :
173 0 : puts("Read unexpected character, please try again.");
174 0 : }
175 : }
176 :
177 0 : int ask_string(char **ret, const char *text, ...) {
178 0 : assert(ret);
179 0 : assert(text);
180 :
181 : for (;;) {
182 : char line[LINE_MAX];
183 : va_list ap;
184 :
185 0 : if (on_tty())
186 0 : fputs(ANSI_HIGHLIGHT_ON, stdout);
187 :
188 0 : va_start(ap, text);
189 0 : vprintf(text, ap);
190 0 : va_end(ap);
191 :
192 0 : if (on_tty())
193 0 : fputs(ANSI_HIGHLIGHT_OFF, stdout);
194 :
195 0 : fflush(stdout);
196 :
197 0 : errno = 0;
198 0 : if (!fgets(line, sizeof(line), stdin))
199 0 : return errno ? -errno : -EIO;
200 :
201 0 : if (!endswith(line, "\n"))
202 0 : putchar('\n');
203 : else {
204 : char *s;
205 :
206 0 : if (isempty(line))
207 0 : continue;
208 :
209 0 : truncate_nl(line);
210 0 : s = strdup(line);
211 0 : if (!s)
212 0 : return -ENOMEM;
213 :
214 0 : *ret = s;
215 0 : return 0;
216 : }
217 0 : }
218 : }
219 :
220 0 : int reset_terminal_fd(int fd, bool switch_to_text) {
221 : struct termios termios;
222 0 : int r = 0;
223 :
224 : /* Set terminal to some sane defaults */
225 :
226 0 : assert(fd >= 0);
227 :
228 : /* We leave locked terminal attributes untouched, so that
229 : * Plymouth may set whatever it wants to set, and we don't
230 : * interfere with that. */
231 :
232 : /* Disable exclusive mode, just in case */
233 0 : ioctl(fd, TIOCNXCL);
234 :
235 : /* Switch to text mode */
236 0 : if (switch_to_text)
237 0 : ioctl(fd, KDSETMODE, KD_TEXT);
238 :
239 : /* Enable console unicode mode */
240 0 : ioctl(fd, KDSKBMODE, K_UNICODE);
241 :
242 0 : if (tcgetattr(fd, &termios) < 0) {
243 0 : r = -errno;
244 0 : goto finish;
245 : }
246 :
247 : /* We only reset the stuff that matters to the software. How
248 : * hardware is set up we don't touch assuming that somebody
249 : * else will do that for us */
250 :
251 0 : termios.c_iflag &= ~(IGNBRK | BRKINT | ISTRIP | INLCR | IGNCR | IUCLC);
252 0 : termios.c_iflag |= ICRNL | IMAXBEL | IUTF8;
253 0 : termios.c_oflag |= ONLCR;
254 0 : termios.c_cflag |= CREAD;
255 0 : termios.c_lflag = ISIG | ICANON | IEXTEN | ECHO | ECHOE | ECHOK | ECHOCTL | ECHOPRT | ECHOKE;
256 :
257 0 : termios.c_cc[VINTR] = 03; /* ^C */
258 0 : termios.c_cc[VQUIT] = 034; /* ^\ */
259 0 : termios.c_cc[VERASE] = 0177;
260 0 : termios.c_cc[VKILL] = 025; /* ^X */
261 0 : termios.c_cc[VEOF] = 04; /* ^D */
262 0 : termios.c_cc[VSTART] = 021; /* ^Q */
263 0 : termios.c_cc[VSTOP] = 023; /* ^S */
264 0 : termios.c_cc[VSUSP] = 032; /* ^Z */
265 0 : termios.c_cc[VLNEXT] = 026; /* ^V */
266 0 : termios.c_cc[VWERASE] = 027; /* ^W */
267 0 : termios.c_cc[VREPRINT] = 022; /* ^R */
268 0 : termios.c_cc[VEOL] = 0;
269 0 : termios.c_cc[VEOL2] = 0;
270 :
271 0 : termios.c_cc[VTIME] = 0;
272 0 : termios.c_cc[VMIN] = 1;
273 :
274 0 : if (tcsetattr(fd, TCSANOW, &termios) < 0)
275 0 : r = -errno;
276 :
277 : finish:
278 : /* Just in case, flush all crap out */
279 0 : tcflush(fd, TCIOFLUSH);
280 :
281 0 : return r;
282 : }
283 :
284 0 : int reset_terminal(const char *name) {
285 0 : _cleanup_close_ int fd = -1;
286 :
287 0 : fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC);
288 0 : if (fd < 0)
289 0 : return fd;
290 :
291 0 : return reset_terminal_fd(fd, true);
292 : }
293 :
294 0 : int open_terminal(const char *name, int mode) {
295 : int fd, r;
296 0 : unsigned c = 0;
297 :
298 : /*
299 : * If a TTY is in the process of being closed opening it might
300 : * cause EIO. This is horribly awful, but unlikely to be
301 : * changed in the kernel. Hence we work around this problem by
302 : * retrying a couple of times.
303 : *
304 : * https://bugs.launchpad.net/ubuntu/+source/linux/+bug/554172/comments/245
305 : */
306 :
307 0 : assert(!(mode & O_CREAT));
308 :
309 : for (;;) {
310 0 : fd = open(name, mode, 0);
311 0 : if (fd >= 0)
312 0 : break;
313 :
314 0 : if (errno != EIO)
315 0 : return -errno;
316 :
317 : /* Max 1s in total */
318 0 : if (c >= 20)
319 0 : return -errno;
320 :
321 0 : usleep(50 * USEC_PER_MSEC);
322 0 : c++;
323 0 : }
324 :
325 0 : r = isatty(fd);
326 0 : if (r < 0) {
327 0 : safe_close(fd);
328 0 : return -errno;
329 : }
330 :
331 0 : if (!r) {
332 0 : safe_close(fd);
333 0 : return -ENOTTY;
334 : }
335 :
336 0 : return fd;
337 : }
338 :
339 0 : int acquire_terminal(
340 : const char *name,
341 : bool fail,
342 : bool force,
343 : bool ignore_tiocstty_eperm,
344 : usec_t timeout) {
345 :
346 0 : int fd = -1, notify = -1, r = 0, wd = -1;
347 0 : usec_t ts = 0;
348 :
349 0 : assert(name);
350 :
351 : /* We use inotify to be notified when the tty is closed. We
352 : * create the watch before checking if we can actually acquire
353 : * it, so that we don't lose any event.
354 : *
355 : * Note: strictly speaking this actually watches for the
356 : * device being closed, it does *not* really watch whether a
357 : * tty loses its controlling process. However, unless some
358 : * rogue process uses TIOCNOTTY on /dev/tty *after* closing
359 : * its tty otherwise this will not become a problem. As long
360 : * as the administrator makes sure not configure any service
361 : * on the same tty as an untrusted user this should not be a
362 : * problem. (Which he probably should not do anyway.) */
363 :
364 0 : if (timeout != USEC_INFINITY)
365 0 : ts = now(CLOCK_MONOTONIC);
366 :
367 0 : if (!fail && !force) {
368 0 : notify = inotify_init1(IN_CLOEXEC | (timeout != USEC_INFINITY ? IN_NONBLOCK : 0));
369 0 : if (notify < 0) {
370 0 : r = -errno;
371 0 : goto fail;
372 : }
373 :
374 0 : wd = inotify_add_watch(notify, name, IN_CLOSE);
375 0 : if (wd < 0) {
376 0 : r = -errno;
377 0 : goto fail;
378 : }
379 : }
380 :
381 : for (;;) {
382 0 : struct sigaction sa_old, sa_new = {
383 : .sa_handler = SIG_IGN,
384 : .sa_flags = SA_RESTART,
385 : };
386 :
387 0 : if (notify >= 0) {
388 0 : r = flush_fd(notify);
389 0 : if (r < 0)
390 0 : goto fail;
391 : }
392 :
393 : /* We pass here O_NOCTTY only so that we can check the return
394 : * value TIOCSCTTY and have a reliable way to figure out if we
395 : * successfully became the controlling process of the tty */
396 0 : fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC);
397 0 : if (fd < 0)
398 0 : return fd;
399 :
400 : /* Temporarily ignore SIGHUP, so that we don't get SIGHUP'ed
401 : * if we already own the tty. */
402 0 : assert_se(sigaction(SIGHUP, &sa_new, &sa_old) == 0);
403 :
404 : /* First, try to get the tty */
405 0 : if (ioctl(fd, TIOCSCTTY, force) < 0)
406 0 : r = -errno;
407 :
408 0 : assert_se(sigaction(SIGHUP, &sa_old, NULL) == 0);
409 :
410 : /* Sometimes it makes sense to ignore TIOCSCTTY
411 : * returning EPERM, i.e. when very likely we already
412 : * are have this controlling terminal. */
413 0 : if (r < 0 && r == -EPERM && ignore_tiocstty_eperm)
414 0 : r = 0;
415 :
416 0 : if (r < 0 && (force || fail || r != -EPERM)) {
417 : goto fail;
418 : }
419 :
420 0 : if (r >= 0)
421 0 : break;
422 :
423 0 : assert(!fail);
424 0 : assert(!force);
425 0 : assert(notify >= 0);
426 :
427 : for (;;) {
428 : union inotify_event_buffer buffer;
429 : struct inotify_event *e;
430 : ssize_t l;
431 :
432 0 : if (timeout != USEC_INFINITY) {
433 : usec_t n;
434 :
435 0 : n = now(CLOCK_MONOTONIC);
436 0 : if (ts + timeout < n) {
437 0 : r = -ETIMEDOUT;
438 0 : goto fail;
439 : }
440 :
441 0 : r = fd_wait_for_event(fd, POLLIN, ts + timeout - n);
442 0 : if (r < 0)
443 0 : goto fail;
444 :
445 0 : if (r == 0) {
446 0 : r = -ETIMEDOUT;
447 0 : goto fail;
448 : }
449 : }
450 :
451 0 : l = read(notify, &buffer, sizeof(buffer));
452 0 : if (l < 0) {
453 0 : if (errno == EINTR || errno == EAGAIN)
454 0 : continue;
455 :
456 0 : r = -errno;
457 0 : goto fail;
458 : }
459 :
460 0 : FOREACH_INOTIFY_EVENT(e, buffer, l) {
461 0 : if (e->wd != wd || !(e->mask & IN_CLOSE)) {
462 0 : r = -EIO;
463 0 : goto fail;
464 : }
465 : }
466 :
467 0 : break;
468 0 : }
469 :
470 : /* We close the tty fd here since if the old session
471 : * ended our handle will be dead. It's important that
472 : * we do this after sleeping, so that we don't enter
473 : * an endless loop. */
474 0 : fd = safe_close(fd);
475 0 : }
476 :
477 0 : safe_close(notify);
478 :
479 0 : r = reset_terminal_fd(fd, true);
480 0 : if (r < 0)
481 0 : log_warning_errno(r, "Failed to reset terminal: %m");
482 :
483 0 : return fd;
484 :
485 : fail:
486 0 : safe_close(fd);
487 0 : safe_close(notify);
488 :
489 0 : return r;
490 : }
491 :
492 0 : int release_terminal(void) {
493 : static const struct sigaction sa_new = {
494 : .sa_handler = SIG_IGN,
495 : .sa_flags = SA_RESTART,
496 : };
497 :
498 0 : _cleanup_close_ int fd = -1;
499 : struct sigaction sa_old;
500 0 : int r = 0;
501 :
502 0 : fd = open("/dev/tty", O_RDWR|O_NOCTTY|O_NDELAY|O_CLOEXEC);
503 0 : if (fd < 0)
504 0 : return -errno;
505 :
506 : /* Temporarily ignore SIGHUP, so that we don't get SIGHUP'ed
507 : * by our own TIOCNOTTY */
508 0 : assert_se(sigaction(SIGHUP, &sa_new, &sa_old) == 0);
509 :
510 0 : if (ioctl(fd, TIOCNOTTY) < 0)
511 0 : r = -errno;
512 :
513 0 : assert_se(sigaction(SIGHUP, &sa_old, NULL) == 0);
514 :
515 0 : return r;
516 : }
517 :
518 0 : int terminal_vhangup_fd(int fd) {
519 0 : assert(fd >= 0);
520 :
521 0 : if (ioctl(fd, TIOCVHANGUP) < 0)
522 0 : return -errno;
523 :
524 0 : return 0;
525 : }
526 :
527 0 : int terminal_vhangup(const char *name) {
528 0 : _cleanup_close_ int fd;
529 :
530 0 : fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC);
531 0 : if (fd < 0)
532 0 : return fd;
533 :
534 0 : return terminal_vhangup_fd(fd);
535 : }
536 :
537 0 : int vt_disallocate(const char *name) {
538 : int fd, r;
539 : unsigned u;
540 :
541 : /* Deallocate the VT if possible. If not possible
542 : * (i.e. because it is the active one), at least clear it
543 : * entirely (including the scrollback buffer) */
544 :
545 0 : if (!startswith(name, "/dev/"))
546 0 : return -EINVAL;
547 :
548 0 : if (!tty_is_vc(name)) {
549 : /* So this is not a VT. I guess we cannot deallocate
550 : * it then. But let's at least clear the screen */
551 :
552 0 : fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC);
553 0 : if (fd < 0)
554 0 : return fd;
555 :
556 0 : loop_write(fd,
557 : "\033[r" /* clear scrolling region */
558 : "\033[H" /* move home */
559 : "\033[2J", /* clear screen */
560 : 10, false);
561 0 : safe_close(fd);
562 :
563 0 : return 0;
564 : }
565 :
566 0 : if (!startswith(name, "/dev/tty"))
567 0 : return -EINVAL;
568 :
569 0 : r = safe_atou(name+8, &u);
570 0 : if (r < 0)
571 0 : return r;
572 :
573 0 : if (u <= 0)
574 0 : return -EINVAL;
575 :
576 : /* Try to deallocate */
577 0 : fd = open_terminal("/dev/tty0", O_RDWR|O_NOCTTY|O_CLOEXEC);
578 0 : if (fd < 0)
579 0 : return fd;
580 :
581 0 : r = ioctl(fd, VT_DISALLOCATE, u);
582 0 : safe_close(fd);
583 :
584 0 : if (r >= 0)
585 0 : return 0;
586 :
587 0 : if (errno != EBUSY)
588 0 : return -errno;
589 :
590 : /* Couldn't deallocate, so let's clear it fully with
591 : * scrollback */
592 0 : fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC);
593 0 : if (fd < 0)
594 0 : return fd;
595 :
596 0 : loop_write(fd,
597 : "\033[r" /* clear scrolling region */
598 : "\033[H" /* move home */
599 : "\033[3J", /* clear screen including scrollback, requires Linux 2.6.40 */
600 : 10, false);
601 0 : safe_close(fd);
602 :
603 0 : return 0;
604 : }
605 :
606 0 : void warn_melody(void) {
607 0 : _cleanup_close_ int fd = -1;
608 :
609 0 : fd = open("/dev/console", O_WRONLY|O_CLOEXEC|O_NOCTTY);
610 0 : if (fd < 0)
611 0 : return;
612 :
613 : /* Yeah, this is synchronous. Kinda sucks. But well... */
614 :
615 0 : ioctl(fd, KIOCSOUND, (int)(1193180/440));
616 0 : usleep(125*USEC_PER_MSEC);
617 :
618 0 : ioctl(fd, KIOCSOUND, (int)(1193180/220));
619 0 : usleep(125*USEC_PER_MSEC);
620 :
621 0 : ioctl(fd, KIOCSOUND, (int)(1193180/220));
622 0 : usleep(125*USEC_PER_MSEC);
623 :
624 0 : ioctl(fd, KIOCSOUND, 0);
625 : }
626 :
627 0 : int make_console_stdio(void) {
628 : int fd, r;
629 :
630 : /* Make /dev/console the controlling terminal and stdin/stdout/stderr */
631 :
632 0 : fd = acquire_terminal("/dev/console", false, true, true, USEC_INFINITY);
633 0 : if (fd < 0)
634 0 : return log_error_errno(fd, "Failed to acquire terminal: %m");
635 :
636 0 : r = make_stdio(fd);
637 0 : if (r < 0)
638 0 : return log_error_errno(r, "Failed to duplicate terminal fd: %m");
639 :
640 0 : return 0;
641 : }
642 :
643 0 : int status_vprintf(const char *status, bool ellipse, bool ephemeral, const char *format, va_list ap) {
644 : static const char status_indent[] = " "; /* "[" STATUS "] " */
645 0 : _cleanup_free_ char *s = NULL;
646 0 : _cleanup_close_ int fd = -1;
647 0 : struct iovec iovec[6] = {};
648 0 : int n = 0;
649 : static bool prev_ephemeral;
650 :
651 0 : assert(format);
652 :
653 : /* This is independent of logging, as status messages are
654 : * optional and go exclusively to the console. */
655 :
656 0 : if (vasprintf(&s, format, ap) < 0)
657 0 : return log_oom();
658 :
659 0 : fd = open_terminal("/dev/console", O_WRONLY|O_NOCTTY|O_CLOEXEC);
660 0 : if (fd < 0)
661 0 : return fd;
662 :
663 0 : if (ellipse) {
664 : char *e;
665 : size_t emax, sl;
666 : int c;
667 :
668 0 : c = fd_columns(fd);
669 0 : if (c <= 0)
670 0 : c = 80;
671 :
672 0 : sl = status ? sizeof(status_indent)-1 : 0;
673 :
674 0 : emax = c - sl - 1;
675 0 : if (emax < 3)
676 0 : emax = 3;
677 :
678 0 : e = ellipsize(s, emax, 50);
679 0 : if (e) {
680 0 : free(s);
681 0 : s = e;
682 : }
683 : }
684 :
685 0 : if (prev_ephemeral)
686 0 : IOVEC_SET_STRING(iovec[n++], "\r" ANSI_ERASE_TO_END_OF_LINE);
687 0 : prev_ephemeral = ephemeral;
688 :
689 0 : if (status) {
690 0 : if (!isempty(status)) {
691 0 : IOVEC_SET_STRING(iovec[n++], "[");
692 0 : IOVEC_SET_STRING(iovec[n++], status);
693 0 : IOVEC_SET_STRING(iovec[n++], "] ");
694 : } else
695 0 : IOVEC_SET_STRING(iovec[n++], status_indent);
696 : }
697 :
698 0 : IOVEC_SET_STRING(iovec[n++], s);
699 0 : if (!ephemeral)
700 0 : IOVEC_SET_STRING(iovec[n++], "\n");
701 :
702 0 : if (writev(fd, iovec, n) < 0)
703 0 : return -errno;
704 :
705 0 : return 0;
706 : }
707 :
708 0 : int status_printf(const char *status, bool ellipse, bool ephemeral, const char *format, ...) {
709 : va_list ap;
710 : int r;
711 :
712 0 : assert(format);
713 :
714 0 : va_start(ap, format);
715 0 : r = status_vprintf(status, ellipse, ephemeral, format, ap);
716 0 : va_end(ap);
717 :
718 0 : return r;
719 : }
720 :
721 12 : bool tty_is_vc(const char *tty) {
722 12 : assert(tty);
723 :
724 12 : return vtnr_from_tty(tty) >= 0;
725 : }
726 :
727 0 : bool tty_is_console(const char *tty) {
728 0 : assert(tty);
729 :
730 0 : if (startswith(tty, "/dev/"))
731 0 : tty += 5;
732 :
733 0 : return streq(tty, "console");
734 : }
735 :
736 12 : int vtnr_from_tty(const char *tty) {
737 : int i, r;
738 :
739 12 : assert(tty);
740 :
741 12 : if (startswith(tty, "/dev/"))
742 0 : tty += 5;
743 :
744 12 : if (!startswith(tty, "tty") )
745 4 : return -EINVAL;
746 :
747 8 : if (tty[3] < '0' || tty[3] > '9')
748 2 : return -EINVAL;
749 :
750 6 : r = safe_atoi(tty+3, &i);
751 6 : if (r < 0)
752 0 : return r;
753 :
754 6 : if (i < 0 || i > 63)
755 0 : return -EINVAL;
756 :
757 6 : return i;
758 : }
759 :
760 2 : char *resolve_dev_console(char **active) {
761 : char *tty;
762 :
763 : /* Resolve where /dev/console is pointing to, if /sys is actually ours
764 : * (i.e. not read-only-mounted which is a sign for container setups) */
765 :
766 2 : if (path_is_read_only_fs("/sys") > 0)
767 0 : return NULL;
768 :
769 2 : if (read_one_line_file("/sys/class/tty/console/active", active) < 0)
770 0 : return NULL;
771 :
772 : /* If multiple log outputs are configured the last one is what
773 : * /dev/console points to */
774 2 : tty = strrchr(*active, ' ');
775 2 : if (tty)
776 0 : tty++;
777 : else
778 2 : tty = *active;
779 :
780 2 : if (streq(tty, "tty0")) {
781 : char *tmp;
782 :
783 : /* Get the active VC (e.g. tty1) */
784 2 : if (read_one_line_file("/sys/class/tty/tty0/active", &tmp) >= 0) {
785 2 : free(*active);
786 2 : tty = *active = tmp;
787 : }
788 : }
789 :
790 2 : return tty;
791 : }
792 :
793 12 : bool tty_is_vc_resolve(const char *tty) {
794 24 : _cleanup_free_ char *active = NULL;
795 :
796 12 : assert(tty);
797 :
798 12 : if (startswith(tty, "/dev/"))
799 6 : tty += 5;
800 :
801 12 : if (streq(tty, "console")) {
802 2 : tty = resolve_dev_console(&active);
803 2 : if (!tty)
804 0 : return false;
805 : }
806 :
807 12 : return tty_is_vc(tty);
808 : }
809 :
810 12 : const char *default_term_for_tty(const char *tty) {
811 12 : assert(tty);
812 :
813 12 : return tty_is_vc_resolve(tty) ? "TERM=linux" : "TERM=vt220";
814 : }
815 :
816 1 : int fd_columns(int fd) {
817 1 : struct winsize ws = {};
818 :
819 1 : if (ioctl(fd, TIOCGWINSZ, &ws) < 0)
820 1 : return -errno;
821 :
822 0 : if (ws.ws_col <= 0)
823 0 : return -EIO;
824 :
825 0 : return ws.ws_col;
826 : }
827 :
828 8 : unsigned columns(void) {
829 : const char *e;
830 : int c;
831 :
832 8 : if (_likely_(cached_columns > 0))
833 7 : return cached_columns;
834 :
835 1 : c = 0;
836 1 : e = getenv("COLUMNS");
837 1 : if (e)
838 0 : (void) safe_atoi(e, &c);
839 :
840 1 : if (c <= 0)
841 1 : c = fd_columns(STDOUT_FILENO);
842 :
843 1 : if (c <= 0)
844 1 : c = 80;
845 :
846 1 : cached_columns = c;
847 1 : return cached_columns;
848 : }
849 :
850 0 : int fd_lines(int fd) {
851 0 : struct winsize ws = {};
852 :
853 0 : if (ioctl(fd, TIOCGWINSZ, &ws) < 0)
854 0 : return -errno;
855 :
856 0 : if (ws.ws_row <= 0)
857 0 : return -EIO;
858 :
859 0 : return ws.ws_row;
860 : }
861 :
862 0 : unsigned lines(void) {
863 : const char *e;
864 : int l;
865 :
866 0 : if (_likely_(cached_lines > 0))
867 0 : return cached_lines;
868 :
869 0 : l = 0;
870 0 : e = getenv("LINES");
871 0 : if (e)
872 0 : (void) safe_atoi(e, &l);
873 :
874 0 : if (l <= 0)
875 0 : l = fd_lines(STDOUT_FILENO);
876 :
877 0 : if (l <= 0)
878 0 : l = 24;
879 :
880 0 : cached_lines = l;
881 0 : return cached_lines;
882 : }
883 :
884 : /* intended to be used as a SIGWINCH sighandler */
885 0 : void columns_lines_cache_reset(int signum) {
886 0 : cached_columns = 0;
887 0 : cached_lines = 0;
888 0 : }
889 :
890 9503 : bool on_tty(void) {
891 : static int cached_on_tty = -1;
892 :
893 9503 : if (_unlikely_(cached_on_tty < 0))
894 6 : cached_on_tty = isatty(STDOUT_FILENO) > 0;
895 :
896 9503 : return cached_on_tty;
897 : }
898 :
899 0 : int make_stdio(int fd) {
900 : int r, s, t;
901 :
902 0 : assert(fd >= 0);
903 :
904 0 : r = dup2(fd, STDIN_FILENO);
905 0 : s = dup2(fd, STDOUT_FILENO);
906 0 : t = dup2(fd, STDERR_FILENO);
907 :
908 0 : if (fd >= 3)
909 0 : safe_close(fd);
910 :
911 0 : if (r < 0 || s < 0 || t < 0)
912 0 : return -errno;
913 :
914 : /* Explicitly unset O_CLOEXEC, since if fd was < 3, then
915 : * dup2() was a NOP and the bit hence possibly set. */
916 0 : fd_cloexec(STDIN_FILENO, false);
917 0 : fd_cloexec(STDOUT_FILENO, false);
918 0 : fd_cloexec(STDERR_FILENO, false);
919 :
920 0 : return 0;
921 : }
922 :
923 0 : int make_null_stdio(void) {
924 : int null_fd;
925 :
926 0 : null_fd = open("/dev/null", O_RDWR|O_NOCTTY);
927 0 : if (null_fd < 0)
928 0 : return -errno;
929 :
930 0 : return make_stdio(null_fd);
931 : }
932 :
933 0 : int getttyname_malloc(int fd, char **ret) {
934 0 : size_t l = 100;
935 : int r;
936 :
937 0 : assert(fd >= 0);
938 0 : assert(ret);
939 :
940 0 : for (;;) {
941 0 : char path[l];
942 :
943 0 : r = ttyname_r(fd, path, sizeof(path));
944 0 : if (r == 0) {
945 : const char *p;
946 : char *c;
947 :
948 0 : p = startswith(path, "/dev/");
949 0 : c = strdup(p ?: path);
950 0 : if (!c)
951 0 : return -ENOMEM;
952 :
953 0 : *ret = c;
954 0 : return 0;
955 : }
956 :
957 0 : if (r != ERANGE)
958 0 : return -r;
959 :
960 0 : l *= 2;
961 0 : }
962 :
963 : return 0;
964 : }
965 :
966 0 : int getttyname_harder(int fd, char **r) {
967 : int k;
968 0 : char *s = NULL;
969 :
970 0 : k = getttyname_malloc(fd, &s);
971 0 : if (k < 0)
972 0 : return k;
973 :
974 0 : if (streq(s, "tty")) {
975 0 : free(s);
976 0 : return get_ctty(0, NULL, r);
977 : }
978 :
979 0 : *r = s;
980 0 : return 0;
981 : }
982 :
983 44 : int get_ctty_devnr(pid_t pid, dev_t *d) {
984 : int r;
985 88 : _cleanup_free_ char *line = NULL;
986 : const char *p;
987 : unsigned long ttynr;
988 :
989 44 : assert(pid >= 0);
990 :
991 44 : p = procfs_file_alloca(pid, "stat");
992 44 : r = read_one_line_file(p, &line);
993 44 : if (r < 0)
994 0 : return r;
995 :
996 44 : p = strrchr(line, ')');
997 44 : if (!p)
998 0 : return -EIO;
999 :
1000 44 : p++;
1001 :
1002 44 : if (sscanf(p, " "
1003 : "%*c " /* state */
1004 : "%*d " /* ppid */
1005 : "%*d " /* pgrp */
1006 : "%*d " /* session */
1007 : "%lu ", /* ttynr */
1008 : &ttynr) != 1)
1009 0 : return -EIO;
1010 :
1011 44 : if (major(ttynr) == 0 && minor(ttynr) == 0)
1012 2 : return -ENXIO;
1013 :
1014 42 : if (d)
1015 1 : *d = (dev_t) ttynr;
1016 :
1017 42 : return 0;
1018 : }
1019 :
1020 2 : int get_ctty(pid_t pid, dev_t *_devnr, char **r) {
1021 2 : char fn[sizeof("/dev/char/")-1 + 2*DECIMAL_STR_MAX(unsigned) + 1 + 1], *b = NULL;
1022 4 : _cleanup_free_ char *s = NULL;
1023 : const char *p;
1024 : dev_t devnr;
1025 : int k;
1026 :
1027 2 : assert(r);
1028 :
1029 2 : k = get_ctty_devnr(pid, &devnr);
1030 2 : if (k < 0)
1031 1 : return k;
1032 :
1033 1 : sprintf(fn, "/dev/char/%u:%u", major(devnr), minor(devnr));
1034 :
1035 1 : k = readlink_malloc(fn, &s);
1036 1 : if (k < 0) {
1037 :
1038 1 : if (k != -ENOENT)
1039 0 : return k;
1040 :
1041 : /* This is an ugly hack */
1042 1 : if (major(devnr) == 136) {
1043 1 : if (asprintf(&b, "pts/%u", minor(devnr)) < 0)
1044 0 : return -ENOMEM;
1045 : } else {
1046 : /* Probably something like the ptys which have no
1047 : * symlink in /dev/char. Let's return something
1048 : * vaguely useful. */
1049 :
1050 0 : b = strdup(fn + 5);
1051 0 : if (!b)
1052 0 : return -ENOMEM;
1053 : }
1054 : } else {
1055 0 : if (startswith(s, "/dev/"))
1056 0 : p = s + 5;
1057 0 : else if (startswith(s, "../"))
1058 0 : p = s + 3;
1059 : else
1060 0 : p = s;
1061 :
1062 0 : b = strdup(p);
1063 0 : if (!b)
1064 0 : return -ENOMEM;
1065 : }
1066 :
1067 1 : *r = b;
1068 1 : if (_devnr)
1069 0 : *_devnr = devnr;
1070 :
1071 1 : return 0;
1072 : }
|