]> mj.ucw.cz Git - eval.git/blob - isolate/isolate.c
Isolate: First working version
[eval.git] / isolate / isolate.c
1 /*
2  *      A Process Isolator based in Linux Containers
3  *
4  *      (c) 2012 Martin Mares <mj@ucw.cz>
5  */
6
7 #define _GNU_SOURCE
8
9 #include "autoconf.h"
10
11 // FIXME: prune
12 #include <errno.h>
13 #include <stdio.h>
14 #include <fcntl.h>
15 #include <stdlib.h>
16 #include <string.h>
17 #include <stdarg.h>
18 #include <stdint.h>
19 #include <unistd.h>
20 #include <getopt.h>
21 #include <sched.h>
22 #include <time.h>
23 #include <grp.h>
24 #include <sys/wait.h>
25 #include <sys/user.h>
26 #include <sys/time.h>
27 #include <sys/ptrace.h>
28 #include <sys/signal.h>
29 #include <sys/sysinfo.h>
30 #include <sys/resource.h>
31 #include <sys/mount.h>
32 #include <sys/stat.h>
33
34 #define NONRET __attribute__((noreturn))
35 #define UNUSED __attribute__((unused))
36 #define ARRAY_SIZE(a) (int)(sizeof(a)/sizeof(a[0]))
37
38 // FIXME: Make configurable, probably in compile time
39 #define BOX_DIR "/tmp/box"
40 #define BOX_UID 60000
41 #define BOX_GID 60000
42
43 static int timeout;                     /* milliseconds */
44 static int wall_timeout;
45 static int extra_timeout;
46 static int pass_environ;
47 static int verbose;
48 static int memory_limit;
49 static int stack_limit;
50 static char *redir_stdin, *redir_stdout, *redir_stderr;
51
52 static uid_t orig_uid;
53 static gid_t orig_gid;
54
55 static pid_t box_pid;
56 static volatile sig_atomic_t timer_tick;
57 static struct timeval start_time;
58 static int ticks_per_sec;
59 static int partial_line;
60 static char cleanup_cmd[256];
61
62 static int total_ms, wall_ms;
63
64 static void die(char *msg, ...) NONRET;
65
66 /*** Meta-files ***/
67
68 static FILE *metafile;
69
70 static void
71 meta_open(const char *name)
72 {
73   if (!strcmp(name, "-"))
74     {
75       metafile = stdout;
76       return;
77     }
78   metafile = fopen(name, "w");
79   if (!metafile)
80     die("Failed to open metafile '%s'",name);
81 }
82
83 static void
84 meta_close(void)
85 {
86   if (metafile && metafile != stdout)
87     fclose(metafile);
88 }
89
90 static void __attribute__((format(printf,1,2)))
91 meta_printf(const char *fmt, ...)
92 {
93   if (!metafile)
94     return;
95
96   va_list args;
97   va_start(args, fmt);
98   vfprintf(metafile, fmt, args);
99   va_end(args);
100 }
101
102 static void
103 final_stats(struct rusage *rus)
104 {
105   struct timeval total, now, wall;
106   timeradd(&rus->ru_utime, &rus->ru_stime, &total);
107   total_ms = total.tv_sec*1000 + total.tv_usec/1000;
108   gettimeofday(&now, NULL);
109   timersub(&now, &start_time, &wall);
110   wall_ms = wall.tv_sec*1000 + wall.tv_usec/1000;
111
112   meta_printf("time:%d.%03d\n", total_ms/1000, total_ms%1000);
113   meta_printf("time-wall:%d.%03d\n", wall_ms/1000, wall_ms%1000);
114 }
115
116 /*** Messages and exits ***/
117
118 static void
119 xsystem(const char *cmd)
120 {
121   int ret = system(cmd);
122   if (ret < 0)
123     die("system(\"%s\"): %m", cmd);
124   if (!WIFEXITED(ret) || WEXITSTATUS(ret))
125     die("system(\"%s\"): Exited with status %d", cmd, ret);
126 }
127
128 static void NONRET
129 box_exit(int rc)
130 {
131   if (box_pid > 0)
132     {
133       kill(-box_pid, SIGKILL);
134       kill(box_pid, SIGKILL);
135       meta_printf("killed:1\n");
136
137       struct rusage rus;
138       int p, stat;
139       do
140         p = wait4(box_pid, &stat, 0, &rus);
141       while (p < 0 && errno == EINTR);
142       if (p < 0)
143         fprintf(stderr, "UGH: Lost track of the process (%m)\n");
144       else
145         final_stats(&rus);
146     }
147
148   if (rc < 2 && cleanup_cmd[0])
149     xsystem(cleanup_cmd);
150
151   meta_close();
152   exit(rc);
153 }
154
155 static void
156 flush_line(void)
157 {
158   if (partial_line)
159     fputc('\n', stderr);
160   partial_line = 0;
161 }
162
163 /* Report an error of the sandbox itself */
164 static void NONRET __attribute__((format(printf,1,2)))
165 die(char *msg, ...)
166 {
167   va_list args;
168   va_start(args, msg);
169   flush_line();
170   char buf[1024];
171   vsnprintf(buf, sizeof(buf), msg, args);
172   meta_printf("status:XX\nmessage:%s\n", buf);
173   fputs(buf, stderr);
174   fputc('\n', stderr);
175   box_exit(2);
176 }
177
178 /* Report an error of the program inside the sandbox */
179 static void NONRET __attribute__((format(printf,1,2)))
180 err(char *msg, ...)
181 {
182   va_list args;
183   va_start(args, msg);
184   flush_line();
185   if (msg[0] && msg[1] && msg[2] == ':' && msg[3] == ' ')
186     {
187       meta_printf("status:%c%c\n", msg[0], msg[1]);
188       msg += 4;
189     }
190   char buf[1024];
191   vsnprintf(buf, sizeof(buf), msg, args);
192   meta_printf("message:%s\n", buf);
193   fputs(buf, stderr);
194   fputc('\n', stderr);
195   box_exit(1);
196 }
197
198 /* Write a message, but only if in verbose mode */
199 static void __attribute__((format(printf,1,2)))
200 msg(char *msg, ...)
201 {
202   va_list args;
203   va_start(args, msg);
204   if (verbose)
205     {
206       int len = strlen(msg);
207       if (len > 0)
208         partial_line = (msg[len-1] != '\n');
209       vfprintf(stderr, msg, args);
210       fflush(stderr);
211     }
212   va_end(args);
213 }
214
215 static void *
216 xmalloc(size_t size)
217 {
218   void *p = malloc(size);
219   if (!p)
220     die("Out of memory");
221   return p;
222 }
223
224 /*** Environment rules ***/
225
226 struct env_rule {
227   char *var;                    // Variable to match
228   char *val;                    // ""=clear, NULL=inherit
229   int var_len;
230   struct env_rule *next;
231 };
232
233 static struct env_rule *first_env_rule;
234 static struct env_rule **last_env_rule = &first_env_rule;
235
236 static struct env_rule default_env_rules[] = {
237   { "LIBC_FATAL_STDERR_", "1" }
238 };
239
240 static int
241 set_env_action(char *a0)
242 {
243   struct env_rule *r = xmalloc(sizeof(*r) + strlen(a0) + 1);
244   char *a = (char *)(r+1);
245   strcpy(a, a0);
246
247   char *sep = strchr(a, '=');
248   if (sep == a)
249     return 0;
250   r->var = a;
251   if (sep)
252     {
253       *sep++ = 0;
254       r->val = sep;
255     }
256   else
257     r->val = NULL;
258   *last_env_rule = r;
259   last_env_rule = &r->next;
260   r->next = NULL;
261   return 1;
262 }
263
264 static int
265 match_env_var(char *env_entry, struct env_rule *r)
266 {
267   if (strncmp(env_entry, r->var, r->var_len))
268     return 0;
269   return (env_entry[r->var_len] == '=');
270 }
271
272 static void
273 apply_env_rule(char **env, int *env_sizep, struct env_rule *r)
274 {
275   // First remove the variable if already set
276   int pos = 0;
277   while (pos < *env_sizep && !match_env_var(env[pos], r))
278     pos++;
279   if (pos < *env_sizep)
280     {
281       (*env_sizep)--;
282       env[pos] = env[*env_sizep];
283       env[*env_sizep] = NULL;
284     }
285
286   // What is the new value?
287   char *new;
288   if (r->val)
289     {
290       if (!r->val[0])
291         return;
292       new = xmalloc(r->var_len + 1 + strlen(r->val) + 1);
293       sprintf(new, "%s=%s", r->var, r->val);
294     }
295   else
296     {
297       pos = 0;
298       while (environ[pos] && !match_env_var(environ[pos], r))
299         pos++;
300       if (!(new = environ[pos]))
301         return;
302     }
303
304   // Add it at the end of the array
305   env[(*env_sizep)++] = new;
306   env[*env_sizep] = NULL;
307 }
308
309 static char **
310 setup_environment(void)
311 {
312   // Link built-in rules with user rules
313   for (int i=ARRAY_SIZE(default_env_rules)-1; i >= 0; i--)
314     {
315       default_env_rules[i].next = first_env_rule;
316       first_env_rule = &default_env_rules[i];
317     }
318
319   // Scan the original environment
320   char **orig_env = environ;
321   int orig_size = 0;
322   while (orig_env[orig_size])
323     orig_size++;
324
325   // For each rule, reserve one more slot and calculate length
326   int num_rules = 0;
327   for (struct env_rule *r = first_env_rule; r; r=r->next)
328     {
329       num_rules++;
330       r->var_len = strlen(r->var);
331     }
332
333   // Create a new environment
334   char **env = xmalloc((orig_size + num_rules + 1) * sizeof(char *));
335   int size;
336   if (pass_environ)
337     {
338       memcpy(env, environ, orig_size * sizeof(char *));
339       size = orig_size;
340     }
341   else
342     size = 0;
343   env[size] = NULL;
344
345   // Apply the rules one by one
346   for (struct env_rule *r = first_env_rule; r; r=r->next)
347     apply_env_rule(env, &size, r);
348
349   // Return the new env and pass some gossip
350   if (verbose > 1)
351     {
352       fprintf(stderr, "Passing environment:\n");
353       for (int i=0; env[i]; i++)
354         fprintf(stderr, "\t%s\n", env[i]);
355     }
356   return env;
357 }
358
359 /*** The keeper process ***/
360
361 static void
362 signal_alarm(int unused UNUSED)
363 {
364   /* Time limit checks are synchronous, so we only schedule them there. */
365   timer_tick = 1;
366   alarm(1);
367 }
368
369 static void
370 signal_int(int unused UNUSED)
371 {
372   /* Interrupts are fatal, so no synchronization requirements. */
373   meta_printf("exitsig:%d\n", SIGINT);
374   err("SG: Interrupted");
375 }
376
377 #define PROC_BUF_SIZE 4096
378 static void
379 read_proc_file(char *buf, char *name, int *fdp)
380 {
381   int c;
382
383   if (!*fdp)
384     {
385       sprintf(buf, "/proc/%d/%s", (int) box_pid, name);
386       *fdp = open(buf, O_RDONLY);
387       if (*fdp < 0)
388         die("open(%s): %m", buf);
389     }
390   lseek(*fdp, 0, SEEK_SET);
391   if ((c = read(*fdp, buf, PROC_BUF_SIZE-1)) < 0)
392     die("read on /proc/$pid/%s: %m", name);
393   if (c >= PROC_BUF_SIZE-1)
394     die("/proc/$pid/%s too long", name);
395   buf[c] = 0;
396 }
397
398 static void
399 check_timeout(void)
400 {
401   if (wall_timeout)
402     {
403       struct timeval now, wall;
404       int wall_ms;
405       gettimeofday(&now, NULL);
406       timersub(&now, &start_time, &wall);
407       wall_ms = wall.tv_sec*1000 + wall.tv_usec/1000;
408       if (wall_ms > wall_timeout)
409         err("TO: Time limit exceeded (wall clock)");
410       if (verbose > 1)
411         fprintf(stderr, "[wall time check: %d msec]\n", wall_ms);
412     }
413   if (timeout)
414     {
415       char buf[PROC_BUF_SIZE], *x;
416       int utime, stime, ms;
417       static int proc_stat_fd;
418       read_proc_file(buf, "stat", &proc_stat_fd);
419       x = buf;
420       while (*x && *x != ' ')
421         x++;
422       while (*x == ' ')
423         x++;
424       if (*x++ != '(')
425         die("proc stat syntax error 1");
426       while (*x && (*x != ')' || x[1] != ' '))
427         x++;
428       while (*x == ')' || *x == ' ')
429         x++;
430       if (sscanf(x, "%*c %*d %*d %*d %*d %*d %*d %*d %*d %*d %*d %d %d", &utime, &stime) != 2)
431         die("proc stat syntax error 2");
432       ms = (utime + stime) * 1000 / ticks_per_sec;
433       if (verbose > 1)
434         fprintf(stderr, "[time check: %d msec]\n", ms);
435       if (ms > timeout && ms > extra_timeout)
436         err("TO: Time limit exceeded");
437     }
438 }
439
440 static void
441 box_keeper(void)
442 {
443   struct sigaction sa;
444
445   bzero(&sa, sizeof(sa));
446   sa.sa_handler = signal_int;
447   sigaction(SIGINT, &sa, NULL);
448
449   gettimeofday(&start_time, NULL);
450   ticks_per_sec = sysconf(_SC_CLK_TCK);
451   if (ticks_per_sec <= 0)
452     die("Invalid ticks_per_sec!");
453
454   if (timeout || wall_timeout)
455     {
456       sa.sa_handler = signal_alarm;
457       sigaction(SIGALRM, &sa, NULL);
458       alarm(1);
459     }
460
461   for(;;)
462     {
463       struct rusage rus;
464       int stat;
465       pid_t p;
466       if (timer_tick)
467         {
468           check_timeout();
469           timer_tick = 0;
470         }
471       p = wait4(box_pid, &stat, 0, &rus);
472       if (p < 0)
473         {
474           if (errno == EINTR)
475             continue;
476           die("wait4: %m");
477         }
478       if (p != box_pid)
479         die("wait4: unknown pid %d exited!", p);
480       if (WIFEXITED(stat))
481         {
482           box_pid = 0;
483           final_stats(&rus);
484           if (WEXITSTATUS(stat))
485             {
486               // FIXME: Recognize internal errors during setup
487               meta_printf("exitcode:%d\n", WEXITSTATUS(stat));
488               err("RE: Exited with error status %d", WEXITSTATUS(stat));
489             }
490           if (timeout && total_ms > timeout)
491             err("TO: Time limit exceeded");
492           if (wall_timeout && wall_ms > wall_timeout)
493             err("TO: Time limit exceeded (wall clock)");
494           flush_line();
495           fprintf(stderr, "OK (%d.%03d sec real, %d.%03d sec wall)\n",
496               total_ms/1000, total_ms%1000,
497               wall_ms/1000, wall_ms%1000);
498           box_exit(0);
499         }
500       if (WIFSIGNALED(stat))
501         {
502           box_pid = 0;
503           meta_printf("exitsig:%d\n", WTERMSIG(stat));
504           final_stats(&rus);
505           err("SG: Caught fatal signal %d", WTERMSIG(stat));
506         }
507       if (WIFSTOPPED(stat))
508         {
509           box_pid = 0;
510           meta_printf("exitsig:%d\n", WSTOPSIG(stat));
511           final_stats(&rus);
512           err("SG: Stopped by signal %d", WSTOPSIG(stat));
513         }
514       else
515         die("wait4: unknown status %x, giving up!", stat);
516     }
517 }
518
519 /*** The process running inside the box ***/
520
521 static void
522 setup_root(void)
523 {
524   umask(0027);
525
526   if (mkdir("root", 0777) < 0 && errno != EEXIST)
527     die("mkdir('root'): %m");
528
529   if (mount("none", "root", "tmpfs", 0, "mode=755") < 0)
530     die("Cannot mount root ramdisk: %m");
531
532   // FIXME: Make the list of bind-mounts configurable
533   // FIXME: Virtual /dev?
534   // FIXME: Read-only mounts?
535
536   static const char * const dirs[] = { "box", "/bin", "/lib", "/usr", "/dev" };
537   for (int i=0; i < ARRAY_SIZE(dirs); i++)
538     {
539       const char *d = dirs[i];
540       char buf[1024];   // FIXME
541       sprintf(buf, "root/%s", (d[0] == '/' ? d+1 : d));
542       msg("Binding %s on %s\n", d, buf);
543       if (mkdir(buf, 0777) < 0)
544         die("mkdir(%s): %m", buf);
545       if (mount(d, buf, "none", MS_BIND | MS_NOSUID | MS_NODEV, "") < 0)
546         die("Cannot bind %s on %s: %m", d, buf);
547     }
548
549   if (mkdir("root/proc", 0777) < 0)
550     die("Cannot create proc: %m");
551   if (mount("none", "root/proc", "proc", 0, "") < 0)
552     die("Cannot mount proc: %m");
553
554   if (chroot("root") < 0)
555     die("Chroot failed: %m");
556
557   if (chdir("root/box") < 0)
558     die("Cannot change current directory: %m");
559 }
560
561 static int
562 box_inside(void *arg)
563 {
564   char **argv = arg;
565   int argc = 0;
566   while (argv[argc])
567     argc++;
568
569   struct rlimit rl;
570   char *args[argc+1];
571
572   memcpy(args, argv, argc * sizeof(char *));
573   args[argc] = NULL;
574
575   setup_root();
576
577   if (setresgid(BOX_GID, BOX_GID, BOX_GID) < 0)
578     die("setresgid: %m");
579   if (setgroups(0, NULL) < 0)
580     die("setgroups: %m");
581   if (setresuid(BOX_UID, BOX_UID, BOX_UID) < 0)
582     die("setresuid: %m");
583
584   if (redir_stdin)
585     {
586       close(0);
587       if (open(redir_stdin, O_RDONLY) != 0)
588         die("open(\"%s\"): %m", redir_stdin);
589     }
590   if (redir_stdout)
591     {
592       close(1);
593       if (open(redir_stdout, O_WRONLY | O_CREAT | O_TRUNC, 0666) != 1)
594         die("open(\"%s\"): %m", redir_stdout);
595     }
596   if (redir_stderr)
597     {
598       close(2);
599       if (open(redir_stderr, O_WRONLY | O_CREAT | O_TRUNC, 0666) != 2)
600         die("open(\"%s\"): %m", redir_stderr);
601     }
602   else
603     dup2(1, 2);
604   setpgrp();
605
606   if (memory_limit)
607     {
608       rl.rlim_cur = rl.rlim_max = memory_limit * 1024;
609       if (setrlimit(RLIMIT_AS, &rl) < 0)
610         die("setrlimit(RLIMIT_AS): %m");
611     }
612
613   rl.rlim_cur = rl.rlim_max = (stack_limit ? (rlim_t)stack_limit * 1024 : RLIM_INFINITY);
614   if (setrlimit(RLIMIT_STACK, &rl) < 0)
615     die("setrlimit(RLIMIT_STACK): %m");
616
617   rl.rlim_cur = rl.rlim_max = 64;
618   if (setrlimit(RLIMIT_NOFILE, &rl) < 0)
619     die("setrlimit(RLIMIT_NOFILE): %m");
620
621   // FIXME: Create multi-process mode
622   rl.rlim_cur = rl.rlim_max = 1;
623   if (setrlimit(RLIMIT_NPROC, &rl) < 0)
624     die("setrlimit(RLIMIT_NPROC): %m");
625
626   rl.rlim_cur = rl.rlim_max = 0;
627   if (setrlimit(RLIMIT_MEMLOCK, &rl) < 0)
628     die("setrlimit(RLIMIT_MEMLOCK): %m");
629
630   char **env = setup_environment();
631   execve(args[0], args, env);
632   die("execve(\"%s\"): %m", args[0]);
633 }
634
635 static void
636 prepare(void)
637 {
638   msg("Preparing sandbox directory\n");
639   xsystem("rm -rf box");
640   if (mkdir("box", 0700) < 0)
641     die("Cannot create box: %m");
642   if (chown("box", orig_uid, orig_gid) < 0)
643     die("Cannot chown box: %m");
644 }
645
646 static void
647 cleanup(void)
648 {
649   msg("Deleting sandbox directory\n");
650   xsystem("rm -rf box");
651 }
652
653 static void
654 run(char **argv)
655 {
656   struct stat st;
657   if (stat("box", &st) < 0 || !S_ISDIR(st.st_mode))
658     die("Box directory not found, did you run `isolate --prepare'?");
659
660   char cmd[256];
661   snprintf(cmd, sizeof(cmd), "chown -R %d.%d box", BOX_UID, BOX_GID);
662   xsystem(cmd);
663   snprintf(cleanup_cmd, sizeof(cleanup_cmd), "chown -R %d.%d box", orig_uid, orig_gid);
664
665   box_pid = clone(
666     box_inside,                 // Function to execute as the body of the new process
667     argv,                       // Pass our stack
668     SIGCHLD | CLONE_NEWIPC | CLONE_NEWNET | CLONE_NEWNS | CLONE_NEWPID,
669     argv);                      // Pass the arguments
670   if (box_pid < 0)
671     die("clone: %m");
672   if (!box_pid)
673     die("clone returned 0");
674   box_keeper();
675 }
676
677 static void
678 show_version(void)
679 {
680   // FIXME
681   printf("Process isolator 0.0\n");
682   printf("(c) 2012 Martin Mares <mj@ucw.cz>\n\n");
683   printf("Sandbox directory: %s\n", BOX_DIR);
684   printf("Sandbox credentials: uid=%u gid=%u\n", BOX_UID, BOX_GID);
685 }
686
687 static void
688 usage(void)
689 {
690   fprintf(stderr, "Invalid arguments!\n");
691   printf("\
692 Usage: isolate [<options>] <command>\n\
693 \n\
694 Options:\n\
695 -e, --full-env\t\tInherit full environment of the parent process\n\
696 -E, --env=<var>\tInherit the environment variable <var> from the parent process\n\
697 -E, --env=<var>=<val>\tSet the environment variable <var> to <val>; unset it if <var> is empty\n\
698 -i, --stdin=<file>\tRedirect stdin from <file>\n\
699 -k, --stack=<size>\tLimit stack size to <size> KB (default: 0=unlimited)\n\
700 -m, --mem=<size>\tLimit address space to <size> KB\n\
701 -M, --meta=<file>\tOutput process information to <file> (name:value)\n\
702 -o, --stdout=<file>\tRedirect stdout to <file>\n\
703 -r, --stderr=<file>\tRedirect stderr to <file>\n\
704 -t, --time=<time>\tSet run time limit (seconds, fractions allowed)\n\
705 -v, --verbose\t\tBe verbose (use multiple times for even more verbosity)\n\
706 -w, --wall-time=<time>\tSet wall clock time limit (seconds, fractions allowed)\n\
707 -x, --extra-time=<time>\tSet extra timeout, before which a timing-out program is not yet killed,\n\
708 \t\t\tso that its real execution time is reported (seconds, fractions allowed)\n\
709 \n\
710 Commands:\n\
711     --prepare\t\tInitialize sandbox\n\
712     --run -- <cmd> ...\tRun given command within sandbox\n\
713     --cleanup\t\tClean up sandbox\n\
714     --version\t\tDisplay program version and configuration\n\
715 ");
716   exit(2);
717 }
718
719 enum opt_code {
720   OPT_PREPARE = 256,
721   OPT_RUN,
722   OPT_CLEANUP,
723   OPT_VERSION,
724 };
725
726 static const char short_opts[] = "eE:i:k:m:M:o:r:t:vw:x:";
727
728 static const struct option long_opts[] = {
729   { "full-env",         0, NULL, 'e' },
730   { "env",              1, NULL, 'E' },
731   { "stdin",            1, NULL, 'i' },
732   { "stack",            1, NULL, 'k' },
733   { "mem",              1, NULL, 'm' },
734   { "meta",             1, NULL, 'M' },
735   { "stdout",           1, NULL, 'o' },
736   { "stderr",           1, NULL, 'r' },
737   { "time",             1, NULL, 't' },
738   { "verbose",          0, NULL, 'v' },
739   { "wall-time",        1, NULL, 'w' },
740   { "extra-time",       1, NULL, 'x' },
741   { "prepare",          0, NULL, OPT_PREPARE },
742   { "run",              0, NULL, OPT_RUN },
743   { "cleanup",          0, NULL, OPT_CLEANUP },
744   { "version",          0, NULL, OPT_VERSION },
745   { NULL,               0, NULL, 0 }
746 };
747
748 int
749 main(int argc, char **argv)
750 {
751   int c;
752   enum opt_code mode = 0;
753
754   while ((c = getopt_long(argc, argv, short_opts, long_opts, NULL)) >= 0)
755     switch (c)
756       {
757       case 'e':
758         pass_environ = 1;
759         break;
760       case 'E':
761         if (!set_env_action(optarg))
762           usage();
763         break;
764       case 'k':
765         stack_limit = atol(optarg);
766         break;
767       case 'i':
768         redir_stdin = optarg;
769         break;
770       case 'm':
771         memory_limit = atol(optarg);
772         break;
773       case 'M':
774         meta_open(optarg);
775         break;
776       case 'o':
777         redir_stdout = optarg;
778         break;
779       case 'r':
780         redir_stderr = optarg;
781         break;
782       case 't':
783         timeout = 1000*atof(optarg);
784         break;
785       case 'v':
786         verbose++;
787         break;
788       case 'w':
789         wall_timeout = 1000*atof(optarg);
790         break;
791       case 'x':
792         extra_timeout = 1000*atof(optarg);
793         break;
794       case OPT_PREPARE:
795       case OPT_RUN:
796       case OPT_CLEANUP:
797       case OPT_VERSION:
798         mode = c;
799         break;
800       default:
801         usage();
802       }
803
804   if (!mode)
805     usage();
806   if (mode == OPT_VERSION)
807     {
808       show_version();
809       return 0;
810     }
811
812   if (geteuid())
813     die("Must be started as root");
814   orig_uid = getuid();
815   orig_gid = getgid();
816
817   if (chdir(BOX_DIR) < 0)
818     die("chdir(%s): %m", BOX_DIR);
819
820   switch (mode)
821     {
822     case OPT_PREPARE:
823       if (optind < argc)
824         usage();
825       prepare();
826       break;
827     case OPT_RUN:
828       if (optind >= argc)
829         usage();
830       run(argv+optind);
831       break;
832     case OPT_CLEANUP:
833       if (optind < argc)
834         usage();
835       cleanup();
836       break;
837     default:
838       die("Internal error: mode mismatch");
839     }
840   exit(0);
841 }