aboutsummaryrefslogtreecommitdiff
path: root/shell/cttyhack.c
blob: 9a5f4bb52950ebe2613bae84aa159db570f38022 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
/* vi: set sw=4 ts=4: */
/*
 * Copyright (c) 2007 Denys Vlasenko <vda.linux@googlemail.com>
 *
 * Licensed under GPLv2, see file LICENSE in this source tree.
 */
#include "libbb.h"

//applet:IF_CTTYHACK(APPLET(cttyhack, BB_DIR_BIN, BB_SUID_DROP))

//kbuild:lib-$(CONFIG_CTTYHACK) += cttyhack.o

//config:config CTTYHACK
//config:	bool "cttyhack (2.5 kb)"
//config:	default y
//config:	help
//config:	  One common problem reported on the mailing list is the "can't
//config:	  access tty; job control turned off" error message, which typically
//config:	  appears when one tries to use a shell with stdin/stdout on
//config:	  /dev/console.
//config:	  This device is special - it cannot be a controlling tty.
//config:
//config:	  The proper solution is to use the correct device instead of
//config:	  /dev/console.
//config:
//config:	  cttyhack provides a "quick and dirty" solution to this problem.
//config:	  It analyzes stdin with various ioctls, trying to determine whether
//config:	  it is a /dev/ttyN or /dev/ttySN (virtual terminal or serial line).
//config:	  On Linux it also checks sysfs for a pointer to the active console.
//config:	  If cttyhack is able to find the real console device, it closes
//config:	  stdin/out/err and reopens that device.
//config:	  Then it executes the given program. Opening the device will make
//config:	  that device a controlling tty. This may require cttyhack
//config:	  to be a session leader.
//config:
//config:	  Example for /etc/inittab (for busybox init):
//config:
//config:	  ::respawn:/bin/cttyhack /bin/sh
//config:
//config:	  Starting an interactive shell from boot shell script:
//config:
//config:	  setsid cttyhack sh
//config:
//config:	  Giving controlling tty to shell running with PID 1:
//config:
//config:	  # exec cttyhack sh
//config:
//config:	  Without cttyhack, you need to know exact tty name,
//config:	  and do something like this:
//config:
//config:	  # exec setsid sh -c 'exec sh </dev/tty1 >/dev/tty1 2>&1'
//config:
//config:	  Starting getty on a controlling tty from a shell script:
//config:
//config:	  # getty 115200 $(cttyhack)

//usage:#define cttyhack_trivial_usage
//usage:       "[PROG ARGS]"
//usage:#define cttyhack_full_usage "\n\n"
//usage:       "Give PROG a controlling tty if possible."
//usage:     "\nExample for /etc/inittab (for busybox init):"
//usage:     "\n	::respawn:/bin/cttyhack /bin/sh"
//usage:     "\nGiving controlling tty to shell running with PID 1:"
//usage:     "\n	$ exec cttyhack sh"
//usage:     "\nStarting interactive shell from boot shell script:"
//usage:     "\n	setsid cttyhack sh"

#if !defined(__linux__) && !defined(TIOCGSERIAL) && !ENABLE_WERROR
# warning cttyhack will not be able to detect a controlling tty on this system
#endif

/* From <linux/vt.h> */
struct vt_stat {
	unsigned short v_active;        /* active vt */
	unsigned short v_signal;        /* signal to send */
	unsigned short v_state;         /* vt bitmask */
};
enum { VT_GETSTATE = 0x5603 }; /* get global vt state info */

/* From <linux/serial.h> */
struct serial_struct {
	int	type;
	int	line;
	unsigned int	port;
	int	irq;
	int	flags;
	int	xmit_fifo_size;
	int	custom_divisor;
	int	baud_base;
	unsigned short	close_delay;
	char	io_type;
	char	reserved_char[1];
	int	hub6;
	unsigned short	closing_wait;   /* time to wait before closing */
	unsigned short	closing_wait2;  /* no longer used... */
	unsigned char	*iomem_base;
	unsigned short	iomem_reg_shift;
	unsigned int	port_high;
	unsigned long	iomap_base;	/* cookie passed into ioremap */
	int	reserved[1];
};

int cttyhack_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
int cttyhack_main(int argc UNUSED_PARAM, char **argv)
{
	int fd;
	char console[sizeof(int)*3 + 16];
	union {
		struct vt_stat vt;
		struct serial_struct sr;
		char paranoia[sizeof(struct serial_struct) * 3];
	} u;

	strcpy(console, "/dev/tty");
	fd = open(console, O_RDWR);
	if (fd < 0) {
		/* We don't have ctty (or don't have "/dev/tty" node...) */
		do {
#ifdef __linux__
			/* Note that this method does not use _stdin_.
			 * Thus, "cttyhack </dev/something" can't be used.
			 * However, this method is more reliable than
			 * TIOCGSERIAL check, which assumes that all
			 * serial lines follow /dev/ttySn convention -
			 * which is not always the case.
			 * Therefore, we use this method first:
			 */
			int s = open_read_close("/sys/class/tty/console/active",
				console + 5, sizeof(console) - 5);
			if (s > 0) {
				char *last;
				/* Found active console via sysfs (Linux 2.6.38+).
				 * It looks like "[tty0 ]ttyS0\n" so zap the newline:
				 */
				console[4 + s] = '\0';
				/* If there are multiple consoles,
				 * take the last one:
				 */
				last = strrchr(console + 5, ' ');
				if (last)
					overlapping_strcpy(console + 5, last + 1);
				break;
			}

			if (ioctl(0, VT_GETSTATE, &u.vt) == 0) {
				/* this is linux virtual tty */
				sprintf(console + 8, "S%u" + 1, (int)u.vt.v_active);
				break;
			}
#endif
#ifdef TIOCGSERIAL
			if (ioctl(0, TIOCGSERIAL, &u.sr) == 0) {
				/* this is a serial console; assuming it is named /dev/ttySn */
				sprintf(console + 8, "S%u", (int)u.sr.line);
				break;
			}
#endif
			/* nope, could not find it */
			console[0] = '\0';
		} while (0);
	}

	argv++;
	if (!argv[0]) {
		if (!console[0])
			return EXIT_FAILURE;
		puts(console);
		return EXIT_SUCCESS;
	}

	if (fd < 0) {
		fd = open_or_warn(console, O_RDWR);
		if (fd < 0)
			goto ret;
	}
	//bb_error_msg("switching to '%s'", console);
	dup2(fd, 0);
	dup2(fd, 1);
	dup2(fd, 2);
	while (fd > 2)
		close(fd--);
	/* Some other session may have it as ctty,
	 * try to steal it from them:
	 */
	ioctl(0, TIOCSCTTY, 1);
 ret:
	BB_EXECVP_or_die(argv);
}