aboutsummaryrefslogtreecommitdiff
path: root/shell/cttyhack.c
blob: 7a5e1ffd2125c2ea8d5b75005136140ba1f58b91 (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
/* vi: set sw=4 ts=4: */
/*
 * Licensed under GPLv2
 *
 * Copyright (c) 2007 Denys Vlasenko <vda.linux@googlemail.com>
 */
#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 "can't access tty;
//config:	  job control turned off" error message which typically appears when
//config:	  one tries to use shell with stdin/stdout opened to /dev/console.
//config:	  This device is special - it cannot be a controlling tty.
//config:
//config:	  Proper solution is to use correct device instead of /dev/console.
//config:
//config:	  cttyhack provides "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:	  If it detects one, it closes stdin/out/err and reopens that device.
//config:	  Then it executes 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...) */
		if (0) {}
#ifdef TIOCGSERIAL
		else if (ioctl(0, TIOCGSERIAL, &u.sr) == 0) {
			/* this is a serial console */
			sprintf(console + 8, "S%d", u.sr.line);
		}
#endif
#ifdef __linux__
		else if (ioctl(0, VT_GETSTATE, &u.vt) == 0) {
			/* this is linux virtual tty */
			sprintf(console + 8, "S%d" + 1, u.vt.v_active);
		}
#endif
		if (console[8]) {
			fd = xopen(console, O_RDWR);
			//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);
		}
	}

	BB_EXECVP_or_die(argv);
}