summaryrefslogtreecommitdiff
path: root/shell/cttyhack.c
blob: 37ea13723eef838e32015bf54ce904db03b9b96f (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
/* 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"
//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:

//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;

	if (!*++argv) {
		bb_show_usage();
	}

	strcpy(console, "/dev/tty");
	fd = open(console, O_RDWR);
	if (fd >= 0) {
		/* We already have ctty, nothing to do */
		close(fd);
	} else {
		/* We don't have ctty (or don't have "/dev/tty" node...) */
		do {
#ifdef __linux__
			int s = open_read_close("/sys/class/tty/console/active",
				console + 5, sizeof(console) - 5);
			if (s > 0) {
				/* found active console via sysfs (Linux 2.6.38+)
				 * sysfs string looks like "ttyS0\n" so zap the newline:
				 */
				console[4 + s] = '\0';
				break;
			}

			if (ioctl(0, VT_GETSTATE, &u.vt) == 0) {
				/* this is linux virtual tty */
				sprintf(console + 8, "S%d" + 1, u.vt.v_active);
				break;
			}
#endif
#ifdef TIOCGSERIAL
			if (ioctl(0, TIOCGSERIAL, &u.sr) == 0) {
				/* this is a serial console, asuming it is named /dev/ttySn */
				sprintf(console + 8, "S%d", u.sr.line);
				break;
			}
#endif
			/* nope, could not find it */
			goto ret;
		} while (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,
		 * steal it from them:
		 */
		ioctl(0, TIOCSCTTY, 1);
	}

ret:
	BB_EXECVP_or_die(argv);
}