busybox/shell/cttyhack.c
<<
>>
Prefs
   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> */
  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