chiark / gitweb /
042b88f2229ec2bc7143333bcdba59c0bcc1ed72
[elogind.git] / src / shared / terminal-util.c
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 int chvt(int vt) {
45         _cleanup_close_ int fd;
46
47         fd = open_terminal("/dev/tty0", O_RDWR|O_NOCTTY|O_CLOEXEC);
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
69 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         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
129 int 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
177 int 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
220 int 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 */
233         ioctl(fd, TIOCNXCL);
234
235         /* Switch to text mode */
236         if (switch_to_text)
237                 ioctl(fd, KDSETMODE, KD_TEXT);
238
239         /* Enable console unicode mode */
240         ioctl(fd, KDSKBMODE, K_UNICODE);
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
277 finish:
278         /* Just in case, flush all crap out */
279         tcflush(fd, TCIOFLUSH);
280
281         return r;
282 }
283
284 int reset_terminal(const char *name) {
285         _cleanup_close_ int fd = -1;
286
287         fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC);
288         if (fd < 0)
289                 return fd;
290
291         return reset_terminal_fd(fd, true);
292 }
293
294 int open_terminal(const char *name, int mode) {
295         int fd, r;
296         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         assert(!(mode & O_CREAT));
308
309         for (;;) {
310                 fd = open(name, mode, 0);
311                 if (fd >= 0)
312                         break;
313
314                 if (errno != EIO)
315                         return -errno;
316
317                 /* Max 1s in total */
318                 if (c >= 20)
319                         return -errno;
320
321                 usleep(50 * USEC_PER_MSEC);
322                 c++;
323         }
324
325         r = isatty(fd);
326         if (r < 0) {
327                 safe_close(fd);
328                 return -errno;
329         }
330
331         if (!r) {
332                 safe_close(fd);
333                 return -ENOTTY;
334         }
335
336         return fd;
337 }
338
339 int acquire_terminal(
340                 const char *name,
341                 bool fail,
342                 bool force,
343                 bool ignore_tiocstty_eperm,
344                 usec_t timeout) {
345
346         int fd = -1, notify = -1, r = 0, wd = -1;
347         usec_t ts = 0;
348
349         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         if (timeout != USEC_INFINITY)
365                 ts = now(CLOCK_MONOTONIC);
366
367         if (!fail && !force) {
368                 notify = inotify_init1(IN_CLOEXEC | (timeout != USEC_INFINITY ? IN_NONBLOCK : 0));
369                 if (notify < 0) {
370                         r = -errno;
371                         goto fail;
372                 }
373
374                 wd = inotify_add_watch(notify, name, IN_CLOSE);
375                 if (wd < 0) {
376                         r = -errno;
377                         goto fail;
378                 }
379         }
380
381         for (;;) {
382                 struct sigaction sa_old, sa_new = {
383                         .sa_handler = SIG_IGN,
384                         .sa_flags = SA_RESTART,
385                 };
386
387                 if (notify >= 0) {
388                         r = flush_fd(notify);
389                         if (r < 0)
390                                 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                 fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC);
397                 if (fd < 0)
398                         return fd;
399
400                 /* Temporarily ignore SIGHUP, so that we don't get SIGHUP'ed
401                  * if we already own the tty. */
402                 assert_se(sigaction(SIGHUP, &sa_new, &sa_old) == 0);
403
404                 /* First, try to get the tty */
405                 if (ioctl(fd, TIOCSCTTY, force) < 0)
406                         r = -errno;
407
408                 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                 if (r < 0 && r == -EPERM && ignore_tiocstty_eperm)
414                         r = 0;
415
416                 if (r < 0 && (force || fail || r != -EPERM)) {
417                         goto fail;
418                 }
419
420                 if (r >= 0)
421                         break;
422
423                 assert(!fail);
424                 assert(!force);
425                 assert(notify >= 0);
426
427                 for (;;) {
428                         union inotify_event_buffer buffer;
429                         struct inotify_event *e;
430                         ssize_t l;
431
432                         if (timeout != USEC_INFINITY) {
433                                 usec_t n;
434
435                                 n = now(CLOCK_MONOTONIC);
436                                 if (ts + timeout < n) {
437                                         r = -ETIMEDOUT;
438                                         goto fail;
439                                 }
440
441                                 r = fd_wait_for_event(fd, POLLIN, ts + timeout - n);
442                                 if (r < 0)
443                                         goto fail;
444
445                                 if (r == 0) {
446                                         r = -ETIMEDOUT;
447                                         goto fail;
448                                 }
449                         }
450
451                         l = read(notify, &buffer, sizeof(buffer));
452                         if (l < 0) {
453                                 if (errno == EINTR || errno == EAGAIN)
454                                         continue;
455
456                                 r = -errno;
457                                 goto fail;
458                         }
459
460                         FOREACH_INOTIFY_EVENT(e, buffer, l) {
461                                 if (e->wd != wd || !(e->mask & IN_CLOSE)) {
462                                         r = -EIO;
463                                         goto fail;
464                                 }
465                         }
466
467                         break;
468                 }
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                 fd = safe_close(fd);
475         }
476
477         safe_close(notify);
478
479         r = reset_terminal_fd(fd, true);
480         if (r < 0)
481                 log_warning_errno(r, "Failed to reset terminal: %m");
482
483         return fd;
484
485 fail:
486         safe_close(fd);
487         safe_close(notify);
488
489         return r;
490 }
491
492 int release_terminal(void) {
493         static const struct sigaction sa_new = {
494                 .sa_handler = SIG_IGN,
495                 .sa_flags = SA_RESTART,
496         };
497
498         _cleanup_close_ int fd = -1;
499         struct sigaction sa_old;
500         int r = 0;
501
502         fd = open("/dev/tty", O_RDWR|O_NOCTTY|O_NDELAY|O_CLOEXEC);
503         if (fd < 0)
504                 return -errno;
505
506         /* Temporarily ignore SIGHUP, so that we don't get SIGHUP'ed
507          * by our own TIOCNOTTY */
508         assert_se(sigaction(SIGHUP, &sa_new, &sa_old) == 0);
509
510         if (ioctl(fd, TIOCNOTTY) < 0)
511                 r = -errno;
512
513         assert_se(sigaction(SIGHUP, &sa_old, NULL) == 0);
514
515         return r;
516 }
517
518 int terminal_vhangup_fd(int fd) {
519         assert(fd >= 0);
520
521         if (ioctl(fd, TIOCVHANGUP) < 0)
522                 return -errno;
523
524         return 0;
525 }
526
527 int terminal_vhangup(const char *name) {
528         _cleanup_close_ int fd;
529
530         fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC);
531         if (fd < 0)
532                 return fd;
533
534         return terminal_vhangup_fd(fd);
535 }
536
537 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         if (!startswith(name, "/dev/"))
546                 return -EINVAL;
547
548         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                 fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC);
553                 if (fd < 0)
554                         return fd;
555
556                 loop_write(fd,
557                            "\033[r"    /* clear scrolling region */
558                            "\033[H"    /* move home */
559                            "\033[2J",  /* clear screen */
560                            10, false);
561                 safe_close(fd);
562
563                 return 0;
564         }
565
566         if (!startswith(name, "/dev/tty"))
567                 return -EINVAL;
568
569         r = safe_atou(name+8, &u);
570         if (r < 0)
571                 return r;
572
573         if (u <= 0)
574                 return -EINVAL;
575
576         /* Try to deallocate */
577         fd = open_terminal("/dev/tty0", O_RDWR|O_NOCTTY|O_CLOEXEC);
578         if (fd < 0)
579                 return fd;
580
581         r = ioctl(fd, VT_DISALLOCATE, u);
582         safe_close(fd);
583
584         if (r >= 0)
585                 return 0;
586
587         if (errno != EBUSY)
588                 return -errno;
589
590         /* Couldn't deallocate, so let's clear it fully with
591          * scrollback */
592         fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC);
593         if (fd < 0)
594                 return fd;
595
596         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         safe_close(fd);
602
603         return 0;
604 }
605
606 void warn_melody(void) {
607         _cleanup_close_ int fd = -1;
608
609         fd = open("/dev/console", O_WRONLY|O_CLOEXEC|O_NOCTTY);
610         if (fd < 0)
611                 return;
612
613         /* Yeah, this is synchronous. Kinda sucks. But well... */
614
615         ioctl(fd, KIOCSOUND, (int)(1193180/440));
616         usleep(125*USEC_PER_MSEC);
617
618         ioctl(fd, KIOCSOUND, (int)(1193180/220));
619         usleep(125*USEC_PER_MSEC);
620
621         ioctl(fd, KIOCSOUND, (int)(1193180/220));
622         usleep(125*USEC_PER_MSEC);
623
624         ioctl(fd, KIOCSOUND, 0);
625 }
626
627 int make_console_stdio(void) {
628         int fd, r;
629
630         /* Make /dev/console the controlling terminal and stdin/stdout/stderr */
631
632         fd = acquire_terminal("/dev/console", false, true, true, USEC_INFINITY);
633         if (fd < 0)
634                 return log_error_errno(fd, "Failed to acquire terminal: %m");
635
636         r = make_stdio(fd);
637         if (r < 0)
638                 return log_error_errno(r, "Failed to duplicate terminal fd: %m");
639
640         return 0;
641 }
642
643 int status_vprintf(const char *status, bool ellipse, bool ephemeral, const char *format, va_list ap) {
644         static const char status_indent[] = "         "; /* "[" STATUS "] " */
645         _cleanup_free_ char *s = NULL;
646         _cleanup_close_ int fd = -1;
647         struct iovec iovec[6] = {};
648         int n = 0;
649         static bool prev_ephemeral;
650
651         assert(format);
652
653         /* This is independent of logging, as status messages are
654          * optional and go exclusively to the console. */
655
656         if (vasprintf(&s, format, ap) < 0)
657                 return log_oom();
658
659         fd = open_terminal("/dev/console", O_WRONLY|O_NOCTTY|O_CLOEXEC);
660         if (fd < 0)
661                 return fd;
662
663         if (ellipse) {
664                 char *e;
665                 size_t emax, sl;
666                 int c;
667
668                 c = fd_columns(fd);
669                 if (c <= 0)
670                         c = 80;
671
672                 sl = status ? sizeof(status_indent)-1 : 0;
673
674                 emax = c - sl - 1;
675                 if (emax < 3)
676                         emax = 3;
677
678                 e = ellipsize(s, emax, 50);
679                 if (e) {
680                         free(s);
681                         s = e;
682                 }
683         }
684
685         if (prev_ephemeral)
686                 IOVEC_SET_STRING(iovec[n++], "\r" ANSI_ERASE_TO_END_OF_LINE);
687         prev_ephemeral = ephemeral;
688
689         if (status) {
690                 if (!isempty(status)) {
691                         IOVEC_SET_STRING(iovec[n++], "[");
692                         IOVEC_SET_STRING(iovec[n++], status);
693                         IOVEC_SET_STRING(iovec[n++], "] ");
694                 } else
695                         IOVEC_SET_STRING(iovec[n++], status_indent);
696         }
697
698         IOVEC_SET_STRING(iovec[n++], s);
699         if (!ephemeral)
700                 IOVEC_SET_STRING(iovec[n++], "\n");
701
702         if (writev(fd, iovec, n) < 0)
703                 return -errno;
704
705         return 0;
706 }
707
708 int status_printf(const char *status, bool ellipse, bool ephemeral, const char *format, ...) {
709         va_list ap;
710         int r;
711
712         assert(format);
713
714         va_start(ap, format);
715         r = status_vprintf(status, ellipse, ephemeral, format, ap);
716         va_end(ap);
717
718         return r;
719 }
720
721 bool tty_is_vc(const char *tty) {
722         assert(tty);
723
724         return vtnr_from_tty(tty) >= 0;
725 }
726
727 bool tty_is_console(const char *tty) {
728         assert(tty);
729
730         if (startswith(tty, "/dev/"))
731                 tty += 5;
732
733         return streq(tty, "console");
734 }
735
736 int vtnr_from_tty(const char *tty) {
737         int i, r;
738
739         assert(tty);
740
741         if (startswith(tty, "/dev/"))
742                 tty += 5;
743
744         if (!startswith(tty, "tty") )
745                 return -EINVAL;
746
747         if (tty[3] < '0' || tty[3] > '9')
748                 return -EINVAL;
749
750         r = safe_atoi(tty+3, &i);
751         if (r < 0)
752                 return r;
753
754         if (i < 0 || i > 63)
755                 return -EINVAL;
756
757         return i;
758 }
759
760 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         if (path_is_read_only_fs("/sys") > 0)
767                 return NULL;
768
769         if (read_one_line_file("/sys/class/tty/console/active", active) < 0)
770                 return NULL;
771
772         /* If multiple log outputs are configured the last one is what
773          * /dev/console points to */
774         tty = strrchr(*active, ' ');
775         if (tty)
776                 tty++;
777         else
778                 tty = *active;
779
780         if (streq(tty, "tty0")) {
781                 char *tmp;
782
783                 /* Get the active VC (e.g. tty1) */
784                 if (read_one_line_file("/sys/class/tty/tty0/active", &tmp) >= 0) {
785                         free(*active);
786                         tty = *active = tmp;
787                 }
788         }
789
790         return tty;
791 }
792
793 bool tty_is_vc_resolve(const char *tty) {
794         _cleanup_free_ char *active = NULL;
795
796         assert(tty);
797
798         if (startswith(tty, "/dev/"))
799                 tty += 5;
800
801         if (streq(tty, "console")) {
802                 tty = resolve_dev_console(&active);
803                 if (!tty)
804                         return false;
805         }
806
807         return tty_is_vc(tty);
808 }
809
810 const char *default_term_for_tty(const char *tty) {
811         assert(tty);
812
813         return tty_is_vc_resolve(tty) ? "TERM=linux" : "TERM=vt220";
814 }
815
816 int fd_columns(int fd) {
817         struct winsize ws = {};
818
819         if (ioctl(fd, TIOCGWINSZ, &ws) < 0)
820                 return -errno;
821
822         if (ws.ws_col <= 0)
823                 return -EIO;
824
825         return ws.ws_col;
826 }
827
828 unsigned columns(void) {
829         const char *e;
830         int c;
831
832         if (_likely_(cached_columns > 0))
833                 return cached_columns;
834
835         c = 0;
836         e = getenv("COLUMNS");
837         if (e)
838                 (void) safe_atoi(e, &c);
839
840         if (c <= 0)
841                 c = fd_columns(STDOUT_FILENO);
842
843         if (c <= 0)
844                 c = 80;
845
846         cached_columns = c;
847         return cached_columns;
848 }
849
850 int fd_lines(int fd) {
851         struct winsize ws = {};
852
853         if (ioctl(fd, TIOCGWINSZ, &ws) < 0)
854                 return -errno;
855
856         if (ws.ws_row <= 0)
857                 return -EIO;
858
859         return ws.ws_row;
860 }
861
862 unsigned lines(void) {
863         const char *e;
864         int l;
865
866         if (_likely_(cached_lines > 0))
867                 return cached_lines;
868
869         l = 0;
870         e = getenv("LINES");
871         if (e)
872                 (void) safe_atoi(e, &l);
873
874         if (l <= 0)
875                 l = fd_lines(STDOUT_FILENO);
876
877         if (l <= 0)
878                 l = 24;
879
880         cached_lines = l;
881         return cached_lines;
882 }
883
884 /* intended to be used as a SIGWINCH sighandler */
885 void columns_lines_cache_reset(int signum) {
886         cached_columns = 0;
887         cached_lines = 0;
888 }
889
890 bool on_tty(void) {
891         static int cached_on_tty = -1;
892
893         if (_unlikely_(cached_on_tty < 0))
894                 cached_on_tty = isatty(STDOUT_FILENO) > 0;
895
896         return cached_on_tty;
897 }
898
899 int make_stdio(int fd) {
900         int r, s, t;
901
902         assert(fd >= 0);
903
904         r = dup2(fd, STDIN_FILENO);
905         s = dup2(fd, STDOUT_FILENO);
906         t = dup2(fd, STDERR_FILENO);
907
908         if (fd >= 3)
909                 safe_close(fd);
910
911         if (r < 0 || s < 0 || t < 0)
912                 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         fd_cloexec(STDIN_FILENO, false);
917         fd_cloexec(STDOUT_FILENO, false);
918         fd_cloexec(STDERR_FILENO, false);
919
920         return 0;
921 }
922
923 int make_null_stdio(void) {
924         int null_fd;
925
926         null_fd = open("/dev/null", O_RDWR|O_NOCTTY);
927         if (null_fd < 0)
928                 return -errno;
929
930         return make_stdio(null_fd);
931 }
932
933 int getttyname_malloc(int fd, char **ret) {
934         size_t l = 100;
935         int r;
936
937         assert(fd >= 0);
938         assert(ret);
939
940         for (;;) {
941                 char path[l];
942
943                 r = ttyname_r(fd, path, sizeof(path));
944                 if (r == 0) {
945                         const char *p;
946                         char *c;
947
948                         p = startswith(path, "/dev/");
949                         c = strdup(p ?: path);
950                         if (!c)
951                                 return -ENOMEM;
952
953                         *ret = c;
954                         return 0;
955                 }
956
957                 if (r != ERANGE)
958                         return -r;
959
960                 l *= 2;
961         }
962
963         return 0;
964 }
965
966 int getttyname_harder(int fd, char **r) {
967         int k;
968         char *s = NULL;
969
970         k = getttyname_malloc(fd, &s);
971         if (k < 0)
972                 return k;
973
974         if (streq(s, "tty")) {
975                 free(s);
976                 return get_ctty(0, NULL, r);
977         }
978
979         *r = s;
980         return 0;
981 }
982
983 int get_ctty_devnr(pid_t pid, dev_t *d) {
984         int r;
985         _cleanup_free_ char *line = NULL;
986         const char *p;
987         unsigned long ttynr;
988
989         assert(pid >= 0);
990
991         p = procfs_file_alloca(pid, "stat");
992         r = read_one_line_file(p, &line);
993         if (r < 0)
994                 return r;
995
996         p = strrchr(line, ')');
997         if (!p)
998                 return -EIO;
999
1000         p++;
1001
1002         if (sscanf(p, " "
1003                    "%*c "  /* state */
1004                    "%*d "  /* ppid */
1005                    "%*d "  /* pgrp */
1006                    "%*d "  /* session */
1007                    "%lu ", /* ttynr */
1008                    &ttynr) != 1)
1009                 return -EIO;
1010
1011         if (major(ttynr) == 0 && minor(ttynr) == 0)
1012                 return -ENXIO;
1013
1014         if (d)
1015                 *d = (dev_t) ttynr;
1016
1017         return 0;
1018 }
1019
1020 int get_ctty(pid_t pid, dev_t *_devnr, char **r) {
1021         char fn[sizeof("/dev/char/")-1 + 2*DECIMAL_STR_MAX(unsigned) + 1 + 1], *b = NULL;
1022         _cleanup_free_ char *s = NULL;
1023         const char *p;
1024         dev_t devnr;
1025         int k;
1026
1027         assert(r);
1028
1029         k = get_ctty_devnr(pid, &devnr);
1030         if (k < 0)
1031                 return k;
1032
1033         sprintf(fn, "/dev/char/%u:%u", major(devnr), minor(devnr));
1034
1035         k = readlink_malloc(fn, &s);
1036         if (k < 0) {
1037
1038                 if (k != -ENOENT)
1039                         return k;
1040
1041                 /* This is an ugly hack */
1042                 if (major(devnr) == 136) {
1043                         if (asprintf(&b, "pts/%u", minor(devnr)) < 0)
1044                                 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                         b = strdup(fn + 5);
1051                         if (!b)
1052                                 return -ENOMEM;
1053                 }
1054         } else {
1055                 if (startswith(s, "/dev/"))
1056                         p = s + 5;
1057                 else if (startswith(s, "../"))
1058                         p = s + 3;
1059                 else
1060                         p = s;
1061
1062                 b = strdup(p);
1063                 if (!b)
1064                         return -ENOMEM;
1065         }
1066
1067         *r = b;
1068         if (_devnr)
1069                 *_devnr = devnr;
1070
1071         return 0;
1072 }