vlogin.c (6362B)
1 // $Id$ 2 3 // Copyright (C) 2006 Benedikt Böhm <hollow@gentoo.org> 4 // Based on vserver-utils' vlogin program. 5 // 6 // This program is free software; you can redistribute it and/or modify 7 // it under the terms of the GNU General Public License as published by 8 // the Free Software Foundation; version 2 of the License. 9 // 10 // This program is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU General Public License for more details. 14 // 15 // You should have received a copy of the GNU General Public License 16 // along with this program; if not, write to the Free Software 17 // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 18 19 20 #ifdef HAVE_CONFIG_H 21 # include <config.h> 22 #endif 23 24 #include "util.h" 25 #include <lib/vserver.h> 26 #include <lib/fmt.h> 27 28 #include <stdlib.h> 29 #include <getopt.h> 30 #include <stdint.h> 31 #include <string.h> 32 #include <errno.h> 33 #include <sys/stat.h> 34 #include <sys/ioctl.h> 35 #include <sys/wait.h> 36 #include <sys/socket.h> 37 #include <termios.h> 38 #include <signal.h> 39 #include <pty.h> 40 #include <fcntl.h> 41 42 #define ENSC_WRAPPERS_PREFIX "vlogin: " 43 #define ENSC_WRAPPERS_IOCTL 1 44 #define ENSC_WRAPPERS_UNISTD 1 45 #define ENSC_WRAPPERS_SOCKET 1 46 #define ENSC_WRAPPERS_IO 1 47 #define ENSC_WRAPPERS_TERMIOS 1 48 #define ENSC_WRAPPERS_FCNTL 1 49 #include <wrappers.h> 50 51 struct terminal { 52 int fd; /* terminal file descriptor */ 53 struct termios term; /* terminal settings */ 54 struct winsize ws; /* terminal size */ 55 pid_t pid; /* terminal process id */ 56 struct termios termo; /* original terminal settings */ 57 enum { TS_RESET, TS_RAW } state; /* terminal state */ 58 }; 59 60 static struct terminal t; 61 extern int wrapper_exit_code; 62 63 /* set terminal to raw mode */ 64 static void 65 terminal_raw(void) 66 { 67 struct termios buf; 68 69 /* save original terminal settings */ 70 Etcgetattr(STDIN_FILENO, &t.termo); 71 72 buf = t.termo; 73 74 /* convert terminal settings to raw mode */ 75 cfmakeraw(&buf); 76 77 /* apply raw terminal settings */ 78 Etcsetattr(STDIN_FILENO, TCSAFLUSH, &buf); 79 80 t.state = TS_RAW; 81 } 82 83 /* reset terminal to original state */ 84 static void 85 terminal_reset(void) 86 { 87 if (t.state != TS_RAW) 88 return; 89 90 Etcsetattr(STDIN_FILENO, TCSAFLUSH, &t.termo); 91 92 t.state = TS_RESET; 93 } 94 95 /* send signal to terminal */ 96 static void 97 terminal_kill(int sig) 98 { 99 pid_t pgrp = -1; 100 101 /* try to get process group leader */ 102 if (ioctl(t.fd, TIOCGPGRP, &pgrp) >= 0 && 103 pgrp != -1 && 104 kill(-pgrp, sig) != -1) 105 return; 106 107 /* fallback using terminal pid */ 108 kill(-t.pid, sig); 109 } 110 111 /* redraw the terminal screen */ 112 static void 113 terminal_redraw(void) 114 { 115 /* get winsize from stdin */ 116 if (ioctl(STDIN_FILENO, TIOCGWINSZ, &t.ws) == -1) 117 return; 118 119 /* set winsize in terminal */ 120 ioctl(t.fd, TIOCSWINSZ, &t.ws); 121 122 /* set winsize change signal to terminal */ 123 terminal_kill(SIGWINCH); 124 } 125 126 /* copy terminal activities */ 127 static ssize_t 128 terminal_copy(int src, int dst) 129 { 130 char buf[64]; 131 ssize_t len; 132 133 /* read terminal activity */ 134 len = read(src, buf, sizeof(buf)); 135 if (len == -1 && errno != EINTR) { 136 perror("read()"); 137 terminal_kill(SIGTERM); 138 exit(1); 139 } else if (len == -1) 140 return -1; 141 142 /* write activity to user */ 143 EwriteAll(dst, buf, len); 144 145 return len; 146 } 147 148 /* shuffle all output, and reset the terminal */ 149 static void 150 terminal_end(void) 151 { 152 char buf[64]; 153 ssize_t len; 154 long options; 155 156 options = Efcntl(t.fd, F_GETFL, 0) | O_NONBLOCK; 157 Efcntl(t.fd, F_SETFL, options); 158 for (;;) { 159 len = read(t.fd, buf, sizeof(buf)); 160 if (len == 0 || len == -1) 161 break; 162 EwriteAll(STDOUT_FILENO, buf, len); 163 } 164 165 /* in case atexit hasn't been setup yet */ 166 terminal_reset(); 167 } 168 169 /* catch signals */ 170 static void 171 signal_handler(int sig) 172 { 173 int status; 174 175 switch(sig) { 176 /* catch interrupt */ 177 case SIGINT: 178 terminal_kill(sig); 179 break; 180 181 /* terminal died */ 182 case SIGCHLD: 183 terminal_end(); 184 wait(&status); 185 exit(WEXITSTATUS(status)); 186 break; 187 188 /* window size has changed */ 189 case SIGWINCH: 190 terminal_redraw(); 191 break; 192 193 default: 194 exit(0); 195 } 196 197 } 198 199 void do_vlogin(int argc, char *argv[], int ind) 200 { 201 int slave; 202 pid_t pid; 203 int n, i; 204 fd_set rfds; 205 206 if (!isatty(0) || !isatty(1)) { 207 execvp(argv[ind], argv+ind); 208 return; 209 } 210 211 /* set terminal to raw mode */ 212 terminal_raw(); 213 214 /* reset terminal to its original mode */ 215 atexit(terminal_reset); 216 217 /* fork new pseudo terminal */ 218 if (openpty(&t.fd, &slave, NULL, NULL, NULL) == -1) { 219 perror(ENSC_WRAPPERS_PREFIX "openpty()"); 220 exit(EXIT_FAILURE); 221 } 222 223 /* setup SIGCHLD here, so we're sure to get the signal */ 224 signal(SIGCHLD, signal_handler); 225 226 pid = Efork(); 227 228 if (pid == 0) { 229 /* we don't need the master side of the terminal */ 230 close(t.fd); 231 232 /* login_tty() stupid dietlibc doesn't have it */ 233 Esetsid(); 234 235 Eioctl(slave, TIOCSCTTY, NULL); 236 237 Edup2(slave, 0); 238 Edup2(slave, 1); 239 Edup2(slave, 2); 240 241 if (slave > 2) 242 close(slave); 243 244 Eexecvp(argv[ind], argv+ind); 245 } 246 247 /* setup SIGINT and SIGWINCH here, as they can cause loops in the child */ 248 signal(SIGWINCH, signal_handler); 249 signal(SIGINT, signal_handler); 250 251 /* save terminals pid */ 252 t.pid = pid; 253 254 /* set process title for ps */ 255 n = strlen(argv[0]); 256 257 for (i = 0; i < argc; i++) 258 memset(argv[i], '\0', strlen(argv[i])); 259 260 strncpy(argv[0], "login", n); 261 262 /* we want a redraw */ 263 terminal_redraw(); 264 265 /* main loop */ 266 for (;;) { 267 /* init file descriptors for select */ 268 FD_ZERO(&rfds); 269 FD_SET(STDIN_FILENO, &rfds); 270 FD_SET(t.fd, &rfds); 271 n = t.fd; 272 273 /* wait for something to happen */ 274 while (select(n + 1, &rfds, NULL, NULL, NULL) == -1) { 275 if (errno == EINTR || errno == EAGAIN) 276 continue; 277 perror(ENSC_WRAPPERS_PREFIX "select()"); 278 exit(wrapper_exit_code); 279 } 280 281 if (FD_ISSET(STDIN_FILENO, &rfds)) { 282 /* EOF */ 283 if (terminal_copy(STDIN_FILENO, t.fd) == 0) { 284 terminal_kill(SIGHUP); 285 exit(0); 286 } 287 } 288 289 if (FD_ISSET(t.fd, &rfds)) { 290 /* EOF */ 291 if (terminal_copy(t.fd, STDOUT_FILENO) == 0) { 292 terminal_kill(SIGHUP); 293 exit(0); 294 } 295 } 296 } 297 298 /* never get here, signal handler exits */ 299 }