Index: common/task.php
===================================================================
--- common/task.php	(revision 1191)
+++ common/task.php	(working copy)
@@ -6,8 +6,9 @@ require_once(IA_ROOT_DIR . "common/textblock.php");
 // Get valid task types.
 function task_get_types() {
     return array(
-            'classic' => 'Clasic',
-            'output-only' => 'Doar de output',
+        'classic' => 'Clasică',
+        'interactive' => 'Interactivă',
+        // 'output-only' => 'Doar de output',
     );
 }
 
@@ -17,9 +18,10 @@ function task_get_types() {
  */
 function task_get_security_types() {
     return array(
-            'private' => 'Private',
-            'protected' => 'Protected',
-            'public' => 'Public');
+        'private' => 'Private',
+        'protected' => 'Protected',
+        'public' => 'Public',
+    );
 }
 
 // Get parameter infos.
@@ -27,20 +29,40 @@ function task_get_parameter_infos() {
     return array(
         'classic' => array(
             'timelimit' => array(
-                    'description' => "Limita de timp (in secunde)",
-                    'default' => 1,
-                    'type' => 'float',
-                    'name' => 'Limita de timp',
+                'description' => 'Limita de timp (in secunde)',
+                'default' => 1,
+                'type' => 'float',
+                'name' => 'Limita de timp',
             ),
             'memlimit' => array(
-                    'description' => "Limita de memorie (in kilobytes)",
-                    'default' => 16384,
-                    'type' => 'integer',
-                    'name' => 'Limita de memorie',
+                'description' => 'Limita de memorie (in kilobytes)',
+                'default' => 16384,
+                'type' => 'integer',
+                'name' => 'Limita de memorie',
             ),
         ),
-        'output-only' => array(
+        'interactive' => array(
+            'timelimit' => array(
+                'description' => 'Limita de timp (in secunde)',
+                'default' => 1,
+                'type' => 'float',
+                'name' => 'Limita de timp',
+            ),
+            'memlimit' => array(
+                'description' => 'Limita de memorie (in kilobytes)',
+                'default' => 16384,
+                'type' => 'integer',
+                'name' => 'Limita de memorie',
+            ),
+            'interact' => array(
+                'description' => 'Sursa programului interactiv.',
+                'default' => 'interact.c',
+                'type' => 'string',
+                'name' => 'Programul interactiv',
+            ),
         ),
+        // 'output-only' => array(
+        // ),
     );
 }
 
@@ -58,7 +80,7 @@ function task_init($task_id, $task_type, $user = null) {
             'test_count' => 10,
             'test_groups' => NULL,
             'public_tests' => NULL,
-            'evaluator' => NULL,
+            'evaluator' => '',
             'use_ok_files' => 1,
             'rating' => NULL,
     );
@@ -76,13 +98,12 @@ function task_init($task_id, $task_type, $user = null) {
 
 // Validates a task.
 // NOTE: this might be incomplete, so don't rely on it exclusively.
-// Use this to check for a valid model. It's also usefull in controllers.
+// Use this to check for a valid model. It's also useful in controllers.
 function task_validate($task) {
-    $errors = array();
-
-    // FIXME How to handle this?
     log_assert(is_array($task), "You didn't even pass an array");
 
+    $errors = array();
+
     if (strlen(getattr($task, 'title', '')) < 1) {
         $errors['title'] = 'Titlu prea scurt.';
     }
@@ -130,39 +151,43 @@ function task_validate($task) {
         $errors['use_ok_files'] = "0/1 only";
     }
 
-    if ($task['evaluator'] == "") {
+    if ($task['evaluator'] === '') {
         if (!$task['use_ok_files']) {
-            $errors['evaluator'] = "Pentru evaluare cu diff e nevoie e fisiere .ok";
+            $errors['evaluator'] =
+                'Pentru evaluare cu diff e nevoie de fisiere .ok';
         }
     } else {
         if (!is_attachment_name($task['evaluator'])) {
-            $errors['evaluator'] = "Nume de fisier invalid.";
+            $errors['evaluator'] = 'Nume de fisier invalid.';
         }
     }
 
     if (strlen(getattr($task, 'test_groups', '')) > 256) {
         $errors['test_groups'] = 'Expresia este prea lunga.';
     } else if (task_get_testgroups($task) === false) {
-        $errors['test_groups'] = "Eroare de sintaxa in expresie.";
+        $errors['test_groups'] = 'Eroare de sintaxa in expresie.';
     }
 
-    if (task_parse_test_group($task["public_tests"], $task["test_count"]) === false) {
-        $errors['public_tests'] = "Eroare de sintaxa in expresie.";
+    if (task_parse_test_group($task["public_tests"],
+                              $task["test_count"]) === false) {
+        $errors['public_tests'] = 'Eroare de sintaxa in expresie.';
     }
 
     return $errors;
 }
 
-// Parse test grouping expression from task and returns groups as an array
-// If there is no grouping parameter defined it returns a group for each test by default
-// If the expression string contains errors the function returns false
+// Parse test grouping expression from task and returns groups as an array.
+// If there is no grouping parameter defined it returns a group for each test
+// by default.
+// If the expression string contains errors the function returns false.
 // Expression syntax:
 // item: number | number-number
 // group: item | item,group
 // groups: group | group;groups
 function task_parse_test_group($string, $test_count) {
-    if (strlen($string) == 0)
+    if (strlen($string) == 0) {
         return array();
+    }
 
     $current_group = array();
     $items = explode(',', $string);
@@ -191,10 +216,12 @@ function task_parse_test_group($string, $test_count) {
         } else {
             $left = (int) $tests[0];
             $right = (int) $tests[1];
-            if ($left < 1 || $right < 1 || $left > $test_count || $right > $test_count) {
+            if ($left < 1 || $right < 1 ||
+                $left > $test_count || $right > $test_count) {
                 return false;
             }
-            for ($test = min($left, $right); $test <= max($left, $right); $test++) {
+            for ($test = min($left, $right); $test <= max($left, $right);
+                 $test++) {
                 $current_group[] = $test;
                 $used_count[$test]++;
             }
@@ -253,9 +280,9 @@ function task_get_testgroups($task) {
 // Validate parameters. Return errors as $form_errors
 function task_validate_parameters($task_type, $parameters) {
     $errors = array();
-    if ($task_type == 'classic') {
+    if ($task_type === 'classic' || $task_type === 'interactive') {
         if (!is_numeric($parameters['timelimit'])) {
-                $errors['timelimit'] = "Limita de timp trebuie sa fie un numar.";
+            $errors['timelimit'] = "Limita de timp trebuie sa fie un numar.";
         } else if ($parameters['timelimit'] < 0.01) {
             $errors['timelimit'] = "Minim 10 milisecunde.";
         } else if ($parameters['timelimit'] > 60) {
@@ -269,10 +296,15 @@ function task_validate_parameters($task_type, $parameters) {
         } else if ($parameters['memlimit'] > 131072) {
             $errors['memlimit'] = "Maxim 128 megabytes.";
         }
-    } else if ($task_type == 'output-only') {
-        // Nothing to validate
-    } else {
-        log_error("Bad task_type");
+    }
+    if ($task_type === 'interactive') {
+        if ($parameters['interact'] === '') {
+            $errors['interact'] = 'Trebuie specificat un program interactiv.';
+        } else {
+            if (!is_attachment_name($parameters['interact'])) {
+                $errors['interact'] = 'Nume de fisier invalid.';
+            }
+        }
     }
     return $errors;
 }
@@ -288,7 +320,7 @@ function task_get_topic($task_id) {
     return $task_page["forum_topic"];
 }
 
-// Receives a list of method and algorithm tag ids and adds links them to task_id
+// Receives a list of method and algorithm tag ids and links them to task_id
 function task_update_tags($task_id, $method_tags_id, $algorithm_tags_id) {
     log_assert(is_array($method_tags_id), 'method_tags must be an array');
     log_assert(is_array($algorithm_tags_id), 'algorithm_tags must be an array');
Index: eval/InteractiveGrader.php
new file mode 100644
===================================================================
--- eval/InteractiveGrader.php	(revision 0)
+++ eval/InteractiveGrader.php	(working copy)
@@ -0,0 +1,115 @@
+<?php
+
+require_once(IA_ROOT_DIR . 'eval/BaseGrader.php');
+
+class InteractiveGrader extends BaseGrader {
+    protected function testCaseJudge($testno, $jaildir) {
+        $this->testResults[$testno] = array();
+        $test_result = &$this->testResults[$testno];
+
+        $in_file = $this->getInFile($jaildir);
+        $interact_file = 'interact';
+        $interact_in_pipe = 'interact.in';
+        $interact_out_pipe = 'interact.out';
+
+        $user_jaildir = $jaildir . 'user/';
+        $user_file = 'user_' . $this->job['id'] . '_' . $testno;
+        $user_in_pipe = 'user.in';
+        $user_out_pipe = 'user.out';
+
+        // Download input file.
+        if (!copy_grader_file($this->task, 'test' . $testno . '.in',
+                              $in_file)) {
+            log_print("Test $testno: input not found");
+            throw new EvalTaskOwnerError(
+                "Lipşeşte intrarea testului {$testno}.\nPagina cu " .
+                "enunţul problemei trebuie să conţină un ataşament " .
+                "'grader_test{$testno}.in'");
+        }
+
+        // Copy user executable
+        eval_assert(clean_dir($user_jaildir), 'Failed to create user jail');
+        eval_assert(@copy(IA_ROOT_DIR.'eval/temp/user',
+                          $user_jaildir . $user_file),
+                    'Failed to copy user program');
+        eval_assert(@chmod($user_jaildir . $user_file, 0555),
+                    'Failed to chmod a+x user program');
+
+        // Copy interact executable
+        eval_assert(@copy(IA_ROOT_DIR.'eval/temp/interact',
+                          $jaildir . $interact_file),
+                    'Failed to copy interact program');
+        eval_assert(@chmod($jaildir . $interact_file, 0555),
+                    'Failed to chmod a+x user program');
+
+        // Create named pipes
+        eval_assert(@posix_mkfifo($user_jaildir . $user_in_pipe, 0600) &&
+                    @posix_mkfifo($user_jaildir . $user_out_pipe, 0600),
+                    'Unable to create named pipes');
+        eval_assert(@symlink($user_jaildir . $user_out_pipe,
+                             $jaildir . $interact_in_pipe) &&
+                    @symlink($user_jaildir . $user_in_pipe,
+                             $jaildir . $interact_out_pipe),
+                    'Unable to create user jail dir');
+
+        // Run user program on a test case.
+        $time_limit = ((int)$this->task['timelimit']) * 1000;
+        $mem_limit = (int)$this->task['memlimit'];
+        $wall_time_limit =
+            $time_limit + IA_JUDGE_TASK_INTERACT_TIMELIMIT + 15000;
+        $interact_process = jail_run(
+            $interact_file, $jaildir,
+            IA_JUDGE_TASK_INTERACT_TIMELIMIT,
+            IA_JUDGE_TASK_INTERACT_MEMLIMIT, false,
+            array('in' => $interact_in_pipe,
+                  'out' => $interact_out_pipe,
+                  'out-before-in' => true), true,
+            '--no-ptrace --wall-time-limit=' . $wall_time_limit);
+        $user_process = jail_run(
+            $user_file, $user_jaildir,
+            $time_limit, $mem_limit, false,
+            array('in' => $user_in_pipe,
+                  'out' => $user_out_pipe), true,
+            '--no-ptrace --wall-time-limit=' . $wall_time_limit);
+        $interact_res = jrun_get_result($interact_process);
+        $user_res = jrun_get_result($user_process);
+
+        eval_assert($interact_res['result'] != 'ERROR',
+                    'Error in jrun for interactive program: ' .
+                    $interact_res['message']);
+        eval_assert($user_res['result'] != 'ERROR',
+                    'Error in jrun for user program: ' .
+                    $user_res['message']);
+
+        if ($interact_res['result'] !== 'OK') {
+            log_warn("Test $testno: Interactive program failed: " .
+                     $interact_res['message'] . ' ' . $interact_res['time'] .
+                     'ms ' . $interact_res['memory'] . 'kb');
+            throw new EvalTaskOwnerError(
+                'Eroare in programul interactiv: ' .
+                $interact_res['message']);
+        }
+        if ($user_res['result'] == 'FAIL') {
+            log_print("Test $testno: User program failed: " .
+                      $user_res['message'] . ' ' . $user_res['time'] .
+                      'ms ' . $user_res['memory'] . 'kb');
+            $message = $user_res['message'];
+            $test_result = array(
+                'test_time' => $user_res['time'],
+                'test_mem' => $user_res['memory'],
+                'grader_time' => null,
+                'grader_mem' => null,
+                'score' => 0,
+                'message' => $message,
+            );
+            // User program failed on this test. Bye bye.
+            return;
+        }
+
+        $test_result = array(
+            'test_time' => $user_res['time'],
+            'test_mem' => $user_res['memory'],
+        );
+        $this->testCaseJudgeOutputs($testno, $jaildir);
+    }
+}
Index: eval/config.php.sample
===================================================================
--- eval/config.php.sample	(revision 1191)
+++ eval/config.php.sample	(working copy)
@@ -26,7 +26,13 @@ define("IA_JUDGE_JRUN_NICE", 0);
 define("IA_JUDGE_TASK_EVAL_TIMELIMIT", 5000);
 
 // Memory limit for graders.
-define("IA_JUDGE_TASK_EVAL_MEMLIMIT", 64000);
+define("IA_JUDGE_TASK_EVAL_MEMLIMIT", 65536);
+
+// Time limit for interactive programs.
+define('IA_JUDGE_TASK_INTERACT_TIMELIMIT', 10000);
+
+// Memory limit for interactive programs.
+define('IA_JUDGE_TASK_INTERACT_MEMLIMIT', 131072);
 
 // Maximum score per task.
 define("IA_JUDGE_MAX_SCORE", 100);
Index: eval/eval
===================================================================
--- eval/eval	(revision 1191)
+++ eval/eval	(working copy)
@@ -18,6 +18,7 @@ require_once(IA_ROOT_DIR.'eval/utilities.php');
 require_once(IA_ROOT_DIR.'eval/download.php');
 require_once(IA_ROOT_DIR.'eval/Exceptions.php');
 require_once(IA_ROOT_DIR.'eval/ClassicGrader.php');
+require_once(IA_ROOT_DIR.'eval/InteractiveGrader.php');
 
 error_reporting(0xFFFF);
 
@@ -51,6 +52,8 @@ function job_handle($job) {
     log_print("Job #{$job['id']} task {$task['id']} round {$job['round_id']} type {$task['type']}");
     if ($task['type'] == 'classic') {
         $grader = new ClassicGrader($task, $task_parameters, $job);
+    } else if ($task['type'] == 'interactive') {
+        $grader = new InteractiveGrader($task, $task_parameters, $job);
     } else {
         log_error("Nu stiu sa evaluez task-uri de tip ".$task['type']);
     }
Index: eval/utilities.php
===================================================================
--- eval/utilities.php	(revision 1191)
+++ eval/utilities.php	(working copy)
@@ -134,15 +134,19 @@ function jrun_make_error($message) {
 // All timings are in miliseconds and memory is in kilobytes
 //
 // If result is ERROR time, memory, stdin and stdout are never set.
-function jail_run($program, $jaildir, $time, $memory, $capture_std = false) {
+function jail_run($program, $jaildir, $time, $memory, $capture_std = false,
+                  $redirect_std = array(), $async = false,
+                  $extra_args = '') {
     eval_assert(is_whole_number($time));
     eval_assert(is_whole_number($memory));
+    eval_assert(is_array($redirect_std));
+    eval_assert(!$capture_std || !$redirect_std,
+                'Can not have $capture_std and $redirect_std at the same time');
 
     $cmdline = IA_ROOT_DIR . 'jrun/jrun';
     $cmdline .= " --prog=./" . $program;
     $cmdline .= " --dir=" . $jaildir;
     $cmdline .= " --chroot";
-    //$cmdline .= " --verbose";
     $cmdline .= " --block-syscalls-file=" . IA_ROOT_DIR . 'jrun/bad_syscalls';
     if (defined('IA_JUDGE_JRUN_NICE') && IA_JUDGE_JRUN_NICE != 0) {
         $cmdline .= " --nice=" . IA_JUDGE_JRUN_NICE;
@@ -157,23 +161,71 @@ function jail_run($program, $jaildir, $time, $memory, $capture_std = false) {
         $cmdline .= " --redirect-stdout=jailed_stdout";
         $cmdline .= " --redirect-stderr=jailed_stderr";
     }
+    if ($redirect_std) {
+        foreach (array('in', 'out', 'err') as $file) {
+            if (array_key_exists($file, $redirect_std)) {
+                $cmdline .= ' --redirect-std' . $file . '=' .
+                    escapeshellarg($redirect_std[$file]);
+            }
+        }
+        if (getattr($redirect_std, 'out-before-in')) {
+            $cmdline .= ' --redirect-out-before-in';
+        }
+    }
     if (isset($time)) {
         $cmdline .= " --time-limit=" . $time;
     }
     if (isset($memory)) {
         $cmdline .= " --memory-limit=" . $memory;
     }
-    //$cmdline .= " --verbose";
+    $cmdline .= ' ' . $extra_args;
+
+    // Warning: verbose setting is subject to deadlock if multiple processes
+    // are opened using $async = true. Once their pipes fill up, execution is
+    // blocked until their contents is read by jrun_get_result, but that is
+    // only performed one process at a time.
+    // $cmdline .= " --verbose";
 
     log_print("Running $cmdline");
-    $message = shell_exec($cmdline);
+    $pipes = array();
+    $process = proc_open(
+        $cmdline,
+        array(
+            0 => array('pipe', 'r'),
+            1 => array('pipe', 'w'),
+            2 => array('pipe', 'w'),
+        ),
+        $pipes);
+
+    $jrun_process = array(
+        'process' => $process,
+        'pipes' => $pipes,
+        'jaildir' => $jaildir,
+        'time' => $time,
+        'memory' => $memory,
+        'capture_std' => $capture_std,
+    );
 
-    $result = jrun_parse_message($message);
+    if ($async) {
+        return $jrun_process;
+    }
+
+    return jrun_get_result($jrun_process);
+}
+
+function jrun_get_result($jrun_process) {
+    $process = $jrun_process['process'];
+    $stdout = stream_get_contents($jrun_process['pipes'][1]);
+    $stderr = stream_get_contents($jrun_process['pipes'][2]);
+    proc_close($process);
+
+    $result = jrun_parse_message($stdout . $stderr);
     if ($result == false) {
         return jrun_make_error('Failed executing jail');
     }
 
-    if ($capture_std) {
+    if ($jrun_process['capture_std']) {
+        $jaildir = $jrun_process['jaildir'];
         $result['stdout'] = @file_get_contents($jaildir.'jailed_stdout');
         if ($result['stdout'] === false) {
             return jrun_make_error('Failed reading captured stdout');
Index: jrun/jrun.h
===================================================================
--- jrun/jrun.h	(revision 1191)
+++ jrun/jrun.h	(working copy)
@@ -44,13 +44,13 @@ typedef struct {
     int memory_limit;
 
     int nice_val;
-    int ptrace;
-    int copy_libs;
+    int ptrace:1;
+    int copy_libs:1;
 
-    int verbose;
-    int chroot;
+    int verbose:1;
+    int chroot:1;
 
-    int min_proc_update_interval;
+    int redirect_out_before_in:1;
 
     // File to redirect program stdout to.
     // Empty is /dev/null
@@ -58,6 +58,8 @@ typedef struct {
     char stdout_file[500];
     char stderr_file[500];
 
+    int min_proc_update_interval;
+
     // 0 or 1 if a syscall is blocked.
     // Blocking means killing the process.
     int syscall_block[MAX_SYSCALL];
Index: jrun/jrun.c
===================================================================
--- jrun/jrun.c	(revision 1191)
+++ jrun/jrun.c	(working copy)
@@ -139,8 +139,16 @@ void child_main(void)
     //
 
     // Redirect standard file descriptors.
-    redirect_fd(0, jopt.stdin_file, "rb");
-    redirect_fd(1, jopt.stdout_file, "wb");
+    // redirect_out_before_in helps avoid a deadlock which occurs for
+    // interactive tasks otherwise. Both programs attempt to open their stdin
+    // pipe, but are blocked waiting for the other to open the other end.
+    if (jopt.redirect_out_before_in) {
+        redirect_fd(1, jopt.stdout_file, "wb");
+        redirect_fd(0, jopt.stdin_file, "rb");
+    } else {
+        redirect_fd(0, jopt.stdin_file, "rb");
+        redirect_fd(1, jopt.stdout_file, "wb");
+    }
     redirect_fd(2, jopt.stderr_file, "wb");
 
     // chroot to jail dir
Index: jrun/options.c
===================================================================
--- jrun/options.c	(revision 1191)
+++ jrun/options.c	(working copy)
@@ -13,39 +13,41 @@ jrun_options jopt;
 #define OPT_DIR                         2
 #define OPT_PROG                        3
 #define OPT_TIME_LIMIT                  4
-#define OPT_WALL_TIME_LIMIT             15
-#define OPT_MEMORY_LIMIT                5
-#define OPT_NICE                        6
-#define OPT_NO_PTRACE                   7
-#define OPT_COPY_LIBS                   8
-#define OPT_VERBOSE                     9
-#define OPT_BLOCK_SYSCALLS              10
-#define OPT_BLOCK_SYSCALLS_FILE         11
-#define OPT_REDIRECT_STDIN               17
-#define OPT_REDIRECT_STDOUT              12
-#define OPT_REDIRECT_STDERR              13
-#define OPT_CHROOT                      14
+#define OPT_WALL_TIME_LIMIT             5
+#define OPT_MEMORY_LIMIT                6
+#define OPT_NICE                        7
+#define OPT_NO_PTRACE                   8
+#define OPT_COPY_LIBS                   9
+#define OPT_VERBOSE                     10
+#define OPT_BLOCK_SYSCALLS              11
+#define OPT_BLOCK_SYSCALLS_FILE         12
+#define OPT_REDIRECT_STDIN              13
+#define OPT_REDIRECT_STDOUT             14
+#define OPT_REDIRECT_STDERR             15
+#define OPT_CHROOT                      16
+#define OPT_REDIRECT_OUT_BEFORE_IN      17
 
 void jrun_parse_options(int argc, char *argv[])
 {
     struct option long_options[] = {
-        {"uid",                 1, 0, OPT_UID},
-        {"gid",                 1, 0, OPT_GID},
-        {"dir",                 1, 0, OPT_DIR},
-        {"prog",                1, 0, OPT_PROG},
-        {"time-limit",          1, 0, OPT_TIME_LIMIT},
-        {"wall-time-limit",     1, 0, OPT_WALL_TIME_LIMIT},
-        {"memory-limit",        1, 0, OPT_MEMORY_LIMIT},
-        {"nice",                1, 0, OPT_NICE},
-        {"no-ptrace",           0, 0, OPT_NO_PTRACE},
-        {"copy-libs",           0, 0, OPT_COPY_LIBS},
-        {"verbose",             0, 0, OPT_VERBOSE},
-        {"block-syscalls",      1, 0, OPT_BLOCK_SYSCALLS},
-        {"block-syscalls-file", 1, 0, OPT_BLOCK_SYSCALLS_FILE},
-        {"redirect-stdin",      1, 0, OPT_REDIRECT_STDIN},
-        {"redirect-stdout",     1, 0, OPT_REDIRECT_STDOUT},
-        {"redirect-stderr",     1, 0, OPT_REDIRECT_STDERR},
-        {"chroot",              0, 0, OPT_CHROOT},
+        {"uid",                         1, 0, OPT_UID},
+        {"gid",                         1, 0, OPT_GID},
+        {"dir",                         1, 0, OPT_DIR},
+        {"prog",                        1, 0, OPT_PROG},
+        {"time-limit",                  1, 0, OPT_TIME_LIMIT},
+        {"wall-time-limit",             1, 0, OPT_WALL_TIME_LIMIT},
+        {"memory-limit",                1, 0, OPT_MEMORY_LIMIT},
+        {"nice",                        1, 0, OPT_NICE},
+        {"no-ptrace",                   0, 0, OPT_NO_PTRACE},
+        {"copy-libs",                   0, 0, OPT_COPY_LIBS},
+        {"verbose",                     0, 0, OPT_VERBOSE},
+        {"block-syscalls",              1, 0, OPT_BLOCK_SYSCALLS},
+        {"block-syscalls-file",         1, 0, OPT_BLOCK_SYSCALLS_FILE},
+        {"redirect-stdin",              1, 0, OPT_REDIRECT_STDIN},
+        {"redirect-stdout",             1, 0, OPT_REDIRECT_STDOUT},
+        {"redirect-stderr",             1, 0, OPT_REDIRECT_STDERR},
+        {"chroot",                      0, 0, OPT_CHROOT},
+        {"redirect-out-before-in",      0, 0, OPT_REDIRECT_OUT_BEFORE_IN},
         {0, 0, 0, 0},
     };
     int option_index = 0;
@@ -57,7 +59,7 @@ void jrun_parse_options(int argc, char *argv[])
 
     jopt.prog[0] = 0;
     getcwd(jopt.dir, sizeof(jopt.dir));
-   
+
     jopt.time_limit = 0;
     jopt.wall_time_limit = -1;
     jopt.memory_limit = 0;
@@ -70,6 +72,7 @@ void jrun_parse_options(int argc, char *argv[])
     jopt.verbose = 0;
     jopt.chroot = 0;
     jopt.stdout_file[0] = jopt.stderr_file[0] = jopt.stdin_file[0] = 0;
+    jopt.redirect_out_before_in = 0;
 
     while (1) {
         int opt = getopt_long(argc, argv, "u:g:d:p:n:t:w:m:v", long_options, &option_index);
@@ -150,7 +153,7 @@ void jrun_parse_options(int argc, char *argv[])
                 char* str = strdup(optarg);
                 char* s;
                 int id;
-               
+
                 s = strtok(str, ",");
                 while (s) {
                     id = syscall_getid(s);
@@ -188,6 +191,10 @@ void jrun_parse_options(int argc, char *argv[])
                 jopt.chroot = 1;
                 break;
             }
+            case OPT_REDIRECT_OUT_BEFORE_IN: {
+                jopt.redirect_out_before_in = 1;
+                break;
+            }
             default: {
                 printf("ERROR: Bad command line arguments");
                 exit(-1);
@@ -196,7 +203,7 @@ void jrun_parse_options(int argc, char *argv[])
     }
 
     // After options:
-    
+
     // You need to pass the program name.
     if (jopt.prog[0] == 0) {
         printf("ERROR: You must give the file to execute\n");
Index: scripts/migrate-interactive-task.php
new file mode 100755
===================================================================
--- scripts/migrate-interactive-task.php	(revision 0)
+++ scripts/migrate-interactive-task.php	(working copy)
@@ -0,0 +1,9 @@
+#!/usr/bin/env php
+<?php
+require_once(dirname($argv[0]) . "/utilities.php");
+db_connect();
+
+$query = "ALTER TABLE ia_task
+          MODIFY type enum('classic','interactive','output-only')";
+
+db_query($query);
