tftpd: show requested file name in open error message
[oweals/busybox.git] / shell / cttyhack.c
1 /* vi: set sw=4 ts=4: */
2 /*
3  * Copyright (c) 2007 Denys Vlasenko <vda.linux@googlemail.com>
4  *
5  * Licensed under GPLv2, see file LICENSE in this source tree.
6  */
7 //config:config CTTYHACK
8 //config:       bool "cttyhack (2.4 kb)"
9 //config:       default y
10 //config:       help
11 //config:       One common problem reported on the mailing list is the "can't
12 //config:       access tty; job control turned off" error message, which typically
13 //config:       appears when one tries to use a shell with stdin/stdout on
14 //config:       /dev/console.
15 //config:       This device is special - it cannot be a controlling tty.
16 //config:
17 //config:       The proper solution is to use the correct device instead of
18 //config:       /dev/console.
19 //config:
20 //config:       cttyhack provides a "quick and dirty" solution to this problem.
21 //config:       It analyzes stdin with various ioctls, trying to determine whether
22 //config:       it is a /dev/ttyN or /dev/ttySN (virtual terminal or serial line).
23 //config:       On Linux it also checks sysfs for a pointer to the active console.
24 //config:       If cttyhack is able to find the real console device, it closes
25 //config:       stdin/out/err and reopens that device.
26 //config:       Then it executes the given program. Opening the device will make
27 //config:       that device a controlling tty. This may require cttyhack
28 //config:       to be a session leader.
29 //config:
30 //config:       Example for /etc/inittab (for busybox init):
31 //config:
32 //config:       ::respawn:/bin/cttyhack /bin/sh
33 //config:
34 //config:       Starting an interactive shell from boot shell script:
35 //config:
36 //config:       setsid cttyhack sh
37 //config:
38 //config:       Giving controlling tty to shell running with PID 1:
39 //config:
40 //config:       # exec cttyhack sh
41 //config:
42 //config:       Without cttyhack, you need to know exact tty name,
43 //config:       and do something like this:
44 //config:
45 //config:       # exec setsid sh -c 'exec sh </dev/tty1 >/dev/tty1 2>&1'
46 //config:
47 //config:       Starting getty on a controlling tty from a shell script:
48 //config:
49 //config:       # getty 115200 $(cttyhack)
50
51 //applet:IF_CTTYHACK(APPLET_NOEXEC(cttyhack, cttyhack, BB_DIR_BIN, BB_SUID_DROP, cttyhack))
52
53 //kbuild:lib-$(CONFIG_CTTYHACK) += cttyhack.o
54
55 //usage:#define cttyhack_trivial_usage
56 //usage:       "[PROG ARGS]"
57 //usage:#define cttyhack_full_usage "\n\n"
58 //usage:       "Give PROG a controlling tty if possible."
59 //usage:     "\nExample for /etc/inittab (for busybox init):"
60 //usage:     "\n        ::respawn:/bin/cttyhack /bin/sh"
61 //usage:     "\nGiving controlling tty to shell running with PID 1:"
62 //usage:     "\n        $ exec cttyhack sh"
63 //usage:     "\nStarting interactive shell from boot shell script:"
64 //usage:     "\n        setsid cttyhack sh"
65
66 #include "libbb.h"
67
68 #if !defined(__linux__) && !defined(TIOCGSERIAL) && !ENABLE_WERROR
69 # warning cttyhack will not be able to detect a controlling tty on this system
70 #endif
71
72 /* From <linux/vt.h> */
73 struct vt_stat {
74         unsigned short v_active;        /* active vt */
75         unsigned short v_signal;        /* signal to send */
76         unsigned short v_state;         /* vt bitmask */
77 };
78 enum { VT_GETSTATE = 0x5603 }; /* get global vt state info */
79
80 /* From <linux/serial.h> */
81 struct serial_struct {
82         int     type;
83         int     line;
84         unsigned int    port;
85         int     irq;
86         int     flags;
87         int     xmit_fifo_size;
88         int     custom_divisor;
89         int     baud_base;
90         unsigned short  close_delay;
91         char    io_type;
92         char    reserved_char[1];
93         int     hub6;
94         unsigned short  closing_wait;   /* time to wait before closing */
95         unsigned short  closing_wait2;  /* no longer used... */
96         unsigned char   *iomem_base;
97         unsigned short  iomem_reg_shift;
98         unsigned int    port_high;
99         unsigned long   iomap_base;     /* cookie passed into ioremap */
100         int     reserved[1];
101 };
102
103 int cttyhack_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
104 int cttyhack_main(int argc UNUSED_PARAM, char **argv)
105 {
106         int fd;
107         char console[sizeof(int)*3 + 16];
108         union {
109                 struct vt_stat vt;
110                 struct serial_struct sr;
111                 char paranoia[sizeof(struct serial_struct) * 3];
112         } u;
113
114         strcpy(console, "/dev/tty");
115         fd = open(console, O_RDWR);
116         if (fd < 0) {
117                 /* We don't have ctty (or don't have "/dev/tty" node...) */
118                 do {
119 #ifdef __linux__
120                         /* Note that this method does not use _stdin_.
121                          * Thus, "cttyhack </dev/something" can't be used.
122                          * However, this method is more reliable than
123                          * TIOCGSERIAL check, which assumes that all
124                          * serial lines follow /dev/ttySn convention -
125                          * which is not always the case.
126                          * Therefore, we use this method first:
127                          */
128                         int s = open_read_close("/sys/class/tty/console/active",
129                                 console + 5, sizeof(console) - 5);
130                         if (s > 0) {
131                                 char *last;
132                                 /* Found active console via sysfs (Linux 2.6.38+).
133                                  * It looks like "[tty0 ]ttyS0\n" so zap the newline:
134                                  */
135                                 console[4 + s] = '\0';
136                                 /* If there are multiple consoles,
137                                  * take the last one:
138                                  */
139                                 last = strrchr(console + 5, ' ');
140                                 if (last)
141                                         overlapping_strcpy(console + 5, last + 1);
142                                 break;
143                         }
144
145                         if (ioctl(0, VT_GETSTATE, &u.vt) == 0) {
146                                 /* this is linux virtual tty */
147                                 sprintf(console + 8, "S%u" + 1, (int)u.vt.v_active);
148                                 break;
149                         }
150 #endif
151 #ifdef TIOCGSERIAL
152                         if (ioctl(0, TIOCGSERIAL, &u.sr) == 0) {
153                                 /* this is a serial console; assuming it is named /dev/ttySn */
154                                 sprintf(console + 8, "S%u", (int)u.sr.line);
155                                 break;
156                         }
157 #endif
158                         /* nope, could not find it */
159                         console[0] = '\0';
160                 } while (0);
161         }
162
163         argv++;
164         if (!argv[0]) {
165                 if (!console[0])
166                         return EXIT_FAILURE;
167                 puts(console);
168                 return EXIT_SUCCESS;
169         }
170
171         if (fd < 0) {
172                 fd = open_or_warn(console, O_RDWR);
173                 if (fd < 0)
174                         goto ret;
175         }
176         //bb_error_msg("switching to '%s'", console);
177         dup2(fd, 0);
178         dup2(fd, 1);
179         dup2(fd, 2);
180         while (fd > 2)
181                 close(fd--);
182         /* Some other session may have it as ctty,
183          * try to steal it from them:
184          */
185         ioctl(0, TIOCSCTTY, 1);
186  ret:
187         BB_EXECVP_or_die(argv);
188 }