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