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#include "libbb.h" 8 9//applet:IF_CTTYHACK(APPLET(cttyhack, BB_DIR_BIN, BB_SUID_DROP)) 10 11//kbuild:lib-$(CONFIG_CTTYHACK) += cttyhack.o 12 13//config:config CTTYHACK 14//config: bool "cttyhack" 15//config: default y 16//config: help 17//config: One common problem reported on the mailing list is the "can't 18//config: access tty; job control turned off" error message, which typically 19//config: appears when one tries to use a shell with stdin/stdout on 20//config: /dev/console. 21//config: This device is special - it cannot be a controlling tty. 22//config: 23//config: The proper solution is to use the correct device instead of 24//config: /dev/console. 25//config: 26//config: cttyhack provides a "quick and dirty" solution to this problem. 27//config: It analyzes stdin with various ioctls, trying to determine whether 28//config: it is a /dev/ttyN or /dev/ttySN (virtual terminal or serial line). 29//config: On Linux it also checks sysfs for a pointer to the active console. 30//config: If cttyhack is able to find the real console device, it closes 31//config: stdin/out/err and reopens that device. 32//config: Then it executes the given program. Opening the device will make 33//config: that device a controlling tty. This may require cttyhack 34//config: to be a session leader. 35//config: 36//config: Example for /etc/inittab (for busybox init): 37//config: 38//config: ::respawn:/bin/cttyhack /bin/sh 39//config: 40//config: Starting an interactive shell from boot shell script: 41//config: 42//config: setsid cttyhack sh 43//config: 44//config: Giving controlling tty to shell running with PID 1: 45//config: 46//config: # exec cttyhack sh 47//config: 48//config: Without cttyhack, you need to know exact tty name, 49//config: and do something like this: 50//config: 51//config: # exec setsid sh -c 'exec sh </dev/tty1 >/dev/tty1 2>&1' 52//config: 53//config: Starting getty on a controlling tty from a shell script: 54//config: 55//config: # getty 115200 $(cttyhack) 56 57//usage:#define cttyhack_trivial_usage 58//usage: "[PROG ARGS]" 59//usage:#define cttyhack_full_usage "\n\n" 60//usage: "Give PROG a controlling tty if possible." 61//usage: "\nExample for /etc/inittab (for busybox init):" 62//usage: "\n ::respawn:/bin/cttyhack /bin/sh" 63//usage: "\nGiving controlling tty to shell running with PID 1:" 64//usage: "\n $ exec cttyhack sh" 65//usage: "\nStarting interactive shell from boot shell script:" 66//usage: "\n setsid cttyhack sh" 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> */ 73struct 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}; 78enum { VT_GETSTATE = 0x5603 }; /* get global vt state info */ 79 80/* From <linux/serial.h> */ 81struct 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 103int cttyhack_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; 104int 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