Description:
update java-bm
Commit status:
[Not Reviewed]
References:
merge java
Comments:
0 Commit comments 0 Inline Comments
Unresolved TODOs:
There are no unresolved TODOs
Add another comment

r214:336e928de90e - - 9 files changed: 60 inserted, 21 deleted

@@ -1,154 +1,180
1 #!/bin/sh
1 #!/bin/sh
2
2
3 echo "This script will install and configure Cafe grader."
3 echo "This script will install and configure Cafe grader."
4
4
5 - echo "This will install Ruby 1.9.2 under rvm"
5 + RUBY_VERSION=2.1.2
6 + echo "This will install Ruby $RUBY_VERSION under RVM"
6
7
7 echo "Installing required apts"
8 echo "Installing required apts"
8
9
9 sudo apt-get update
10 sudo apt-get update
10 sudo apt-get install mysql-server mysql-client \
11 sudo apt-get install mysql-server mysql-client \
11 g++ gcc apache2 libmysqlclient15-dev build-essential \
12 g++ gcc apache2 libmysqlclient15-dev build-essential \
12 git-core openssl libreadline6 libreadline6-dev \
13 git-core openssl libreadline6 libreadline6-dev \
13 zlib1g zlib1g-dev libssl-dev libyaml-dev libsqlite3-dev \
14 zlib1g zlib1g-dev libssl-dev libyaml-dev libsqlite3-dev \
14 sqlite3 libxml2-dev libxslt-dev autoconf libc6-dev \
15 sqlite3 libxml2-dev libxslt-dev autoconf libc6-dev \
15 ncurses-dev automake libtool bison subversion \
16 ncurses-dev automake libtool bison subversion \
16 - pkg-config curl nodejs unzip
17 + pkg-config curl nodejs unzip pyflakes ruby default-jdk
17
18
18 echo "Installing RVM"
19 echo "Installing RVM"
19 curl -k -L https://get.rvm.io | bash -s stable
20 curl -k -L https://get.rvm.io | bash -s stable
20 - ~/.rvm/scripts/rvm
21 + source ~/.rvm/scripts/rvm
21
22
22 - echo "Installing Ruby 1.9.2 in RVM"
23 + echo "Installing Ruby $RUBY_VERSION in RVM"
23
24
24 - rvm install 1.9.2
25 + rvm install $RUBY_VERSION
25 - rvm use 1.9.2
26 + rvm use $RUBY_VERSION
26
27
27 echo "Fetching Cafe Grader from Git repositories"
28 echo "Fetching Cafe Grader from Git repositories"
28
29
29 echo "Fetching web interface"
30 echo "Fetching web interface"
30
31
31 mkdir cafe_grader
32 mkdir cafe_grader
32 cd cafe_grader
33 cd cafe_grader
33 git clone -q git://github.com/jittat/cafe-grader-web.git web
34 git clone -q git://github.com/jittat/cafe-grader-web.git web
34
35
35 echo "Configuring rails app"
36 echo "Configuring rails app"
36
37
37 cp web/config/application.rb.SAMPLE web/config/application.rb
38 cp web/config/application.rb.SAMPLE web/config/application.rb
38 cp web/config/initializers/cafe_grader_config.rb.SAMPLE web/config/initializers/cafe_grader_config.rb
39 cp web/config/initializers/cafe_grader_config.rb.SAMPLE web/config/initializers/cafe_grader_config.rb
39
40
41 + #replace UTC in application.rb with the system timezone
42 + timezone='UTC'
43 + if [ -f '/etc/timezone' ]; then
44 + timezone=\"`cat /etc/timezone`\"
45 + else
46 + if [ -f '/etc/sysconfig/clock' ]; then
47 + timezone=`grep -e '^TIMEZONE' /etc/sysconfig/clock | grep -o -e '\".*\"'`
48 + fi
49 + fi
50 + replace="s!'UTC'!$timezone!g"
51 + sed -i $replace web/config/application.rb
52 +
40 echo "At this point we will need MySQL user and database."
53 echo "At this point we will need MySQL user and database."
41 echo "Have you created MySQL user and database for Cafe grader? (Y/N) "
54 echo "Have you created MySQL user and database for Cafe grader? (Y/N) "
42 read ch
55 read ch
43
56
44 if [ "$ch" = "n" -o "$ch" = "N" ]
57 if [ "$ch" = "n" -o "$ch" = "N" ]
45 then
58 then
46 echo "Please open another terminal and create the user and database for Cafe grader."
59 echo "Please open another terminal and create the user and database for Cafe grader."
47 echo "Don't forget to grant access to that database for the user."
60 echo "Don't forget to grant access to that database for the user."
48 echo "Please have username, password, and database name ready before continue."
61 echo "Please have username, password, and database name ready before continue."
49 echo
62 echo
50 echo "The following are instructions:"
63 echo "The following are instructions:"
51 echo "1. Run mysql:"
64 echo "1. Run mysql:"
52 echo
65 echo
53 echo " mysql -u root -p"
66 echo " mysql -u root -p"
54 echo
67 echo
55 echo " if you have just installed mysql, the root password is the one that you have just entered"
68 echo " if you have just installed mysql, the root password is the one that you have just entered"
56 echo "2. Create a new database, a new user, and grant access to grader database:"
69 echo "2. Create a new database, a new user, and grant access to grader database:"
57 echo
70 echo
58 echo " create user 'USERNAME'@'localhost' identified by 'PASSWORD';"
71 echo " create user 'USERNAME'@'localhost' identified by 'PASSWORD';"
59 echo " create database \`DATABASENEME\`;"
72 echo " create database \`DATABASENEME\`;"
60 echo " grant all on \`DATABASENAME\`.* to 'USERNAME'@'localhost';"
73 echo " grant all on \`DATABASENAME\`.* to 'USERNAME'@'localhost';"
61 echo
74 echo
62 echo " Replace USERNAME, PASSWORD, and DATABASENAME accordingly."
75 echo " Replace USERNAME, PASSWORD, and DATABASENAME accordingly."
63 echo
76 echo
64 echo "Hit enter when ready..."
77 echo "Hit enter when ready..."
65 read dummy
78 read dummy
66 fi
79 fi
67
80
68 CAFE_PATH=`pwd`
81 CAFE_PATH=`pwd`
69
82
70 cd web
83 cd web
71
84
72 echo "Please provide grader database:"
85 echo "Please provide grader database:"
73 read database
86 read database
74
87
75 echo "Please provide grader username:"
88 echo "Please provide grader username:"
76 read username
89 read username
77
90
78 echo "Please provide $username password:"
91 echo "Please provide $username password:"
79 read password
92 read password
80
93
81 echo "development:" > config/database.yml
94 echo "development:" > config/database.yml
82 echo " adapter: mysql2" >> config/database.yml
95 echo " adapter: mysql2" >> config/database.yml
83 echo " encoding: utf8" >> config/database.yml
96 echo " encoding: utf8" >> config/database.yml
84 echo " reconnect: false" >> config/database.yml
97 echo " reconnect: false" >> config/database.yml
85 echo " database: $database" >> config/database.yml
98 echo " database: $database" >> config/database.yml
86 echo " pool: 5" >> config/database.yml
99 echo " pool: 5" >> config/database.yml
87 echo " username: $username" >> config/database.yml
100 echo " username: $username" >> config/database.yml
88 echo " password: $password" >> config/database.yml
101 echo " password: $password" >> config/database.yml
89 echo " host: localhost" >> config/database.yml
102 echo " host: localhost" >> config/database.yml
90 echo " socket: /var/run/mysqld/mysqld.sock" >> config/database.yml
103 echo " socket: /var/run/mysqld/mysqld.sock" >> config/database.yml
91 echo "" >> config/database.yml
104 echo "" >> config/database.yml
92 echo "production:" >> config/database.yml
105 echo "production:" >> config/database.yml
93 echo " adapter: mysql2" >> config/database.yml
106 echo " adapter: mysql2" >> config/database.yml
94 echo " encoding: utf8" >> config/database.yml
107 echo " encoding: utf8" >> config/database.yml
95 echo " reconnect: false" >> config/database.yml
108 echo " reconnect: false" >> config/database.yml
96 echo " database: $database" >> config/database.yml
109 echo " database: $database" >> config/database.yml
97 echo " pool: 5" >> config/database.yml
110 echo " pool: 5" >> config/database.yml
98 echo " username: $username" >> config/database.yml
111 echo " username: $username" >> config/database.yml
99 echo " password: $password" >> config/database.yml
112 echo " password: $password" >> config/database.yml
100 echo " host: localhost" >> config/database.yml
113 echo " host: localhost" >> config/database.yml
101 echo " socket: /var/run/mysqld/mysqld.sock" >> config/database.yml
114 echo " socket: /var/run/mysqld/mysqld.sock" >> config/database.yml
102
115
103 echo "Object.instance_eval{remove_const :GRADER_ROOT_DIR}" >> config/initializers/cafe_grader_config.rb
116 echo "Object.instance_eval{remove_const :GRADER_ROOT_DIR}" >> config/initializers/cafe_grader_config.rb
104 echo "Object.instance_eval{remove_const :GRADING_RESULT_DIR}" >> config/initializers/cafe_grader_config.rb
117 echo "Object.instance_eval{remove_const :GRADING_RESULT_DIR}" >> config/initializers/cafe_grader_config.rb
105 echo "GRADER_ROOT_DIR = '$CAFE_PATH/judge'" >> config/initializers/cafe_grader_config.rb
118 echo "GRADER_ROOT_DIR = '$CAFE_PATH/judge'" >> config/initializers/cafe_grader_config.rb
106 echo "GRADING_RESULT_DIR = '$CAFE_PATH/judge/result'" >> config/initializers/cafe_grader_config.rb
119 echo "GRADING_RESULT_DIR = '$CAFE_PATH/judge/result'" >> config/initializers/cafe_grader_config.rb
107
120
108 echo "Installing required gems"
121 echo "Installing required gems"
109 gem install bundler
122 gem install bundler
110 bundle install
123 bundle install
111
124
112 echo "Running rake tasks to initialize database"
125 echo "Running rake tasks to initialize database"
113
126
114 rake db:migrate
127 rake db:migrate
115 rake db:seed
128 rake db:seed
116
129
130 + echo "Running rake tasks to precompile the assets"
131 +
132 + rake assets:precompile
133 +
117 echo "Intalling web interface complete..."
134 echo "Intalling web interface complete..."
118 echo
135 echo
119 echo "Fetching grader"
136 echo "Fetching grader"
120
137
121 cd ..
138 cd ..
122
139
123 mkdir judge
140 mkdir judge
124 cd judge
141 cd judge
125 git clone -q git://github.com/jittat/cafe-grader-judge-scripts.git scripts
142 git clone -q git://github.com/jittat/cafe-grader-judge-scripts.git scripts
126 mkdir raw
143 mkdir raw
127 mkdir ev-exam
144 mkdir ev-exam
128 mkdir ev
145 mkdir ev
129 mkdir result
146 mkdir result
130 mkdir log
147 mkdir log
131
148
132 echo "Configuring grader"
149 echo "Configuring grader"
133
150
134 cp scripts/config/env_exam.rb.SAMPLE scripts/config/env_exam.rb
151 cp scripts/config/env_exam.rb.SAMPLE scripts/config/env_exam.rb
135 cp scripts/config/env_grading.rb.SAMPLE scripts/config/env_grading.rb
152 cp scripts/config/env_grading.rb.SAMPLE scripts/config/env_grading.rb
136
153
137 # create new environment.rb file
154 # create new environment.rb file
138 echo "RAILS_ROOT = '$CAFE_PATH/web'" > scripts/config/environment.rb
155 echo "RAILS_ROOT = '$CAFE_PATH/web'" > scripts/config/environment.rb
139 echo "GRADER_ROOT = '$CAFE_PATH/judge/scripts'" >> scripts/config/environment.rb
156 echo "GRADER_ROOT = '$CAFE_PATH/judge/scripts'" >> scripts/config/environment.rb
140 echo "require File.join(File.dirname(__FILE__),'../lib/boot')" >> scripts/config/environment.rb
157 echo "require File.join(File.dirname(__FILE__),'../lib/boot')" >> scripts/config/environment.rb
141 echo "require File.dirname(__FILE__) + \"/env_#{GRADER_ENV}.rb\"" >> scripts/config/environment.rb
158 echo "require File.dirname(__FILE__) + \"/env_#{GRADER_ENV}.rb\"" >> scripts/config/environment.rb
142
159
160 + # compiling box
161 + MACHINE_TYPE=`uname -m`
162 + if [ ${MACHINE_TYPE} == 'x86_64' ]; then
163 + gcc -std=c99 -o scripts/std-script/box scripts/std-script/box64-new.c
164 + else
165 + g++ -o scripts/std-script/box scripts/std-script/box.cc
166 + fi
167 +
168 +
143 cd ..
169 cd ..
144
170
145 echo "Now you are ready to run cafe grader...."
171 echo "Now you are ready to run cafe grader...."
146 echo
172 echo
147 echo "Try:"
173 echo "Try:"
148 echo
174 echo
149 echo " cd web"
175 echo " cd web"
150 echo " rails s"
176 echo " rails s"
151 echo
177 echo
152 echo "and access web at http://localhost:3000/"
178 echo "and access web at http://localhost:3000/"
153 echo "The root username is 'root', its password is 'ioionrails'."
179 echo "The root username is 'root', its password is 'ioionrails'."
154
180
@@ -1,73 +1,73
1 - #!/usr/bin/ruby
1 + #!/usr/bin/env ruby
2
2
3 # new_problem:
3 # new_problem:
4 # * creates a directory for a problem in the current directory,
4 # * creates a directory for a problem in the current directory,
5 # * create standard testcase config file
5 # * create standard testcase config file
6
6
7 require 'erb'
7 require 'erb'
8
8
9 def process_options(options)
9 def process_options(options)
10 i = 2
10 i = 2
11 while i<ARGV.length
11 while i<ARGV.length
12 if ARGV[i]=='-t'
12 if ARGV[i]=='-t'
13 options[:time_limit] = ARGV[i+1].to_i if ARGV.length>i+1
13 options[:time_limit] = ARGV[i+1].to_i if ARGV.length>i+1
14 i += 1
14 i += 1
15 end
15 end
16 if ARGV[i]=='-m'
16 if ARGV[i]=='-m'
17 options[:mem_limit] = ARGV[i+1].to_i if ARGV.length>i+1
17 options[:mem_limit] = ARGV[i+1].to_i if ARGV.length>i+1
18 i += 1
18 i += 1
19 end
19 end
20 i += 1
20 i += 1
21 end
21 end
22 end
22 end
23
23
24
24
25 puts "This script is out of dated, shall be fixed soon"
25 puts "This script is out of dated, shall be fixed soon"
26 puts "Right now, you can create raw_ev and import"
26 puts "Right now, you can create raw_ev and import"
27 exit(0)
27 exit(0)
28
28
29 GRADER_DIR = File.dirname(__FILE__)
29 GRADER_DIR = File.dirname(__FILE__)
30
30
31 # print usage
31 # print usage
32 if ARGV.length < 2
32 if ARGV.length < 2
33 puts <<USAGE
33 puts <<USAGE
34 using: new_problem problem number_of_testcase [options]
34 using: new_problem problem number_of_testcase [options]
35 * creates a directory for a problem in the current directory,
35 * creates a directory for a problem in the current directory,
36 * create standard testcase config file
36 * create standard testcase config file
37 * options: -t time-limit (in seconds)
37 * options: -t time-limit (in seconds)
38 -m mem-limit (in MB)
38 -m mem-limit (in MB)
39 USAGE
39 USAGE
40 exit(127)
40 exit(127)
41 end
41 end
42
42
43 # processing arguments
43 # processing arguments
44 problem = ARGV[0]
44 problem = ARGV[0]
45 num_testcases = ARGV[1].to_i
45 num_testcases = ARGV[1].to_i
46
46
47 options = {:time_limit => 1, :mem_limit => 16}
47 options = {:time_limit => 1, :mem_limit => 16}
48 process_options(options)
48 process_options(options)
49
49
50 # start working
50 # start working
51 puts "creating directories"
51 puts "creating directories"
52
52
53 system("mkdir #{problem}")
53 system("mkdir #{problem}")
54 system("mkdir #{problem}/script")
54 system("mkdir #{problem}/script")
55 system("mkdir #{problem}/test_cases")
55 system("mkdir #{problem}/test_cases")
56
56
57 puts "creating testcases directories"
57 puts "creating testcases directories"
58
58
59 1.upto(num_testcases) do |i|
59 1.upto(num_testcases) do |i|
60 system("mkdir #{problem}/test_cases/#{i}")
60 system("mkdir #{problem}/test_cases/#{i}")
61 end
61 end
62
62
63 # generating all_tests.cfg
63 # generating all_tests.cfg
64 puts "generating testcase config file"
64 puts "generating testcase config file"
65
65
66 template = File.open(File.dirname(__FILE__) + "/templates/all_tests.cfg.erb").read
66 template = File.open(File.dirname(__FILE__) + "/templates/all_tests.cfg.erb").read
67 all_test_cfg = ERB.new(template)
67 all_test_cfg = ERB.new(template)
68
68
69 cfg_file = File.open("#{problem}/test_cases/all_tests.cfg","w")
69 cfg_file = File.open("#{problem}/test_cases/all_tests.cfg","w")
70 cfg_file.puts all_test_cfg.result
70 cfg_file.puts all_test_cfg.result
71 cfg_file.close
71 cfg_file.close
72
72
73 puts "done"
73 puts "done"
@@ -891,857 +891,870
891 }
891 }
892 else
892 else
893 r->val = NULL;
893 r->val = NULL;
894 *last_env_rule = r;
894 *last_env_rule = r;
895 last_env_rule = &r->next;
895 last_env_rule = &r->next;
896 r->next = NULL;
896 r->next = NULL;
897 return 1;
897 return 1;
898 }
898 }
899
899
900 static int
900 static int
901 match_env_var(char *env_entry, struct env_rule *r)
901 match_env_var(char *env_entry, struct env_rule *r)
902 {
902 {
903 if (strncmp(env_entry, r->var, r->var_len))
903 if (strncmp(env_entry, r->var, r->var_len))
904 return 0;
904 return 0;
905 return (env_entry[r->var_len] == '=');
905 return (env_entry[r->var_len] == '=');
906 }
906 }
907
907
908 static void
908 static void
909 apply_env_rule(char **env, int *env_sizep, struct env_rule *r)
909 apply_env_rule(char **env, int *env_sizep, struct env_rule *r)
910 {
910 {
911 // First remove the variable if already set
911 // First remove the variable if already set
912 int pos = 0;
912 int pos = 0;
913 while (pos < *env_sizep && !match_env_var(env[pos], r))
913 while (pos < *env_sizep && !match_env_var(env[pos], r))
914 pos++;
914 pos++;
915 if (pos < *env_sizep)
915 if (pos < *env_sizep)
916 {
916 {
917 (*env_sizep)--;
917 (*env_sizep)--;
918 env[pos] = env[*env_sizep];
918 env[pos] = env[*env_sizep];
919 env[*env_sizep] = NULL;
919 env[*env_sizep] = NULL;
920 }
920 }
921
921
922 // What is the new value?
922 // What is the new value?
923 char *new;
923 char *new;
924 if (r->val)
924 if (r->val)
925 {
925 {
926 if (!r->val[0])
926 if (!r->val[0])
927 return;
927 return;
928 new = xmalloc(r->var_len + 1 + strlen(r->val) + 1);
928 new = xmalloc(r->var_len + 1 + strlen(r->val) + 1);
929 sprintf(new, "%s=%s", r->var, r->val);
929 sprintf(new, "%s=%s", r->var, r->val);
930 }
930 }
931 else
931 else
932 {
932 {
933 pos = 0;
933 pos = 0;
934 while (environ[pos] && !match_env_var(environ[pos], r))
934 while (environ[pos] && !match_env_var(environ[pos], r))
935 pos++;
935 pos++;
936 if (!(new = environ[pos]))
936 if (!(new = environ[pos]))
937 return;
937 return;
938 }
938 }
939
939
940 // Add it at the end of the array
940 // Add it at the end of the array
941 env[(*env_sizep)++] = new;
941 env[(*env_sizep)++] = new;
942 env[*env_sizep] = NULL;
942 env[*env_sizep] = NULL;
943 }
943 }
944
944
945 static char **
945 static char **
946 setup_environment(void)
946 setup_environment(void)
947 {
947 {
948 // Link built-in rules with user rules
948 // Link built-in rules with user rules
949 for (int i=ARRAY_SIZE(default_env_rules)-1; i >= 0; i--)
949 for (int i=ARRAY_SIZE(default_env_rules)-1; i >= 0; i--)
950 {
950 {
951 default_env_rules[i].next = first_env_rule;
951 default_env_rules[i].next = first_env_rule;
952 first_env_rule = &default_env_rules[i];
952 first_env_rule = &default_env_rules[i];
953 }
953 }
954
954
955 // Scan the original environment
955 // Scan the original environment
956 char **orig_env = environ;
956 char **orig_env = environ;
957 int orig_size = 0;
957 int orig_size = 0;
958 while (orig_env[orig_size])
958 while (orig_env[orig_size])
959 orig_size++;
959 orig_size++;
960
960
961 // For each rule, reserve one more slot and calculate length
961 // For each rule, reserve one more slot and calculate length
962 int num_rules = 0;
962 int num_rules = 0;
963 for (struct env_rule *r = first_env_rule; r; r=r->next)
963 for (struct env_rule *r = first_env_rule; r; r=r->next)
964 {
964 {
965 num_rules++;
965 num_rules++;
966 r->var_len = strlen(r->var);
966 r->var_len = strlen(r->var);
967 }
967 }
968
968
969 // Create a new environment
969 // Create a new environment
970 char **env = xmalloc((orig_size + num_rules + 1) * sizeof(char *));
970 char **env = xmalloc((orig_size + num_rules + 1) * sizeof(char *));
971 int size;
971 int size;
972 if (pass_environ)
972 if (pass_environ)
973 {
973 {
974 memcpy(env, environ, orig_size * sizeof(char *));
974 memcpy(env, environ, orig_size * sizeof(char *));
975 size = orig_size;
975 size = orig_size;
976 }
976 }
977 else
977 else
978 size = 0;
978 size = 0;
979 env[size] = NULL;
979 env[size] = NULL;
980
980
981 // Apply the rules one by one
981 // Apply the rules one by one
982 for (struct env_rule *r = first_env_rule; r; r=r->next)
982 for (struct env_rule *r = first_env_rule; r; r=r->next)
983 apply_env_rule(env, &size, r);
983 apply_env_rule(env, &size, r);
984
984
985 // Return the new env and pass some gossip
985 // Return the new env and pass some gossip
986 if (verbose > 1)
986 if (verbose > 1)
987 {
987 {
988 fprintf(stderr, "Passing environment:\n");
988 fprintf(stderr, "Passing environment:\n");
989 for (int i=0; env[i]; i++)
989 for (int i=0; env[i]; i++)
990 fprintf(stderr, "\t%s\n", env[i]);
990 fprintf(stderr, "\t%s\n", env[i]);
991 }
991 }
992 return env;
992 return env;
993 }
993 }
994
994
995 /*** Low-level parsing of syscalls ***/
995 /*** Low-level parsing of syscalls ***/
996
996
997 #ifdef CONFIG_BOX_KERNEL_AMD64
997 #ifdef CONFIG_BOX_KERNEL_AMD64
998 typedef uint64_t arg_t;
998 typedef uint64_t arg_t;
999 #else
999 #else
1000 typedef uint32_t arg_t;
1000 typedef uint32_t arg_t;
1001 #endif
1001 #endif
1002
1002
1003 struct syscall_args {
1003 struct syscall_args {
1004 arg_t sys;
1004 arg_t sys;
1005 arg_t arg1, arg2, arg3;
1005 arg_t arg1, arg2, arg3;
1006 arg_t result;
1006 arg_t result;
1007 struct user user;
1007 struct user user;
1008 };
1008 };
1009
1009
1010 static int user_mem_fd;
1010 static int user_mem_fd;
1011
1011
1012 static int read_user_mem(arg_t addr, char *buf, int len)
1012 static int read_user_mem(arg_t addr, char *buf, int len)
1013 {
1013 {
1014 if (!user_mem_fd)
1014 if (!user_mem_fd)
1015 {
1015 {
1016 char memname[64];
1016 char memname[64];
1017 sprintf(memname, "/proc/%d/mem", (int) box_pid);
1017 sprintf(memname, "/proc/%d/mem", (int) box_pid);
1018 user_mem_fd = open(memname, O_RDONLY);
1018 user_mem_fd = open(memname, O_RDONLY);
1019 if (user_mem_fd < 0)
1019 if (user_mem_fd < 0)
1020 die("open(%s): %m", memname);
1020 die("open(%s): %m", memname);
1021 }
1021 }
1022 if (lseek64(user_mem_fd, addr, SEEK_SET) < 0)
1022 if (lseek64(user_mem_fd, addr, SEEK_SET) < 0)
1023 die("lseek64(mem): %m");
1023 die("lseek64(mem): %m");
1024 return read(user_mem_fd, buf, len);
1024 return read(user_mem_fd, buf, len);
1025 }
1025 }
1026
1026
1027 static void close_user_mem(void)
1027 static void close_user_mem(void)
1028 {
1028 {
1029 if (user_mem_fd)
1029 if (user_mem_fd)
1030 {
1030 {
1031 close(user_mem_fd);
1031 close(user_mem_fd);
1032 user_mem_fd = 0;
1032 user_mem_fd = 0;
1033 }
1033 }
1034 }
1034 }
1035
1035
1036 #ifdef CONFIG_BOX_KERNEL_AMD64
1036 #ifdef CONFIG_BOX_KERNEL_AMD64
1037
1037
1038 static void
1038 static void
1039 get_syscall_args(struct syscall_args *a, int is_exit)
1039 get_syscall_args(struct syscall_args *a, int is_exit)
1040 {
1040 {
1041 if (ptrace(PTRACE_GETREGS, box_pid, NULL, &a->user) < 0)
1041 if (ptrace(PTRACE_GETREGS, box_pid, NULL, &a->user) < 0)
1042 die("ptrace(PTRACE_GETREGS): %m");
1042 die("ptrace(PTRACE_GETREGS): %m");
1043 a->sys = a->user.regs.orig_rax;
1043 a->sys = a->user.regs.orig_rax;
1044 a->result = a->user.regs.rax;
1044 a->result = a->user.regs.rax;
1045
1045
1046 /*
1046 /*
1047 * CAVEAT: We have to check carefully that this is a real 64-bit syscall.
1047 * CAVEAT: We have to check carefully that this is a real 64-bit syscall.
1048 * We test whether the process runs in 64-bit mode, but surprisingly this
1048 * We test whether the process runs in 64-bit mode, but surprisingly this
1049 * is not enough: a 64-bit process can still issue the INT 0x80 instruction
1049 * is not enough: a 64-bit process can still issue the INT 0x80 instruction
1050 * which performs a 32-bit syscall. Currently, the only known way how to
1050 * which performs a 32-bit syscall. Currently, the only known way how to
1051 * detect this situation is to inspect the instruction code (the kernel
1051 * detect this situation is to inspect the instruction code (the kernel
1052 * keeps a syscall type flag internally, but it is not accessible from
1052 * keeps a syscall type flag internally, but it is not accessible from
1053 * user space). Hopefully, there is no instruction whose suffix is the
1053 * user space). Hopefully, there is no instruction whose suffix is the
1054 * code of the SYSCALL instruction. Sometimes, one would wish the
1054 * code of the SYSCALL instruction. Sometimes, one would wish the
1055 * instruction codes to be unique even when read backwards :)
1055 * instruction codes to be unique even when read backwards :)
1056 */
1056 */
1057
1057
1058 if (is_exit)
1058 if (is_exit)
1059 return;
1059 return;
1060
1060
1061 int sys_type;
1061 int sys_type;
1062 uint16_t instr;
1062 uint16_t instr;
1063
1063
1064 switch (a->user.regs.cs)
1064 switch (a->user.regs.cs)
1065 {
1065 {
1066 case 0x23:
1066 case 0x23:
1067 // 32-bit CPU mode => only 32-bit syscalls can be issued
1067 // 32-bit CPU mode => only 32-bit syscalls can be issued
1068 sys_type = 32;
1068 sys_type = 32;
1069 break;
1069 break;
1070 case 0x33:
1070 case 0x33:
1071 // 64-bit CPU mode
1071 // 64-bit CPU mode
1072 if (read_user_mem(a->user.regs.rip-2, (char *) &instr, 2) != 2)
1072 if (read_user_mem(a->user.regs.rip-2, (char *) &instr, 2) != 2)
1073 err("FO: Cannot read syscall instruction");
1073 err("FO: Cannot read syscall instruction");
1074 switch (instr)
1074 switch (instr)
1075 {
1075 {
1076 case 0x050f:
1076 case 0x050f:
1077 break;
1077 break;
1078 case 0x80cd:
1078 case 0x80cd:
1079 err("FO: Forbidden 32-bit syscall in 64-bit mode");
1079 err("FO: Forbidden 32-bit syscall in 64-bit mode");
1080 default:
1080 default:
1081 err("XX: Unknown syscall instruction %04x", instr);
1081 err("XX: Unknown syscall instruction %04x", instr);
1082 }
1082 }
1083 sys_type = 64;
1083 sys_type = 64;
1084 break;
1084 break;
1085 default:
1085 default:
1086 err("XX: Unknown code segment %04jx", (intmax_t) a->user.regs.cs);
1086 err("XX: Unknown code segment %04jx", (intmax_t) a->user.regs.cs);
1087 }
1087 }
1088
1088
1089 #ifdef CONFIG_BOX_USER_AMD64
1089 #ifdef CONFIG_BOX_USER_AMD64
1090 if (sys_type != 64)
1090 if (sys_type != 64)
1091 err("FO: Forbidden %d-bit mode syscall", sys_type);
1091 err("FO: Forbidden %d-bit mode syscall", sys_type);
1092 #else
1092 #else
1093 if (sys_type != (exec_seen ? 32 : 64))
1093 if (sys_type != (exec_seen ? 32 : 64))
1094 err("FO: Forbidden %d-bit mode syscall", sys_type);
1094 err("FO: Forbidden %d-bit mode syscall", sys_type);
1095 #endif
1095 #endif
1096
1096
1097 if (sys_type == 32)
1097 if (sys_type == 32)
1098 {
1098 {
1099 a->arg1 = a->user.regs.rbx;
1099 a->arg1 = a->user.regs.rbx;
1100 a->arg2 = a->user.regs.rcx;
1100 a->arg2 = a->user.regs.rcx;
1101 a->arg3 = a->user.regs.rdx;
1101 a->arg3 = a->user.regs.rdx;
1102 }
1102 }
1103 else
1103 else
1104 {
1104 {
1105 a->arg1 = a->user.regs.rdi;
1105 a->arg1 = a->user.regs.rdi;
1106 a->arg2 = a->user.regs.rsi;
1106 a->arg2 = a->user.regs.rsi;
1107 a->arg3 = a->user.regs.rdx;
1107 a->arg3 = a->user.regs.rdx;
1108 }
1108 }
1109 }
1109 }
1110
1110
1111 static void
1111 static void
1112 set_syscall_nr(struct syscall_args *a, arg_t sys)
1112 set_syscall_nr(struct syscall_args *a, arg_t sys)
1113 {
1113 {
1114 a->sys = sys;
1114 a->sys = sys;
1115 a->user.regs.orig_rax = sys;
1115 a->user.regs.orig_rax = sys;
1116 if (ptrace(PTRACE_SETREGS, box_pid, NULL, &a->user) < 0)
1116 if (ptrace(PTRACE_SETREGS, box_pid, NULL, &a->user) < 0)
1117 die("ptrace(PTRACE_SETREGS): %m");
1117 die("ptrace(PTRACE_SETREGS): %m");
1118 }
1118 }
1119
1119
1120 static void
1120 static void
1121 sanity_check(void)
1121 sanity_check(void)
1122 {
1122 {
1123 }
1123 }
1124
1124
1125 #else
1125 #else
1126
1126
1127 static void
1127 static void
1128 get_syscall_args(struct syscall_args *a, int is_exit UNUSED)
1128 get_syscall_args(struct syscall_args *a, int is_exit UNUSED)
1129 {
1129 {
1130 if (ptrace(PTRACE_GETREGS, box_pid, NULL, &a->user) < 0)
1130 if (ptrace(PTRACE_GETREGS, box_pid, NULL, &a->user) < 0)
1131 die("ptrace(PTRACE_GETREGS): %m");
1131 die("ptrace(PTRACE_GETREGS): %m");
1132 a->sys = a->user.regs.orig_eax;
1132 a->sys = a->user.regs.orig_eax;
1133 a->arg1 = a->user.regs.ebx;
1133 a->arg1 = a->user.regs.ebx;
1134 a->arg2 = a->user.regs.ecx;
1134 a->arg2 = a->user.regs.ecx;
1135 a->arg3 = a->user.regs.edx;
1135 a->arg3 = a->user.regs.edx;
1136 a->result = a->user.regs.eax;
1136 a->result = a->user.regs.eax;
1137 }
1137 }
1138
1138
1139 static void
1139 static void
1140 set_syscall_nr(struct syscall_args *a, arg_t sys)
1140 set_syscall_nr(struct syscall_args *a, arg_t sys)
1141 {
1141 {
1142 a->sys = sys;
1142 a->sys = sys;
1143 a->user.regs.orig_eax = sys;
1143 a->user.regs.orig_eax = sys;
1144 if (ptrace(PTRACE_SETREGS, box_pid, NULL, &a->user) < 0)
1144 if (ptrace(PTRACE_SETREGS, box_pid, NULL, &a->user) < 0)
1145 die("ptrace(PTRACE_SETREGS): %m");
1145 die("ptrace(PTRACE_SETREGS): %m");
1146 }
1146 }
1147
1147
1148 static void
1148 static void
1149 sanity_check(void)
1149 sanity_check(void)
1150 {
1150 {
1151 #if !defined(CONFIG_BOX_ALLOW_INSECURE)
1151 #if !defined(CONFIG_BOX_ALLOW_INSECURE)
1152 struct utsname uts;
1152 struct utsname uts;
1153 if (uname(&uts) < 0)
1153 if (uname(&uts) < 0)
1154 die("uname() failed: %m");
1154 die("uname() failed: %m");
1155
1155
1156 if (!strcmp(uts.machine, "x86_64"))
1156 if (!strcmp(uts.machine, "x86_64"))
1157 die("Running 32-bit sandbox on 64-bit kernels is inherently unsafe. Please get a 64-bit version.");
1157 die("Running 32-bit sandbox on 64-bit kernels is inherently unsafe. Please get a 64-bit version.");
1158 #endif
1158 #endif
1159 }
1159 }
1160
1160
1161 #endif
1161 #endif
1162
1162
1163 /*** Syscall checks ***/
1163 /*** Syscall checks ***/
1164
1164
1165 static void
1165 static void
1166 valid_filename(arg_t addr)
1166 valid_filename(arg_t addr)
1167 {
1167 {
1168 char namebuf[4096], *p, *end;
1168 char namebuf[4096], *p, *end;
1169
1169
1170 if (!file_access)
1170 if (!file_access)
1171 err("FA: File access forbidden");
1171 err("FA: File access forbidden");
1172 if (file_access >= 9)
1172 if (file_access >= 9)
1173 return;
1173 return;
1174
1174
1175 p = end = namebuf;
1175 p = end = namebuf;
1176 do
1176 do
1177 {
1177 {
1178 if (p >= end)
1178 if (p >= end)
1179 {
1179 {
1180 int remains = PAGE_SIZE - (addr & (PAGE_SIZE-1));
1180 int remains = PAGE_SIZE - (addr & (PAGE_SIZE-1));
1181 int l = namebuf + sizeof(namebuf) - end;
1181 int l = namebuf + sizeof(namebuf) - end;
1182 if (l > remains)
1182 if (l > remains)
1183 l = remains;
1183 l = remains;
1184 if (!l)
1184 if (!l)
1185 err("FA: Access to file with name too long");
1185 err("FA: Access to file with name too long");
1186 remains = read_user_mem(addr, end, l);
1186 remains = read_user_mem(addr, end, l);
1187 if (remains < 0)
1187 if (remains < 0)
1188 die("read(mem): %m");
1188 die("read(mem): %m");
1189 if (!remains)
1189 if (!remains)
1190 err("FA: Access to file with name out of memory");
1190 err("FA: Access to file with name out of memory");
1191 end += remains;
1191 end += remains;
1192 addr += remains;
1192 addr += remains;
1193 }
1193 }
1194 }
1194 }
1195 while (*p++);
1195 while (*p++);
1196
1196
1197 msg("[%s] ", namebuf);
1197 msg("[%s] ", namebuf);
1198 if (file_access >= 3)
1198 if (file_access >= 3)
1199 return;
1199 return;
1200
1200
1201 // Everything in current directory is permitted
1201 // Everything in current directory is permitted
1202 if (!strchr(namebuf, '/') && strcmp(namebuf, ".."))
1202 if (!strchr(namebuf, '/') && strcmp(namebuf, ".."))
1203 return;
1203 return;
1204
1204
1205 // ".." anywhere in the path is forbidden
1205 // ".." anywhere in the path is forbidden
1206 enum action act = A_DEFAULT;
1206 enum action act = A_DEFAULT;
1207 if (strstr(namebuf, ".."))
1207 if (strstr(namebuf, ".."))
1208 act = A_NO;
1208 act = A_NO;
1209
1209
1210 // Scan user rules
1210 // Scan user rules
1211 for (struct path_rule *r = user_path_rules; r && !act; r=r->next)
1211 for (struct path_rule *r = user_path_rules; r && !act; r=r->next)
1212 act = match_path_rule(r, namebuf);
1212 act = match_path_rule(r, namebuf);
1213
1213
1214 // Scan built-in rules
1214 // Scan built-in rules
1215 if (file_access >= 2)
1215 if (file_access >= 2)
1216 for (int i=0; i<ARRAY_SIZE(default_path_rules) && !act; i++)
1216 for (int i=0; i<ARRAY_SIZE(default_path_rules) && !act; i++)
1217 act = match_path_rule(&default_path_rules[i], namebuf);
1217 act = match_path_rule(&default_path_rules[i], namebuf);
1218
1218
1219 if (act != A_YES)
1219 if (act != A_YES)
1220 err("FA: Forbidden access to file `%s'", namebuf);
1220 err("FA: Forbidden access to file `%s'", namebuf);
1221 }
1221 }
1222
1222
1223 // Check syscall. If invalid, return -1, otherwise return the action mask.
1223 // Check syscall. If invalid, return -1, otherwise return the action mask.
1224 static int
1224 static int
1225 valid_syscall(struct syscall_args *a)
1225 valid_syscall(struct syscall_args *a)
1226 {
1226 {
1227 unsigned int sys = a->sys;
1227 unsigned int sys = a->sys;
1228 unsigned int act = (sys < NUM_ACTIONS) ? syscall_action[sys] : A_DEFAULT;
1228 unsigned int act = (sys < NUM_ACTIONS) ? syscall_action[sys] : A_DEFAULT;
1229
1229
1230 if (act & A_LIBERAL)
1230 if (act & A_LIBERAL)
1231 {
1231 {
1232 if (filter_syscalls != 1)
1232 if (filter_syscalls != 1)
1233 act = A_DEFAULT;
1233 act = A_DEFAULT;
1234 }
1234 }
1235
1235
1236 switch (act & A_ACTION_MASK)
1236 switch (act & A_ACTION_MASK)
1237 {
1237 {
1238 case A_YES:
1238 case A_YES:
1239 return act;
1239 return act;
1240 case A_NO:
1240 case A_NO:
1241 return -1;
1241 return -1;
1242 case A_FILENAME:
1242 case A_FILENAME:
1243 valid_filename(a->arg1);
1243 valid_filename(a->arg1);
1244 return act;
1244 return act;
1245 default: ;
1245 default: ;
1246 }
1246 }
1247
1247
1248 switch (sys)
1248 switch (sys)
1249 {
1249 {
1250 case __NR_kill:
1250 case __NR_kill:
1251 if (a->arg1 == (arg_t) box_pid)
1251 if (a->arg1 == (arg_t) box_pid)
1252 {
1252 {
1253 meta_printf("exitsig:%d\n", (int) a->arg2);
1253 meta_printf("exitsig:%d\n", (int) a->arg2);
1254 err("SG: Committed suicide by signal %d", (int) a->arg2);
1254 err("SG: Committed suicide by signal %d", (int) a->arg2);
1255 }
1255 }
1256 return -1;
1256 return -1;
1257 case __NR_tgkill:
1257 case __NR_tgkill:
1258 if (a->arg1 == (arg_t) box_pid && a->arg2 == (arg_t) box_pid)
1258 if (a->arg1 == (arg_t) box_pid && a->arg2 == (arg_t) box_pid)
1259 {
1259 {
1260 meta_printf("exitsig:%d\n", (int) a->arg3);
1260 meta_printf("exitsig:%d\n", (int) a->arg3);
1261 err("SG: Committed suicide by signal %d", (int) a->arg3);
1261 err("SG: Committed suicide by signal %d", (int) a->arg3);
1262 }
1262 }
1263 return -1;
1263 return -1;
1264 default:
1264 default:
1265 return -1;
1265 return -1;
1266 }
1266 }
1267 }
1267 }
1268
1268
1269 static void
1269 static void
1270 signal_alarm(int unused UNUSED)
1270 signal_alarm(int unused UNUSED)
1271 {
1271 {
1272 /* Time limit checks are synchronous, so we only schedule them there. */
1272 /* Time limit checks are synchronous, so we only schedule them there. */
1273 timer_tick = 1;
1273 timer_tick = 1;
1274 alarm(1);
1274 alarm(1);
1275 }
1275 }
1276
1276
1277 static void
1277 static void
1278 signal_int(int unused UNUSED)
1278 signal_int(int unused UNUSED)
1279 {
1279 {
1280 /* Interrupts are fatal, so no synchronization requirements. */
1280 /* Interrupts are fatal, so no synchronization requirements. */
1281 meta_printf("exitsig:%d\n", SIGINT);
1281 meta_printf("exitsig:%d\n", SIGINT);
1282 err("SG: Interrupted");
1282 err("SG: Interrupted");
1283 }
1283 }
1284
1284
1285 #define PROC_BUF_SIZE 4096
1285 #define PROC_BUF_SIZE 4096
1286 static void
1286 static void
1287 read_proc_file(char *buf, char *name, int *fdp)
1287 read_proc_file(char *buf, char *name, int *fdp)
1288 {
1288 {
1289 int c;
1289 int c;
1290
1290
1291 if (!*fdp)
1291 if (!*fdp)
1292 {
1292 {
1293 sprintf(buf, "/proc/%d/%s", (int) box_pid, name);
1293 sprintf(buf, "/proc/%d/%s", (int) box_pid, name);
1294 *fdp = open(buf, O_RDONLY);
1294 *fdp = open(buf, O_RDONLY);
1295 if (*fdp < 0)
1295 if (*fdp < 0)
1296 die("open(%s): %m", buf);
1296 die("open(%s): %m", buf);
1297 }
1297 }
1298 lseek(*fdp, 0, SEEK_SET);
1298 lseek(*fdp, 0, SEEK_SET);
1299 if ((c = read(*fdp, buf, PROC_BUF_SIZE-1)) < 0)
1299 if ((c = read(*fdp, buf, PROC_BUF_SIZE-1)) < 0)
1300 die("read on /proc/$pid/%s: %m", name);
1300 die("read on /proc/$pid/%s: %m", name);
1301 if (c >= PROC_BUF_SIZE-1)
1301 if (c >= PROC_BUF_SIZE-1)
1302 die("/proc/$pid/%s too long", name);
1302 die("/proc/$pid/%s too long", name);
1303 buf[c] = 0;
1303 buf[c] = 0;
1304 }
1304 }
1305
1305
1306 static void
1306 static void
1307 check_timeout(void)
1307 check_timeout(void)
1308 {
1308 {
1309 if (wall_timeout)
1309 if (wall_timeout)
1310 {
1310 {
1311 struct timeval now, wall;
1311 struct timeval now, wall;
1312 int wall_ms;
1312 int wall_ms;
1313 gettimeofday(&now, NULL);
1313 gettimeofday(&now, NULL);
1314 timersub(&now, &start_time, &wall);
1314 timersub(&now, &start_time, &wall);
1315 wall_ms = wall.tv_sec*1000 + wall.tv_usec/1000;
1315 wall_ms = wall.tv_sec*1000 + wall.tv_usec/1000;
1316 if (wall_ms > wall_timeout)
1316 if (wall_ms > wall_timeout)
1317 err("TO: Time limit exceeded (wall clock)");
1317 err("TO: Time limit exceeded (wall clock)");
1318 if (verbose > 1)
1318 if (verbose > 1)
1319 fprintf(stderr, "[wall time check: %d msec]\n", wall_ms);
1319 fprintf(stderr, "[wall time check: %d msec]\n", wall_ms);
1320 }
1320 }
1321 if (timeout)
1321 if (timeout)
1322 {
1322 {
1323 char buf[PROC_BUF_SIZE], *x;
1323 char buf[PROC_BUF_SIZE], *x;
1324 int utime, stime, ms;
1324 int utime, stime, ms;
1325 static int proc_stat_fd;
1325 static int proc_stat_fd;
1326 read_proc_file(buf, "stat", &proc_stat_fd);
1326 read_proc_file(buf, "stat", &proc_stat_fd);
1327 x = buf;
1327 x = buf;
1328 while (*x && *x != ' ')
1328 while (*x && *x != ' ')
1329 x++;
1329 x++;
1330 while (*x == ' ')
1330 while (*x == ' ')
1331 x++;
1331 x++;
1332 if (*x++ != '(')
1332 if (*x++ != '(')
1333 die("proc stat syntax error 1");
1333 die("proc stat syntax error 1");
1334 while (*x && (*x != ')' || x[1] != ' '))
1334 while (*x && (*x != ')' || x[1] != ' '))
1335 x++;
1335 x++;
1336 while (*x == ')' || *x == ' ')
1336 while (*x == ')' || *x == ' ')
1337 x++;
1337 x++;
1338 if (sscanf(x, "%*c %*d %*d %*d %*d %*d %*d %*d %*d %*d %*d %d %d", &utime, &stime) != 2)
1338 if (sscanf(x, "%*c %*d %*d %*d %*d %*d %*d %*d %*d %*d %*d %d %d", &utime, &stime) != 2)
1339 die("proc stat syntax error 2");
1339 die("proc stat syntax error 2");
1340 ms = (utime + stime) * 1000 / ticks_per_sec;
1340 ms = (utime + stime) * 1000 / ticks_per_sec;
1341 if (verbose > 1)
1341 if (verbose > 1)
1342 fprintf(stderr, "[time check: %d msec]\n", ms);
1342 fprintf(stderr, "[time check: %d msec]\n", ms);
1343 if (ms > timeout && ms > extra_timeout)
1343 if (ms > timeout && ms > extra_timeout)
1344 err("TO: Time limit exceeded");
1344 err("TO: Time limit exceeded");
1345 }
1345 }
1346 }
1346 }
1347
1347
1348 static void
1348 static void
1349 sample_mem_peak(void)
1349 sample_mem_peak(void)
1350 {
1350 {
1351 /*
1351 /*
1352 * We want to find out the peak memory usage of the process, which is
1352 * We want to find out the peak memory usage of the process, which is
1353 * maintained by the kernel, but unforunately it gets lost when the
1353 * maintained by the kernel, but unforunately it gets lost when the
1354 * process exits (it is not reported in struct rusage). Therefore we
1354 * process exits (it is not reported in struct rusage). Therefore we
1355 * have to sample it whenever we suspect that the process is about
1355 * have to sample it whenever we suspect that the process is about
1356 * to exit.
1356 * to exit.
1357 */
1357 */
1358 char buf[PROC_BUF_SIZE], *x;
1358 char buf[PROC_BUF_SIZE], *x;
1359 static int proc_status_fd;
1359 static int proc_status_fd;
1360 read_proc_file(buf, "status", &proc_status_fd);
1360 read_proc_file(buf, "status", &proc_status_fd);
1361
1361
1362 x = buf;
1362 x = buf;
1363 while (*x)
1363 while (*x)
1364 {
1364 {
1365 char *key = x;
1365 char *key = x;
1366 while (*x && *x != ':' && *x != '\n')
1366 while (*x && *x != ':' && *x != '\n')
1367 x++;
1367 x++;
1368 if (!*x || *x == '\n')
1368 if (!*x || *x == '\n')
1369 break;
1369 break;
1370 *x++ = 0;
1370 *x++ = 0;
1371 while (*x == ' ' || *x == '\t')
1371 while (*x == ' ' || *x == '\t')
1372 x++;
1372 x++;
1373
1373
1374 char *val = x;
1374 char *val = x;
1375 while (*x && *x != '\n')
1375 while (*x && *x != '\n')
1376 x++;
1376 x++;
1377 if (!*x)
1377 if (!*x)
1378 break;
1378 break;
1379 *x++ = 0;
1379 *x++ = 0;
1380
1380
1381 if (!strcmp(key, "VmPeak"))
1381 if (!strcmp(key, "VmPeak"))
1382 {
1382 {
1383 int peak = atoi(val);
1383 int peak = atoi(val);
1384 if (peak > mem_peak_kb)
1384 if (peak > mem_peak_kb)
1385 mem_peak_kb = peak;
1385 mem_peak_kb = peak;
1386 }
1386 }
1387 }
1387 }
1388
1388
1389 if (verbose > 1)
1389 if (verbose > 1)
1390 msg("[mem-peak: %u KB]\n", mem_peak_kb);
1390 msg("[mem-peak: %u KB]\n", mem_peak_kb);
1391 }
1391 }
1392
1392
1393 static void
1393 static void
1394 boxkeeper(void)
1394 boxkeeper(void)
1395 {
1395 {
1396 int syscall_count = (filter_syscalls ? 0 : 1);
1396 int syscall_count = (filter_syscalls ? 0 : 1);
1397 struct sigaction sa;
1397 struct sigaction sa;
1398
1398
1399 is_ptraced = 1;
1399 is_ptraced = 1;
1400
1400
1401 bzero(&sa, sizeof(sa));
1401 bzero(&sa, sizeof(sa));
1402 sa.sa_handler = signal_int;
1402 sa.sa_handler = signal_int;
1403 sigaction(SIGINT, &sa, NULL);
1403 sigaction(SIGINT, &sa, NULL);
1404
1404
1405 gettimeofday(&start_time, NULL);
1405 gettimeofday(&start_time, NULL);
1406 ticks_per_sec = sysconf(_SC_CLK_TCK);
1406 ticks_per_sec = sysconf(_SC_CLK_TCK);
1407 if (ticks_per_sec <= 0)
1407 if (ticks_per_sec <= 0)
1408 die("Invalid ticks_per_sec!");
1408 die("Invalid ticks_per_sec!");
1409
1409
1410 if (timeout || wall_timeout)
1410 if (timeout || wall_timeout)
1411 {
1411 {
1412 sa.sa_handler = signal_alarm;
1412 sa.sa_handler = signal_alarm;
1413 sigaction(SIGALRM, &sa, NULL);
1413 sigaction(SIGALRM, &sa, NULL);
1414 alarm(1);
1414 alarm(1);
1415 }
1415 }
1416
1416
1417 for(;;)
1417 for(;;)
1418 {
1418 {
1419 struct rusage rus;
1419 struct rusage rus;
1420 int stat;
1420 int stat;
1421 pid_t p;
1421 pid_t p;
1422 if (timer_tick)
1422 if (timer_tick)
1423 {
1423 {
1424 check_timeout();
1424 check_timeout();
1425 timer_tick = 0;
1425 timer_tick = 0;
1426 }
1426 }
1427 p = wait4(box_pid, &stat, WUNTRACED, &rus);
1427 p = wait4(box_pid, &stat, WUNTRACED, &rus);
1428 if (p < 0)
1428 if (p < 0)
1429 {
1429 {
1430 if (errno == EINTR)
1430 if (errno == EINTR)
1431 continue;
1431 continue;
1432 die("wait4: %m");
1432 die("wait4: %m");
1433 }
1433 }
1434 if (p != box_pid)
1434 if (p != box_pid)
1435 die("wait4: unknown pid %d exited!", p);
1435 die("wait4: unknown pid %d exited!", p);
1436 if (WIFEXITED(stat))
1436 if (WIFEXITED(stat))
1437 {
1437 {
1438 box_pid = 0;
1438 box_pid = 0;
1439 final_stats(&rus);
1439 final_stats(&rus);
1440 if (WEXITSTATUS(stat))
1440 if (WEXITSTATUS(stat))
1441 {
1441 {
1442 if (syscall_count)
1442 if (syscall_count)
1443 {
1443 {
1444 meta_printf("exitcode:%d\n", WEXITSTATUS(stat));
1444 meta_printf("exitcode:%d\n", WEXITSTATUS(stat));
1445 err("RE: Exited with error status %d", WEXITSTATUS(stat));
1445 err("RE: Exited with error status %d", WEXITSTATUS(stat));
1446 }
1446 }
1447 else
1447 else
1448 {
1448 {
1449 // Internal error happened inside the child process and it has been already reported.
1449 // Internal error happened inside the child process and it has been already reported.
1450 box_exit(2);
1450 box_exit(2);
1451 }
1451 }
1452 }
1452 }
1453 if (timeout && total_ms > timeout)
1453 if (timeout && total_ms > timeout)
1454 err("TO: Time limit exceeded");
1454 err("TO: Time limit exceeded");
1455 if (wall_timeout && wall_ms > wall_timeout)
1455 if (wall_timeout && wall_ms > wall_timeout)
1456 err("TO: Time limit exceeded (wall clock)");
1456 err("TO: Time limit exceeded (wall clock)");
1457 flush_line();
1457 flush_line();
1458 fprintf(stderr,"OK\n");
1458 fprintf(stderr,"OK\n");
1459 box_exit(0);
1459 box_exit(0);
1460 }
1460 }
1461 if (WIFSIGNALED(stat))
1461 if (WIFSIGNALED(stat))
1462 {
1462 {
1463 box_pid = 0;
1463 box_pid = 0;
1464 meta_printf("exitsig:%d\n", WTERMSIG(stat));
1464 meta_printf("exitsig:%d\n", WTERMSIG(stat));
1465 final_stats(&rus);
1465 final_stats(&rus);
1466 err("SG: Caught fatal signal %d%s", WTERMSIG(stat), (syscall_count ? "" : " during startup"));
1466 err("SG: Caught fatal signal %d%s", WTERMSIG(stat), (syscall_count ? "" : " during startup"));
1467 }
1467 }
1468 if (WIFSTOPPED(stat))
1468 if (WIFSTOPPED(stat))
1469 {
1469 {
1470 int sig = WSTOPSIG(stat);
1470 int sig = WSTOPSIG(stat);
1471 if (sig == SIGTRAP)
1471 if (sig == SIGTRAP)
1472 {
1472 {
1473 if (verbose > 2)
1473 if (verbose > 2)
1474 msg("[ptrace status %08x] ", stat);
1474 msg("[ptrace status %08x] ", stat);
1475 static int stop_count;
1475 static int stop_count;
1476 if (!stop_count++) /* Traceme request */
1476 if (!stop_count++) /* Traceme request */
1477 msg(">> Traceme request caught\n");
1477 msg(">> Traceme request caught\n");
1478 else
1478 else
1479 err("SG: Breakpoint");
1479 err("SG: Breakpoint");
1480 ptrace(PTRACE_SYSCALL, box_pid, 0, 0);
1480 ptrace(PTRACE_SYSCALL, box_pid, 0, 0);
1481 }
1481 }
1482 else if (sig == (SIGTRAP | 0x80))
1482 else if (sig == (SIGTRAP | 0x80))
1483 {
1483 {
1484 if (verbose > 2)
1484 if (verbose > 2)
1485 msg("[ptrace status %08x] ", stat);
1485 msg("[ptrace status %08x] ", stat);
1486 struct syscall_args a;
1486 struct syscall_args a;
1487 static unsigned int sys_tick, last_act;
1487 static unsigned int sys_tick, last_act;
1488 static arg_t last_sys;
1488 static arg_t last_sys;
1489 if (++sys_tick & 1) /* Syscall entry */
1489 if (++sys_tick & 1) /* Syscall entry */
1490 {
1490 {
1491 char namebuf[32];
1491 char namebuf[32];
1492 int act;
1492 int act;
1493
1493
1494 get_syscall_args(&a, 0);
1494 get_syscall_args(&a, 0);
1495 arg_t sys = a.sys;
1495 arg_t sys = a.sys;
1496 msg(">> Syscall %-12s (%08jx,%08jx,%08jx) ", syscall_name(sys, namebuf), (intmax_t) a.arg1, (intmax_t) a.arg2, (intmax_t) a.arg3);
1496 msg(">> Syscall %-12s (%08jx,%08jx,%08jx) ", syscall_name(sys, namebuf), (intmax_t) a.arg1, (intmax_t) a.arg2, (intmax_t) a.arg3);
1497 if (!exec_seen)
1497 if (!exec_seen)
1498 {
1498 {
1499 msg("[master] ");
1499 msg("[master] ");
1500 if (sys == NATIVE_NR_execve)
1500 if (sys == NATIVE_NR_execve)
1501 {
1501 {
1502 exec_seen = 1;
1502 exec_seen = 1;
1503 close_user_mem();
1503 close_user_mem();
1504 }
1504 }
1505 }
1505 }
1506 else if ((act = valid_syscall(&a)) >= 0)
1506 else if ((act = valid_syscall(&a)) >= 0)
1507 {
1507 {
1508 last_act = act;
1508 last_act = act;
1509 syscall_count++;
1509 syscall_count++;
1510 if (act & A_SAMPLE_MEM)
1510 if (act & A_SAMPLE_MEM)
1511 sample_mem_peak();
1511 sample_mem_peak();
1512 }
1512 }
1513 else
1513 else
1514 {
1514 {
1515 /*
1515 /*
1516 * Unfortunately, PTRACE_KILL kills _after_ the syscall completes,
1516 * Unfortunately, PTRACE_KILL kills _after_ the syscall completes,
1517 * so we have to change it to something harmless (e.g., an undefined
1517 * so we have to change it to something harmless (e.g., an undefined
1518 * syscall) and make the program continue.
1518 * syscall) and make the program continue.
1519 */
1519 */
1520 set_syscall_nr(&a, ~(arg_t)0);
1520 set_syscall_nr(&a, ~(arg_t)0);
1521 err("FO: Forbidden syscall %s", syscall_name(sys, namebuf));
1521 err("FO: Forbidden syscall %s", syscall_name(sys, namebuf));
1522 }
1522 }
1523 last_sys = sys;
1523 last_sys = sys;
1524 }
1524 }
1525 else /* Syscall return */
1525 else /* Syscall return */
1526 {
1526 {
1527 get_syscall_args(&a, 1);
1527 get_syscall_args(&a, 1);
1528 if (a.sys == ~(arg_t)0)
1528 if (a.sys == ~(arg_t)0)
1529 {
1529 {
1530 /* Some syscalls (sigreturn et al.) do not return a value */
1530 /* Some syscalls (sigreturn et al.) do not return a value */
1531 if (!(last_act & A_NO_RETVAL))
1531 if (!(last_act & A_NO_RETVAL))
1532 err("XX: Syscall does not return, but it should");
1532 err("XX: Syscall does not return, but it should");
1533 }
1533 }
1534 else
1534 else
1535 {
1535 {
1536 if (a.sys != last_sys)
1536 if (a.sys != last_sys)
1537 err("XX: Mismatched syscall entry/exit");
1537 err("XX: Mismatched syscall entry/exit");
1538 }
1538 }
1539 if (last_act & A_NO_RETVAL)
1539 if (last_act & A_NO_RETVAL)
1540 msg("= ?\n");
1540 msg("= ?\n");
1541 else
1541 else
1542 msg("= %jd\n", (intmax_t) a.result);
1542 msg("= %jd\n", (intmax_t) a.result);
1543 }
1543 }
1544 ptrace(PTRACE_SYSCALL, box_pid, 0, 0);
1544 ptrace(PTRACE_SYSCALL, box_pid, 0, 0);
1545 }
1545 }
1546 else if (sig == SIGSTOP)
1546 else if (sig == SIGSTOP)
1547 {
1547 {
1548 msg(">> SIGSTOP\n");
1548 msg(">> SIGSTOP\n");
1549 if (ptrace(PTRACE_SETOPTIONS, box_pid, NULL, (void *) PTRACE_O_TRACESYSGOOD) < 0)
1549 if (ptrace(PTRACE_SETOPTIONS, box_pid, NULL, (void *) PTRACE_O_TRACESYSGOOD) < 0)
1550 die("ptrace(PTRACE_SETOPTIONS): %m");
1550 die("ptrace(PTRACE_SETOPTIONS): %m");
1551 ptrace(PTRACE_SYSCALL, box_pid, 0, 0);
1551 ptrace(PTRACE_SYSCALL, box_pid, 0, 0);
1552 }
1552 }
1553 else if (sig != SIGXCPU && sig != SIGXFSZ)
1553 else if (sig != SIGXCPU && sig != SIGXFSZ)
1554 {
1554 {
1555 msg(">> Signal %d\n", sig);
1555 msg(">> Signal %d\n", sig);
1556 sample_mem_peak(); /* Signal might be fatal, so update mem-peak */
1556 sample_mem_peak(); /* Signal might be fatal, so update mem-peak */
1557 ptrace(PTRACE_SYSCALL, box_pid, 0, sig);
1557 ptrace(PTRACE_SYSCALL, box_pid, 0, sig);
1558 }
1558 }
1559 else
1559 else
1560 {
1560 {
1561 meta_printf("exitsig:%d", sig);
1561 meta_printf("exitsig:%d", sig);
1562 err("SG: Received signal %d", sig);
1562 err("SG: Received signal %d", sig);
1563 }
1563 }
1564 }
1564 }
1565 else
1565 else
1566 die("wait4: unknown status %x, giving up!", stat);
1566 die("wait4: unknown status %x, giving up!", stat);
1567 }
1567 }
1568 }
1568 }
1569
1569
1570 static void
1570 static void
1571 box_inside(int argc, char **argv)
1571 box_inside(int argc, char **argv)
1572 {
1572 {
1573 struct rlimit rl;
1573 struct rlimit rl;
1574 char *args[argc+1];
1574 char *args[argc+1];
1575
1575
1576 memcpy(args, argv, argc * sizeof(char *));
1576 memcpy(args, argv, argc * sizeof(char *));
1577 args[argc] = NULL;
1577 args[argc] = NULL;
1578 if (set_cwd && chdir(set_cwd))
1578 if (set_cwd && chdir(set_cwd))
1579 die("chdir: %m");
1579 die("chdir: %m");
1580 if (redir_stdin)
1580 if (redir_stdin)
1581 {
1581 {
1582 close(0);
1582 close(0);
1583 if (open(redir_stdin, O_RDONLY) != 0)
1583 if (open(redir_stdin, O_RDONLY) != 0)
1584 die("open(\"%s\"): %m", redir_stdin);
1584 die("open(\"%s\"): %m", redir_stdin);
1585 }
1585 }
1586 if (redir_stdout)
1586 if (redir_stdout)
1587 {
1587 {
1588 close(1);
1588 close(1);
1589 if (open(redir_stdout, O_WRONLY | O_CREAT | O_TRUNC, 0666) != 1)
1589 if (open(redir_stdout, O_WRONLY | O_CREAT | O_TRUNC, 0666) != 1)
1590 die("open(\"%s\"): %m", redir_stdout);
1590 die("open(\"%s\"): %m", redir_stdout);
1591 }
1591 }
1592 if (redir_stderr)
1592 if (redir_stderr)
1593 {
1593 {
1594 close(2);
1594 close(2);
1595 if (open(redir_stderr, O_WRONLY | O_CREAT | O_TRUNC, 0666) != 2)
1595 if (open(redir_stderr, O_WRONLY | O_CREAT | O_TRUNC, 0666) != 2)
1596 die("open(\"%s\"): %m", redir_stderr);
1596 die("open(\"%s\"): %m", redir_stderr);
1597 }
1597 }
1598 else
1598 else
1599 dup2(1, 2);
1599 dup2(1, 2);
1600 setpgrp();
1600 setpgrp();
1601
1601
1602 if (memory_limit)
1602 if (memory_limit)
1603 {
1603 {
1604 rl.rlim_cur = rl.rlim_max = memory_limit * 1024;
1604 rl.rlim_cur = rl.rlim_max = memory_limit * 1024;
1605 if (setrlimit(RLIMIT_AS, &rl) < 0)
1605 if (setrlimit(RLIMIT_AS, &rl) < 0)
1606 die("setrlimit(RLIMIT_AS): %m");
1606 die("setrlimit(RLIMIT_AS): %m");
1607 }
1607 }
1608
1608
1609 rl.rlim_cur = rl.rlim_max = (stack_limit ? (rlim_t)stack_limit * 1024 : RLIM_INFINITY);
1609 rl.rlim_cur = rl.rlim_max = (stack_limit ? (rlim_t)stack_limit * 1024 : RLIM_INFINITY);
1610 if (setrlimit(RLIMIT_STACK, &rl) < 0)
1610 if (setrlimit(RLIMIT_STACK, &rl) < 0)
1611 die("setrlimit(RLIMIT_STACK): %m");
1611 die("setrlimit(RLIMIT_STACK): %m");
1612
1612
1613 rl.rlim_cur = rl.rlim_max = 64;
1613 rl.rlim_cur = rl.rlim_max = 64;
1614 if (setrlimit(RLIMIT_NOFILE, &rl) < 0)
1614 if (setrlimit(RLIMIT_NOFILE, &rl) < 0)
1615 die("setrlimit(RLIMIT_NOFILE): %m");
1615 die("setrlimit(RLIMIT_NOFILE): %m");
1616
1616
1617 char **env = setup_environment();
1617 char **env = setup_environment();
1618 if (filter_syscalls)
1618 if (filter_syscalls)
1619 {
1619 {
1620 if (ptrace(PTRACE_TRACEME) < 0)
1620 if (ptrace(PTRACE_TRACEME) < 0)
1621 die("ptrace(PTRACE_TRACEME): %m");
1621 die("ptrace(PTRACE_TRACEME): %m");
1622 /* Trick: Make sure that we are stopped until the boxkeeper wakes up. */
1622 /* Trick: Make sure that we are stopped until the boxkeeper wakes up. */
1623 raise(SIGSTOP);
1623 raise(SIGSTOP);
1624 }
1624 }
1625 execve(args[0], args, env);
1625 execve(args[0], args, env);
1626 die("execve(\"%s\"): %m", args[0]);
1626 die("execve(\"%s\"): %m", args[0]);
1627 }
1627 }
1628
1628
1629 static void
1629 static void
1630 usage(void)
1630 usage(void)
1631 {
1631 {
1632 fprintf(stderr, "Invalid arguments!\n");
1632 fprintf(stderr, "Invalid arguments!\n");
1633 printf("\
1633 printf("\
1634 Usage: box [<options>] -- <command> <arguments>\n\
1634 Usage: box [<options>] -- <command> <arguments>\n\
1635 \n\
1635 \n\
1636 Options:\n\
1636 Options:\n\
1637 -a <level>\tSet file access level (0=none, 1=cwd, 2=/etc,/lib,..., 3=whole fs, 9=no checks; needs -f)\n\
1637 -a <level>\tSet file access level (0=none, 1=cwd, 2=/etc,/lib,..., 3=whole fs, 9=no checks; needs -f)\n\
1638 -c <dir>\tChange directory to <dir> first\n\
1638 -c <dir>\tChange directory to <dir> first\n\
1639 -e\t\tInherit full environment of the parent process\n\
1639 -e\t\tInherit full environment of the parent process\n\
1640 -E <var>\tInherit the environment variable <var> from the parent process\n\
1640 -E <var>\tInherit the environment variable <var> from the parent process\n\
1641 -E <var>=<val>\tSet the environment variable <var> to <val>; unset it if <var> is empty\n\
1641 -E <var>=<val>\tSet the environment variable <var> to <val>; unset it if <var> is empty\n\
1642 -f\t\tFilter system calls (-ff=very restricted)\n\
1642 -f\t\tFilter system calls (-ff=very restricted)\n\
1643 -i <file>\tRedirect stdin from <file>\n\
1643 -i <file>\tRedirect stdin from <file>\n\
1644 -k <size>\tLimit stack size to <size> KB (default: 0=unlimited)\n\
1644 -k <size>\tLimit stack size to <size> KB (default: 0=unlimited)\n\
1645 -m <size>\tLimit address space to <size> KB\n\
1645 -m <size>\tLimit address space to <size> KB\n\
1646 -M <file>\tOutput process information to <file> (name:value)\n\
1646 -M <file>\tOutput process information to <file> (name:value)\n\
1647 -o <file>\tRedirect stdout to <file>\n\
1647 -o <file>\tRedirect stdout to <file>\n\
1648 -p <path>\tPermit access to the specified path (or subtree if it ends with a `/')\n\
1648 -p <path>\tPermit access to the specified path (or subtree if it ends with a `/')\n\
1649 -p <path>=<act>\tDefine action for the specified path (<act>=yes/no)\n\
1649 -p <path>=<act>\tDefine action for the specified path (<act>=yes/no)\n\
1650 -r <file>\tRedirect stderr to <file>\n\
1650 -r <file>\tRedirect stderr to <file>\n\
1651 -s <sys>\tPermit the specified syscall (be careful)\n\
1651 -s <sys>\tPermit the specified syscall (be careful)\n\
1652 -s <sys>=<act>\tDefine action for the specified syscall (<act>=yes/no/file)\n\
1652 -s <sys>=<act>\tDefine action for the specified syscall (<act>=yes/no/file)\n\
1653 -t <time>\tSet run time limit (seconds, fractions allowed)\n\
1653 -t <time>\tSet run time limit (seconds, fractions allowed)\n\
1654 -T\t\tAllow syscalls for measuring run time\n\
1654 -T\t\tAllow syscalls for measuring run time\n\
1655 -v\t\tBe verbose (use multiple times for even more verbosity)\n\
1655 -v\t\tBe verbose (use multiple times for even more verbosity)\n\
1656 -w <time>\tSet wall clock time limit (seconds, fractions allowed)\n\
1656 -w <time>\tSet wall clock time limit (seconds, fractions allowed)\n\
1657 -x <time>\tSet extra timeout, before which a timing-out program is not yet killed,\n\
1657 -x <time>\tSet extra timeout, before which a timing-out program is not yet killed,\n\
1658 \t\tso that its real execution time is reported (seconds, fractions allowed)\n\
1658 \t\tso that its real execution time is reported (seconds, fractions allowed)\n\
1659 + -A <opt>\tPass <opt> as additional argument to the <command>\n\
1660 + \t\tBe noted that this option will be appended after <arguments> respectively\n\
1659 ");
1661 ");
1660 exit(2);
1662 exit(2);
1661 }
1663 }
1662
1664
1663 int
1665 int
1664 main(int argc, char **argv)
1666 main(int argc, char **argv)
1665 {
1667 {
1666 int c;
1668 int c;
1667 uid_t uid;
1669 uid_t uid;
1670 + char **prog_argv = xmalloc(sizeof(char*) * argc);
1671 + int prog_argc = 0;
1668
1672
1669 - while ((c = getopt(argc, argv, "a:c:eE:fi:k:m:M:o:p:r:s:t:Tvw:x:")) >= 0)
1673 + while ((c = getopt(argc, argv, "a:c:eE:fi:k:m:M:o:p:r:s:t:Tvw:x:A:")) >= 0)
1670 switch (c)
1674 switch (c)
1671 {
1675 {
1672 case 'a':
1676 case 'a':
1673 file_access = atol(optarg);
1677 file_access = atol(optarg);
1674 break;
1678 break;
1675 case 'c':
1679 case 'c':
1676 set_cwd = optarg;
1680 set_cwd = optarg;
1677 break;
1681 break;
1678 case 'e':
1682 case 'e':
1679 pass_environ = 1;
1683 pass_environ = 1;
1680 break;
1684 break;
1681 case 'E':
1685 case 'E':
1682 if (!set_env_action(optarg))
1686 if (!set_env_action(optarg))
1683 usage();
1687 usage();
1684 break;
1688 break;
1685 case 'f':
1689 case 'f':
1686 filter_syscalls++;
1690 filter_syscalls++;
1687 break;
1691 break;
1688 case 'k':
1692 case 'k':
1689 stack_limit = atol(optarg);
1693 stack_limit = atol(optarg);
1690 break;
1694 break;
1691 case 'i':
1695 case 'i':
1692 redir_stdin = optarg;
1696 redir_stdin = optarg;
1693 break;
1697 break;
1694 case 'm':
1698 case 'm':
1695 memory_limit = atol(optarg);
1699 memory_limit = atol(optarg);
1696 break;
1700 break;
1697 case 'M':
1701 case 'M':
1698 meta_open(optarg);
1702 meta_open(optarg);
1699 break;
1703 break;
1700 case 'o':
1704 case 'o':
1701 redir_stdout = optarg;
1705 redir_stdout = optarg;
1702 break;
1706 break;
1703 case 'p':
1707 case 'p':
1704 if (!set_path_action(optarg))
1708 if (!set_path_action(optarg))
1705 usage();
1709 usage();
1706 break;
1710 break;
1707 case 'r':
1711 case 'r':
1708 redir_stderr = optarg;
1712 redir_stderr = optarg;
1709 break;
1713 break;
1710 case 's':
1714 case 's':
1711 if (!set_syscall_action(optarg))
1715 if (!set_syscall_action(optarg))
1712 usage();
1716 usage();
1713 break;
1717 break;
1714 case 't':
1718 case 't':
1715 timeout = 1000*atof(optarg);
1719 timeout = 1000*atof(optarg);
1716 break;
1720 break;
1717 case 'T':
1721 case 'T':
1718 syscall_action[__NR_times] = A_YES;
1722 syscall_action[__NR_times] = A_YES;
1719 break;
1723 break;
1720 case 'v':
1724 case 'v':
1721 verbose++;
1725 verbose++;
1722 break;
1726 break;
1723 case 'w':
1727 case 'w':
1724 wall_timeout = 1000*atof(optarg);
1728 wall_timeout = 1000*atof(optarg);
1725 break;
1729 break;
1726 case 'x':
1730 case 'x':
1727 extra_timeout = 1000*atof(optarg);
1731 extra_timeout = 1000*atof(optarg);
1732 + case 'A':
1733 + prog_argv[prog_argc++] = strdup(optarg);
1734 + break;
1728 break;
1735 break;
1729 default:
1736 default:
1730 usage();
1737 usage();
1731 }
1738 }
1732 if (optind >= argc)
1739 if (optind >= argc)
1733 usage();
1740 usage();
1734
1741
1735 sanity_check();
1742 sanity_check();
1736 uid = geteuid();
1743 uid = geteuid();
1737 if (setreuid(uid, uid) < 0)
1744 if (setreuid(uid, uid) < 0)
1738 die("setreuid: %m");
1745 die("setreuid: %m");
1739 box_pid = fork();
1746 box_pid = fork();
1740 if (box_pid < 0)
1747 if (box_pid < 0)
1741 die("fork: %m");
1748 die("fork: %m");
1742 - if (!box_pid)
1749 + if (!box_pid) {
1743 - box_inside(argc-optind, argv+optind);
1750 + int real_argc = prog_argc + argc - optind;
1744 - else
1751 + char **real_argv = xmalloc(sizeof(char*) * (real_argc));
1752 + for (int i = 0;i < argc-optind;i++)
1753 + real_argv[i] = strdup(argv[i+optind]);
1754 + for (int i = 0;i < prog_argc;i++)
1755 + real_argv[argc - optind + i] = strdup(prog_argv[i]);
1756 + box_inside(real_argc, real_argv);
1757 + } else
1745 boxkeeper();
1758 boxkeeper();
1746 die("Internal error: fell over edge of the world");
1759 die("Internal error: fell over edge of the world");
1747 }
1760 }
@@ -1,189 +1,189
1 #!/usr/bin/env ruby
1 #!/usr/bin/env ruby
2
2
3 require 'fileutils'
3 require 'fileutils'
4
4
5 def log(str='')
5 def log(str='')
6 if ENV['TALKATIVE']!=nil
6 if ENV['TALKATIVE']!=nil
7 puts str
7 puts str
8 end
8 end
9 if ENV['GRADER_LOGGING']!=nil
9 if ENV['GRADER_LOGGING']!=nil
10 log_fname = ENV['GRADER_LOGGING']
10 log_fname = ENV['GRADER_LOGGING']
11 fp = File.open(log_fname,"a")
11 fp = File.open(log_fname,"a")
12 fp.puts("run: #{Time.new.strftime("%H:%M")} #{str}")
12 fp.puts("run: #{Time.new.strftime("%H:%M")} #{str}")
13 fp.close
13 fp.close
14 end
14 end
15 end
15 end
16
16
17 def extract_time(t)
17 def extract_time(t)
18 # puts "TIME: #{t}"
18 # puts "TIME: #{t}"
19 if (result=/^(.*)r(.*)u(.*)s/.match(t))
19 if (result=/^(.*)r(.*)u(.*)s/.match(t))
20 {:real => result[1], :user => result[2], :sys => result[3]}
20 {:real => result[1], :user => result[2], :sys => result[3]}
21 else
21 else
22 #{:real => 0, :user => 0, :sys => 0}
22 #{:real => 0, :user => 0, :sys => 0}
23 #puts "ERROR READING RUNNING TIME: #{t}"
23 #puts "ERROR READING RUNNING TIME: #{t}"
24 raise "Error reading running time: #{t}"
24 raise "Error reading running time: #{t}"
25 end
25 end
26 end
26 end
27
27
28 def compile_box(source,bin)
28 def compile_box(source,bin)
29 system("g++ #{source} -o #{bin}")
29 system("g++ #{source} -o #{bin}")
30 end
30 end
31
31
32 if ARGV.length < 2 || ARGV.length > 3
32 if ARGV.length < 2 || ARGV.length > 3
33 puts "Usage: run <language> <test-num> [<program-name>]"
33 puts "Usage: run <language> <test-num> [<program-name>]"
34 exit(127)
34 exit(127)
35 end
35 end
36
36
37 language = ARGV[0]
37 language = ARGV[0]
38 test_num = ARGV[1].to_i
38 test_num = ARGV[1].to_i
39 if ARGV.length > 2
39 if ARGV.length > 2
40 program_name = ARGV[2]
40 program_name = ARGV[2]
41 else
41 else
42 program_name = "a.out"
42 program_name = "a.out"
43 end
43 end
44
44
45 problem_home = ENV['PROBLEM_HOME']
45 problem_home = ENV['PROBLEM_HOME']
46 source_name = ENV['SOURCE_NAME']
46 source_name = ENV['SOURCE_NAME']
47 require "#{problem_home}/script/test_dsl.rb"
47 require "#{problem_home}/script/test_dsl.rb"
48 load "#{problem_home}/test_cases/all_tests.cfg"
48 load "#{problem_home}/test_cases/all_tests.cfg"
49 problem = Problem.get_instance
49 problem = Problem.get_instance
50
50
51 sandbox_dir = Dir.getwd
51 sandbox_dir = Dir.getwd
52
52
53 if problem.well_formed? == false
53 if problem.well_formed? == false
54 log "The problem specification is not well formed."
54 log "The problem specification is not well formed."
55 exit(127)
55 exit(127)
56 end
56 end
57
57
58 # Check if the test number is okay.
58 # Check if the test number is okay.
59 if test_num <= 0 || test_num > problem.num_tests
59 if test_num <= 0 || test_num > problem.num_tests
60 log "You have specified a wrong test number."
60 log "You have specified a wrong test number."
61 exit(127)
61 exit(127)
62 end
62 end
63
63
64 #####################################
64 #####################################
65 # Set the relavant file names here. #
65 # Set the relavant file names here. #
66 #####################################
66 #####################################
67
67
68 input_file_name = "#{problem_home}/test_cases/#{test_num}/input-#{test_num}.txt"
68 input_file_name = "#{problem_home}/test_cases/#{test_num}/input-#{test_num}.txt"
69
69
70 #####################################
70 #####################################
71
71
72 time_limit = problem.get_time_limit test_num
72 time_limit = problem.get_time_limit test_num
73 mem_limit = problem.get_mem_limit(test_num) * 1024
73 mem_limit = problem.get_mem_limit(test_num) * 1024
74
74
75 # Copy the input file.
75 # Copy the input file.
76 #`cp #{problem_home}/test_cases/#{test_num}/#{input_file_name} .`
76 #`cp #{problem_home}/test_cases/#{test_num}/#{input_file_name} .`
77
77
78 # check if box is there, if not, compile it!
78 # check if box is there, if not, compile it!
79 if !File.exists?("#{problem_home}/script/box")
79 if !File.exists?("#{problem_home}/script/box")
80 log "WARNING: Compiling box: to increase efficiency, it should be compile manually"
80 log "WARNING: Compiling box: to increase efficiency, it should be compile manually"
81 compile_box("#{problem_home}/script/box.cc",
81 compile_box("#{problem_home}/script/box.cc",
82 "#{problem_home}/script/box")
82 "#{problem_home}/script/box")
83 end
83 end
84
84
85 # Hide PROBLEM_HOME
85 # Hide PROBLEM_HOME
86 ENV['PROBLEM_HOME'] = nil
86 ENV['PROBLEM_HOME'] = nil
87 ENV['SOURCE_NAME'] = nil
87 ENV['SOURCE_NAME'] = nil
88
88
89 # Run the program.
89 # Run the program.
90 #run_command = "/usr/bin/time -f \"#{time_output_format}\" 2>run_result #{problem_home}/script/box_new -a 2 -f -t #{time_limit} -m #{mem_limit} -i #{input_file_name} -o output.txt #{program_name}"
90 #run_command = "/usr/bin/time -f \"#{time_output_format}\" 2>run_result #{problem_home}/script/box_new -a 2 -f -t #{time_limit} -m #{mem_limit} -i #{input_file_name} -o output.txt #{program_name}"
91 #
91 #
92
92
93 JAVA_OPTION = "-s set_robust_list -s futex -s clone -s getppid -s clone -s wait4 -p /usr/bin/ -p ./"
93 JAVA_OPTION = "-s set_robust_list -s futex -s clone -s getppid -s clone -s wait4 -p /usr/bin/ -p ./"
94 RUBY_OPTION = "-p /usr/lib64/ -p /usr/local/lib64/ -p /usr/local/lib/ -p /lib64/ -p /dev/urandom -p #{sandbox_dir}/#{program_name} -p #{sandbox_dir}/ -s set_robust_list -s sched_getaffinity -s clock_gettime -s sigaltstack -s pipe2 -s clone -s futex -s openat -s pipe"
94 RUBY_OPTION = "-p /usr/lib64/ -p /usr/local/lib64/ -p /usr/local/lib/ -p /lib64/ -p /dev/urandom -p #{sandbox_dir}/#{program_name} -p #{sandbox_dir}/ -s set_robust_list -s sched_getaffinity -s clock_gettime -s sigaltstack -s pipe2 -s clone -s futex -s openat -s pipe"
95 PYTHON_OPTION = "-p /usr/lib64/ -p /usr/local/lib64/ -p /usr/local/lib/ -p /usr/bin/ -p /lib64/ -p #{sandbox_dir}/#{program_name} -p ./#{program_name} -p #{sandbox_dir}/#{source_name} -s set_robust_list -s openat -s recvmsg -s connect -s socket -s sendto -s futex -E PYTHONNOUSERSITE=yes"
95 PYTHON_OPTION = "-p /usr/lib64/ -p /usr/local/lib64/ -p /usr/local/lib/ -p /usr/bin/ -p /lib64/ -p #{sandbox_dir}/#{program_name} -p ./#{program_name} -p #{sandbox_dir}/#{source_name} -s set_robust_list -s openat -s recvmsg -s connect -s socket -s sendto -s futex -E PYTHONNOUSERSITE=yes"
96 PHP_OPTION = "-p /usr/lib64/ -p/lib64/ -p /usr/bin/ -p #{sandbox_dir}/#{program_name} -p ./#{program_name} -p /usr/share/ -s setfsuid -s setfsgid -s openat -s set_robust_list -s futex -s clone -s socket -s connect"
96 PHP_OPTION = "-p /usr/lib64/ -p/lib64/ -p /usr/bin/ -p #{sandbox_dir}/#{program_name} -p ./#{program_name} -p /usr/share/ -s setfsuid -s setfsgid -s openat -s set_robust_list -s futex -s clone -s socket -s connect"
97
97
98 case language
98 case language
99 when "java"
99 when "java"
100 # for java, extract the classname
100 # for java, extract the classname
101 # wne have to add additional systemcall and we don't check the mem limit (dunno how to fix...)
101 # wne have to add additional systemcall and we don't check the mem limit (dunno how to fix...)
102 classname = 'DUMMY'
102 classname = 'DUMMY'
103 File.open(program_name,"r").each do |line|
103 File.open(program_name,"r").each do |line|
104 classname = line
104 classname = line
105 end
105 end
106 #for java, we cannot really check the memory limit...
106 #for java, we cannot really check the memory limit...
107 - run_command = "#{problem_home}/script/box -a 3 -f -T -t #{time_limit} #{JAVA_OPTION} -i #{input_file_name} -o output.txt /usr/bin/java #{classname} "
107 + run_command = "#{problem_home}/script/box -a 3 -f -T -t #{time_limit} #{JAVA_OPTION} -i #{input_file_name} -o output.txt /usr/bin/java -A -Xmx#{mem_limit}k -A #{classname} "
108 when "ruby"
108 when "ruby"
109 run_command = "#{problem_home}/script/box -a 2 -f -T -t #{time_limit*=2} -m #{mem_limit} #{RUBY_OPTION} -i #{input_file_name} -o output.txt /usr/bin/ruby #{program_name} "
109 run_command = "#{problem_home}/script/box -a 2 -f -T -t #{time_limit*=2} -m #{mem_limit} #{RUBY_OPTION} -i #{input_file_name} -o output.txt /usr/bin/ruby #{program_name} "
110 when "python"
110 when "python"
111 run_command = "#{problem_home}/script/box -a 2 -f -T -t #{time_limit*=2} -m #{mem_limit} #{PYTHON_OPTION} -i #{input_file_name} -o output.txt /usr/bin/python #{program_name} "
111 run_command = "#{problem_home}/script/box -a 2 -f -T -t #{time_limit*=2} -m #{mem_limit} #{PYTHON_OPTION} -i #{input_file_name} -o output.txt /usr/bin/python #{program_name} "
112 when "php"
112 when "php"
113 - run_command = "#{problem_home}/script/box -a 2 -f -T -t #{time_limit*=2} #{PHP_OPTION} -i #{input_file_name} -o output.txt /usr/bin/php #{program_name} "
113 + run_command = "#{problem_home}/script/box -a 2 -f -T -t #{time_limit*=2} -m #{[128*1024,mem_limit].max} #{PHP_OPTION} -i #{input_file_name} -o output.txt /usr/bin/php -A -d -A memory_limit=#{mem_limit}k -A #{program_name} "
114 else # for c++, pascal, we do the normal checking
114 else # for c++, pascal, we do the normal checking
115 run_command = "#{problem_home}/script/box -a 2 -f -T -t #{time_limit} -m #{mem_limit} -i #{input_file_name} -o output.txt #{program_name} "
115 run_command = "#{problem_home}/script/box -a 2 -f -T -t #{time_limit} -m #{mem_limit} -i #{input_file_name} -o output.txt #{program_name} "
116 end
116 end
117
117
118
118
119 log "Running test #{test_num}..."
119 log "Running test #{test_num}..."
120 log run_command
120 log run_command
121 log
121 log
122 system(run_command,err: 'run_result')
122 system(run_command,err: 'run_result')
123
123
124 # Restore PROBLEM_HOME
124 # Restore PROBLEM_HOME
125 ENV['PROBLEM_HOME'] = problem_home
125 ENV['PROBLEM_HOME'] = problem_home
126
126
127 # Create the result file.
127 # Create the result file.
128 result_file = File.new("result", "w")
128 result_file = File.new("result", "w")
129 comment_file = File.new("comment", "w")
129 comment_file = File.new("comment", "w")
130
130
131 # Check if the program actually produced any output.
131 # Check if the program actually produced any output.
132 run_result_file = File.new("run_result", "r")
132 run_result_file = File.new("run_result", "r")
133 run_result = run_result_file.readlines
133 run_result = run_result_file.readlines
134 run_result_file.close
134 run_result_file.close
135
135
136 run_stat = run_result[run_result.length-1]
136 run_stat = run_result[run_result.length-1]
137 running_time = extract_time(run_stat)
137 running_time = extract_time(run_stat)
138
138
139 report = lambda{ |status, points, comment|
139 report = lambda{ |status, points, comment|
140 result_file.write status.strip
140 result_file.write status.strip
141 result_file.write "\n"
141 result_file.write "\n"
142 result_file.write points.to_s.strip
142 result_file.write points.to_s.strip
143 result_file.write "\n"
143 result_file.write "\n"
144 result_file.write run_stat.strip
144 result_file.write run_stat.strip
145 result_file.write "\n"
145 result_file.write "\n"
146 result_file.close
146 result_file.close
147 FileUtils.rm "run_result"
147 FileUtils.rm "run_result"
148 # `rm output.txt` --- keep the output
148 # `rm output.txt` --- keep the output
149
149
150 comment_file.write comment
150 comment_file.write comment
151
151
152 # added for debuggin --- jittat
152 # added for debuggin --- jittat
153 comment_file.write "--run-result--\n"
153 comment_file.write "--run-result--\n"
154 run_result.each do |l|
154 run_result.each do |l|
155 comment_file.write l
155 comment_file.write l
156 end
156 end
157
157
158 comment_file.close
158 comment_file.close
159
159
160 log "Done!"
160 log "Done!"
161 exit(0)
161 exit(0)
162 }
162 }
163
163
164
164
165 if run_result[0][0,2] != "OK"
165 if run_result[0][0,2] != "OK"
166 log "There was a runtime error."
166 log "There was a runtime error."
167 report.call(run_result[0], 0, "No comment.\n")
167 report.call(run_result[0], 0, "No comment.\n")
168 end
168 end
169
169
170 if running_time[:user].to_f > time_limit
170 if running_time[:user].to_f > time_limit
171 log "Time limit exceeded."
171 log "Time limit exceeded."
172 report.call("Time limit exceeded", 0, "No comment.\n")
172 report.call("Time limit exceeded", 0, "No comment.\n")
173 end
173 end
174
174
175 # Run 'check' to evaluate the output.
175 # Run 'check' to evaluate the output.
176 #puts "There was no runtime error. Proceed to checking the output."
176 #puts "There was no runtime error. Proceed to checking the output."
177 check_command = "#{problem_home}/script/check #{language} #{test_num}"
177 check_command = "#{problem_home}/script/check #{language} #{test_num}"
178 log "Checking the output..."
178 log "Checking the output..."
179 log check_command
179 log check_command
180 if not system(check_command)
180 if not system(check_command)
181 log "Problem with check script"
181 log "Problem with check script"
182 report.call("Incorrect",0,"Check script error.\n")
182 report.call("Incorrect",0,"Check script error.\n")
183 exit(127)
183 exit(127)
184 end
184 end
185
185
186 check_file = File.new("check_result", "r")
186 check_file = File.new("check_result", "r")
187 check_file_lines = check_file.readlines
187 check_file_lines = check_file.readlines
188
188
189 report.call(check_file_lines[0], check_file_lines[1], "No comment.\n")
189 report.call(check_file_lines[0], check_file_lines[1], "No comment.\n")
@@ -1,48 +1,48
1 - #!/usr/bin/ruby
1 + #!/usr/bin/env ruby
2
2
3 #
3 #
4 # This is a check script wrapper. It read all required information
4 # This is a check script wrapper. It read all required information
5 # and call a real check script call REAL_CHECK_SCRIPT in directory
5 # and call a real check script call REAL_CHECK_SCRIPT in directory
6 # [problem_home]/script
6 # [problem_home]/script
7 #
7 #
8
8
9 REAL_CHECK_SCRIPT = "<%= script_name %>"
9 REAL_CHECK_SCRIPT = "<%= script_name %>"
10
10
11 # The REAL_CHECK_SCRIPT is called with:
11 # The REAL_CHECK_SCRIPT is called with:
12 #
12 #
13 # (script) <lang> <test-num> <in-file> <out-file> <ans-file> <full-score>
13 # (script) <lang> <test-num> <in-file> <out-file> <ans-file> <full-score>
14 #
14 #
15 # and REAL_CHECK_SCRIPT's output to standard out is redirected to
15 # and REAL_CHECK_SCRIPT's output to standard out is redirected to
16 # 'check_result' as required by normal check script.
16 # 'check_result' as required by normal check script.
17
17
18 problem_home = ENV['PROBLEM_HOME']
18 problem_home = ENV['PROBLEM_HOME']
19 require "#{problem_home}/script/test_dsl.rb"
19 require "#{problem_home}/script/test_dsl.rb"
20
20
21 if ARGV.length < 2
21 if ARGV.length < 2
22 puts "Usage: check <language> <test-number> [<output-file>]"
22 puts "Usage: check <language> <test-number> [<output-file>]"
23 exit(0)
23 exit(0)
24 end
24 end
25
25
26 language = ARGV[0]
26 language = ARGV[0]
27 test_num = ARGV[1].to_i
27 test_num = ARGV[1].to_i
28 if ARGV.length >= 3
28 if ARGV.length >= 3
29 output_file_name = ARGV[2]
29 output_file_name = ARGV[2]
30 else
30 else
31 output_file_name = "output.txt"
31 output_file_name = "output.txt"
32 end
32 end
33
33
34 load "#{problem_home}/test_cases/all_tests.cfg"
34 load "#{problem_home}/test_cases/all_tests.cfg"
35 problem = Problem.get_instance
35 problem = Problem.get_instance
36
36
37 answer_file_name = "#{problem_home}/test_cases/#{test_num}/answer-#{test_num}.txt"
37 answer_file_name = "#{problem_home}/test_cases/#{test_num}/answer-#{test_num}.txt"
38 input_file_name = "#{problem_home}/test_cases/#{test_num}/input-#{test_num}.txt"
38 input_file_name = "#{problem_home}/test_cases/#{test_num}/input-#{test_num}.txt"
39
39
40 score = problem.get_score(test_num)
40 score = problem.get_score(test_num)
41
41
42 cmd = "#{problem_home}/script/#{REAL_CHECK_SCRIPT} " +
42 cmd = "#{problem_home}/script/#{REAL_CHECK_SCRIPT} " +
43 "#{language} #{test_num} #{input_file_name} #{output_file_name} " +
43 "#{language} #{test_num} #{input_file_name} #{output_file_name} " +
44 "#{answer_file_name} #{score} > check_result"
44 "#{answer_file_name} #{score} > check_result"
45
45
46 #puts "wrapper-CMD: #{cmd}"
46 #puts "wrapper-CMD: #{cmd}"
47
47
48 system(cmd)
48 system(cmd)
@@ -1,59 +1,59
1 - #!/usr/bin/ruby
1 + #!/usr/bin/env ruby
2
2
3 problem_home = ENV['PROBLEM_HOME']
3 problem_home = ENV['PROBLEM_HOME']
4 require "#{problem_home}/script/test_dsl.rb"
4 require "#{problem_home}/script/test_dsl.rb"
5
5
6 if ARGV.length < 2
6 if ARGV.length < 2
7 puts "Usage: check <language> <test-number> [<output-file>]"
7 puts "Usage: check <language> <test-number> [<output-file>]"
8 exit(0)
8 exit(0)
9 end
9 end
10
10
11 language = ARGV[0]
11 language = ARGV[0]
12 test_num = ARGV[1].to_i
12 test_num = ARGV[1].to_i
13 if ARGV.length >= 3
13 if ARGV.length >= 3
14 output_file_name = ARGV[2]
14 output_file_name = ARGV[2]
15 else
15 else
16 output_file_name = "output.txt"
16 output_file_name = "output.txt"
17 end
17 end
18
18
19 load "#{problem_home}/test_cases/all_tests.cfg"
19 load "#{problem_home}/test_cases/all_tests.cfg"
20 problem = Problem.get_instance
20 problem = Problem.get_instance
21
21
22 output_file = File.new(output_file_name, "r")
22 output_file = File.new(output_file_name, "r")
23 answer_file = File.new("#{problem_home}/test_cases/#{test_num}/answer-#{test_num}.txt")
23 answer_file = File.new("#{problem_home}/test_cases/#{test_num}/answer-#{test_num}.txt")
24 result_file = File.new("check_result", "w")
24 result_file = File.new("check_result", "w")
25
25
26 output_file_content = output_file.read
26 output_file_content = output_file.read
27 answer_file_content = answer_file.read
27 answer_file_content = answer_file.read
28
28
29 report_correct = lambda {
29 report_correct = lambda {
30 result_file.write "Correct\n"
30 result_file.write "Correct\n"
31 result_file.write problem.get_score(test_num)
31 result_file.write problem.get_score(test_num)
32 result_file.write "\n"
32 result_file.write "\n"
33 result_file.close
33 result_file.close
34 exit(0)
34 exit(0)
35 }
35 }
36
36
37 report_wrong = lambda {
37 report_wrong = lambda {
38 result_file.write "Incorrect\n"
38 result_file.write "Incorrect\n"
39 result_file.write "0\n"
39 result_file.write "0\n"
40 result_file.close
40 result_file.close
41 exit(0)
41 exit(0)
42 }
42 }
43
43
44 ##################
44 ##################
45 # Your code here #
45 # Your code here #
46 ##################
46 ##################
47 num_pattern = /^[0-9]*/
47 num_pattern = /^[0-9]*/
48 if (output_file_content =~ num_pattern) == nil
48 if (output_file_content =~ num_pattern) == nil
49 report_wrong.call
49 report_wrong.call
50 end
50 end
51
51
52 output_i = output_file_content.to_i
52 output_i = output_file_content.to_i
53 answer_i = answer_file_content.to_i
53 answer_i = answer_file_content.to_i
54
54
55 if output_i == answer_i
55 if output_i == answer_i
56 report_correct.call
56 report_correct.call
57 else
57 else
58 report_wrong.call
58 report_wrong.call
59 end
59 end
@@ -1,59 +1,59
1 - #!/usr/bin/ruby
1 + #!/usr/bin/env ruby
2
2
3 problem_home = ENV['PROBLEM_HOME']
3 problem_home = ENV['PROBLEM_HOME']
4 require "#{problem_home}/script/test_dsl.rb"
4 require "#{problem_home}/script/test_dsl.rb"
5
5
6 if ARGV.length < 2
6 if ARGV.length < 2
7 puts "Usage: check <language> <test-number> [<output-file>]"
7 puts "Usage: check <language> <test-number> [<output-file>]"
8 exit(0)
8 exit(0)
9 end
9 end
10
10
11 language = ARGV[0]
11 language = ARGV[0]
12 test_num = ARGV[1].to_i
12 test_num = ARGV[1].to_i
13 if ARGV.length >= 3
13 if ARGV.length >= 3
14 output_file_name = ARGV[2]
14 output_file_name = ARGV[2]
15 else
15 else
16 output_file_name = "output.txt"
16 output_file_name = "output.txt"
17 end
17 end
18
18
19 load "#{problem_home}/test_cases/all_tests.cfg"
19 load "#{problem_home}/test_cases/all_tests.cfg"
20 problem = Problem.get_instance
20 problem = Problem.get_instance
21
21
22 output_file = File.new(output_file_name, "r")
22 output_file = File.new(output_file_name, "r")
23 answer_file = File.new("#{problem_home}/test_cases/#{test_num}/answer-#{test_num}.txt")
23 answer_file = File.new("#{problem_home}/test_cases/#{test_num}/answer-#{test_num}.txt")
24 result_file = File.new("check_result", "w")
24 result_file = File.new("check_result", "w")
25
25
26 output_file_content = output_file.read
26 output_file_content = output_file.read
27 answer_file_content = answer_file.read
27 answer_file_content = answer_file.read
28
28
29 report_correct = lambda {
29 report_correct = lambda {
30 result_file.write "Correct\n"
30 result_file.write "Correct\n"
31 result_file.write problem.get_score(test_num)
31 result_file.write problem.get_score(test_num)
32 result_file.write "\n"
32 result_file.write "\n"
33 result_file.close
33 result_file.close
34 exit(0)
34 exit(0)
35 }
35 }
36
36
37 report_wrong = lambda {
37 report_wrong = lambda {
38 result_file.write "Incorrect\n"
38 result_file.write "Incorrect\n"
39 result_file.write "0\n"
39 result_file.write "0\n"
40 result_file.close
40 result_file.close
41 exit(0)
41 exit(0)
42 }
42 }
43
43
44 ##################
44 ##################
45 # Your code here #
45 # Your code here #
46 ##################
46 ##################
47 num_pattern = /^[0-9]*/
47 num_pattern = /^[0-9]*/
48 if (output_file_content =~ num_pattern) == nil
48 if (output_file_content =~ num_pattern) == nil
49 report_wrong.call
49 report_wrong.call
50 end
50 end
51
51
52 output_i = output_file_content.to_i
52 output_i = output_file_content.to_i
53 answer_i = answer_file_content.to_i
53 answer_i = answer_file_content.to_i
54
54
55 if output_i == answer_i
55 if output_i == answer_i
56 report_correct.call
56 report_correct.call
57 else
57 else
58 report_wrong.call
58 report_wrong.call
59 end
59 end
@@ -1,59 +1,59
1 - #!/usr/bin/ruby
1 + #!/usr/bin/env ruby
2
2
3 problem_home = ENV['PROBLEM_HOME']
3 problem_home = ENV['PROBLEM_HOME']
4 require "#{problem_home}/script/test_dsl.rb"
4 require "#{problem_home}/script/test_dsl.rb"
5
5
6 if ARGV.length < 2
6 if ARGV.length < 2
7 puts "Usage: check <language> <test-number> [<output-file>]"
7 puts "Usage: check <language> <test-number> [<output-file>]"
8 exit(0)
8 exit(0)
9 end
9 end
10
10
11 language = ARGV[0]
11 language = ARGV[0]
12 test_num = ARGV[1].to_i
12 test_num = ARGV[1].to_i
13 if ARGV.length >= 3
13 if ARGV.length >= 3
14 output_file_name = ARGV[2]
14 output_file_name = ARGV[2]
15 else
15 else
16 output_file_name = "output.txt"
16 output_file_name = "output.txt"
17 end
17 end
18
18
19 load "#{problem_home}/test_cases/all_tests.cfg"
19 load "#{problem_home}/test_cases/all_tests.cfg"
20 problem = Problem.get_instance
20 problem = Problem.get_instance
21
21
22 output_file = File.new(output_file_name, "r")
22 output_file = File.new(output_file_name, "r")
23 answer_file = File.new("#{problem_home}/test_cases/#{test_num}/answer-#{test_num}.txt")
23 answer_file = File.new("#{problem_home}/test_cases/#{test_num}/answer-#{test_num}.txt")
24 result_file = File.new("check_result", "w")
24 result_file = File.new("check_result", "w")
25
25
26 output_file_content = output_file.read
26 output_file_content = output_file.read
27 answer_file_content = answer_file.read
27 answer_file_content = answer_file.read
28
28
29 report_correct = lambda {
29 report_correct = lambda {
30 result_file.write "Correct\n"
30 result_file.write "Correct\n"
31 result_file.write problem.get_score(test_num)
31 result_file.write problem.get_score(test_num)
32 result_file.write "\n"
32 result_file.write "\n"
33 result_file.close
33 result_file.close
34 exit(0)
34 exit(0)
35 }
35 }
36
36
37 report_wrong = lambda {
37 report_wrong = lambda {
38 result_file.write "Incorrect\n"
38 result_file.write "Incorrect\n"
39 result_file.write "0\n"
39 result_file.write "0\n"
40 result_file.close
40 result_file.close
41 exit(0)
41 exit(0)
42 }
42 }
43
43
44 ##################
44 ##################
45 # Your code here #
45 # Your code here #
46 ##################
46 ##################
47 num_pattern = /^[0-9]*/
47 num_pattern = /^[0-9]*/
48 if (output_file_content =~ num_pattern) == nil
48 if (output_file_content =~ num_pattern) == nil
49 report_wrong.call
49 report_wrong.call
50 end
50 end
51
51
52 output_i = output_file_content.to_i
52 output_i = output_file_content.to_i
53 answer_i = answer_file_content.to_i
53 answer_i = answer_file_content.to_i
54
54
55 if output_i == answer_i
55 if output_i == answer_i
56 report_correct.call
56 report_correct.call
57 else
57 else
58 report_wrong.call
58 report_wrong.call
59 end
59 end
@@ -1,328 +1,328
1 require File.join(File.dirname(__FILE__),'spec_helper')
1 require File.join(File.dirname(__FILE__),'spec_helper')
2 require File.join(File.dirname(__FILE__),'engine_spec_helper')
2 require File.join(File.dirname(__FILE__),'engine_spec_helper')
3
3
4 describe "A grader engine, when grading submissions" do
4 describe "A grader engine, when grading submissions" do
5
5
6 include GraderEngineHelperMethods
6 include GraderEngineHelperMethods
7
7
8 before(:each) do
8 before(:each) do
9 @config = Grader::Configuration.get_instance
9 @config = Grader::Configuration.get_instance
10
10
11 # this test is from Pong
11 # this test is from Pong
12 @problem_test_normal = stub(Problem,
12 @problem_test_normal = stub(Problem,
13 :id => 1, :name => 'test_normal',
13 :id => 1, :name => 'test_normal',
14 :full_score => 135)
14 :full_score => 135)
15 @user_user1 = stub(User,
15 @user_user1 = stub(User,
16 :id => 1, :login => 'user1')
16 :id => 1, :login => 'user1')
17
17
18 @engine = Grader::Engine.new
18 @engine = Grader::Engine.new
19 init_sandbox
19 init_sandbox
20 end
20 end
21
21
22 it "should grade normal submission" do
22 it "should grade normal submission" do
23 grader_should(:grade => "test1_correct.c",
23 grader_should(:grade => "test1_correct.c",
24 :on => @problem_test_normal,
24 :on => @problem_test_normal,
25 :and_report => {
25 :and_report => {
26 :score => 135,
26 :score => 135,
27 :comment => /^(\[|P|\])+/})
27 :comment => /^(\[|P|\])+/})
28 end
28 end
29
29
30
30
31 it "should grade normal submission in C++" do
31 it "should grade normal submission in C++" do
32 cpp_lang = stub(Language, :name => 'cpp', :ext => 'cpp')
32 cpp_lang = stub(Language, :name => 'cpp', :ext => 'cpp')
33
33
34 grader_should(:grade => "test1_correct_cc.cc",
34 grader_should(:grade => "test1_correct_cc.cc",
35 :language => cpp_lang,
35 :language => cpp_lang,
36 :on => @problem_test_normal,
36 :on => @problem_test_normal,
37 :and_report => {
37 :and_report => {
38 :score => 135,
38 :score => 135,
39 :comment => /^(\[|P|\])+/})
39 :comment => /^(\[|P|\])+/})
40 end
40 end
41
41
42 it "should produce error message when submission cannot compile" do
42 it "should produce error message when submission cannot compile" do
43 grader_should(:grade => "test1_compile_error.c",
43 grader_should(:grade => "test1_compile_error.c",
44 :on => @problem_test_normal,
44 :on => @problem_test_normal,
45 :and_report => {
45 :and_report => {
46 :score => 0,
46 :score => 0,
47 :comment => 'compilation error',
47 :comment => 'compilation error',
48 :compiler_message => /[Ee]rror/})
48 :compiler_message => /[Ee]rror/})
49 end
49 end
50
50
51 it "should produce timeout error when submission runs forever" do
51 it "should produce timeout error when submission runs forever" do
52 @problem_test_timeout = stub(Problem,
52 @problem_test_timeout = stub(Problem,
53 :id => 1, :name => 'test_timeout',
53 :id => 1, :name => 'test_timeout',
54 :full_score => 10)
54 :full_score => 10)
55 grader_should(:grade => "test2_timeout.c",
55 grader_should(:grade => "test2_timeout.c",
56 :on => @problem_test_timeout,
56 :on => @problem_test_timeout,
57 :and_report => {
57 :and_report => {
58 :score => 0,
58 :score => 0,
59 :comment => 'TT'})
59 :comment => 'TT'})
60 end
60 end
61
61
62 it "should produce timeout error correctly with fractional running time and fractional time limits" do
62 it "should produce timeout error correctly with fractional running time and fractional time limits" do
63 @problem_test_timeout = stub(Problem,
63 @problem_test_timeout = stub(Problem,
64 :id => 1, :name => 'test_timeout',
64 :id => 1, :name => 'test_timeout',
65 :full_score => 20)
65 :full_score => 20)
66 grader_should(:grade => "test2_05sec.c",
66 grader_should(:grade => "test2_05sec.c",
67 :on => @problem_test_timeout,
67 :on => @problem_test_timeout,
68 :and_report => {
68 :and_report => {
69 :score => 10,
69 :score => 10,
70 :comment => 'TP'})
70 :comment => 'TP'})
71 end
71 end
72
72
73 it "should produce runtime error when submission uses too much static memory" do
73 it "should produce runtime error when submission uses too much static memory" do
74 @problem_test_memory = stub(Problem,
74 @problem_test_memory = stub(Problem,
75 :id => 1, :name => 'test_memory',
75 :id => 1, :name => 'test_memory',
76 :full_score => 20)
76 :full_score => 20)
77 grader_should(:grade => "add_too_much_memory_static.c",
77 grader_should(:grade => "add_too_much_memory_static.c",
78 :on => @problem_test_memory,
78 :on => @problem_test_memory,
79 :and_report => {
79 :and_report => {
80 :score => 10,
80 :score => 10,
81 :comment => /[^P]P/})
81 :comment => /[^P]P/})
82 end
82 end
83
83
84 it "should not allow submission to allocate too much dynamic memory" do
84 it "should not allow submission to allocate too much dynamic memory" do
85 @problem_test_memory = stub(Problem,
85 @problem_test_memory = stub(Problem,
86 :id => 1, :name => 'test_memory',
86 :id => 1, :name => 'test_memory',
87 :full_score => 20)
87 :full_score => 20)
88 grader_should(:grade => "add_too_much_memory_dynamic.c",
88 grader_should(:grade => "add_too_much_memory_dynamic.c",
89 :on => @problem_test_memory,
89 :on => @problem_test_memory,
90 :and_report => {
90 :and_report => {
91 :score => 10,
91 :score => 10,
92 :comment => /[^P]P/})
92 :comment => /[^P]P/})
93 end
93 end
94
94
95 it "should score test runs correctly when submission fails in some test case" do
95 it "should score test runs correctly when submission fails in some test case" do
96 grader_should(:grade => "add_fail_test_case_1.c",
96 grader_should(:grade => "add_fail_test_case_1.c",
97 :on => @problem_test_normal,
97 :on => @problem_test_normal,
98 :and_report => {
98 :and_report => {
99 :score => 105,
99 :score => 105,
100 :comment => /^.*(-|x).*$/})
100 :comment => /^.*(-|x).*$/})
101 end
101 end
102
102
103 it "should fail submission with non-zero exit status" do
103 it "should fail submission with non-zero exit status" do
104 grader_should(:grade => "add_nonzero_exit_status.c",
104 grader_should(:grade => "add_nonzero_exit_status.c",
105 :on => @problem_test_normal,
105 :on => @problem_test_normal,
106 :and_report => {
106 :and_report => {
107 :score => 0,
107 :score => 0,
108 :comment => /^(\[|x|\])+$/})
108 :comment => /^(\[|x|\])+$/})
109 end
109 end
110
110
111 it "should not allow malicious submission to see PROBLEM_HOME" do
111 it "should not allow malicious submission to see PROBLEM_HOME" do
112 problem_test_yesno = stub(Problem,
112 problem_test_yesno = stub(Problem,
113 :id => 1, :name => 'test_yesno',
113 :id => 1, :name => 'test_yesno',
114 :full_score => 10)
114 :full_score => 10)
115 grader_should(:grade => "yesno_access_problem_home.c",
115 grader_should(:grade => "yesno_access_problem_home.c",
116 :on => problem_test_yesno,
116 :on => problem_test_yesno,
117 :and_report => {
117 :and_report => {
118 :score => 0,
118 :score => 0,
119 :comment => /(-|x)/})
119 :comment => /(-|x)/})
120 end
120 end
121
121
122 it "should not allow malicious submission to open files" do
122 it "should not allow malicious submission to open files" do
123 problem_test_yesno = stub(Problem,
123 problem_test_yesno = stub(Problem,
124 :id => 1, :name => 'test_yesno',
124 :id => 1, :name => 'test_yesno',
125 :full_score => 10)
125 :full_score => 10)
126 grader_should(:grade => "yesno_open_file.c",
126 grader_should(:grade => "yesno_open_file.c",
127 :on => problem_test_yesno,
127 :on => problem_test_yesno,
128 :and_report => {
128 :and_report => {
129 :score => 0,
129 :score => 0,
130 :comment => /(-|x)/})
130 :comment => /(-|x)/})
131 end
131 end
132
132
133 def grader_should(args)
133 def grader_should(args)
134 @user1 = stub(User,
134 @user1 = stub(User,
135 :id => 1, :login => 'user1')
135 :id => 1, :login => 'user1')
136
136
137 submission =
137 submission =
138 create_submission_from_file(1, @user1, args[:on], args[:grade], args[:language])
138 create_submission_from_file(1, @user1, args[:on], args[:grade], args[:language])
139 submission.should_receive(:graded_at=)
139 submission.should_receive(:graded_at=)
140
140
141 expected_score = args[:and_report][:score]
141 expected_score = args[:and_report][:score]
142 expected_comment = args[:and_report][:comment]
142 expected_comment = args[:and_report][:comment]
143 if args[:and_report][:compiler_message]!=nil
143 if args[:and_report][:compiler_message]!=nil
144 expected_compiler_message = args[:and_report][:compiler_message]
144 expected_compiler_message = args[:and_report][:compiler_message]
145 else
145 else
146 expected_compiler_message = ''
146 expected_compiler_message = ''
147 end
147 end
148
148
149 submission.should_receive(:points=).with(expected_score)
149 submission.should_receive(:points=).with(expected_score)
150 submission.should_receive(:grader_comment=).with(expected_comment)
150 submission.should_receive(:grader_comment=).with(expected_comment)
151 submission.should_receive(:compiler_message=).with(expected_compiler_message)
151 submission.should_receive(:compiler_message=).with(expected_compiler_message)
152 submission.should_receive(:save)
152 submission.should_receive(:save)
153
153
154 @engine.grade(submission)
154 @engine.grade(submission)
155 end
155 end
156
156
157 protected
157 protected
158
158
159 def create_normal_submission_mock_from_file(source_fname)
159 def create_normal_submission_mock_from_file(source_fname)
160 create_submission_from_file(1, @user_user1, @problem_test_normal, source_fname)
160 create_submission_from_file(1, @user_user1, @problem_test_normal, source_fname)
161 end
161 end
162
162
163 end
163 end
164
164
165 describe "A grader engine, when grading test requests" do
165 describe "A grader engine, when grading test requests" do
166
166
167 include GraderEngineHelperMethods
167 include GraderEngineHelperMethods
168
168
169 before(:each) do
169 before(:each) do
170 @config = Grader::Configuration.get_instance
170 @config = Grader::Configuration.get_instance
171 @engine = Grader::Engine.new(:room_maker => Grader::TestRequestRoomMaker.new,
171 @engine = Grader::Engine.new(:room_maker => Grader::TestRequestRoomMaker.new,
172 :reporter => Grader::TestRequestReporter.new)
172 :reporter => Grader::TestRequestReporter.new)
173 init_sandbox
173 init_sandbox
174 end
174 end
175
175
176 it "should report error if there is no problem template" do
176 it "should report error if there is no problem template" do
177 problem = stub(Problem,
177 problem = stub(Problem,
178 :id => 1, :name => 'nothing')
178 :id => 1, :name => 'nothing')
179 grader_should(:grade => 'test1_correct.c',
179 grader_should(:grade => 'test1_correct.c',
180 :on => problem,
180 :on => problem,
181 :with => 'in1.txt',
181 :with => 'in1.txt',
182 :and_report => {
182 :and_report => {
183 :graded_at= => nil,
183 :graded_at= => nil,
184 :compiler_message= => '',
184 :compiler_message= => '',
185 :grader_comment= => '',
185 :grader_comment= => '',
186 :running_stat= => /template not found/,
186 :running_stat= => /template not found/,
187 :running_time= => nil,
187 :running_time= => nil,
188 :exit_status= => nil,
188 :exit_status= => nil,
189 :memory_usage= => nil,
189 :memory_usage= => nil,
190 :save => nil})
190 :save => nil})
191 end
191 end
192
192
193 it "should run test request and produce output file" do
193 it "should run test request and produce output file" do
194 problem = stub(Problem,
194 problem = stub(Problem,
195 :id => 1, :name => 'test_normal')
195 :id => 1, :name => 'test_normal')
196 grader_should(:grade => 'test1_correct.c',
196 grader_should(:grade => 'test1_correct.c',
197 :on => problem,
197 :on => problem,
198 :with => 'in1.txt',
198 :with => 'in1.txt',
199 :and_report => {
199 :and_report => {
200 :graded_at= => nil,
200 :graded_at= => nil,
201 :compiler_message= => '',
201 :compiler_message= => '',
202 :grader_comment= => '',
202 :grader_comment= => '',
203 :running_stat= => /0.0\d* sec./,
203 :running_stat= => /0.0\d* sec./,
204 :output_file_name= => lambda { |fname|
204 :output_file_name= => lambda { |fname|
205 - File.exists?(fname).should be_true
205 + File.exists?(fname).should be true
206 },
206 },
207 :running_time= => nil,
207 :running_time= => nil,
208 :exit_status= => nil,
208 :exit_status= => nil,
209 :memory_usage= => nil,
209 :memory_usage= => nil,
210 :save => nil})
210 :save => nil})
211 end
211 end
212
212
213 it "should clean up problem directory after running test request" do
213 it "should clean up problem directory after running test request" do
214 problem = stub(Problem,
214 problem = stub(Problem,
215 :id => 1, :name => 'test_normal')
215 :id => 1, :name => 'test_normal')
216 grader_should(:grade => 'test1_correct.c',
216 grader_should(:grade => 'test1_correct.c',
217 :on => problem,
217 :on => problem,
218 :with => 'in1.txt',
218 :with => 'in1.txt',
219 :and_report => {
219 :and_report => {
220 :graded_at= => nil,
220 :graded_at= => nil,
221 :compiler_message= => '',
221 :compiler_message= => '',
222 :grader_comment= => '',
222 :grader_comment= => '',
223 :running_stat= => nil,
223 :running_stat= => nil,
224 :output_file_name= => nil,
224 :output_file_name= => nil,
225 :running_time= => nil,
225 :running_time= => nil,
226 :exit_status= => nil,
226 :exit_status= => nil,
227 :memory_usage= => nil,
227 :memory_usage= => nil,
228 :save => nil})
228 :save => nil})
229 - File.exists?(@config.user_result_dir + "/test_request/test_normal/test_cases/1/input-1.txt").should be_false
229 + File.exists?(@config.user_result_dir + "/test_request/test_normal/test_cases/1/input-1.txt").should be false
230 end
230 end
231
231
232 it "should compile test request with error and report compilation error" do
232 it "should compile test request with error and report compilation error" do
233 problem = stub(Problem,
233 problem = stub(Problem,
234 :id => 1, :name => 'test_normal')
234 :id => 1, :name => 'test_normal')
235 grader_should(:grade => 'test1_compile_error.c',
235 grader_should(:grade => 'test1_compile_error.c',
236 :on => problem,
236 :on => problem,
237 :with => 'in1.txt',
237 :with => 'in1.txt',
238 :and_report => {
238 :and_report => {
239 :graded_at= => nil,
239 :graded_at= => nil,
240 :compiler_message= => /.+/,
240 :compiler_message= => /.+/,
241 :grader_comment= => /[Cc]ompil.*error/,
241 :grader_comment= => /[Cc]ompil.*error/,
242 :running_stat= => '',
242 :running_stat= => '',
243 :save => nil})
243 :save => nil})
244 end
244 end
245
245
246 it "should report exit status" do
246 it "should report exit status" do
247 problem = stub(Problem,
247 problem = stub(Problem,
248 :id => 1, :name => 'test_normal')
248 :id => 1, :name => 'test_normal')
249 grader_should(:grade => 'add_nonzero_exit_status.c',
249 grader_should(:grade => 'add_nonzero_exit_status.c',
250 :on => problem,
250 :on => problem,
251 :with => 'in1.txt',
251 :with => 'in1.txt',
252 :and_report => {
252 :and_report => {
253 :graded_at= => nil,
253 :graded_at= => nil,
254 :compiler_message= => '',
254 :compiler_message= => '',
255 :grader_comment= => '',
255 :grader_comment= => '',
256 :running_stat= => /[Ee]xit.*status.*10.*0\.0\d* sec/m,
256 :running_stat= => /[Ee]xit.*status.*10.*0\.0\d* sec/m,
257 :output_file_name= => lambda { |fname|
257 :output_file_name= => lambda { |fname|
258 - File.exists?(fname).should be_true
258 + File.exists?(fname).should be true
259 },
259 },
260 :running_time= => nil,
260 :running_time= => nil,
261 :exit_status= => /10/,
261 :exit_status= => /10/,
262 :memory_usage= => nil,
262 :memory_usage= => nil,
263 :save => nil})
263 :save => nil})
264 end
264 end
265
265
266 it "should produce running statistics for normal submission" do
266 it "should produce running statistics for normal submission" do
267 problem = stub(Problem,
267 problem = stub(Problem,
268 :id => 1, :name => 'test_normal')
268 :id => 1, :name => 'test_normal')
269 grader_should(:grade => 'test_run_stat.c',
269 grader_should(:grade => 'test_run_stat.c',
270 :on => problem,
270 :on => problem,
271 :with => 'in1.txt',
271 :with => 'in1.txt',
272 :and_report => {
272 :and_report => {
273 :graded_at= => nil,
273 :graded_at= => nil,
274 :compiler_message= => '',
274 :compiler_message= => '',
275 :grader_comment= => '',
275 :grader_comment= => '',
276 :running_stat= => nil,
276 :running_stat= => nil,
277 :output_file_name= => lambda { |fname|
277 :output_file_name= => lambda { |fname|
278 - File.exists?(fname).should be_true
278 + File.exists?(fname).should be true
279 },
279 },
280 :running_time= => lambda { |t|
280 :running_time= => lambda { |t|
281 (t>=0.14) and (t<=0.16)
281 (t>=0.14) and (t<=0.16)
282 },
282 },
283 :exit_status= => nil,
283 :exit_status= => nil,
284 :memory_usage= => lambda { |s|
284 :memory_usage= => lambda { |s|
285 (s>=500) and (s<=1000)
285 (s>=500) and (s<=1000)
286 },
286 },
287 :save => nil})
287 :save => nil})
288 end
288 end
289
289
290 protected
290 protected
291 def grader_should(args)
291 def grader_should(args)
292 @user1 = stub(User,
292 @user1 = stub(User,
293 :id => 1, :login => 'user1')
293 :id => 1, :login => 'user1')
294
294
295 problem = args[:on]
295 problem = args[:on]
296 input_file = @config.test_request_input_base_dir + "/" + args[:with]
296 input_file = @config.test_request_input_base_dir + "/" + args[:with]
297
297
298 submission =
298 submission =
299 create_submission_from_file(1, @user1, args[:on], args[:grade])
299 create_submission_from_file(1, @user1, args[:on], args[:grade])
300
300
301 test_request = stub(TestRequest,
301 test_request = stub(TestRequest,
302 :id => 1,
302 :id => 1,
303 :user => @user1,
303 :user => @user1,
304 :problem => problem,
304 :problem => problem,
305 :submission => submission,
305 :submission => submission,
306 :input_file_name => input_file,
306 :input_file_name => input_file,
307 :language => submission.language,
307 :language => submission.language,
308 :problem_name => problem.name)
308 :problem_name => problem.name)
309
309
310 expectations = args[:and_report]
310 expectations = args[:and_report]
311
311
312 expectations.each do |key,val|
312 expectations.each do |key,val|
313 if val==nil
313 if val==nil
314 test_request.should_receive(key)
314 test_request.should_receive(key)
315 elsif val.class == Proc
315 elsif val.class == Proc
316 test_request.should_receive(key) { |fname|
316 test_request.should_receive(key) { |fname|
317 val.call(fname)
317 val.call(fname)
318 }
318 }
319 else
319 else
320 test_request.should_receive(key).with(val)
320 test_request.should_receive(key).with(val)
321 end
321 end
322 end
322 end
323
323
324 @engine.grade(test_request)
324 @engine.grade(test_request)
325 end
325 end
326
326
327 end
327 end
328
328
You need to be logged in to leave comments. Login now