Tweak INSTALL text
[oweals/busybox.git] / shell / cttyhack.c
index bbe5149330a8b50a037970240f38fe0c7cde5a91..f9b59c26321906d9b7039fc977ebf80cbe8ca6b4 100644 (file)
@@ -1,11 +1,74 @@
 /* vi: set sw=4 ts=4: */
 /*
- * Licensed under GPLv2
- *
  * 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:
+//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 */
@@ -38,7 +101,7 @@ struct serial_struct {
 };
 
 int cttyhack_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
-int cttyhack_main(int argc ATTRIBUTE_UNUSED, char **argv)
+int cttyhack_main(int argc UNUSED_PARAM, char **argv)
 {
        int fd;
        char console[sizeof(int)*3 + 16];
@@ -48,30 +111,78 @@ int cttyhack_main(int argc ATTRIBUTE_UNUSED, char **argv)
                char paranoia[sizeof(struct serial_struct) * 3];
        } u;
 
-       if (!*++argv) {
-               bb_show_usage();
-       }
-
        strcpy(console, "/dev/tty");
-       if (ioctl(0, TIOCGSERIAL, &u.sr) == 0) {
-               /* this is a serial console */
-               sprintf(console + 8, "S%d", u.sr.line);
-       } else if (ioctl(0, VT_GETSTATE, &u.vt) == 0) {
-               /* this is linux virtual tty */
-               sprintf(console + 8, "S%d" + 1, u.vt.v_active);
+       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);
        }
 
-       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);
+       argv++;
+       if (!argv[0]) {
+               if (!console[0])
+                       return EXIT_FAILURE;
+               puts(console);
+               return EXIT_SUCCESS;
        }
 
-       BB_EXECVP(argv[0], argv);
-       bb_perror_msg_and_die("cannot exec '%s'", argv[0]);
+       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);
 }