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