Use prctl(PR_SET_NAME) instead.
[util-vserver.git] / src / vlogin.c
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 #include <sys/prctl.h>
42
43 #define ENSC_WRAPPERS_PREFIX    "vlogin: "
44 #define ENSC_WRAPPERS_IOCTL     1
45 #define ENSC_WRAPPERS_UNISTD    1
46 #define ENSC_WRAPPERS_SOCKET    1
47 #define ENSC_WRAPPERS_IO        1
48 #define ENSC_WRAPPERS_TERMIOS   1
49 #define ENSC_WRAPPERS_FCNTL     1
50 #include <wrappers.h>
51
52 struct terminal {
53   int fd;                           /* terminal file descriptor */
54   struct termios term;              /* terminal settings */
55   struct winsize ws;                /* terminal size */
56   pid_t pid;                        /* terminal process id */
57   struct termios termo;             /* original terminal settings */
58   enum { TS_RESET, TS_RAW } state;  /* terminal state */
59 };
60
61 static struct terminal t;
62 extern int wrapper_exit_code;
63
64 /* set terminal to raw mode */
65 static void
66 terminal_raw(void)
67 {
68   struct termios buf;
69
70   /* save original terminal settings */
71   Etcgetattr(STDIN_FILENO, &t.termo);
72
73   buf = t.termo;
74   
75   /* convert terminal settings to raw mode */
76   cfmakeraw(&buf);
77
78   /* apply raw terminal settings */
79   Etcsetattr(STDIN_FILENO, TCSAFLUSH, &buf);
80
81   t.state = TS_RAW;
82 }
83
84 /* reset terminal to original state */
85 static void
86 terminal_reset(void)
87 {
88   if (t.state != TS_RAW)
89     return;
90
91   Etcsetattr(STDIN_FILENO, TCSAFLUSH, &t.termo);
92
93   t.state = TS_RESET;
94 }
95
96 /* send signal to terminal */
97 static void
98 terminal_kill(int sig)
99 {
100   pid_t pgrp = -1;
101
102   /* try to get process group leader */
103   if (ioctl(t.fd, TIOCGPGRP, &pgrp) >= 0 &&
104       pgrp != -1 &&
105       kill(-pgrp, sig) != -1)
106     return;
107
108   /* fallback using terminal pid */
109   kill(-t.pid, sig);
110 }
111
112 /* redraw the terminal screen */
113 static void
114 terminal_redraw(void)
115 {
116   /* get winsize from stdin */
117   if (ioctl(STDIN_FILENO, TIOCGWINSZ, &t.ws) == -1)
118     return;
119
120   /* set winsize in terminal */
121   ioctl(t.fd, TIOCSWINSZ, &t.ws);
122
123   /* set winsize change signal to terminal */
124   terminal_kill(SIGWINCH);
125 }
126
127 /* copy terminal activities */
128 static ssize_t
129 terminal_copy(int src, int dst)
130 {
131   char buf[64];
132   ssize_t len;
133
134   /* read terminal activity */
135   len = read(src, buf, sizeof(buf));
136   if (len == -1 && errno != EINTR) {
137     perror("read()");
138     terminal_kill(SIGTERM);
139     exit(1);
140   } else if (len == -1)
141     return -1;
142
143   /* write activity to user */
144   EwriteAll(dst, buf, len);
145
146   return len;
147 }
148
149 /* shuffle all output, and reset the terminal */
150 static void
151 terminal_end(void)
152 {
153   char buf[64];
154   ssize_t len;
155   long options;
156
157   options = Efcntl(t.fd, F_GETFL, 0) | O_NONBLOCK;
158   Efcntl(t.fd, F_SETFL, options);
159   for (;;) {
160     len = read(t.fd, buf, sizeof(buf));
161     if (len == 0 || len == -1)
162       break;
163     EwriteAll(STDOUT_FILENO, buf, len);
164   }
165
166   /* in case atexit hasn't been setup yet */
167   terminal_reset();
168 }
169
170 /* catch signals */
171 static void
172 signal_handler(int sig)
173 {
174   int status;
175
176   switch(sig) {
177     /* catch interrupt */
178     case SIGINT:
179       terminal_kill(sig);
180       break;
181
182     /* terminal died */
183     case SIGCHLD:
184       terminal_end();
185       wait(&status);
186       exit(WEXITSTATUS(status));
187       break;
188
189     /* window size has changed */
190     case SIGWINCH:
191       terminal_redraw();
192       break;
193
194     default:
195       exit(0);
196   }
197
198 }
199
200 void do_vlogin(int UNUSED argc, char *argv[], int ind)
201 {
202   int slave;
203   pid_t pid;
204   int n;
205   fd_set rfds;
206
207   if (!isatty(0) || !isatty(1)) {
208     execvp(argv[ind], argv+ind);
209     return;
210   }
211
212   /* set terminal to raw mode */
213   terminal_raw();
214
215   /* reset terminal to its original mode */
216   atexit(terminal_reset);
217
218   /* fork new pseudo terminal */
219   if (openpty(&t.fd, &slave, NULL, NULL, NULL) == -1) {
220     perror(ENSC_WRAPPERS_PREFIX "openpty()");
221     exit(EXIT_FAILURE);
222   }
223
224   /* setup SIGCHLD here, so we're sure to get the signal */
225   signal(SIGCHLD, signal_handler);
226
227   pid = Efork();
228
229   if (pid == 0) {
230     /* we don't need the master side of the terminal */
231     close(t.fd);
232
233     /* login_tty() stupid dietlibc doesn't have it */
234     Esetsid();
235
236     Eioctl(slave, TIOCSCTTY, NULL);
237
238     Edup2(slave, 0);
239     Edup2(slave, 1);
240     Edup2(slave, 2);
241
242     if (slave > 2)
243       close(slave);
244
245     Eexecvp(argv[ind], argv+ind);
246   }
247
248   /* setup SIGINT and SIGWINCH here, as they can cause loops in the child */
249   signal(SIGWINCH, signal_handler);
250   signal(SIGINT, signal_handler);
251
252   /* save terminals pid */
253   t.pid = pid;
254
255   /* set process title for ps */
256   prctl(PR_SET_NAME, (unsigned long) "login");
257
258   /* we want a redraw */
259   terminal_redraw();
260
261   /* main loop */
262   for (;;) {
263     /* init file descriptors for select */
264     FD_ZERO(&rfds);
265     FD_SET(STDIN_FILENO, &rfds);
266     FD_SET(t.fd, &rfds);
267     n = t.fd;
268
269     /* wait for something to happen */
270     while (select(n + 1, &rfds, NULL, NULL, NULL) == -1) {
271       if (errno == EINTR || errno == EAGAIN)
272         continue;
273       perror(ENSC_WRAPPERS_PREFIX "select()");
274       exit(wrapper_exit_code);
275     }
276
277     if (FD_ISSET(STDIN_FILENO, &rfds)) {
278       /* EOF */
279       if (terminal_copy(STDIN_FILENO, t.fd) == 0) {
280         terminal_kill(SIGHUP);
281         exit(0);
282       }
283     }
284
285     if (FD_ISSET(t.fd, &rfds)) {
286       /* EOF */
287       if (terminal_copy(t.fd, STDOUT_FILENO) == 0) {
288         terminal_kill(SIGHUP);
289         exit(0);
290       }
291     }
292   }
293
294   /* never get here, signal handler exits */
295 }