From ec17b0fea7ed4c5724abd5db7ce6efb9c500bae5 Mon Sep 17 00:00:00 2001 From: Daniel Hokka Zakrisson Date: Tue, 12 Sep 2006 16:55:42 +0000 Subject: [PATCH] Add vlogin, a terminal proxy for vserver ... enter. (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 | 9 ++ ensc_wrappers/wrappers-termios.hc | 32 +++++ ensc_wrappers/wrappers.h | 4 + scripts/vserver | 1 + src/Makefile-files | 2 +- src/vcontext.c | 19 ++- src/vlogin.c | 287 ++++++++++++++++++++++++++++++++++++++ 7 files changed, 349 insertions(+), 5 deletions(-) create mode 100644 ensc_wrappers/wrappers-termios.hc create mode 100644 src/vlogin.c diff --git a/configure.ac b/configure.ac index 5642959..9843756 100644 --- a/configure.ac +++ b/configure.ac @@ -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 ]) 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 index 0000000..9d21e49 --- /dev/null +++ b/ensc_wrappers/wrappers-termios.hc @@ -0,0 +1,32 @@ +// $Id$ --*- c -*-- + +// Copyright (C) 2006 Enrico Scholz +// +// 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()"); +} diff --git a/ensc_wrappers/wrappers.h b/ensc_wrappers/wrappers.h index c0c4370..08de6b1 100644 --- a/ensc_wrappers/wrappers.h +++ b/ensc_wrappers/wrappers.h @@ -111,6 +111,10 @@ # include "wrappers-stat.hc" #endif +#ifdef ENSC_WRAPPERS_TERMIOS +# include "wrappers-termios.hc" +#endif + #undef ENSC_DETAIL2 #undef ENSC_DETAIL1 #undef ENSC_DOQUOTE_COND diff --git a/scripts/vserver b/scripts/vserver index 48e5120..9eb7249 100755 --- a/scripts/vserver +++ b/scripts/vserver @@ -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[@]}" ;; diff --git a/src/Makefile-files b/src/Makefile-files index 233ab0f..c6e8588 100644 --- a/src/Makefile-files +++ b/src/Makefile-files @@ -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) diff --git a/src/vcontext.c b/src/vcontext.c index ac8016f..3cae6ea 100644 --- a/src/vcontext.c +++ b/src/vcontext.c @@ -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 \n" " ... use 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 index 0000000..56a1df0 --- /dev/null +++ b/src/vlogin.c @@ -0,0 +1,287 @@ +// $Id$ + +// Copyright (C) 2006 Benedikt Böhm +// 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 +#endif + +#include "util.h" +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 + +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 */ +} -- 1.8.1.5