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