Index: common/job.php
===================================================================
--- common/job.php	(revision 991)
+++ common/job.php	(working copy)
@@ -9,7 +9,7 @@
 //      task_id: Task to submit for.
 //      round_id: Round to submit for. Optional, if missing the job is sent
 //              to all parent rounds.
-//      compiler_id: c, cpp or fpc.
+//      compiler_id: c, cpp, fpc, or py.
 //      solution: A string with the file to submit.
 //
 // Returns an array of errors, or array() on success.
@@ -37,12 +37,18 @@
     }
 
     // Validate compiler id
-    $valid_compilers = array('c', 'cpp', 'fpc');
+    $valid_compilers = array('c', 'cpp', 'fpc', 'py');
     if (!array_key_exists('compiler_id', $args)) {
         $errors['compiler_id'] = "Lipseste compilatorul.";
     } else if (array_search($args['compiler_id'], $valid_compilers) === false) {
         $errors['compiler_id'] = "Compilator invalid.";
     }
+    // HACK: For the moment, only admin`s are allowed to submit Python jobs.
+    // TODO: Remove this once Python support is stable.
+    if ('py' == $args['compiler_id'] && !user_is_admin($user)) {
+        $errors['compiler_id'] = 'Deocamdata, numai administratorii pot '
+                .'trimite surse Python.';
+    }
 
     // Validate solution
     if (!array_key_exists('solution', $args)) {
@@ -55,10 +61,10 @@
     }
 
     // Check task submit security
-    if (!security_query($user, 'task-submit', $task)) {
+    if ($task && !security_query($user, 'task-submit', $task)) {
         $errors[] = "Nu ai voie sa trimiti la acest task.";
     }
-    if ($round != null && !security_query($round, 'task-submit', $round)) {
+    if ($round && !security_query($round, 'task-submit', $round)) {
         $errors[] = "Nu poti sa trimiti la aceasta runda.";
     }
 
Index: common/user.php
===================================================================
--- common/user.php	(revision 991)
+++ common/user.php	(working copy)
@@ -106,4 +106,12 @@
     return $errors;
 }
 
+function user_is_admin($user) {
+    if (!$user) {
+        return false;
+    }
+    log_assert_valid(user_validate($user));
+    return $user['security_level'] === 'admin';
+}
+
 ?>
Index: eval/classic_grader.php
===================================================================
--- eval/classic_grader.php	(revision 990)
+++ eval/classic_grader.php	(working copy)
@@ -55,6 +55,16 @@
         $result['log'] = "Compilare:\n" . $compiler_messages . "\n";
     }
 
+    // HACK: Capture run-time stderr for Python jobs.
+    //
+    // WARNING! This is a security hole! Users may dump input tests
+    // on stderr and see them in the monitor page. Currently, only
+    // admins are allowed to submit Python.
+    //
+    // TODO: Come up with a safe way of reporting run-time errors
+    // for Python scripts.
+    $capture_std = ('py' == $job['compiler_id']);
+
     // Running tests.
     $test_score = array();
     for ($testno = 1; $testno <= $tparams['tests']; ++$testno) {
@@ -100,13 +110,23 @@
             log_assert("Failed to chmod a+x user program");
 
             // Run user program.
-            $jrunres = jail_run($userfile, $jaildir, $tparams['timelimit'] * 1000, $tparams['memlimit']);
+            $jrunres = jail_run($userfile, $jaildir, $tparams['timelimit'] * 1000,
+                        $tparams['memlimit'], $capture_std);
             log_assert($jrunres['result'] != 'ERROR', "Error in jrun.");
             if ($jrunres['result'] == 'FAIL') {
                 log_print("Test $testno: User program failed: {$jrunres['message']} ".
                         "{$jrunres['time']}ms {$jrunres['memory']}kb");
+                if ($capture_std) {
+                    // TODO: Come up with a safe way of reporting run-time
+                    // errors for Python scripts.
+                    $job_message = $jrunres['message'].": "
+                            .substr($jrunres['stderr'],
+                                    -min(100, strlen($jrunres['stderr'])));
+                } else {
+                    $job_message = $jrunres['message'];
+                }
                 job_test_update($job['id'], $testno, $group_idx, $jrunres['time'], $jrunres['memory'], 
-                        null, null, 0, $jrunres['message']);
+                        null, null, 0, $job_message);
                 // User program failed on this test. Bye bye.
                 continue;
             }
Index: eval/utilities.php
===================================================================
--- eval/utilities.php	(revision 990)
+++ eval/utilities.php	(working copy)
@@ -24,7 +24,7 @@
 // Compile a certain file.
 // Returns success value, and a friendly error message in $compiler_message.
 //
-// Can currently handle C, C++ and FreePascal
+// Can currently handle C, C++, FreePascal, and Python.
 function compile_file($input_file_name, $output_file_name, &$compiler_message)
 {
     $compiler_message = false;
@@ -34,8 +34,9 @@
             'cpp' => 'g++ -Wall -O2 -static %file_name% -o %exe_name% -lm',
             'pas' => 'fpc -O2 -Xs %file_name%',
             'fpc' => 'fpc -O2 -Xs %file_name%',
+            'py' => IA_JUDGE_PY_COMPILER.' %file_name% %exe_name%',
     );
-    if (!preg_match("/^(.*)\.(c|cpp|pas|fpc)$/i", $input_file_name, $matches)) {
+    if (!preg_match("/^(.*)\.(c|cpp|pas|fpc|py)$/i", $input_file_name, $matches)) {
         $compiler_message = "Nu am putut sa determin compilatorul ".
                 "pentru '$input_file_name'.";
         return false;
@@ -52,7 +53,7 @@
     $cmdline = preg_replace('/%exe_name%/', $exe_name, $cmdline);
 
     // Running compiler
-    $compiler_message = shell_exec("$cmdline 2>&1 | head -n 25");
+    $compiler_message = shell_exec("$cmdline 2>&1 | head -n 50");
 
     // This is the BEST way to fail on compilation errors.
     if (!is_executable($exe_name)) {
@@ -167,7 +168,7 @@
         return jrun_make_error('Failed executing jail');
     }
 
-    if ($result['result'] == 'OK' && $capture_std) {
+    if ($capture_std) {
         $result['stdout'] = @file_get_contents($jaildir.'jailed_stdout');
         if ($result['stdout'] === false) {
             return jrun_make_error('Failed reading captured stdout');
Index: jrun/bad_syscalls
===================================================================
--- jrun/bad_syscalls	(revision 990)
+++ jrun/bad_syscalls	(working copy)
@@ -33,3 +33,15 @@
 reboot
 mount
 umount
+accept
+bind
+connect
+getsockname
+getsockopt
+listen
+recv
+recvfrom
+send
+setsockopt
+socket
+socketpair
Index: jrun/run-tests.php
===================================================================
--- jrun/run-tests.php	(revision 990)
+++ jrun/run-tests.php	(working copy)
@@ -2,16 +2,22 @@
 <?php
 
 require_once(dirname($argv[0]) . "/../config.php");
+require_once(IA_ROOT_DIR . 'eval/config.php');
 require_once(IA_ROOT_DIR . 'common/log.php');
 
 // Config options.
 $jail_dir = "jail";
 $exe_name = "prog";
-$extra_args = "--block-syscalls-file=bad_syscalls --chroot --verbose";
+$extra_args = "--block-syscalls-file=bad_syscalls --verbose";
 //$extra_args = "--nice -5 --block-syscalls-file=bad_syscalls --verbose";
-//$extra_args = "--uid 65534 --gid 65534 --block-syscalls-file=bad_syscalls --verbose";
+//$extra_args = "--uid 65534 --gid 65534 --block-syscalls-file=bad_syscalls --chroot";
 //$extra_args = "--uid 65534 --gid 65534 --block-syscalls-file=bad_syscalls";
 
+// In order to run python tests, download a python distribution, configure,
+// make, make install, then specify the top level directory here.
+$py_compiler = IA_ROOT_DIR."scripts/pybin.sh ".IA_JUDGE_PY_DISTRO
+        ." {$jail_dir}";
+
 // Parse a source file
 // Returns the arguments to run the jail with and the expected response.
 // test_exp_res is a posix regex that should match the jail run response.
@@ -19,10 +25,10 @@
 {
     $test_args = $test_exp_res = null;
     foreach (file("$filename") as $line) {
-        if (preg_match("/\/\/ JRUN_ARGS =(.*)$/", $line, $matches)) {
+        if (preg_match("/(?:\/\/|#) JRUN_ARGS =(.*)$/", $line, $matches)) {
             $test_args = trim($matches[1]);
         }
-        if (preg_match("/\/\/ JRUN_RES =(.*)$/", $line, $matches)) {
+        if (preg_match("/(?:\/\/|#) JRUN_RES =(.*)$/", $line, $matches)) {
             $test_exp_res = trim($matches[1]);
         }
     }
@@ -33,10 +39,13 @@
 
 function compile_source($source, $exe)
 {
+    global $py_compiler;
     if (strpos($source, ".cpp") === strlen($source) - 4) {
-        system("g++ -Wall -lm -O2 $source -o $exe", $ret);
+        system("g++ -Wall -lm -O2 $source --static -o $exe", $ret);
     } else if (strpos($source, ".c") === strlen($source) - 2) {
-        system("gcc -Wall -lm -O2 $source -o $exe", $ret);
+        system("gcc -Wall -lm -O2 $source --static -o $exe", $ret);
+    } else if (strpos($source, ".py") === strlen($source) - 3) {
+        system("{$py_compiler} $source $exe", $ret);
     } else {
         log_error("Can't compile $source, unknown file extension\n");
     }
@@ -52,11 +61,10 @@
 {
     global $jail_dir, $exe_name;
 
-    system("rm -rf jail");
+    system("rm -rf $jail_dir");
     system("mkdir -p $jail_dir");
     system("chmod 777 $jail_dir");
     compile_source($filename, "$jail_dir/$exe_name");
-
     $cmd = "./jrun --dir=$jail_dir --prog=$exe_name $args";
 
     log_print("Running $cmd");
@@ -129,6 +137,6 @@
     } else {
         print("Invalid arguments\n");
     }
-} while (true);
+} while (false);
 
 ?>
Index: jrun/tests/do_math.py
===================================================================
--- jrun/tests/do_math.py	(revision 0)
+++ jrun/tests/do_math.py	(revision 0)
@@ -0,0 +1,13 @@
+# Automated test for jrun.
+# The following are parsed by test.php
+# JRUN_ARGS = --time-limit 1000 --memory-limit 16000
+# JRUN_RES = OK: time [0-9]+ms memory [0-9]+kb: Execution successful.
+
+import math
+from math import sin, cos, pi
+
+n = 36
+circle = [ (math.cos(i*2*pi/n), sin(i*2*pi/n)) for i in xrange(n + 1) ]
+assert (circle[0][0] - circle[-1][0] < 0.0001 and \
+    circle[0][1] - circle[-1][1] < 1.0000)
+
Index: jrun/tests/eval_fork.py
===================================================================
--- jrun/tests/eval_fork.py	(revision 0)
+++ jrun/tests/eval_fork.py	(revision 0)
@@ -0,0 +1,9 @@
+# Automated test for jrun.
+# The following are parsed by test.php
+# JRUN_ARGS = --time-limit 1000 --memory-limit 16000
+# JRUN_RES = FAIL: time [0-9]+ms memory [0-9]+kb: Blocked system call: [[:alnum:]]+.
+
+code = compile("from os import fork\nfork()", 'me', 'exec')
+eval(code)
+print "CLICK"
+
Index: jrun/tests/exception.py
===================================================================
--- jrun/tests/exception.py	(revision 0)
+++ jrun/tests/exception.py	(revision 0)
@@ -0,0 +1,6 @@
+# Automated test for jrun.
+# The following are parsed by test.php
+# JRUN_ARGS = --time-limit 1000 --memory-limit 5000
+# JRUN_RES = FAIL: time [0-9]+ms memory [0-9]{4,}kb: Non-zero exit status.
+
+raise ValueError("shaved monkey")
Index: jrun/tests/file.py
===================================================================
--- jrun/tests/file.py	(revision 0)
+++ jrun/tests/file.py	(revision 0)
@@ -0,0 +1,8 @@
+# Automated test for jrun.
+# The following are parsed by test.php
+# JRUN_ARGS = --time-limit 1000 --memory-limit 16000
+# JRUN_RES = OK: time [0-9]+ms memory [0-9]+kb: Execution successful.
+
+open("piggybank.dat", "wt").write("\n".join(map(str, range(13))))
+assert range(13) == map(int, open("piggybank.dat", "rt").readlines())
+
Index: jrun/tests/fork_bomb.py
===================================================================
--- jrun/tests/fork_bomb.py	(revision 0)
+++ jrun/tests/fork_bomb.py	(revision 0)
@@ -0,0 +1,10 @@
+# Automated test for jrun.
+# The following are parsed by test.php
+# JRUN_ARGS = --time-limit 1000 --memory-limit 16000
+# JRUN_RES = FAIL: time [0-9]+ms memory [0-9]+kb: Blocked system call: [[:alnum:]]+.
+
+import os
+
+while True:
+  os.fork()
+
Index: jrun/tests/mem_limit_huge.py
===================================================================
--- jrun/tests/mem_limit_huge.py	(revision 0)
+++ jrun/tests/mem_limit_huge.py	(revision 0)
@@ -0,0 +1,10 @@
+# Automated test for jrun.
+# The following are parsed by test.php
+# JRUN_ARGS = --time-limit 1000 --memory-limit 10000
+# JRUN_RES = FAIL: time [0-9]+ms memory [0-9]{5,}kb: Memory limit exceeded.
+
+buffer = []
+while len(buffer) < 100000:
+  buffer.append("%d: %s" % (len(buffer), "SPAM" * 1000))
+print "I shouldn't be here."
+
Index: jrun/tests/missing_import.py
===================================================================
--- jrun/tests/missing_import.py	(revision 0)
+++ jrun/tests/missing_import.py	(revision 0)
@@ -0,0 +1,7 @@
+# Automated test for jrun.
+# The following are parsed by test.php
+# JRUN_ARGS = --time-limit 1000 --memory-limit 5000
+# JRUN_RES = FAIL: time [0-9]+ms memory [0-9]{4,}kb: Non-zero exit status.
+
+import anti_gravity
+anti_gravity.levitate()
Index: jrun/tests/network.py
===================================================================
--- jrun/tests/network.py	(revision 0)
+++ jrun/tests/network.py	(revision 0)
@@ -0,0 +1,7 @@
+# Automated test for jrun.
+# The following are parsed by test.php
+# JRUN_ARGS = --time-limit 1000 --memory-limit 16000
+# JRUN_RES = FAIL: time [0-9]+ms memory [0-9]+kb: Blocked system call: [[:alnum:]]+.
+
+import urllib
+print urllib.urlopen("http://infoarena.ro/").read()
Index: jrun/tests/time_limit.py
===================================================================
--- jrun/tests/time_limit.py	(revision 0)
+++ jrun/tests/time_limit.py	(revision 0)
@@ -0,0 +1,14 @@
+# Automated test for jrun.
+# The following are parsed by test.php
+# JRUN_ARGS = --time-limit 2000 --memory-limit 16000
+# JRUN_RES = FAIL: time [0-9]{3,}ms memory [0-9]+kb: Time limit exceeded.
+
+def count_sheep():
+  num_sheep = 0
+  while True:
+    num_sheep += 1
+    yield num_sheep
+
+for num_sheeps in count_sheep():
+  print "%d sheep" % num_sheeps
+
Index: scripts/pybin.sh
===================================================================
--- scripts/pybin.sh	(revision 0)
+++ scripts/pybin.sh	(revision 0)
@@ -0,0 +1,47 @@
+#!/usr/bin/env bash
+
+# This tries to compile a Python script into a self-contained binary.
+# It uses the freeze.py tool in the standard Python distribution.
+# Dependencies are statically linked, with the exception of glibc.
+# Compilation will exclude most of the "batteries included" in the
+# Python distribution, as they are not appropriate for infoarena.
+#
+# Usage:
+#   pybin.sh <python-dist-dir> <temp-dir> <python-script> <output-binary>
+#   (Note: It will fill your temp dir with tons of files!)
+#
+# Example:
+#   ./pybin.sh ~/Desktop/Python-2.6.1 /tmp/pybin myscript.py mybinary
+
+if [ $# -ne 4 ]
+then
+    echo -n "Usage: "
+    echo "pybin.sh <python-dist-dir> <temp-dir> <python-script> <output-binary>"
+    exit 1
+fi
+
+DIR_PY_DIST=$1
+DIR_TMP=$2
+PY_SRC=$3
+PY_BINARY=$4
+PY_TMP_BINARY=$DIR_TMP/`basename $PY_SRC | cut -f1 -d"."`
+
+echo Freezing python module...
+FREEZE="$DIR_PY_DIST/python $DIR_PY_DIST/Tools/freeze/freeze.py"
+$FREEZE -q -o $DIR_TMP \
+        -X BaseHTTPServer -X distutils -X difflib -X EasyDialogs -X email \
+        -X email.utils -X FixTk -X ftplib -X getopt -X gettext \
+        -X httplib -X MacOS -X mimetools -X msvcrt -X ntpath \
+        -X pdb -X pkgutil -X pydoc -X readline -X riscos -X shlex -X socket \
+        -X SocketServer -X sre -X sre_compile -X sre_constants -X sre_parse \
+        -X ssl -X subprocess -X textwrap -X threading -X Tkinter \
+        -X unittest -X urllib -X uu -X _warnings -X webbrowser $PY_SRC
+
+echo Compiling python binary...
+# Change Makefile, add static link flag.
+mv $DIR_TMP/Makefile $DIR_TMP/Makefile.dynamic
+sed $DIR_TMP/Makefile.dynamic -e 's/^LDFLAGS=\(.*\)/LDFLAGS=\1 --static/' > $DIR_TMP/Makefile
+make -C $DIR_TMP 1>/dev/null
+cp -p $PY_TMP_BINARY $PY_BINARY 2>/dev/null
+echo Done
+

Property changes on: scripts/pybin.sh
___________________________________________________________________
Added: svn:executable
   + *

Index: www/macros/macro_tasksubmit.php
===================================================================
--- www/macros/macro_tasksubmit.php	(revision 990)
+++ www/macros/macro_tasksubmit.php	(working copy)
@@ -59,6 +59,9 @@
             <option value="c">GNU C</option>
             <option value="cpp">GNU C++</option>
             <option value="fpc">FreePascal</option>
+            <?php if (user_is_admin($identity_user)) { ?>
+                <option value="py">Python (FOARTE EXPERIMENTAL!)</option>
+            <?php } ?>
         </select>
     </li>
 
Index: www/static/js/submit.js
===================================================================
--- www/static/js/submit.js	(revision 990)
+++ www/static/js/submit.js	(working copy)
@@ -42,7 +42,7 @@
         }
     }
     var ext = f.value.substring(k + 1).toLowerCase();
-    if ('c' == ext || 'cpp' == ext || 'pas' == ext) {
+    if ('c' == ext || 'cpp' == ext || 'pas' == ext || 'py' == ext) {
         if ('pas' == ext) {
             // choose FreePascal compiler
             compiler.value = 'fpc';
Index: www/views/submit.php
===================================================================
--- www/views/submit.php	(revision 990)
+++ www/views/submit.php	(working copy)
@@ -53,6 +53,9 @@
             <option value="c"<?= 'c' == fval('compiler_id') ? ' selected="selected"' : '' ?>>GNU C</option>
             <option value="cpp"<?= 'cpp' == fval('compiler_id') ? ' selected="selected"' : '' ?>>GNU C++</option>
             <option value="fpc"<?= 'fpc' == fval('compiler_id') ? ' selected="selected"' : '' ?>>FreePascal</option>
+            <?php if (user_is_admin($identity_user)) { ?>
+                <option value="py"<?= 'py' == fval('compiler_id') ? ' selected="selected"' : '' ?>>Python (FOARTE EXPERIMENTAL!)</option>
+            <?php } ?>
         </select>
         <?= ferr_span('compiler_id') ?>
     </li>
