util-vserver 0.30.214
[util-vserver.git] / src / vlogin.c
1 // $Id: vlogin.c 2525 2007-04-08 00:40:16Z dhozac $
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 }