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;
cttyhack_main(int argc UNUSED_PARAM,char ** argv)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 }
189