Add vlogin, a terminal proxy for vserver ... enter.
authorDaniel Hokka Zakrisson <daniel@hozac.com>
Tue, 12 Sep 2006 16:55:42 +0000 (16:55 +0000)
committerDaniel Hokka Zakrisson <daniel@hozac.com>
Tue, 12 Sep 2006 16:55:42 +0000 (16:55 +0000)
(https://savannah.nongnu.org/patch/?4969)

git-svn-id: http://svn.linux-vserver.org/svn/util-vserver/trunk@2306 94cd875c-1c1d-0410-91d2-eb244daf1a30

configure.ac
ensc_wrappers/wrappers-termios.hc [new file with mode: 0644]
ensc_wrappers/wrappers.h
scripts/vserver
src/Makefile-files
src/vcontext.c
src/vlogin.c [new file with mode: 0644]

index 5642959..9843756 100644 (file)
@@ -141,6 +141,7 @@ dnl
 case $host_cpu in
        (i*86|athlon)   min_diet_ver=0.25;;
        (ia64|hppa*)    min_diet_ver=0.29;;
+       (sparc*)        min_diet_ver=0.30;;
        (*)             min_diet_ver=0.28;;
 esac
 
@@ -281,6 +282,14 @@ AC_CHECK_TYPES(nid_t,,,[#include <sys/types.h>])
 
 AC_CHECK_HEADERS([sys/capability.h])
 
+dnl vlogin might need -lutil
+if test x"$ensc_have_dietlibc" = xno; then
+       AC_CHECK_FUNC([openpty],, [AC_CHECK_LIB([util], [openpty],, [AC_MSG_ERROR([
+****
+**** openpty could not be found
+****])])])
+fi
+
 
 dnl ########################
 dnl
diff --git a/ensc_wrappers/wrappers-termios.hc b/ensc_wrappers/wrappers-termios.hc
new file mode 100644 (file)
index 0000000..9d21e49
--- /dev/null
@@ -0,0 +1,32 @@
+// $Id$    --*- c -*--
+
+// Copyright (C) 2006 Enrico Scholz <enrico.scholz@informatik.tu-chemnitz.de>
+//  
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; version 2 of the License.
+//  
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//  
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+#ifndef H_ENSC_IN_WRAPPERS_H
+#  error wrappers-termios.hc can not be used in this way
+#endif
+
+inline static WRAPPER_DECL void
+Etcgetattr(int fd, struct termios *termios_p)          
+{                                                              
+  FatalErrnoError(tcgetattr(fd, termios_p)==-1, "tcgetattr()");
+}
+
+inline static WRAPPER_DECL void
+Etcsetattr(int fd, int optional_actions, struct termios *termios_p)            
+{                                                              
+  FatalErrnoError(tcsetattr(fd, optional_actions, termios_p)==-1, "tcsetattr()");
+}
index c0c4370..08de6b1 100644 (file)
 #  include "wrappers-stat.hc"
 #endif
 
+#ifdef ENSC_WRAPPERS_TERMIOS
+#  include "wrappers-termios.hc"
+#endif
+
 #undef ENSC_DETAIL2
 #undef ENSC_DETAIL1
 #undef ENSC_DOQUOTE_COND
index 48e5120..9eb7249 100755 (executable)
@@ -220,6 +220,7 @@ case "$2" in
        suexec root chkconfig "$@"
        ;;
     (enter)
+       OPTS_VCONTEXT_ENTER=( "${OPTS_VCONTEXT_ENTER[@]}" --vlogin )
        getEnterShell "$VSERVER_DIR"
        suexec root "${ENTER_SHELL[@]}"
        ;;
index 233ab0f..c6e8588 100644 (file)
@@ -167,7 +167,7 @@ src_chcontext_compat_SOURCES =      src/chcontext.c
 src_chcontext_compat_LDADD =   $(VSERVER_LDADDS) $(LIBINTERNAL)
 src_chcontext_compat_LDFLAGS = $(VSERVER_LDFLGS)
 
-src_vcontext_SOURCES           =  src/vcontext.c
+src_vcontext_SOURCES           =  src/vcontext.c src/vlogin.c
 src_vcontext_LDADD             =  $(VSERVER_LDADDS) $(LIBINTERNAL)
 src_vcontext_LDFLAGS           =  $(VSERVER_LDFLGS)
 
index ac8016f..3cae6ea 100644 (file)
@@ -62,6 +62,7 @@
 #define CMD_NAMESPACE          0x400d
 #define CMD_PERSTYPE           0x400e
 #define CMD_PERSFLAG           0x400f
+#define CMD_VLOGIN             0x4010
 
 
 struct option const
@@ -85,6 +86,7 @@ CMDLINE_OPTIONS[] = {
   { "syncmsg",      required_argument,         0, CMD_SYNCMSG },
   { "personality-type",  required_argument, 0, CMD_PERSTYPE },
   { "personality-flags", required_argument, 0, CMD_PERSFLAG },
+  { "vlogin",       no_argument,        0, CMD_VLOGIN },
 #if 1  
   { "fakeinit",     no_argument,               0, CMD_INITPID },       // compatibility
 #endif  
@@ -100,6 +102,7 @@ struct Arguments {
     bool               is_initpid;
     bool               is_silentexist;
     bool               set_namespace;
+    bool               do_vlogin;
     uint_least32_t     personality_flags;
     uint_least32_t     personality_type;
     int                        verbosity;
@@ -112,6 +115,8 @@ struct Arguments {
 
 int            wrapper_exit_code = 255;
 
+void do_vlogin(int argc, char *argv[], int ind);
+
 static void
 showHelp(int fd, char const *cmd, int res)
 {
@@ -145,6 +150,7 @@ showHelp(int fd, char const *cmd, int res)
            "    --syncmsg <message>\n"
            "                    ...  use <message> as synchronization message; by\n"
            "                         default, 'ok' will be used\n"
+           "    --vlogin        ...  enable terminal proxy\n"
            "\n"
            "'vcontext --create' exits with code 254 iff the context exists already.\n"
            "\n"
@@ -233,7 +239,7 @@ doExternalSync(int fd, char const *msg)
 }
 
 static inline ALWAYSINLINE int
-doit(struct Arguments const *args, char *argv[])
+doit(struct Arguments const *args, int argc, char *argv[])
 {
   int                  p[2][2];
   pid_t                        pid = initSync(p, args->do_disconnect);
@@ -287,12 +293,15 @@ doit(struct Arguments const *args, char *argv[])
        sys_personality(args->personality_type | args->personality_flags)==-1) {
       perror(ENSC_WRAPPERS_PREFIX "personality()");
       exit(wrapper_exit_code);
-    }  
+    }
 
     doExternalSync(ext_sync_fd, args->sync_msg);
     doSyncStage1(p, args->do_disconnect);
     DPRINTF("doit: pid=%u, ppid=%u\n", getpid(), getppid());
-    execvp (argv[optind],argv+optind);
+    if (!args->do_vlogin)
+      execvp (argv[optind],argv+optind);
+    else
+      do_vlogin(argc, argv, optind);
     doSyncStage2(p, args->do_disconnect);
 
     PERROR_Q(ENSC_WRAPPERS_PREFIX "execvp", argv[optind]);
@@ -341,6 +350,7 @@ int main (int argc, char *argv[])
     .do_migrateself    = false,
     .do_disconnect     = false,
     .do_endsetup       = false,
+    .do_vlogin         = false,
     .is_initpid        = false,
     .is_silentexist    = false,
     .set_namespace     = false,
@@ -363,6 +373,7 @@ int main (int argc, char *argv[])
       case CMD_MIGRATE         :  args.do_migrate     = true;   break;
       case CMD_DISCONNECT      :  args.do_disconnect  = true;   break;
       case CMD_ENDSETUP                :  args.do_endsetup    = true;   break;
+      case CMD_VLOGIN          :  args.do_vlogin      = true;   break;
       case CMD_INITPID         :  args.is_initpid     = true;   break;
       case CMD_CHROOT          :  args.do_chroot      = true;   break;
       case CMD_NAMESPACE       :  args.set_namespace  = true;   break;
@@ -408,7 +419,7 @@ int main (int argc, char *argv[])
   else if (optind>=argc)
     WRITE_MSG(2, "No command given; use '--help' for more information.\n");
   else
-    return doit(&args, argv);
+    return doit(&args, argc, argv);
 
   return wrapper_exit_code;
 }
diff --git a/src/vlogin.c b/src/vlogin.c
new file mode 100644 (file)
index 0000000..56a1df0
--- /dev/null
@@ -0,0 +1,287 @@
+// $Id$
+
+// Copyright (C) 2006 Benedikt Böhm <hollow@gentoo.org>
+// Based on vserver-utils' vlogin program.
+//  
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; version 2 of the License.
+//  
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//  
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif
+
+#include "util.h"
+#include <lib/vserver.h>
+#include <lib/fmt.h>
+
+#include <stdlib.h>
+#include <getopt.h>
+#include <stdint.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <sys/wait.h>
+#include <sys/socket.h>
+#include <termios.h>
+#include <signal.h>
+#include <pty.h>
+#include <fcntl.h>
+
+#define ENSC_WRAPPERS_PREFIX   "vlogin: "
+#define ENSC_WRAPPERS_IOCTL    1
+#define ENSC_WRAPPERS_UNISTD   1
+#define ENSC_WRAPPERS_SOCKET   1
+#define ENSC_WRAPPERS_IO       1
+#define ENSC_WRAPPERS_TERMIOS  1
+#define ENSC_WRAPPERS_FCNTL    1
+#include <wrappers.h>
+
+struct terminal {
+  int fd;                           /* terminal file descriptor */
+  struct termios term;              /* terminal settings */
+  struct winsize ws;                /* terminal size */
+  pid_t pid;                        /* terminal process id */
+  struct termios termo;             /* original terminal settings */
+  enum { TS_RESET, TS_RAW } state;  /* terminal state */
+};
+
+static struct terminal t;
+extern int wrapper_exit_code;
+
+/* set terminal to raw mode */
+static void
+terminal_raw(void)
+{
+  struct termios buf;
+
+  /* save original terminal settings */
+  Etcgetattr(STDIN_FILENO, &t.termo);
+
+  buf = t.termo;
+  
+  /* convert terminal settings to raw mode */
+  cfmakeraw(&buf);
+
+  /* apply raw terminal settings */
+  Etcsetattr(STDIN_FILENO, TCSAFLUSH, &buf);
+
+  t.state = TS_RAW;
+}
+
+/* reset terminal to original state */
+static void
+terminal_reset(void)
+{
+  if (t.state != TS_RAW)
+    return;
+
+  Etcsetattr(STDIN_FILENO, TCSAFLUSH, &t.termo);
+
+  t.state = TS_RESET;
+}
+
+/* send signal to terminal */
+static void
+terminal_kill(int sig)
+{
+  pid_t pgrp = -1;
+
+  /* try to get process group leader */
+  if (ioctl(t.fd, TIOCGPGRP, &pgrp) >= 0 &&
+      pgrp != -1 &&
+      kill(-pgrp, sig) != -1)
+    return;
+
+  /* fallback using terminal pid */
+  kill(-t.pid, sig);
+}
+
+/* redraw the terminal screen */
+static void
+terminal_redraw(void)
+{
+  /* get winsize from stdin */
+  if (ioctl(STDIN_FILENO, TIOCGWINSZ, &t.ws) == -1)
+    return;
+
+  /* set winsize in terminal */
+  ioctl(t.fd, TIOCSWINSZ, &t.ws);
+
+  /* set winsize change signal to terminal */
+  terminal_kill(SIGWINCH);
+}
+
+/* copy terminal activities */
+static void
+terminal_copy(int src, int dst)
+{
+  char buf[64];
+  ssize_t len;
+
+  /* read terminal activity */
+  len = read(src, buf, sizeof(buf));
+  if (len == -1 && errno != EINTR) {
+    perror("read()");
+    terminal_kill(SIGTERM);
+    exit(1);
+  } else if (len == -1)
+    return;
+
+  /* write activity to user */
+  EwriteAll(dst, buf, len);
+}
+
+/* shuffle all output, and reset the terminal */
+static void
+terminal_end(void)
+{
+  char buf[64];
+  ssize_t len;
+  long options;
+
+  options = Efcntl(t.fd, F_GETFL, 0) | O_NONBLOCK;
+  Efcntl(t.fd, F_SETFL, options);
+  for (;;) {
+    len = read(t.fd, buf, sizeof(buf));
+    if (len == 0 || len == -1)
+      break;
+    EwriteAll(STDOUT_FILENO, buf, len);
+  }
+
+  /* in case atexit hasn't been setup yet */
+  terminal_reset();
+}
+
+/* catch signals */
+static void
+signal_handler(int sig)
+{
+  int status;
+
+  switch(sig) {
+    /* catch interrupt */
+    case SIGINT:
+      terminal_kill(sig);
+      break;
+
+    /* terminal died */
+    case SIGCHLD:
+      terminal_end();
+      wait(&status);
+      exit(WEXITSTATUS(status));
+      break;
+
+    /* window size has changed */
+    case SIGWINCH:
+      terminal_redraw();
+      break;
+
+    default:
+      exit(0);
+  }
+
+}
+
+void do_vlogin(int argc, char *argv[], int ind)
+{
+  int slave;
+  pid_t pid;
+  int n, i;
+  fd_set rfds;
+
+  if (!isatty(0) || !isatty(1)) {
+    execvp(argv[ind], argv+ind);
+    return;
+  }
+
+  /* set terminal to raw mode */
+  terminal_raw();
+
+  /* fork new pseudo terminal */
+  if (openpty(&t.fd, &slave, NULL, NULL, NULL) == -1) {
+    perror(ENSC_WRAPPERS_PREFIX "openpty()");
+    exit(EXIT_FAILURE);
+  }
+
+  /* setup SIGCHLD here, so we're sure to get the signal */
+  signal(SIGCHLD, signal_handler);
+
+  pid = Efork();
+
+  if (pid == 0) {
+    /* we don't need the master side of the terminal */
+    close(t.fd);
+
+    /* login_tty() stupid dietlibc doesn't have it */
+    Esetsid();
+
+    Eioctl(slave, TIOCSCTTY, NULL);
+
+    Edup2(slave, 0);
+    Edup2(slave, 1);
+    Edup2(slave, 2);
+
+    if (slave > 2)
+      close(slave);
+
+    Eexecvp(argv[ind], argv+ind);
+  }
+
+  /* setup SIGINT and SIGWINCH here, as they can cause loops in the child */
+  signal(SIGWINCH, signal_handler);
+  signal(SIGINT, signal_handler);
+
+  /* save terminals pid */
+  t.pid = pid;
+
+  /* set process title for ps */
+  n = strlen(argv[0]);
+
+  for (i = 0; i < argc; i++)
+    memset(argv[i], '\0', strlen(argv[i]));
+
+  strncpy(argv[0], "login", n);
+
+  /* reset terminal to its original mode */
+  atexit(terminal_reset);
+
+  /* we want a redraw */
+  terminal_redraw();
+
+  /* main loop */
+  for (;;) {
+    /* init file descriptors for select */
+    FD_ZERO(&rfds);
+    FD_SET(STDIN_FILENO, &rfds);
+    FD_SET(t.fd, &rfds);
+    n = t.fd;
+
+    /* wait for something to happen */
+    while (select(n + 1, &rfds, NULL, NULL, NULL) == -1) {
+      if (errno == EINTR || errno == EAGAIN)
+       continue;
+      perror(ENSC_WRAPPERS_PREFIX "select()");
+      exit(wrapper_exit_code);
+    }
+
+    if (FD_ISSET(STDIN_FILENO, &rfds))
+      terminal_copy(STDIN_FILENO, t.fd);
+
+    if (FD_ISSET(t.fd, &rfds))
+      terminal_copy(t.fd, STDOUT_FILENO);
+  }
+
+  /* never get here, signal handler exits */
+}