Description:
Merge pull request #14 from nattee/master upgrade to current working snapshot
Commit status:
[Not Reviewed]
References:
merge default
Comments:
0 Commit comments 0 Inline Comments
Unresolved TODOs:
There are no unresolved TODOs
Add another comment

r263:7950cf0bb006 - - 12 files changed: 448 inserted, 40 deleted

@@ -0,0 +1,93
1 + #!/usr/bin/env ruby
2 +
3 + def config
4 + Grader::Configuration.get_instance
5 + end
6 +
7 + def display_manual
8 + puts <<USAGE
9 + Check similarity between submission
10 + using: check_similar sub1 sub2
11 + -- or --
12 + check_similar problem_name
13 + sub1 and sub2 are submission IDs to be checked
14 + problem_name will check all submissions of the problem wit problem short name is 'problem_name'
15 +
16 + The output are given as
17 + sub1.login, sub1.id, sub1.point, sub2.login, sub2.id, sub2.point, similarity
18 +
19 + USAGE
20 + end
21 +
22 + def process_options_and_stop_file
23 +
24 + # Process 'help' option
25 + if (ARGV.length == 0) or ((ARGV.length==1) and (/help/.match(ARGV[0])))
26 + display_manual
27 + exit(0)
28 + end
29 +
30 + #default options
31 + options = {
32 + :dry_run => false,
33 + }
34 +
35 +
36 + if ARGV.length == 2
37 + options[:sub1] = ARGV[0].to_i
38 + options[:sub2] = ARGV[1].to_i
39 + elsif ARGV.length == 1
40 + options[:problem] = ARGV[0]
41 + end
42 +
43 +
44 + return options
45 + end
46 +
47 + def compare(sub1,sub2,full = sub1.problem.full_score)
48 + dis = @jarow.getDistance(sub1.source, sub2.source)
49 + puts [sub1.user.login,"##{sub1.id}",(sub1.points * 100.0 / full).to_i,
50 + sub2.user.login,"##{sub2.id}",(sub2.points * 100.0 / full).to_i,
51 + "#{dis * 100}%"].join(',')
52 + end
53 +
54 + #########################################
55 + # main program
56 + #########################################
57 +
58 + options = process_options_and_stop_file
59 +
60 + # load grader environment
61 + GRADER_ENV = 'grading'
62 + require File.join(File.dirname(__FILE__),'config/environment')
63 +
64 + # boot rails, to be able to use the active record
65 + RAILS_ENV = config.rails_env
66 + require RAILS_ROOT + '/config/environment'
67 +
68 + # load comparator
69 + require 'fuzzystringmatch'
70 + @jarow = FuzzyStringMatch::JaroWinkler.create( :native )
71 +
72 + if options[:problem]
73 + p = Problem.where(name: options[:problem]).first
74 + unless p
75 + puts "cannot find problem #{options[:problem]}"
76 + exit(0)
77 + end
78 + subs = Submission.where(problem: p)
79 + full_score = p.full_score.to_i
80 + subs.each.with_index do |s1,i|
81 + puts "processing #{i+1} out of #{subs.length}"
82 + subs.each do |s2|
83 + if s1.user != s2.user
84 + compare(s1,s2,full_score)
85 + end
86 + end
87 + end
88 + else
89 + sub1 = Submission.find(options[:sub1])
90 + sub2 = Submission.find(options[:sub2])
91 + compare(sub1,sub2)
92 + end
93 +
@@ -0,0 +1,6
1 + #!/bin/bash
2 + count=`ps aux | grep "cafe_grader" | grep "grader grading queue" | wc -l`
3 + if [ $count -lt 1 ]; then
4 + cd /home/dae/cafe_grader/judge
5 + /home/dae/.rvm/wrappers/ruby-2.3.0/ruby /home/dae/cafe_grader/judge/scripts/grader grading queue > /home/dae/grading.log &
6 + fi
@@ -0,0 +1,179
1 + #!/bin/sh
2 +
3 + echo "This script will install and configure Cafe grader."
4 +
5 + RUBY_VERSION=2.1.2
6 + echo "This will install Ruby $RUBY_VERSION under RVM"
7 +
8 + echo "Installing required apts"
9 +
10 + sudo zypper install \
11 + g++ gcc libmysqlclient18 build-essential \
12 + git-core openssl libreadline6 libreadline6-devel \
13 + zlib1g zlib1g-devel libssl37 libyaml-devel sqlite3-devel \
14 + sqlite3 libxml2-devel libxslt-devel autoconf libc6-devel \
15 + ncurses-devel automake libtool bison subversion \
16 + pkg-config curl nodejs unzip pyflakes java-1_8_0-openjdk \
17 + libmysqld-devel mercurial python-setuptools python-devel
18 +
19 +
20 + echo "Installing Ruby $RUBY_VERSION in RVM"
21 +
22 + rvm install $RUBY_VERSION
23 + rvm use $RUBY_VERSION
24 +
25 + echo "Fetching Cafe Grader from Git repositories"
26 +
27 + echo "Fetching web interface"
28 +
29 + mkdir cafe_grader
30 + cd cafe_grader
31 + #git clone -q git://github.com/jittat/cafe-grader-web.git web
32 + hg clone git+ssh://git@github.com/nattee/cafe-grader-web.git web
33 +
34 + echo "Configuring rails app"
35 +
36 + cp web/config/application.rb.SAMPLE web/config/application.rb
37 + cp web/config/initializers/cafe_grader_config.rb.SAMPLE web/config/initializers/cafe_grader_config.rb
38 +
39 + #replace UTC in application.rb with the system timezone
40 + timezone='UTC'
41 + if [ -f '/etc/timezone' ]; then
42 + timezone=\"`cat /etc/timezone`\"
43 + else
44 + if [ -f '/etc/sysconfig/clock' ]; then
45 + timezone=`grep -e '^TIMEZONE' /etc/sysconfig/clock | grep -o -e '\".*\"'`
46 + fi
47 + fi
48 + replace="s!'UTC'!$timezone!g"
49 + sed -i $replace web/config/application.rb
50 +
51 + echo "At this point we will need MySQL user and database."
52 + echo "Have you created MySQL user and database for Cafe grader? (Y/N) "
53 + read ch
54 +
55 + if [ "$ch" = "n" -o "$ch" = "N" ]
56 + then
57 + echo "Please open another terminal and create the user and database for Cafe grader."
58 + echo "Don't forget to grant access to that database for the user."
59 + echo "Please have username, password, and database name ready before continue."
60 + echo
61 + echo "The following are instructions:"
62 + echo "1. Run mysql:"
63 + echo
64 + echo " mysql -u root -p"
65 + echo
66 + echo " if you have just installed mysql, the root password is the one that you have just entered"
67 + echo "2. Create a new database, a new user, and grant access to grader database:"
68 + echo
69 + echo " create user 'USERNAME'@'localhost' identified by 'PASSWORD';"
70 + echo " create database \`DATABASENEME\`;"
71 + echo " grant all on \`DATABASENAME\`.* to 'USERNAME'@'localhost';"
72 + echo
73 + echo " Replace USERNAME, PASSWORD, and DATABASENAME accordingly."
74 + echo
75 + echo "Hit enter when ready..."
76 + read dummy
77 + fi
78 +
79 + CAFE_PATH=`pwd`
80 +
81 + cd web
82 +
83 + echo "Please provide grader database:"
84 + read database
85 +
86 + echo "Please provide grader username:"
87 + read username
88 +
89 + echo "Please provide $username password:"
90 + read password
91 +
92 + echo "development:" > config/database.yml
93 + echo " adapter: mysql2" >> config/database.yml
94 + echo " encoding: utf8" >> config/database.yml
95 + echo " reconnect: false" >> config/database.yml
96 + echo " database: $database" >> config/database.yml
97 + echo " pool: 5" >> config/database.yml
98 + echo " username: $username" >> config/database.yml
99 + echo " password: $password" >> config/database.yml
100 + echo " host: localhost" >> config/database.yml
101 + echo " socket: /run/mysql/mysql.sock" >> config/database.yml
102 + echo "" >> config/database.yml
103 + echo "production:" >> config/database.yml
104 + echo " adapter: mysql2" >> config/database.yml
105 + echo " encoding: utf8" >> config/database.yml
106 + echo " reconnect: false" >> config/database.yml
107 + echo " database: $database" >> config/database.yml
108 + echo " pool: 5" >> config/database.yml
109 + echo " username: $username" >> config/database.yml
110 + echo " password: $password" >> config/database.yml
111 + echo " host: localhost" >> config/database.yml
112 + echo " socket: /run/mysql/mysql.sock" >> config/database.yml
113 +
114 + echo "Object.instance_eval{remove_const :GRADER_ROOT_DIR}" >> config/initializers/cafe_grader_config.rb
115 + echo "Object.instance_eval{remove_const :GRADING_RESULT_DIR}" >> config/initializers/cafe_grader_config.rb
116 + echo "GRADER_ROOT_DIR = '$CAFE_PATH/judge'" >> config/initializers/cafe_grader_config.rb
117 + echo "GRADING_RESULT_DIR = '$CAFE_PATH/judge/result'" >> config/initializers/cafe_grader_config.rb
118 +
119 + echo "Installing required gems"
120 + gem install bundler
121 + bundle install
122 +
123 + echo "Running rake tasks to initialize database"
124 +
125 + rake db:migrate
126 + rake db:seed
127 +
128 + echo "Running rake tasks to precompile the assets"
129 +
130 + rake assets:precompile
131 +
132 + echo "Intalling web interface complete..."
133 + echo
134 + echo "Fetching grader"
135 +
136 + cd ..
137 +
138 + mkdir judge
139 + cd judge
140 + #git clone -q git://github.com/jittat/cafe-grader-judge-scripts.git scripts
141 + hg clone git+ssh://git@github.com/nattee/cafe-grader-judge-scripts.git scripts
142 + mkdir raw
143 + mkdir ev-exam
144 + mkdir ev
145 + mkdir result
146 + mkdir log
147 +
148 + echo "Configuring grader"
149 +
150 + cp scripts/config/env_exam.rb.SAMPLE scripts/config/env_exam.rb
151 + cp scripts/config/env_grading.rb.SAMPLE scripts/config/env_grading.rb
152 +
153 + # create new environment.rb file
154 + echo "RAILS_ROOT = '$CAFE_PATH/web'" > scripts/config/environment.rb
155 + echo "GRADER_ROOT = '$CAFE_PATH/judge/scripts'" >> scripts/config/environment.rb
156 + echo "require File.join(File.dirname(__FILE__),'../lib/boot')" >> scripts/config/environment.rb
157 + echo "require File.dirname(__FILE__) + \"/env_#{GRADER_ENV}.rb\"" >> scripts/config/environment.rb
158 +
159 + # compiling box
160 + MACHINE_TYPE=`uname -m`
161 + if [ ${MACHINE_TYPE} == 'x86_64' ]; then
162 + gcc -std=c99 -o scripts/std-script/box scripts/std-script/box64-new.c
163 + else
164 + g++ -o scripts/std-script/box scripts/std-script/box.cc
165 + fi
166 +
167 +
168 + cd ..
169 +
170 + echo "Now you are ready to run cafe grader...."
171 + echo
172 + echo "Try:"
173 + echo
174 + echo " cd web"
175 + echo " rails s"
176 + echo
177 + echo "and access web at http://localhost:3000/"
178 + echo "The root username is 'root', its password is 'ioionrails'."
179 +
@@ -0,0 +1,85
1 + #!/usr/bin/env ruby
2 +
3 + def config
4 + Grader::Configuration.get_instance
5 + end
6 +
7 + def display_manual
8 + puts <<USAGE
9 + load_testcases
10 + using: load_testcases [problem_name ...]
11 + problem_name are list of "short name" of the problems
12 +
13 + options:
14 + --dry-run do nothing, just simulate the run
15 + --all import all problem. This might take several minutes
16 +
17 + USAGE
18 + end
19 +
20 + def process_options_and_stop_file
21 +
22 + # Process 'help' option
23 + if (ARGV.length == 0) or ((ARGV.length==1) and (/help/.match(ARGV[0])))
24 + display_manual
25 + exit(0)
26 + end
27 +
28 + #default options
29 + options = {
30 + :dry_run => false,
31 + }
32 +
33 + options[:dry_run] = (ARGV.delete('--dry') != nil)
34 + options[:all] = (ARGV.delete('--all') != nil)
35 +
36 + return options
37 + end
38 +
39 + def process_problem(prob,dry_run = false)
40 + prob.testcases.destroy_all
41 + testcases_root = File.expand_path(GRADER_ROOT+"/../ev/#{prob.name}/test_cases/")
42 + num = 1
43 + puts "Processing problem #{prob.name}"
44 + loop do
45 + file_root = testcases_root + "/#{num}/"
46 + puts " checking file #{file_root}"
47 + break unless File.exists? file_root
48 + input = File.read(file_root + "/input-#{num}.txt")
49 + answer = File.read(file_root + "/answer-#{num}.txt")
50 + puts " got test case ##{num} of size #{input.size} and #{answer.size}"
51 +
52 + #THIS IS JUST A PLACE HOLDER
53 + group = num #this is wrong!!! fix it!!
54 + score = 10
55 + #BEWARE
56 +
57 + prob.testcases.create(input: input,sol: answer, num: num, score:score,group: group) unless dry_run
58 + num += 1
59 + end
60 + end
61 +
62 + #########################################
63 + # main program
64 + #########################################
65 +
66 + options = process_options_and_stop_file
67 +
68 + # load grader environment
69 + GRADER_ENV = 'grading'
70 + require File.join(File.dirname(__FILE__),'config/environment')
71 +
72 + # boot rails, to be able to use the active record
73 + RAILS_ENV = config.rails_env
74 + require RAILS_ROOT + '/config/environment'
75 +
76 + if options[:all]
77 + Problem.all.each { |prob| process_problem(prob,options[:dry_run]) }
78 + else
79 + ARGV.each do |name|
80 + prob = Problem.find_by(name: name)
81 + process_problem(prob,options[:dry_run]) if prob
82 + puts "Cannot find the problem #{name}" unless prob
83 + end
84 + end
85 +
@@ -1,236 +1,242
1 1 #!/usr/bin/env ruby
2 2
3 3 require 'erb'
4 4 require 'fileutils'
5 5 require File.join(File.dirname(__FILE__),'lib/import_helper')
6 6
7 7 JUDGE_ENVIRONMENTS = [:grading, :exam]
8 8 ENV_INFO = {
9 9 :grading => {
10 10 :ev_dir => 'ev',
11 11 :raw_prefix => '',
12 12 },
13 13 :exam => {
14 14 :ev_dir => 'ev-exam',
15 15 :raw_prefix => 'ex.',
16 16 }
17 17 }
18 18
19 19 def input_filename(dir,i)
20 20 "#{dir}/input-#{i}.txt"
21 21 end
22 22
23 23 def answer_filename(dir,i)
24 24 "#{dir}/answer-#{i}.txt"
25 25 end
26 26
27 27 def build_testrun_info_from_dir(num_testruns, importing_test_dir, raw_prefix='')
28 28 filenames = Dir["#{importing_test_dir}/#{raw_prefix}*.in"].collect do |filename|
29 29 File.basename((/(.*)\.in/.match(filename))[1])
30 30 end
31 31 build_testrun_info(num_testruns,filenames,raw_prefix)
32 32 end
33 33
34 34 def copy_testcase(importing_test_dir,fname,dir,i)
35 35 FileUtils.cp("#{importing_test_dir}/#{fname}.in", "#{input_filename(dir,i)}")
36 36 FileUtils.cp("#{importing_test_dir}/#{fname}.sol", "#{answer_filename(dir,i)}")
37 37 end
38 38
39 39 def process_options(options)
40 40 i = 3
41 41 while i<ARGV.length
42 42 if ARGV[i]=='-t'
43 43 options[:time_limit] = ARGV[i+1].to_f if ARGV.length>i+1
44 44 i += 1
45 45 end
46 46 if ARGV[i]=='-m'
47 47 options[:mem_limit] = ARGV[i+1].to_i if ARGV.length>i+1
48 48 i += 1
49 49 end
50 50 i += 1
51 51 end
52 52 end
53 53
54 54 def print_usage
55 55 puts "using: import_problem_new name dir check [options]
56 56
57 57 where: name = problem_name (put '-' (dash) to use dir name)
58 58 dir = importing testcase directory
59 59 check = check script, which can be
60 60 'integer', 'text' (for standard script),
61 61 path_to_your_script, or
62 62 'wrapper:(path_to_your_wrapped_script)'
63 63 options: -t time-limit (in seconds)
64 64 -m memory-limit (in megabytes)
65 65
66 66 The script looks at test data files in the dir of the forms: *.in and
67 67 *.sol and import them to the evaluation dir for their environment,
68 68 based on their prefixes.
69 69
70 70 Currently supporting environments are:"
71 71
72 72 JUDGE_ENVIRONMENTS.each do |env|
73 73 prefix = ENV_INFO[env][:raw_prefix]
74 74 prefix = 'no prefix' if prefix==''
75 75 puts " * #{env}"
76 76 puts " import to: #{ENV_INFO[env][:ev_dir]}"
77 77 puts " prefix with: #{prefix} (e.g., #{ENV_INFO[env][:raw_prefix]}1.in, #{ENV_INFO[env][:raw_prefix]}5a.sol)"
78 78 end
79 79
80 80 puts"
81 81 For each environment, the script
82 82 * creates a directory for a problem in ev dir of that environment,
83 83 * copies testdata in the old format and create standard testcase config file
84 84 * copies a check script for grading
85 85 * creates a test_request template in the ev dir + '/test_request'
86 86
87 87 For wrapped checked script see comment in templates/check_wrapper for
88 88 information."
89 89 end
90 90
91 91 def count_testruns(testcase_dir, raw_prefix)
92 92 n = 0
93 93 begin
94 94 # check for test case n+1
95 95 if ((Dir["#{testcase_dir}/#{raw_prefix}#{n+1}.in"].length==0) and
96 96 (Dir["#{testcase_dir}/#{raw_prefix}#{n+1}[a-z].in"].length==0))
97 97 return n
98 98 end
99 99 n += 1
100 100 end while true
101 101 end
102 102
103 103 def create_dir_if_not_exists(dir)
104 104 if ! FileTest.exists? dir
105 105 FileUtils.mkdir(dir)
106 106 end
107 107 end
108 108
109 109 def import_problem(ev_dir, problem, testcase_dir, num_testruns, raw_prefix, check_script, options)
110 110 testrun_info = build_testrun_info_from_dir(num_testruns, testcase_dir, raw_prefix)
111 111
112 112 if !(FileTest.exists? ev_dir)
113 113 puts "Testdata dir (#{ev_dir}) not found."
114 114 return
115 115 end
116 116
117 117 problem_dir = "#{ev_dir}/#{problem}"
118 118
119 119 # start working
120 120 puts "creating directories"
121 121
122 122 create_dir_if_not_exists("#{problem_dir}")
123 123 create_dir_if_not_exists("#{problem_dir}/script")
124 124 create_dir_if_not_exists("#{problem_dir}/test_cases")
125 125
126 126 puts "copying testcases"
127 127
128 128 tr_num = 0
129 129
130 130 num_testcases = 0
131 131
132 132 testrun_info.each do |testrun|
133 133 tr_num += 1
134 134 puts "testrun: #{tr_num}"
135 -
135 +
136 136 testrun.each do |testcase_info|
137 137 testcase_num, testcase_fname = testcase_info
138 -
138 +
139 139 puts "copy #{testcase_fname} to #{testcase_num}"
140 -
140 +
141 141 create_dir_if_not_exists("#{problem_dir}/test_cases/#{testcase_num}")
142 142 copy_testcase("#{testcase_dir}",testcase_fname,"#{problem_dir}/test_cases/#{testcase_num}",testcase_num)
143 -
143 +
144 144 num_testcases += 1
145 145 end
146 146 end
147 -
147 +
148 + #also include any .txt files
149 + Dir.glob("#{testcase_dir}/*.txt") do |file|
150 + puts "copy data file #{file}"
151 + FileUtils.cp(file,"#{problem_dir}")
152 + end
153 +
148 154 # generating all_tests.cfg
149 155 puts "generating testcase config file"
150 -
156 +
151 157 template = File.open(SCRIPT_DIR + "/templates/all_tests.cfg.erb").read
152 158 all_test_cfg = ERB.new(template)
153 -
159 +
154 160 cfg_file = File.open("#{problem_dir}/test_cases/all_tests.cfg","w")
155 161 cfg_file.puts all_test_cfg.result binding
156 162 cfg_file.close
157 -
163 +
158 164 # copy check script
159 165 if res = /^wrapper:(.*)$/.match(check_script)
160 166 # wrapper script
161 167 check_script_fname = res[1]
162 168 script_name = File.basename(check_script_fname)
163 169 check_wrapper_template = File.open(SCRIPT_DIR + "/templates/check_wrapper").read
164 170 check_wrapper = ERB.new(check_wrapper_template)
165 -
171 +
166 172 check_file = File.open("#{problem_dir}/script/check","w")
167 173 check_file.puts check_wrapper.result binding
168 174 check_file.close
169 -
175 +
170 176 File.chmod(0755,"#{problem_dir}/script/check")
171 -
177 +
172 178 FileUtils.cp("#{check_script_fname}", "#{problem_dir}/script/#{script_name}")
173 179 else
174 180 if File.exists?(SCRIPT_DIR + "/templates/check.#{check_script}")
175 181 check_script_fname = SCRIPT_DIR + "/templates/check.#{check_script}"
176 182 else
177 183 check_script_fname = check_script
178 184 end
179 185 FileUtils.cp("#{check_script_fname}", "#{problem_dir}/script/check", :preserve => true)
180 186 end
181 -
187 +
182 188 # generating test_request directory
183 189 puts "generating test_request template"
184 190 FileUtils.mkdir_p("#{ev_dir}/test_request/#{problem}/script")
185 191 FileUtils.mkdir_p("#{ev_dir}/test_request/#{problem}/test_cases/1")
186 -
192 +
187 193 template = File.open(SCRIPT_DIR + "/templates/test_request_all_tests.cfg.erb").read
188 194 test_request_all_test_cfg = ERB.new(template)
189 -
195 +
190 196 cfg_file = File.open("#{ev_dir}/test_request/#{problem}/test_cases/all_tests.cfg","w")
191 197 cfg_file.puts test_request_all_test_cfg.result
192 198 cfg_file.close
193 -
199 +
194 200 FileUtils.cp("#{SCRIPT_DIR}/templates/check_empty",
195 201 "#{ev_dir}/test_request/#{problem}/script/check")
196 202 FileUtils.cp("#{SCRIPT_DIR}/templates/answer-1.txt",
197 203 "#{ev_dir}/test_request/#{problem}/test_cases/1")
198 -
204 +
199 205 puts "done"
200 206 end
201 207
202 208
203 209 SCRIPT_DIR = File.dirname(__FILE__)
204 210
205 211 # print usage
206 212 if (ARGV.length < 3) or (ARGV[2][0,1]=="-")
207 213 print_usage
208 214 exit(127)
209 215 end
210 216
211 217 # processing arguments
212 218 problem = ARGV[0]
213 219 testcase_dir = ARGV[1]
214 220 problem = File.basename(testcase_dir) if problem=="-"
215 221 check_script = ARGV[2]
216 222 options = {:time_limit => 1, :mem_limit => 16}
217 223 process_options(options)
218 224
219 225 JUDGE_ENVIRONMENTS.each do |env|
220 226 ev_dir = ENV_INFO[env][:ev_dir]
221 227 raw_prefix = ENV_INFO[env][:raw_prefix]
222 228
223 229 num_testruns = count_testruns(testcase_dir,raw_prefix)
224 230
225 231 puts ""
226 232 puts "*** Environment: #{env} (#{num_testruns} test runs) ***"
227 233 puts ""
228 234
229 235 import_problem(ev_dir,
230 236 problem,
231 237 testcase_dir,
232 238 num_testruns,
233 239 raw_prefix,
234 240 check_script,
235 241 options)
236 242 end
@@ -1,180 +1,181
1 1 #!/bin/sh
2 2
3 3 echo "This script will install and configure Cafe grader."
4 4
5 5 RUBY_VERSION=2.1.2
6 6 echo "This will install Ruby $RUBY_VERSION under RVM"
7 7
8 8 echo "Installing required apts"
9 9
10 10 sudo apt-get update
11 11 sudo apt-get install mysql-server mysql-client \
12 - g++ gcc apache2 libmysqlclient15-dev build-essential \
12 + g++ gcc apache2 libmysqlclient20 build-essential \
13 13 git-core openssl libreadline6 libreadline6-dev \
14 14 zlib1g zlib1g-dev libssl-dev libyaml-dev libsqlite3-dev \
15 15 sqlite3 libxml2-dev libxslt-dev autoconf libc6-dev \
16 16 ncurses-dev automake libtool bison subversion \
17 - pkg-config curl nodejs unzip pyflakes ruby default-jdk
17 + pkg-config curl nodejs unzip pyflakes ruby default-jdk \
18 + libmysqld-dev mercurial python-setuptools python-dev python3-numpy
18 19
19 20 echo "Installing RVM"
20 21 curl -k -L https://get.rvm.io | bash -s stable
21 22 source ~/.rvm/scripts/rvm
22 23
23 24 echo "Installing Ruby $RUBY_VERSION in RVM"
24 25
25 26 rvm install $RUBY_VERSION
26 27 rvm use $RUBY_VERSION
27 28
28 29 echo "Fetching Cafe Grader from Git repositories"
29 30
30 31 echo "Fetching web interface"
31 32
32 33 mkdir cafe_grader
33 34 cd cafe_grader
34 35 git clone -q git://github.com/jittat/cafe-grader-web.git web
35 36
36 37 echo "Configuring rails app"
37 38
38 39 cp web/config/application.rb.SAMPLE web/config/application.rb
39 40 cp web/config/initializers/cafe_grader_config.rb.SAMPLE web/config/initializers/cafe_grader_config.rb
40 41
41 42 #replace UTC in application.rb with the system timezone
42 43 timezone='UTC'
43 44 if [ -f '/etc/timezone' ]; then
44 45 timezone=\"`cat /etc/timezone`\"
45 46 else
46 47 if [ -f '/etc/sysconfig/clock' ]; then
47 48 timezone=`grep -e '^TIMEZONE' /etc/sysconfig/clock | grep -o -e '\".*\"'`
48 49 fi
49 50 fi
50 51 replace="s!'UTC'!$timezone!g"
51 52 sed -i $replace web/config/application.rb
52 53
53 54 echo "At this point we will need MySQL user and database."
54 55 echo "Have you created MySQL user and database for Cafe grader? (Y/N) "
55 56 read ch
56 57
57 58 if [ "$ch" = "n" -o "$ch" = "N" ]
58 59 then
59 60 echo "Please open another terminal and create the user and database for Cafe grader."
60 61 echo "Don't forget to grant access to that database for the user."
61 62 echo "Please have username, password, and database name ready before continue."
62 63 echo
63 64 echo "The following are instructions:"
64 65 echo "1. Run mysql:"
65 66 echo
66 67 echo " mysql -u root -p"
67 68 echo
68 69 echo " if you have just installed mysql, the root password is the one that you have just entered"
69 70 echo "2. Create a new database, a new user, and grant access to grader database:"
70 71 echo
71 72 echo " create user 'USERNAME'@'localhost' identified by 'PASSWORD';"
72 73 echo " create database \`DATABASENEME\`;"
73 74 echo " grant all on \`DATABASENAME\`.* to 'USERNAME'@'localhost';"
74 75 echo
75 76 echo " Replace USERNAME, PASSWORD, and DATABASENAME accordingly."
76 77 echo
77 78 echo "Hit enter when ready..."
78 79 read dummy
79 80 fi
80 81
81 82 CAFE_PATH=`pwd`
82 83
83 84 cd web
84 85
85 86 echo "Please provide grader database:"
86 87 read database
87 88
88 89 echo "Please provide grader username:"
89 90 read username
90 91
91 92 echo "Please provide $username password:"
92 93 read password
93 94
94 95 echo "development:" > config/database.yml
95 96 echo " adapter: mysql2" >> config/database.yml
96 97 echo " encoding: utf8" >> config/database.yml
97 98 echo " reconnect: false" >> config/database.yml
98 99 echo " database: $database" >> config/database.yml
99 100 echo " pool: 5" >> config/database.yml
100 101 echo " username: $username" >> config/database.yml
101 102 echo " password: $password" >> config/database.yml
102 103 echo " host: localhost" >> config/database.yml
103 104 echo " socket: /var/run/mysqld/mysqld.sock" >> config/database.yml
104 105 echo "" >> config/database.yml
105 106 echo "production:" >> config/database.yml
106 107 echo " adapter: mysql2" >> config/database.yml
107 108 echo " encoding: utf8" >> config/database.yml
108 109 echo " reconnect: false" >> config/database.yml
109 110 echo " database: $database" >> config/database.yml
110 111 echo " pool: 5" >> config/database.yml
111 112 echo " username: $username" >> config/database.yml
112 113 echo " password: $password" >> config/database.yml
113 114 echo " host: localhost" >> config/database.yml
114 115 echo " socket: /var/run/mysqld/mysqld.sock" >> config/database.yml
115 116
116 117 echo "Object.instance_eval{remove_const :GRADER_ROOT_DIR}" >> config/initializers/cafe_grader_config.rb
117 118 echo "Object.instance_eval{remove_const :GRADING_RESULT_DIR}" >> config/initializers/cafe_grader_config.rb
118 119 echo "GRADER_ROOT_DIR = '$CAFE_PATH/judge'" >> config/initializers/cafe_grader_config.rb
119 120 echo "GRADING_RESULT_DIR = '$CAFE_PATH/judge/result'" >> config/initializers/cafe_grader_config.rb
120 121
121 122 echo "Installing required gems"
122 123 gem install bundler
123 124 bundle install
124 125
125 126 echo "Running rake tasks to initialize database"
126 127
127 128 rake db:migrate
128 129 rake db:seed
129 130
130 131 echo "Running rake tasks to precompile the assets"
131 132
132 133 rake assets:precompile
133 134
134 135 echo "Intalling web interface complete..."
135 136 echo
136 137 echo "Fetching grader"
137 138
138 139 cd ..
139 140
140 141 mkdir judge
141 142 cd judge
142 143 git clone -q git://github.com/jittat/cafe-grader-judge-scripts.git scripts
143 144 mkdir raw
144 145 mkdir ev-exam
145 146 mkdir ev
146 147 mkdir result
147 148 mkdir log
148 149
149 150 echo "Configuring grader"
150 151
151 152 cp scripts/config/env_exam.rb.SAMPLE scripts/config/env_exam.rb
152 153 cp scripts/config/env_grading.rb.SAMPLE scripts/config/env_grading.rb
153 154
154 155 # create new environment.rb file
155 156 echo "RAILS_ROOT = '$CAFE_PATH/web'" > scripts/config/environment.rb
156 157 echo "GRADER_ROOT = '$CAFE_PATH/judge/scripts'" >> scripts/config/environment.rb
157 158 echo "require File.join(File.dirname(__FILE__),'../lib/boot')" >> scripts/config/environment.rb
158 159 echo "require File.dirname(__FILE__) + \"/env_#{GRADER_ENV}.rb\"" >> scripts/config/environment.rb
159 160
160 161 # compiling box
161 162 MACHINE_TYPE=`uname -m`
162 163 if [ ${MACHINE_TYPE} == 'x86_64' ]; then
163 164 gcc -std=c99 -o scripts/std-script/box scripts/std-script/box64-new.c
164 165 else
165 166 g++ -o scripts/std-script/box scripts/std-script/box.cc
166 167 fi
167 168
168 169
169 170 cd ..
170 171
171 172 echo "Now you are ready to run cafe grader...."
172 173 echo
173 174 echo "Try:"
174 175 echo
175 176 echo " cd web"
176 177 echo " rails s"
177 178 echo
178 179 echo "and access web at http://localhost:3000/"
179 180 echo "The root username is 'root', its password is 'ioionrails'."
180 181
@@ -1,100 +1,100
1 1 #!/usr/bin/env ruby
2 2
3 3 ENVIRONMENT_DIRS = ['ev', 'ev-exam']
4 4
5 5 def config
6 6 Grader::Configuration.get_instance
7 7 end
8 8
9 9 def rename_problem(old_problem_name, new_problem_name)
10 10
11 - if valid_problem_name(new_problem_name)
11 + unless valid_problem_name(new_problem_name)
12 12 puts "Bad new problem name: #{new_problem_name}"
13 13 return
14 14 end
15 15
16 16 problem = Problem.find_by_name(old_problem_name)
17 17 if problem==nil
18 18 puts "Problem #{old_problem_name} does not exist."
19 19 return
20 20 end
21 21
22 22 puts "Problem: #{old_problem_name} -> #{new_problem_name}"
23 23
24 24 ENVIRONMENT_DIRS.each do |dir|
25 25 problem_dir = File.join(GRADER_ROOT,'..',dir,old_problem_name)
26 26 new_problem_dir = File.join(GRADER_ROOT,'..',dir,new_problem_name)
27 27
28 28 if FileTest.exists? problem_dir
29 29 puts "Moving #{problem_dir} to #{new_problem_dir}."
30 30 File.rename(problem_dir, new_problem_dir)
31 31
32 32 tr_problem_dir = File.join(GRADER_ROOT,'..',dir,
33 33 'test_request',old_problem_name)
34 34 new_tr_problem_dir = File.join(GRADER_ROOT,'..',dir,
35 35 'test_request',new_problem_name)
36 36 File.rename(tr_problem_dir, new_tr_problem_dir)
37 37 end
38 38 end
39 39
40 40 problem.name = new_problem_name
41 41 problem.save
42 42 end
43 43
44 44 def usage
45 45 puts <<USAGE
46 46 Usage:
47 47 rename_problem [old_name] [new_name]
48 48 or
49 49 rename_problem -f [filename]
50 50
51 51 When using with -f, that file should contain, for each line, the old
52 52 problem name and its new name.
53 53
54 54 This script should be called at the judge root dir where dirs 'ev' and
55 55 'ev-exam' are.
56 56 USAGE
57 57 end
58 58
59 59 def valid_problem_name(name)
60 60 if name.length==0:
61 61 return false
62 62 else
63 - return !(/^[a-zA-Z0-9_\-]+$/ === name)
63 + return (/^[a-zA-Z0-9_\-]+$/ === name)
64 64 end
65 65 end
66 66
67 67 if (ARGV.length!=2)
68 68 usage
69 69 exit(0)
70 70 end
71 71
72 72 if ARGV[0]=='-f' and !FileTest.exists?(ARGV[1])
73 73 puts "File #{ARGV[1]} does not exist."
74 74 usage
75 75 exit(0)
76 76 end
77 77
78 78 # load grader environment
79 79 GRADER_ENV = 'grading'
80 80 require File.join(File.dirname(__FILE__),'config/environment')
81 81
82 82 # boot rails, to be able to rename the problem
83 83 RAILS_ENV = config.rails_env
84 84 require RAILS_ROOT + '/config/environment'
85 85
86 86 if ARGV[0]!='-f'
87 87 old_problem_name = ARGV[0]
88 88 new_problem_name = ARGV[1]
89 89
90 90 rename_problem(old_problem_name, new_problem_name)
91 91 else
92 92 lines = IO.readlines(ARGV[1])
93 93 lines.each do |line|
94 94 items = line.split
95 95 if items.length==2
96 96 old_name, new_name = items
97 97 rename_problem(old_name, new_name)
98 98 end
99 99 end
100 100 end
@@ -1,1760 +1,1772
1 1 /*
2 2 * A Simple Sandbox for Moe
3 3 *
4 4 * (c) 2001--2010 Martin Mares <mj@ucw.cz>
5 5 */
6 6
7 7 #define _LARGEFILE64_SOURCE
8 8 #define _GNU_SOURCE
9 9
10 10 /* Generated automatically by ./configure, please don't touch manually. */
11 11 #define CONFIG_BOX_KERNEL_AMD64 1
12 12 #define CONFIG_BOX_USER_AMD64 1
13 13 #define CONFIG_DIR "cf"
14 14 #define CONFIG_DIRECT_IO 1
15 15 #define CONFIG_ISOLATE_BOX_DIR "/tmp/box"
16 16 #define CONFIG_ISOLATE_CGROUP_ROOT "/sys/fs/cgroup"
17 17 #define CONFIG_ISOLATE_FIRST_GID 60000
18 18 #define CONFIG_ISOLATE_FIRST_UID 60000
19 19 #define CONFIG_ISOLATE_NUM_BOXES 100
20 20 #define CONFIG_LARGE_FILES 1
21 21 #define CONFIG_LFS 1
22 22 #define CONFIG_LINUX 1
23 23 #define CONFIG_LOCAL 1
24 24 #define CONFIG_UCW_PARTMAP_IS_MMAP 1
25 25 #define CONFIG_UCW_PERL 1
26 26 #define CONFIG_UCW_POOL_IS_MMAP 1
27 27 #define CONFIG_UCW_RADIX_SORTER_BITS 10
28 28 #define CONFIG_UCW_SHELL_UTILS 1
29 29 #define CPU_64BIT_POINTERS 1
30 30 #define CPU_ALLOW_UNALIGNED 1
31 31 #define CPU_AMD64 1
32 32 #define CPU_ARCH "default"
33 33 #define CPU_LITTLE_ENDIAN 1
34 34 #define CPU_PAGE_SIZE 4096
35 35 #define CPU_STRUCT_ALIGN 8
36 36 #define CWARNS_OFF " -Wno-pointer-sign"
37 37 #define HAVE_ASCII_DOC "none"
38 38 #define INSTALL_BIN_DIR "bin"
39 39 #define INSTALL_CONFIG_DIR "cf"
40 40 #define INSTALL_DOC_DIR "share/doc"
41 41 #define INSTALL_INCLUDE_DIR "include"
42 42 #define INSTALL_LIB_DIR "lib"
43 43 #define INSTALL_LOG_DIR "log"
44 44 #define INSTALL_MAN_DIR "share/man"
45 45 #define INSTALL_PERL_DIR "lib/perl5"
46 46 #define INSTALL_PKGCONFIG_DIR "lib/pkgconfig"
47 47 #define INSTALL_PREFIX
48 48 #define INSTALL_RUN_DIR "run"
49 49 #define INSTALL_SBIN_DIR "sbin"
50 50 #define INSTALL_SHARE_DIR "share"
51 51 #define INSTALL_STATE_DIR "lib"
52 52 #define INSTALL_USR_PREFIX
53 53 #define INSTALL_VAR_PREFIX
54 54 #define SHERLOCK_VERSION "3.99.2"
55 55 #define SHERLOCK_VERSION_CODE 3099002
56 56 #define SONAME_PREFIX "lib/"
57 57 #define UCW_VERSION "3.99.2"
58 58 #define UCW_VERSION_CODE 3099002
59 59
60 60 #include <errno.h>
61 61 #include <stdio.h>
62 62 #include <fcntl.h>
63 63 #include <stdlib.h>
64 64 #include <string.h>
65 65 #include <stdarg.h>
66 66 #include <stdint.h>
67 67 #include <unistd.h>
68 68 #include <getopt.h>
69 69 #include <time.h>
70 70 #include <sys/wait.h>
71 71 #include <sys/user.h>
72 72 #include <sys/time.h>
73 73 #include <sys/ptrace.h>
74 74 #include <sys/signal.h>
75 75 #include <sys/sysinfo.h>
76 76 #include <sys/resource.h>
77 77 #include <sys/utsname.h>
78 78 //#include <linux/ptrace.h>
79 79
80 80 #if defined(CONFIG_BOX_KERNEL_AMD64) && !defined(CONFIG_BOX_USER_AMD64)
81 81 #include <asm/unistd_32.h>
82 82 #define NATIVE_NR_execve 59 /* 64-bit execve */
83 83 #else
84 84 #include <asm/unistd.h>
85 85 #define NATIVE_NR_execve __NR_execve
86 86 #endif
87 87
88 88 #define NONRET __attribute__((noreturn))
89 89 #define UNUSED __attribute__((unused))
90 90 #define ARRAY_SIZE(a) (int)(sizeof(a)/sizeof(a[0]))
91 91
92 92 static int filter_syscalls; /* 0=off, 1=liberal, 2=totalitarian */
93 93 static int timeout; /* milliseconds */
94 94 static int wall_timeout;
95 95 static int extra_timeout;
96 96 static int pass_environ;
97 97 static int file_access;
98 98 static int verbose;
99 99 static int memory_limit;
100 100 static int stack_limit;
101 101 static char *redir_stdin, *redir_stdout, *redir_stderr;
102 102 static char *set_cwd;
103 103
104 104 static pid_t box_pid;
105 105 static int is_ptraced;
106 106 static volatile int timer_tick;
107 107 static struct timeval start_time;
108 108 static int ticks_per_sec;
109 109 static int exec_seen;
110 110 static int partial_line;
111 111
112 112 static int mem_peak_kb;
113 113 static int total_ms, wall_ms, sys_ms;
114 114
115 115 static void die(char *msg, ...) NONRET;
116 116 static void sample_mem_peak(void);
117 117
118 118 /*** Meta-files ***/
119 119
120 120 static FILE *metafile;
121 121
122 122 static void
123 123 meta_open(const char *name)
124 124 {
125 125 if (!strcmp(name, "-"))
126 126 {
127 127 metafile = stdout;
128 128 return;
129 129 }
130 130 metafile = fopen(name, "w");
131 131 if (!metafile)
132 132 die("Failed to open metafile '%s'",name);
133 133 }
134 134
135 135 static void
136 136 meta_close(void)
137 137 {
138 138 if (metafile && metafile != stdout)
139 139 fclose(metafile);
140 140 }
141 141
142 142 static void __attribute__((format(printf,1,2)))
143 143 meta_printf(const char *fmt, ...)
144 144 {
145 145 if (!metafile)
146 146 return;
147 147
148 148 va_list args;
149 149 va_start(args, fmt);
150 150 vfprintf(metafile, fmt, args);
151 151 va_end(args);
152 152 }
153 153
154 154
155 155 static void print_running_stat(double wall_time,
156 156 double user_time,
157 157 double system_time,
158 158 int mem_usage)
159 159 {
160 160 //total is user
161 161 //wall is wall
162 162 //
163 163 fprintf(stderr,"%.4lfr%.4lfu%.4lfs%dkbytes\n",
164 164 wall_time, user_time, system_time, mem_usage);
165 165 }
166 166
167 167 static void
168 168 final_stats(struct rusage *rus)
169 169 {
170 170 struct timeval total, now, wall;
171 171 timeradd(&rus->ru_utime, &rus->ru_stime, &total);
172 172 total_ms = total.tv_sec*1000 + total.tv_usec/1000;
173 173 gettimeofday(&now, NULL);
174 174 timersub(&now, &start_time, &wall);
175 175 wall_ms = wall.tv_sec*1000 + wall.tv_usec/1000;
176 176 sys_ms = rus->ru_stime.tv_sec * 1000 + rus->ru_stime.tv_usec / 1000;
177 177
178 178 meta_printf("time:%d.%03d\n", total_ms/1000, total_ms%1000);
179 179 meta_printf("time-wall:%d.%03d\n", wall_ms/1000, wall_ms%1000);
180 180 meta_printf("mem:%llu\n", (unsigned long long) mem_peak_kb * 1024);
181 181 }
182 182
183 183 /*** Messages and exits ***/
184 184
185 185 static void NONRET
186 186 box_exit(int rc)
187 187 {
188 188 if (box_pid > 0)
189 189 {
190 190 sample_mem_peak();
191 191 if (is_ptraced)
192 192 ptrace(PTRACE_KILL, box_pid);
193 193 kill(-box_pid, SIGKILL);
194 194 kill(box_pid, SIGKILL);
195 195 meta_printf("killed:1\n");
196 196
197 197 struct rusage rus;
198 198 int p, stat;
199 199 do
200 200 p = wait4(box_pid, &stat, 0, &rus);
201 201 while (p < 0 && errno == EINTR);
202 202 if (p < 0)
203 203 fprintf(stderr, "UGH: Lost track of the process (%m)\n");
204 204 else {
205 205 final_stats(&rus);
206 206 }
207 207 }
208 208 print_running_stat(
209 209 (double)wall_ms/1000,
210 210 (double)total_ms/1000,
211 211 (double)sys_ms/1000,
212 212 mem_peak_kb);
213 213 meta_close();
214 214 exit(rc);
215 215 }
216 216
217 217 static void
218 218 flush_line(void)
219 219 {
220 220 if (partial_line)
221 221 fputc('\n', stderr);
222 222 partial_line = 0;
223 223 }
224 224
225 225 /* Report an error of the sandbox itself */
226 226 static void NONRET __attribute__((format(printf,1,2)))
227 227 die(char *msg, ...)
228 228 {
229 229 va_list args;
230 230 va_start(args, msg);
231 231 flush_line();
232 232 char buf[1024];
233 233 vsnprintf(buf, sizeof(buf), msg, args);
234 234 meta_printf("status:XX\nmessage:%s\n", buf);
235 235 fputs(buf, stderr);
236 236 fputc('\n', stderr);
237 237 box_exit(2);
238 238 }
239 239
240 240 /* Report an error of the program inside the sandbox */
241 241 static void NONRET __attribute__((format(printf,1,2)))
242 242 err(char *msg, ...)
243 243 {
244 244 va_list args;
245 245 va_start(args, msg);
246 246 flush_line();
247 247 if (msg[0] && msg[1] && msg[2] == ':' && msg[3] == ' ')
248 248 {
249 249 meta_printf("status:%c%c\n", msg[0], msg[1]);
250 250 msg += 4;
251 251 }
252 252 char buf[1024];
253 253 vsnprintf(buf, sizeof(buf), msg, args);
254 254 meta_printf("message:%s\n", buf);
255 255 fputs(buf, stderr);
256 256 fputc('\n', stderr);
257 257 box_exit(1);
258 258 }
259 259
260 260 /* Write a message, but only if in verbose mode */
261 261 static void __attribute__((format(printf,1,2)))
262 262 msg(char *msg, ...)
263 263 {
264 264 va_list args;
265 265 va_start(args, msg);
266 266 if (verbose)
267 267 {
268 268 int len = strlen(msg);
269 269 if (len > 0)
270 270 partial_line = (msg[len-1] != '\n');
271 271 vfprintf(stderr, msg, args);
272 272 fflush(stderr);
273 273 }
274 274 va_end(args);
275 275 }
276 276
277 277 static void *
278 278 xmalloc(size_t size)
279 279 {
280 280 void *p = malloc(size);
281 281 if (!p)
282 282 die("Out of memory");
283 283 return p;
284 284 }
285 285
286 286 /*** Syscall rules ***/
287 287
288 288 static const char * const syscall_names[] = {
289 289
290 290 /* Syscall table automatically generated by mk-syscall-table */
291 291
292 292 /* 0 */ [ __NR_read ] = "read",
293 293 /* 1 */ [ __NR_write ] = "write",
294 294 /* 2 */ [ __NR_open ] = "open",
295 295 /* 3 */ [ __NR_close ] = "close",
296 296 /* 4 */ [ __NR_stat ] = "stat",
297 297 /* 5 */ [ __NR_fstat ] = "fstat",
298 298 /* 6 */ [ __NR_lstat ] = "lstat",
299 299 /* 7 */ [ __NR_poll ] = "poll",
300 300 /* 8 */ [ __NR_lseek ] = "lseek",
301 301 /* 9 */ [ __NR_mmap ] = "mmap",
302 302 /* 10 */ [ __NR_mprotect ] = "mprotect",
303 303 /* 11 */ [ __NR_munmap ] = "munmap",
304 304 /* 12 */ [ __NR_brk ] = "brk",
305 305 /* 13 */ [ __NR_rt_sigaction ] = "rt_sigaction",
306 306 /* 14 */ [ __NR_rt_sigprocmask ] = "rt_sigprocmask",
307 307 /* 15 */ [ __NR_rt_sigreturn ] = "rt_sigreturn",
308 308 /* 16 */ [ __NR_ioctl ] = "ioctl",
309 309 /* 17 */ [ __NR_pread64 ] = "pread64",
310 310 /* 18 */ [ __NR_pwrite64 ] = "pwrite64",
311 311 /* 19 */ [ __NR_readv ] = "readv",
312 312 /* 20 */ [ __NR_writev ] = "writev",
313 313 /* 21 */ [ __NR_access ] = "access",
314 314 /* 22 */ [ __NR_pipe ] = "pipe",
315 315 /* 23 */ [ __NR_select ] = "select",
316 316 /* 24 */ [ __NR_sched_yield ] = "sched_yield",
317 317 /* 25 */ [ __NR_mremap ] = "mremap",
318 318 /* 26 */ [ __NR_msync ] = "msync",
319 319 /* 27 */ [ __NR_mincore ] = "mincore",
320 320 /* 28 */ [ __NR_madvise ] = "madvise",
321 321 /* 29 */ [ __NR_shmget ] = "shmget",
322 322 /* 30 */ [ __NR_shmat ] = "shmat",
323 323 /* 31 */ [ __NR_shmctl ] = "shmctl",
324 324 /* 32 */ [ __NR_dup ] = "dup",
325 325 /* 33 */ [ __NR_dup2 ] = "dup2",
326 326 /* 34 */ [ __NR_pause ] = "pause",
327 327 /* 35 */ [ __NR_nanosleep ] = "nanosleep",
328 328 /* 36 */ [ __NR_getitimer ] = "getitimer",
329 329 /* 37 */ [ __NR_alarm ] = "alarm",
330 330 /* 38 */ [ __NR_setitimer ] = "setitimer",
331 331 /* 39 */ [ __NR_getpid ] = "getpid",
332 332 /* 40 */ [ __NR_sendfile ] = "sendfile",
333 333 /* 41 */ [ __NR_socket ] = "socket",
334 334 /* 42 */ [ __NR_connect ] = "connect",
335 335 /* 43 */ [ __NR_accept ] = "accept",
336 336 /* 44 */ [ __NR_sendto ] = "sendto",
337 337 /* 45 */ [ __NR_recvfrom ] = "recvfrom",
338 338 /* 46 */ [ __NR_sendmsg ] = "sendmsg",
339 339 /* 47 */ [ __NR_recvmsg ] = "recvmsg",
340 340 /* 48 */ [ __NR_shutdown ] = "shutdown",
341 341 /* 49 */ [ __NR_bind ] = "bind",
342 342 /* 50 */ [ __NR_listen ] = "listen",
343 343 /* 51 */ [ __NR_getsockname ] = "getsockname",
344 344 /* 52 */ [ __NR_getpeername ] = "getpeername",
345 345 /* 53 */ [ __NR_socketpair ] = "socketpair",
346 346 /* 54 */ [ __NR_setsockopt ] = "setsockopt",
347 347 /* 55 */ [ __NR_getsockopt ] = "getsockopt",
348 348 /* 56 */ [ __NR_clone ] = "clone",
349 349 /* 57 */ [ __NR_fork ] = "fork",
350 350 /* 58 */ [ __NR_vfork ] = "vfork",
351 351 /* 59 */ [ __NR_execve ] = "execve",
352 352 /* 60 */ [ __NR_exit ] = "exit",
353 353 /* 61 */ [ __NR_wait4 ] = "wait4",
354 354 /* 62 */ [ __NR_kill ] = "kill",
355 355 /* 63 */ [ __NR_uname ] = "uname",
356 356 /* 64 */ [ __NR_semget ] = "semget",
357 357 /* 65 */ [ __NR_semop ] = "semop",
358 358 /* 66 */ [ __NR_semctl ] = "semctl",
359 359 /* 67 */ [ __NR_shmdt ] = "shmdt",
360 360 /* 68 */ [ __NR_msgget ] = "msgget",
361 361 /* 69 */ [ __NR_msgsnd ] = "msgsnd",
362 362 /* 70 */ [ __NR_msgrcv ] = "msgrcv",
363 363 /* 71 */ [ __NR_msgctl ] = "msgctl",
364 364 /* 72 */ [ __NR_fcntl ] = "fcntl",
365 365 /* 73 */ [ __NR_flock ] = "flock",
366 366 /* 74 */ [ __NR_fsync ] = "fsync",
367 367 /* 75 */ [ __NR_fdatasync ] = "fdatasync",
368 368 /* 76 */ [ __NR_truncate ] = "truncate",
369 369 /* 77 */ [ __NR_ftruncate ] = "ftruncate",
370 370 /* 78 */ [ __NR_getdents ] = "getdents",
371 371 /* 79 */ [ __NR_getcwd ] = "getcwd",
372 372 /* 80 */ [ __NR_chdir ] = "chdir",
373 373 /* 81 */ [ __NR_fchdir ] = "fchdir",
374 374 /* 82 */ [ __NR_rename ] = "rename",
375 375 /* 83 */ [ __NR_mkdir ] = "mkdir",
376 376 /* 84 */ [ __NR_rmdir ] = "rmdir",
377 377 /* 85 */ [ __NR_creat ] = "creat",
378 378 /* 86 */ [ __NR_link ] = "link",
379 379 /* 87 */ [ __NR_unlink ] = "unlink",
380 380 /* 88 */ [ __NR_symlink ] = "symlink",
381 381 /* 89 */ [ __NR_readlink ] = "readlink",
382 382 /* 90 */ [ __NR_chmod ] = "chmod",
383 383 /* 91 */ [ __NR_fchmod ] = "fchmod",
384 384 /* 92 */ [ __NR_chown ] = "chown",
385 385 /* 93 */ [ __NR_fchown ] = "fchown",
386 386 /* 94 */ [ __NR_lchown ] = "lchown",
387 387 /* 95 */ [ __NR_umask ] = "umask",
388 388 /* 96 */ [ __NR_gettimeofday ] = "gettimeofday",
389 389 /* 97 */ [ __NR_getrlimit ] = "getrlimit",
390 390 /* 98 */ [ __NR_getrusage ] = "getrusage",
391 391 /* 99 */ [ __NR_sysinfo ] = "sysinfo",
392 392 /* 100 */ [ __NR_times ] = "times",
393 393 /* 101 */ [ __NR_ptrace ] = "ptrace",
394 394 /* 102 */ [ __NR_getuid ] = "getuid",
395 395 /* 103 */ [ __NR_syslog ] = "syslog",
396 396 /* 104 */ [ __NR_getgid ] = "getgid",
397 397 /* 105 */ [ __NR_setuid ] = "setuid",
398 398 /* 106 */ [ __NR_setgid ] = "setgid",
399 399 /* 107 */ [ __NR_geteuid ] = "geteuid",
400 400 /* 108 */ [ __NR_getegid ] = "getegid",
401 401 /* 109 */ [ __NR_setpgid ] = "setpgid",
402 402 /* 110 */ [ __NR_getppid ] = "getppid",
403 403 /* 111 */ [ __NR_getpgrp ] = "getpgrp",
404 404 /* 112 */ [ __NR_setsid ] = "setsid",
405 405 /* 113 */ [ __NR_setreuid ] = "setreuid",
406 406 /* 114 */ [ __NR_setregid ] = "setregid",
407 407 /* 115 */ [ __NR_getgroups ] = "getgroups",
408 408 /* 116 */ [ __NR_setgroups ] = "setgroups",
409 409 /* 117 */ [ __NR_setresuid ] = "setresuid",
410 410 /* 118 */ [ __NR_getresuid ] = "getresuid",
411 411 /* 119 */ [ __NR_setresgid ] = "setresgid",
412 412 /* 120 */ [ __NR_getresgid ] = "getresgid",
413 413 /* 121 */ [ __NR_getpgid ] = "getpgid",
414 414 /* 122 */ [ __NR_setfsuid ] = "setfsuid",
415 415 /* 123 */ [ __NR_setfsgid ] = "setfsgid",
416 416 /* 124 */ [ __NR_getsid ] = "getsid",
417 417 /* 125 */ [ __NR_capget ] = "capget",
418 418 /* 126 */ [ __NR_capset ] = "capset",
419 419 /* 127 */ [ __NR_rt_sigpending ] = "rt_sigpending",
420 420 /* 128 */ [ __NR_rt_sigtimedwait ] = "rt_sigtimedwait",
421 421 /* 129 */ [ __NR_rt_sigqueueinfo ] = "rt_sigqueueinfo",
422 422 /* 130 */ [ __NR_rt_sigsuspend ] = "rt_sigsuspend",
423 423 /* 131 */ [ __NR_sigaltstack ] = "sigaltstack",
424 424 /* 132 */ [ __NR_utime ] = "utime",
425 425 /* 133 */ [ __NR_mknod ] = "mknod",
426 426 /* 134 */ [ __NR_uselib ] = "uselib",
427 427 /* 135 */ [ __NR_personality ] = "personality",
428 428 /* 136 */ [ __NR_ustat ] = "ustat",
429 429 /* 137 */ [ __NR_statfs ] = "statfs",
430 430 /* 138 */ [ __NR_fstatfs ] = "fstatfs",
431 431 /* 139 */ [ __NR_sysfs ] = "sysfs",
432 432 /* 140 */ [ __NR_getpriority ] = "getpriority",
433 433 /* 141 */ [ __NR_setpriority ] = "setpriority",
434 434 /* 142 */ [ __NR_sched_setparam ] = "sched_setparam",
435 435 /* 143 */ [ __NR_sched_getparam ] = "sched_getparam",
436 436 /* 144 */ [ __NR_sched_setscheduler ] = "sched_setscheduler",
437 437 /* 145 */ [ __NR_sched_getscheduler ] = "sched_getscheduler",
438 438 /* 146 */ [ __NR_sched_get_priority_max ] = "sched_get_priority_max",
439 439 /* 147 */ [ __NR_sched_get_priority_min ] = "sched_get_priority_min",
440 440 /* 148 */ [ __NR_sched_rr_get_interval ] = "sched_rr_get_interval",
441 441 /* 149 */ [ __NR_mlock ] = "mlock",
442 442 /* 150 */ [ __NR_munlock ] = "munlock",
443 443 /* 151 */ [ __NR_mlockall ] = "mlockall",
444 444 /* 152 */ [ __NR_munlockall ] = "munlockall",
445 445 /* 153 */ [ __NR_vhangup ] = "vhangup",
446 446 /* 154 */ [ __NR_modify_ldt ] = "modify_ldt",
447 447 /* 155 */ [ __NR_pivot_root ] = "pivot_root",
448 448 /* 156 */ [ __NR__sysctl ] = "_sysctl",
449 449 /* 157 */ [ __NR_prctl ] = "prctl",
450 450 /* 158 */ [ __NR_arch_prctl ] = "arch_prctl",
451 451 /* 159 */ [ __NR_adjtimex ] = "adjtimex",
452 452 /* 160 */ [ __NR_setrlimit ] = "setrlimit",
453 453 /* 161 */ [ __NR_chroot ] = "chroot",
454 454 /* 162 */ [ __NR_sync ] = "sync",
455 455 /* 163 */ [ __NR_acct ] = "acct",
456 456 /* 164 */ [ __NR_settimeofday ] = "settimeofday",
457 457 /* 165 */ [ __NR_mount ] = "mount",
458 458 /* 166 */ [ __NR_umount2 ] = "umount2",
459 459 /* 167 */ [ __NR_swapon ] = "swapon",
460 460 /* 168 */ [ __NR_swapoff ] = "swapoff",
461 461 /* 169 */ [ __NR_reboot ] = "reboot",
462 462 /* 170 */ [ __NR_sethostname ] = "sethostname",
463 463 /* 171 */ [ __NR_setdomainname ] = "setdomainname",
464 464 /* 172 */ [ __NR_iopl ] = "iopl",
465 465 /* 173 */ [ __NR_ioperm ] = "ioperm",
466 466 /* 174 */ [ __NR_create_module ] = "create_module",
467 467 /* 175 */ [ __NR_init_module ] = "init_module",
468 468 /* 176 */ [ __NR_delete_module ] = "delete_module",
469 469 /* 177 */ [ __NR_get_kernel_syms ] = "get_kernel_syms",
470 470 /* 178 */ [ __NR_query_module ] = "query_module",
471 471 /* 179 */ [ __NR_quotactl ] = "quotactl",
472 472 /* 180 */ [ __NR_nfsservctl ] = "nfsservctl",
473 473 /* 181 */ [ __NR_getpmsg ] = "getpmsg",
474 474 /* 182 */ [ __NR_putpmsg ] = "putpmsg",
475 475 /* 183 */ [ __NR_afs_syscall ] = "afs_syscall",
476 476 /* 184 */ [ __NR_tuxcall ] = "tuxcall",
477 477 /* 185 */ [ __NR_security ] = "security",
478 478 /* 186 */ [ __NR_gettid ] = "gettid",
479 479 /* 187 */ [ __NR_readahead ] = "readahead",
480 480 /* 188 */ [ __NR_setxattr ] = "setxattr",
481 481 /* 189 */ [ __NR_lsetxattr ] = "lsetxattr",
482 482 /* 190 */ [ __NR_fsetxattr ] = "fsetxattr",
483 483 /* 191 */ [ __NR_getxattr ] = "getxattr",
484 484 /* 192 */ [ __NR_lgetxattr ] = "lgetxattr",
485 485 /* 193 */ [ __NR_fgetxattr ] = "fgetxattr",
486 486 /* 194 */ [ __NR_listxattr ] = "listxattr",
487 487 /* 195 */ [ __NR_llistxattr ] = "llistxattr",
488 488 /* 196 */ [ __NR_flistxattr ] = "flistxattr",
489 489 /* 197 */ [ __NR_removexattr ] = "removexattr",
490 490 /* 198 */ [ __NR_lremovexattr ] = "lremovexattr",
491 491 /* 199 */ [ __NR_fremovexattr ] = "fremovexattr",
492 492 /* 200 */ [ __NR_tkill ] = "tkill",
493 493 /* 201 */ [ __NR_time ] = "time",
494 494 /* 202 */ [ __NR_futex ] = "futex",
495 495 /* 203 */ [ __NR_sched_setaffinity ] = "sched_setaffinity",
496 496 /* 204 */ [ __NR_sched_getaffinity ] = "sched_getaffinity",
497 497 /* 205 */ [ __NR_set_thread_area ] = "set_thread_area",
498 498 /* 206 */ [ __NR_io_setup ] = "io_setup",
499 499 /* 207 */ [ __NR_io_destroy ] = "io_destroy",
500 500 /* 208 */ [ __NR_io_getevents ] = "io_getevents",
501 501 /* 209 */ [ __NR_io_submit ] = "io_submit",
502 502 /* 210 */ [ __NR_io_cancel ] = "io_cancel",
503 503 /* 211 */ [ __NR_get_thread_area ] = "get_thread_area",
504 504 /* 212 */ [ __NR_lookup_dcookie ] = "lookup_dcookie",
505 505 /* 213 */ [ __NR_epoll_create ] = "epoll_create",
506 506 /* 214 */ [ __NR_epoll_ctl_old ] = "epoll_ctl_old",
507 507 /* 215 */ [ __NR_epoll_wait_old ] = "epoll_wait_old",
508 508 /* 216 */ [ __NR_remap_file_pages ] = "remap_file_pages",
509 509 /* 217 */ [ __NR_getdents64 ] = "getdents64",
510 510 /* 218 */ [ __NR_set_tid_address ] = "set_tid_address",
511 511 /* 219 */ [ __NR_restart_syscall ] = "restart_syscall",
512 512 /* 220 */ [ __NR_semtimedop ] = "semtimedop",
513 513 /* 221 */ [ __NR_fadvise64 ] = "fadvise64",
514 514 /* 222 */ [ __NR_timer_create ] = "timer_create",
515 515 /* 223 */ [ __NR_timer_settime ] = "timer_settime",
516 516 /* 224 */ [ __NR_timer_gettime ] = "timer_gettime",
517 517 /* 225 */ [ __NR_timer_getoverrun ] = "timer_getoverrun",
518 518 /* 226 */ [ __NR_timer_delete ] = "timer_delete",
519 519 /* 227 */ [ __NR_clock_settime ] = "clock_settime",
520 520 /* 228 */ [ __NR_clock_gettime ] = "clock_gettime",
521 521 /* 229 */ [ __NR_clock_getres ] = "clock_getres",
522 522 /* 230 */ [ __NR_clock_nanosleep ] = "clock_nanosleep",
523 523 /* 231 */ [ __NR_exit_group ] = "exit_group",
524 524 /* 232 */ [ __NR_epoll_wait ] = "epoll_wait",
525 525 /* 233 */ [ __NR_epoll_ctl ] = "epoll_ctl",
526 526 /* 234 */ [ __NR_tgkill ] = "tgkill",
527 527 /* 235 */ [ __NR_utimes ] = "utimes",
528 528 /* 236 */ [ __NR_vserver ] = "vserver",
529 529 /* 237 */ [ __NR_mbind ] = "mbind",
530 530 /* 238 */ [ __NR_set_mempolicy ] = "set_mempolicy",
531 531 /* 239 */ [ __NR_get_mempolicy ] = "get_mempolicy",
532 532 /* 240 */ [ __NR_mq_open ] = "mq_open",
533 533 /* 241 */ [ __NR_mq_unlink ] = "mq_unlink",
534 534 /* 242 */ [ __NR_mq_timedsend ] = "mq_timedsend",
535 535 /* 243 */ [ __NR_mq_timedreceive ] = "mq_timedreceive",
536 536 /* 244 */ [ __NR_mq_notify ] = "mq_notify",
537 537 /* 245 */ [ __NR_mq_getsetattr ] = "mq_getsetattr",
538 538 /* 246 */ [ __NR_kexec_load ] = "kexec_load",
539 539 /* 247 */ [ __NR_waitid ] = "waitid",
540 540 /* 248 */ [ __NR_add_key ] = "add_key",
541 541 /* 249 */ [ __NR_request_key ] = "request_key",
542 542 /* 250 */ [ __NR_keyctl ] = "keyctl",
543 543 /* 251 */ [ __NR_ioprio_set ] = "ioprio_set",
544 544 /* 252 */ [ __NR_ioprio_get ] = "ioprio_get",
545 545 /* 253 */ [ __NR_inotify_init ] = "inotify_init",
546 546 /* 254 */ [ __NR_inotify_add_watch ] = "inotify_add_watch",
547 547 /* 255 */ [ __NR_inotify_rm_watch ] = "inotify_rm_watch",
548 548 /* 256 */ [ __NR_migrate_pages ] = "migrate_pages",
549 549 /* 257 */ [ __NR_openat ] = "openat",
550 550 /* 258 */ [ __NR_mkdirat ] = "mkdirat",
551 551 /* 259 */ [ __NR_mknodat ] = "mknodat",
552 552 /* 260 */ [ __NR_fchownat ] = "fchownat",
553 553 /* 261 */ [ __NR_futimesat ] = "futimesat",
554 554 /* 262 */ [ __NR_newfstatat ] = "newfstatat",
555 555 /* 263 */ [ __NR_unlinkat ] = "unlinkat",
556 556 /* 264 */ [ __NR_renameat ] = "renameat",
557 557 /* 265 */ [ __NR_linkat ] = "linkat",
558 558 /* 266 */ [ __NR_symlinkat ] = "symlinkat",
559 559 /* 267 */ [ __NR_readlinkat ] = "readlinkat",
560 560 /* 268 */ [ __NR_fchmodat ] = "fchmodat",
561 561 /* 269 */ [ __NR_faccessat ] = "faccessat",
562 562 /* 270 */ [ __NR_pselect6 ] = "pselect6",
563 563 /* 271 */ [ __NR_ppoll ] = "ppoll",
564 564 /* 272 */ [ __NR_unshare ] = "unshare",
565 565 /* 273 */ [ __NR_set_robust_list ] = "set_robust_list",
566 566 /* 274 */ [ __NR_get_robust_list ] = "get_robust_list",
567 567 /* 275 */ [ __NR_splice ] = "splice",
568 568 /* 276 */ [ __NR_tee ] = "tee",
569 569 /* 277 */ [ __NR_sync_file_range ] = "sync_file_range",
570 570 /* 278 */ [ __NR_vmsplice ] = "vmsplice",
571 571 /* 279 */ [ __NR_move_pages ] = "move_pages",
572 572 /* 280 */ [ __NR_utimensat ] = "utimensat",
573 573 /* 281 */ [ __NR_epoll_pwait ] = "epoll_pwait",
574 574 /* 282 */ [ __NR_signalfd ] = "signalfd",
575 575 /* 283 */ [ __NR_timerfd_create ] = "timerfd_create",
576 576 /* 284 */ [ __NR_eventfd ] = "eventfd",
577 577 /* 285 */ [ __NR_fallocate ] = "fallocate",
578 578 /* 286 */ [ __NR_timerfd_settime ] = "timerfd_settime",
579 579 /* 287 */ [ __NR_timerfd_gettime ] = "timerfd_gettime",
580 580 /* 288 */ [ __NR_accept4 ] = "accept4",
581 581 /* 289 */ [ __NR_signalfd4 ] = "signalfd4",
582 582 /* 290 */ [ __NR_eventfd2 ] = "eventfd2",
583 583 /* 291 */ [ __NR_epoll_create1 ] = "epoll_create1",
584 584 /* 292 */ [ __NR_dup3 ] = "dup3",
585 585 /* 293 */ [ __NR_pipe2 ] = "pipe2",
586 586 /* 294 */ [ __NR_inotify_init1 ] = "inotify_init1",
587 587 /* 295 */ [ __NR_preadv ] = "preadv",
588 588 /* 296 */ [ __NR_pwritev ] = "pwritev",
589 589 /* 297 */ [ __NR_rt_tgsigqueueinfo ] = "rt_tgsigqueueinfo",
590 590 /* 298 */ [ __NR_perf_event_open ] = "perf_event_open",
591 591 /* 299 */ [ __NR_recvmmsg ] = "recvmmsg",
592 592 /* 300 */ [ __NR_fanotify_init ] = "fanotify_init",
593 593 /* 301 */ [ __NR_fanotify_mark ] = "fanotify_mark",
594 594 /* 302 */ [ __NR_prlimit64 ] = "prlimit64",
595 595 /* 303 */ [ __NR_name_to_handle_at ] = "name_to_handle_at",
596 596 /* 304 */ [ __NR_open_by_handle_at ] = "open_by_handle_at",
597 597 /* 305 */ [ __NR_clock_adjtime ] = "clock_adjtime",
598 598 /* 306 */ [ __NR_syncfs ] = "syncfs",
599 599 /* 307 */ [ __NR_sendmmsg ] = "sendmmsg",
600 600 /* 308 */ [ __NR_setns ] = "setns",
601 601 /* 309 */ [ __NR_getcpu ] = "getcpu",
602 602 /* 310 */ [ __NR_process_vm_readv ] = "process_vm_readv",
603 603 /* 311 */ [ __NR_process_vm_writev ] = "process_vm_writev",
604 604 /* 312 */ [ __NR_kcmp ] = "kcmp",
605 605 /* 313 */ [ __NR_finit_module ] = "finit_module",
606 + /* 314 */ [ __NR_sched_setattr ] = "sched_setattr",
607 + /* 315 */ [ __NR_sched_getattr ] = "sched_getattr",
608 + /* 316 */ [ __NR_renameat2 ] = "renameat2",
609 + /* 317 */ [ __NR_seccomp ] = "seccomp",
610 + /* 318 */ [ __NR_getrandom ] = "getrandom",
611 + /* 319 */ [ __NR_memfd_create ] = "memfd_create",
612 + /* 320 */ [ __NR_kexec_file_load ] = "kexec_file_load",
613 + /* 321 */ [ __NR_bpf ] = "bpf",
614 + /* 322 */ [ __NR_execveat ] = "execveat",
615 + /* 323 */ [ __NR_userfaultfd ] = "userfaultfd",
616 + /* 324 */ [ __NR_membarrier ] = "membarrier",
617 + /* 325 */ [ __NR_mlock2 ] = "mlock2",
606 618 };
607 619 #define NUM_SYSCALLS ARRAY_SIZE(syscall_names)
608 620 #define NUM_ACTIONS (NUM_SYSCALLS+64)
609 621
610 622 enum action {
611 623 A_DEFAULT, // Use the default action
612 624 A_NO, // Always forbid
613 625 A_YES, // Always permit
614 626 A_FILENAME, // Permit if arg1 is a known filename
615 627 A_ACTION_MASK = 15,
616 628 A_NO_RETVAL = 32, // Does not return a value
617 629 A_SAMPLE_MEM = 64, // Sample memory usage before the syscall
618 630 A_LIBERAL = 128, // Valid only in liberal mode
619 631 // Must fit in a unsigned char
620 632 };
621 633
622 634 static unsigned char syscall_action[NUM_ACTIONS] = {
623 635 #define S(x) [__NR_##x]
624 636
625 637 // Syscalls permitted for specific file names
626 638 S(open) = A_FILENAME,
627 639 S(creat) = A_FILENAME,
628 640 S(unlink) = A_FILENAME,
629 641 S(access) = A_FILENAME,
630 642 S(truncate) = A_FILENAME,
631 643 S(stat) = A_FILENAME,
632 644 S(lstat) = A_FILENAME,
633 645 S(readlink) = A_FILENAME,
634 646 #ifndef CONFIG_BOX_USER_AMD64
635 647 S(oldstat) = A_FILENAME,
636 648 S(oldlstat) = A_FILENAME,
637 649 S(truncate64) = A_FILENAME,
638 650 S(stat64) = A_FILENAME,
639 651 S(lstat64) = A_FILENAME,
640 652 #endif
641 653
642 654 // Syscalls permitted always
643 655 S(exit) = A_YES | A_SAMPLE_MEM,
644 656 S(read) = A_YES,
645 657 S(write) = A_YES,
646 658 S(close) = A_YES,
647 659 S(lseek) = A_YES,
648 660 S(getpid) = A_YES,
649 661 S(getuid) = A_YES,
650 662 S(dup) = A_YES,
651 663 S(brk) = A_YES,
652 664 S(getgid) = A_YES,
653 665 S(geteuid) = A_YES,
654 666 S(getegid) = A_YES,
655 667 S(dup2) = A_YES,
656 668 S(ftruncate) = A_YES,
657 669 S(fstat) = A_YES,
658 670 S(personality) = A_YES,
659 671 S(readv) = A_YES,
660 672 S(writev) = A_YES,
661 673 S(getresuid) = A_YES,
662 674 #ifdef __NR_pread64
663 675 S(pread64) = A_YES,
664 676 S(pwrite64) = A_YES,
665 677 #else
666 678 S(pread) = A_YES,
667 679 S(pwrite) = A_YES,
668 680 #endif
669 681 S(fcntl) = A_YES,
670 682 S(mmap) = A_YES,
671 683 S(munmap) = A_YES,
672 684 S(ioctl) = A_YES,
673 685 S(uname) = A_YES,
674 686 S(gettid) = A_YES,
675 687 S(set_thread_area) = A_YES,
676 688 S(get_thread_area) = A_YES,
677 689 S(set_tid_address) = A_YES,
678 690 S(exit_group) = A_YES | A_SAMPLE_MEM,
679 691 #ifdef CONFIG_BOX_USER_AMD64
680 692 S(arch_prctl) = A_YES,
681 693 #else
682 694 S(oldfstat) = A_YES,
683 695 S(ftruncate64) = A_YES,
684 696 S(_llseek) = A_YES,
685 697 S(fstat64) = A_YES,
686 698 S(fcntl64) = A_YES,
687 699 S(mmap2) = A_YES,
688 700 #endif
689 701
690 702 // Syscalls permitted only in liberal mode
691 703 S(time) = A_YES | A_LIBERAL,
692 704 S(alarm) = A_YES | A_LIBERAL,
693 705 S(pause) = A_YES | A_LIBERAL,
694 706 S(fchmod) = A_YES | A_LIBERAL,
695 707 S(getrlimit) = A_YES | A_LIBERAL,
696 708 S(getrusage) = A_YES | A_LIBERAL,
697 709 S(gettimeofday) = A_YES | A_LIBERAL,
698 710 S(select) = A_YES | A_LIBERAL,
699 711 S(setitimer) = A_YES | A_LIBERAL,
700 712 S(getitimer) = A_YES | A_LIBERAL,
701 713 S(mprotect) = A_YES | A_LIBERAL,
702 714 S(getdents) = A_YES | A_LIBERAL,
703 715 S(getdents64) = A_YES | A_LIBERAL,
704 716 S(fdatasync) = A_YES | A_LIBERAL,
705 717 S(mremap) = A_YES | A_LIBERAL,
706 718 S(poll) = A_YES | A_LIBERAL,
707 719 S(getcwd) = A_YES | A_LIBERAL,
708 720 S(nanosleep) = A_YES | A_LIBERAL,
709 721 S(rt_sigreturn) = A_YES | A_LIBERAL | A_NO_RETVAL,
710 722 S(rt_sigaction) = A_YES | A_LIBERAL,
711 723 S(rt_sigprocmask) = A_YES | A_LIBERAL,
712 724 S(rt_sigpending) = A_YES | A_LIBERAL,
713 725 S(rt_sigtimedwait) = A_YES | A_LIBERAL,
714 726 S(rt_sigqueueinfo) = A_YES | A_LIBERAL,
715 727 S(rt_sigsuspend) = A_YES | A_LIBERAL,
716 728 S(_sysctl) = A_YES | A_LIBERAL,
717 729 #ifndef CONFIG_BOX_USER_AMD64
718 730 S(sigaction) = A_YES | A_LIBERAL,
719 731 S(sgetmask) = A_YES | A_LIBERAL,
720 732 S(ssetmask) = A_YES | A_LIBERAL,
721 733 S(sigsuspend) = A_YES | A_LIBERAL,
722 734 S(sigpending) = A_YES | A_LIBERAL,
723 735 S(sigreturn) = A_YES | A_LIBERAL | A_NO_RETVAL,
724 736 S(sigprocmask) = A_YES | A_LIBERAL,
725 737 S(ugetrlimit) = A_YES | A_LIBERAL,
726 738 S(readdir) = A_YES | A_LIBERAL,
727 739 S(signal) = A_YES | A_LIBERAL,
728 740 S(_newselect) = A_YES | A_LIBERAL,
729 741 #endif
730 742
731 743 #undef S
732 744 };
733 745
734 746 static const char *
735 747 syscall_name(unsigned int id, char *buf)
736 748 {
737 749 if (id < NUM_SYSCALLS && syscall_names[id])
738 750 return syscall_names[id];
739 751 else
740 752 {
741 753 sprintf(buf, "#%d", id);
742 754 return buf;
743 755 }
744 756 }
745 757
746 758 static int
747 759 syscall_by_name(char *name)
748 760 {
749 761 for (unsigned int i=0; i<NUM_SYSCALLS; i++)
750 762 if (syscall_names[i] && !strcmp(syscall_names[i], name))
751 763 return i;
752 764 if (name[0] == '#')
753 765 name++;
754 766 if (!*name)
755 767 return -1;
756 768 char *ep;
757 769 unsigned long l = strtoul(name, &ep, 0);
758 770 if (*ep)
759 771 return -1;
760 772 if (l >= NUM_ACTIONS)
761 773 return NUM_ACTIONS;
762 774 return l;
763 775 }
764 776
765 777 static int
766 778 set_syscall_action(char *a)
767 779 {
768 780 char *sep = strchr(a, '=');
769 781 enum action act = A_YES;
770 782 if (sep)
771 783 {
772 784 *sep++ = 0;
773 785 if (!strcmp(sep, "yes"))
774 786 act = A_YES;
775 787 else if (!strcmp(sep, "no"))
776 788 act = A_NO;
777 789 else if (!strcmp(sep, "file"))
778 790 act = A_FILENAME;
779 791 else
780 792 return 0;
781 793 }
782 794
783 795 int sys = syscall_by_name(a);
784 796 if (sys < 0)
785 797 die("Unknown syscall `%s'", a);
786 798 if (sys >= NUM_ACTIONS)
787 799 die("Syscall `%s' out of range", a);
788 800 syscall_action[sys] = act;
789 801 return 1;
790 802 }
791 803
792 804 /*** Path rules ***/
793 805
794 806 struct path_rule {
795 807 char *path;
796 808 enum action action;
797 809 struct path_rule *next;
798 810 };
799 811
800 812 static struct path_rule default_path_rules[] = {
801 813 { "/etc/", A_YES },
802 814 { "/lib/", A_YES },
803 815 { "/usr/lib/", A_YES },
804 816 { "/opt/lib/", A_YES },
805 817 { "/usr/share/zoneinfo/", A_YES },
806 818 { "/usr/share/locale/", A_YES },
807 819 { "/dev/null", A_YES },
808 820 { "/dev/zero", A_YES },
809 821 { "/proc/meminfo", A_YES },
810 822 { "/proc/self/stat", A_YES },
811 823 { "/proc/self/exe", A_YES }, // Needed by FPC 2.0.x runtime
812 824 { "/proc/self/maps", A_YES }, // Needed by glibc when it reports arena corruption
813 825 };
814 826
815 827 static struct path_rule *user_path_rules;
816 828 static struct path_rule **last_path_rule = &user_path_rules;
817 829
818 830 static int
819 831 set_path_action(char *a)
820 832 {
821 833 char *sep = strchr(a, '=');
822 834 enum action act = A_YES;
823 835 if (sep)
824 836 {
825 837 *sep++ = 0;
826 838 if (!strcmp(sep, "yes"))
827 839 act = A_YES;
828 840 else if (!strcmp(sep, "no"))
829 841 act = A_NO;
830 842 else
831 843 return 0;
832 844 }
833 845
834 846 struct path_rule *r = xmalloc(sizeof(*r) + strlen(a) + 1);
835 847 r->path = (char *)(r+1);
836 848 strcpy(r->path, a);
837 849 r->action = act;
838 850 r->next = NULL;
839 851 *last_path_rule = r;
840 852 last_path_rule = &r->next;
841 853 return 1;
842 854 }
843 855
844 856 static enum action
845 857 match_path_rule(struct path_rule *r, char *path)
846 858 {
847 859 char *rr = r->path;
848 860 while (*rr)
849 861 if (*rr++ != *path++)
850 862 {
851 863 if (rr[-1] == '/' && !path[-1])
852 864 break;
853 865 return A_DEFAULT;
854 866 }
855 867 if (rr > r->path && rr[-1] != '/' && *path)
856 868 return A_DEFAULT;
857 869 return r->action;
858 870 }
859 871
860 872 /*** Environment rules ***/
861 873
862 874 struct env_rule {
863 875 char *var; // Variable to match
864 876 char *val; // ""=clear, NULL=inherit
865 877 int var_len;
866 878 struct env_rule *next;
867 879 };
868 880
869 881 static struct env_rule *first_env_rule;
870 882 static struct env_rule **last_env_rule = &first_env_rule;
871 883
872 884 static struct env_rule default_env_rules[] = {
873 885 { "LIBC_FATAL_STDERR_", "1" }
874 886 };
875 887
876 888 static int
877 889 set_env_action(char *a0)
878 890 {
879 891 struct env_rule *r = xmalloc(sizeof(*r) + strlen(a0) + 1);
880 892 char *a = (char *)(r+1);
881 893 strcpy(a, a0);
882 894
883 895 char *sep = strchr(a, '=');
884 896 if (sep == a)
885 897 return 0;
886 898 r->var = a;
887 899 if (sep)
888 900 {
889 901 *sep++ = 0;
890 902 r->val = sep;
891 903 }
892 904 else
893 905 r->val = NULL;
894 906 *last_env_rule = r;
895 907 last_env_rule = &r->next;
896 908 r->next = NULL;
897 909 return 1;
898 910 }
899 911
900 912 static int
901 913 match_env_var(char *env_entry, struct env_rule *r)
902 914 {
903 915 if (strncmp(env_entry, r->var, r->var_len))
904 916 return 0;
905 917 return (env_entry[r->var_len] == '=');
906 918 }
907 919
908 920 static void
909 921 apply_env_rule(char **env, int *env_sizep, struct env_rule *r)
910 922 {
911 923 // First remove the variable if already set
912 924 int pos = 0;
913 925 while (pos < *env_sizep && !match_env_var(env[pos], r))
914 926 pos++;
915 927 if (pos < *env_sizep)
916 928 {
917 929 (*env_sizep)--;
918 930 env[pos] = env[*env_sizep];
919 931 env[*env_sizep] = NULL;
920 932 }
921 933
922 934 // What is the new value?
923 935 char *new;
924 936 if (r->val)
925 937 {
926 938 if (!r->val[0])
927 939 return;
928 940 new = xmalloc(r->var_len + 1 + strlen(r->val) + 1);
929 941 sprintf(new, "%s=%s", r->var, r->val);
930 942 }
931 943 else
932 944 {
933 945 pos = 0;
934 946 while (environ[pos] && !match_env_var(environ[pos], r))
935 947 pos++;
936 948 if (!(new = environ[pos]))
937 949 return;
938 950 }
939 951
940 952 // Add it at the end of the array
941 953 env[(*env_sizep)++] = new;
942 954 env[*env_sizep] = NULL;
943 955 }
944 956
945 957 static char **
946 958 setup_environment(void)
947 959 {
948 960 // Link built-in rules with user rules
949 961 for (int i=ARRAY_SIZE(default_env_rules)-1; i >= 0; i--)
950 962 {
951 963 default_env_rules[i].next = first_env_rule;
952 964 first_env_rule = &default_env_rules[i];
953 965 }
954 966
955 967 // Scan the original environment
956 968 char **orig_env = environ;
957 969 int orig_size = 0;
958 970 while (orig_env[orig_size])
959 971 orig_size++;
960 972
961 973 // For each rule, reserve one more slot and calculate length
962 974 int num_rules = 0;
963 975 for (struct env_rule *r = first_env_rule; r; r=r->next)
964 976 {
965 977 num_rules++;
966 978 r->var_len = strlen(r->var);
967 979 }
968 980
969 981 // Create a new environment
970 982 char **env = xmalloc((orig_size + num_rules + 1) * sizeof(char *));
971 983 int size;
972 984 if (pass_environ)
973 985 {
974 986 memcpy(env, environ, orig_size * sizeof(char *));
975 987 size = orig_size;
976 988 }
977 989 else
978 990 size = 0;
979 991 env[size] = NULL;
980 992
981 993 // Apply the rules one by one
982 994 for (struct env_rule *r = first_env_rule; r; r=r->next)
983 995 apply_env_rule(env, &size, r);
984 996
985 997 // Return the new env and pass some gossip
986 998 if (verbose > 1)
987 999 {
988 1000 fprintf(stderr, "Passing environment:\n");
989 1001 for (int i=0; env[i]; i++)
990 1002 fprintf(stderr, "\t%s\n", env[i]);
991 1003 }
992 1004 return env;
993 1005 }
994 1006
995 1007 /*** Low-level parsing of syscalls ***/
996 1008
997 1009 #ifdef CONFIG_BOX_KERNEL_AMD64
998 1010 typedef uint64_t arg_t;
999 1011 #else
1000 1012 typedef uint32_t arg_t;
1001 1013 #endif
1002 1014
1003 1015 struct syscall_args {
1004 1016 arg_t sys;
1005 1017 arg_t arg1, arg2, arg3;
1006 1018 arg_t result;
1007 1019 struct user user;
1008 1020 };
1009 1021
1010 1022 static int user_mem_fd;
1011 1023
1012 1024 static int read_user_mem(arg_t addr, char *buf, int len)
1013 1025 {
1014 1026 if (!user_mem_fd)
1015 1027 {
1016 1028 char memname[64];
1017 1029 sprintf(memname, "/proc/%d/mem", (int) box_pid);
1018 1030 user_mem_fd = open(memname, O_RDONLY);
1019 1031 if (user_mem_fd < 0)
1020 1032 die("open(%s): %m", memname);
1021 1033 }
1022 1034 if (lseek64(user_mem_fd, addr, SEEK_SET) < 0)
1023 1035 die("lseek64(mem): %m");
1024 1036 return read(user_mem_fd, buf, len);
1025 1037 }
1026 1038
1027 1039 static void close_user_mem(void)
1028 1040 {
1029 1041 if (user_mem_fd)
1030 1042 {
1031 1043 close(user_mem_fd);
1032 1044 user_mem_fd = 0;
1033 1045 }
1034 1046 }
1035 1047
1036 1048 #ifdef CONFIG_BOX_KERNEL_AMD64
1037 1049
1038 1050 static void
1039 1051 get_syscall_args(struct syscall_args *a, int is_exit)
1040 1052 {
1041 1053 if (ptrace(PTRACE_GETREGS, box_pid, NULL, &a->user) < 0)
1042 1054 die("ptrace(PTRACE_GETREGS): %m");
1043 1055 a->sys = a->user.regs.orig_rax;
1044 1056 a->result = a->user.regs.rax;
1045 1057
1046 1058 /*
1047 1059 * CAVEAT: We have to check carefully that this is a real 64-bit syscall.
1048 1060 * We test whether the process runs in 64-bit mode, but surprisingly this
1049 1061 * is not enough: a 64-bit process can still issue the INT 0x80 instruction
1050 1062 * which performs a 32-bit syscall. Currently, the only known way how to
1051 1063 * detect this situation is to inspect the instruction code (the kernel
1052 1064 * keeps a syscall type flag internally, but it is not accessible from
1053 1065 * user space). Hopefully, there is no instruction whose suffix is the
1054 1066 * code of the SYSCALL instruction. Sometimes, one would wish the
1055 1067 * instruction codes to be unique even when read backwards :)
1056 1068 */
1057 1069
1058 1070 if (is_exit)
1059 1071 return;
1060 1072
1061 1073 int sys_type;
1062 1074 uint16_t instr;
1063 1075
1064 1076 switch (a->user.regs.cs)
1065 1077 {
1066 1078 case 0x23:
1067 1079 // 32-bit CPU mode => only 32-bit syscalls can be issued
1068 1080 sys_type = 32;
1069 1081 break;
1070 1082 case 0x33:
1071 1083 // 64-bit CPU mode
1072 1084 if (read_user_mem(a->user.regs.rip-2, (char *) &instr, 2) != 2)
1073 1085 err("FO: Cannot read syscall instruction");
1074 1086 switch (instr)
1075 1087 {
1076 1088 case 0x050f:
1077 1089 break;
1078 1090 case 0x80cd:
1079 1091 err("FO: Forbidden 32-bit syscall in 64-bit mode");
1080 1092 default:
1081 1093 err("XX: Unknown syscall instruction %04x", instr);
1082 1094 }
1083 1095 sys_type = 64;
1084 1096 break;
1085 1097 default:
1086 1098 err("XX: Unknown code segment %04jx", (intmax_t) a->user.regs.cs);
1087 1099 }
1088 1100
1089 1101 #ifdef CONFIG_BOX_USER_AMD64
1090 1102 if (sys_type != 64)
1091 1103 err("FO: Forbidden %d-bit mode syscall", sys_type);
1092 1104 #else
1093 1105 if (sys_type != (exec_seen ? 32 : 64))
1094 1106 err("FO: Forbidden %d-bit mode syscall", sys_type);
1095 1107 #endif
1096 1108
1097 1109 if (sys_type == 32)
1098 1110 {
1099 1111 a->arg1 = a->user.regs.rbx;
1100 1112 a->arg2 = a->user.regs.rcx;
1101 1113 a->arg3 = a->user.regs.rdx;
1102 1114 }
1103 1115 else
1104 1116 {
1105 1117 a->arg1 = a->user.regs.rdi;
1106 1118 a->arg2 = a->user.regs.rsi;
1107 1119 a->arg3 = a->user.regs.rdx;
1108 1120 }
1109 1121 }
1110 1122
1111 1123 static void
1112 1124 set_syscall_nr(struct syscall_args *a, arg_t sys)
1113 1125 {
1114 1126 a->sys = sys;
1115 1127 a->user.regs.orig_rax = sys;
1116 1128 if (ptrace(PTRACE_SETREGS, box_pid, NULL, &a->user) < 0)
1117 1129 die("ptrace(PTRACE_SETREGS): %m");
1118 1130 }
1119 1131
1120 1132 static void
1121 1133 sanity_check(void)
1122 1134 {
1123 1135 }
1124 1136
1125 1137 #else
1126 1138
1127 1139 static void
1128 1140 get_syscall_args(struct syscall_args *a, int is_exit UNUSED)
1129 1141 {
1130 1142 if (ptrace(PTRACE_GETREGS, box_pid, NULL, &a->user) < 0)
1131 1143 die("ptrace(PTRACE_GETREGS): %m");
1132 1144 a->sys = a->user.regs.orig_eax;
1133 1145 a->arg1 = a->user.regs.ebx;
1134 1146 a->arg2 = a->user.regs.ecx;
1135 1147 a->arg3 = a->user.regs.edx;
1136 1148 a->result = a->user.regs.eax;
1137 1149 }
1138 1150
1139 1151 static void
1140 1152 set_syscall_nr(struct syscall_args *a, arg_t sys)
1141 1153 {
1142 1154 a->sys = sys;
1143 1155 a->user.regs.orig_eax = sys;
1144 1156 if (ptrace(PTRACE_SETREGS, box_pid, NULL, &a->user) < 0)
1145 1157 die("ptrace(PTRACE_SETREGS): %m");
1146 1158 }
1147 1159
1148 1160 static void
1149 1161 sanity_check(void)
1150 1162 {
1151 1163 #if !defined(CONFIG_BOX_ALLOW_INSECURE)
1152 1164 struct utsname uts;
1153 1165 if (uname(&uts) < 0)
1154 1166 die("uname() failed: %m");
1155 1167
1156 1168 if (!strcmp(uts.machine, "x86_64"))
1157 1169 die("Running 32-bit sandbox on 64-bit kernels is inherently unsafe. Please get a 64-bit version.");
1158 1170 #endif
1159 1171 }
1160 1172
1161 1173 #endif
1162 1174
1163 1175 /*** Syscall checks ***/
1164 1176
1165 1177 static void
1166 1178 valid_filename(arg_t addr)
1167 1179 {
1168 1180 char namebuf[4096], *p, *end;
1169 1181
1170 1182 if (!file_access)
1171 1183 err("FA: File access forbidden");
1172 1184 if (file_access >= 9)
1173 1185 return;
1174 1186
1175 1187 p = end = namebuf;
1176 1188 do
1177 1189 {
1178 1190 if (p >= end)
1179 1191 {
1180 1192 int remains = PAGE_SIZE - (addr & (PAGE_SIZE-1));
1181 1193 int l = namebuf + sizeof(namebuf) - end;
1182 1194 if (l > remains)
1183 1195 l = remains;
1184 1196 if (!l)
1185 1197 err("FA: Access to file with name too long");
1186 1198 remains = read_user_mem(addr, end, l);
1187 1199 if (remains < 0)
1188 1200 die("read(mem): %m");
1189 1201 if (!remains)
1190 1202 err("FA: Access to file with name out of memory");
1191 1203 end += remains;
1192 1204 addr += remains;
1193 1205 }
1194 1206 }
1195 1207 while (*p++);
1196 1208
1197 1209 msg("[%s] ", namebuf);
1198 1210 if (file_access >= 3)
1199 1211 return;
1200 1212
1201 1213 // Everything in current directory is permitted
1202 1214 if (!strchr(namebuf, '/') && strcmp(namebuf, ".."))
1203 1215 return;
1204 1216
1205 1217 // ".." anywhere in the path is forbidden
1206 1218 enum action act = A_DEFAULT;
1207 1219 if (strstr(namebuf, ".."))
1208 1220 act = A_NO;
1209 1221
1210 1222 // Scan user rules
1211 1223 for (struct path_rule *r = user_path_rules; r && !act; r=r->next)
1212 1224 act = match_path_rule(r, namebuf);
1213 1225
1214 1226 // Scan built-in rules
1215 1227 if (file_access >= 2)
1216 1228 for (int i=0; i<ARRAY_SIZE(default_path_rules) && !act; i++)
1217 1229 act = match_path_rule(&default_path_rules[i], namebuf);
1218 1230
1219 1231 if (act != A_YES)
1220 1232 err("FA: Forbidden access to file `%s'", namebuf);
1221 1233 }
1222 1234
1223 1235 // Check syscall. If invalid, return -1, otherwise return the action mask.
1224 1236 static int
1225 1237 valid_syscall(struct syscall_args *a)
1226 1238 {
1227 1239 unsigned int sys = a->sys;
1228 1240 unsigned int act = (sys < NUM_ACTIONS) ? syscall_action[sys] : A_DEFAULT;
1229 1241
1230 1242 if (act & A_LIBERAL)
1231 1243 {
1232 1244 if (filter_syscalls != 1)
1233 1245 act = A_DEFAULT;
1234 1246 }
1235 1247
1236 1248 switch (act & A_ACTION_MASK)
1237 1249 {
1238 1250 case A_YES:
1239 1251 return act;
1240 1252 case A_NO:
1241 1253 return -1;
1242 1254 case A_FILENAME:
1243 1255 valid_filename(a->arg1);
1244 1256 return act;
1245 1257 default: ;
1246 1258 }
1247 1259
1248 1260 switch (sys)
1249 1261 {
1250 1262 case __NR_kill:
1251 1263 if (a->arg1 == (arg_t) box_pid)
1252 1264 {
1253 1265 meta_printf("exitsig:%d\n", (int) a->arg2);
1254 1266 err("SG: Committed suicide by signal %d", (int) a->arg2);
1255 1267 }
1256 1268 return -1;
1257 1269 case __NR_tgkill:
1258 1270 if (a->arg1 == (arg_t) box_pid && a->arg2 == (arg_t) box_pid)
1259 1271 {
1260 1272 meta_printf("exitsig:%d\n", (int) a->arg3);
1261 1273 err("SG: Committed suicide by signal %d", (int) a->arg3);
1262 1274 }
1263 1275 return -1;
1264 1276 default:
1265 1277 return -1;
1266 1278 }
1267 1279 }
1268 1280
1269 1281 static void
1270 1282 signal_alarm(int unused UNUSED)
1271 1283 {
1272 1284 /* Time limit checks are synchronous, so we only schedule them there. */
1273 1285 timer_tick = 1;
1274 1286 alarm(1);
1275 1287 }
1276 1288
1277 1289 static void
1278 1290 signal_int(int unused UNUSED)
1279 1291 {
1280 1292 /* Interrupts are fatal, so no synchronization requirements. */
1281 1293 meta_printf("exitsig:%d\n", SIGINT);
1282 1294 err("SG: Interrupted");
1283 1295 }
1284 1296
1285 1297 #define PROC_BUF_SIZE 4096
1286 1298 static void
1287 1299 read_proc_file(char *buf, char *name, int *fdp)
1288 1300 {
1289 1301 int c;
1290 1302
1291 1303 if (!*fdp)
1292 1304 {
1293 1305 sprintf(buf, "/proc/%d/%s", (int) box_pid, name);
1294 1306 *fdp = open(buf, O_RDONLY);
1295 1307 if (*fdp < 0)
1296 1308 die("open(%s): %m", buf);
1297 1309 }
1298 1310 lseek(*fdp, 0, SEEK_SET);
1299 1311 if ((c = read(*fdp, buf, PROC_BUF_SIZE-1)) < 0)
1300 1312 die("read on /proc/$pid/%s: %m", name);
1301 1313 if (c >= PROC_BUF_SIZE-1)
1302 1314 die("/proc/$pid/%s too long", name);
1303 1315 buf[c] = 0;
1304 1316 }
1305 1317
1306 1318 static void
1307 1319 check_timeout(void)
1308 1320 {
1309 1321 if (wall_timeout)
1310 1322 {
1311 1323 struct timeval now, wall;
1312 1324 int wall_ms;
1313 1325 gettimeofday(&now, NULL);
1314 1326 timersub(&now, &start_time, &wall);
1315 1327 wall_ms = wall.tv_sec*1000 + wall.tv_usec/1000;
1316 1328 if (wall_ms > wall_timeout)
1317 1329 err("TO: Time limit exceeded (wall clock)");
1318 1330 if (verbose > 1)
1319 1331 fprintf(stderr, "[wall time check: %d msec]\n", wall_ms);
1320 1332 }
1321 1333 if (timeout)
1322 1334 {
1323 1335 char buf[PROC_BUF_SIZE], *x;
1324 1336 int utime, stime, ms;
1325 1337 static int proc_stat_fd;
1326 1338 read_proc_file(buf, "stat", &proc_stat_fd);
1327 1339 x = buf;
1328 1340 while (*x && *x != ' ')
1329 1341 x++;
1330 1342 while (*x == ' ')
1331 1343 x++;
1332 1344 if (*x++ != '(')
1333 1345 die("proc stat syntax error 1");
1334 1346 while (*x && (*x != ')' || x[1] != ' '))
1335 1347 x++;
1336 1348 while (*x == ')' || *x == ' ')
1337 1349 x++;
1338 1350 if (sscanf(x, "%*c %*d %*d %*d %*d %*d %*d %*d %*d %*d %*d %d %d", &utime, &stime) != 2)
1339 1351 die("proc stat syntax error 2");
1340 1352 ms = (utime + stime) * 1000 / ticks_per_sec;
1341 1353 if (verbose > 1)
1342 1354 fprintf(stderr, "[time check: %d msec]\n", ms);
1343 1355 if (ms > timeout && ms > extra_timeout)
1344 1356 err("TO: Time limit exceeded");
1345 1357 }
1346 1358 }
1347 1359
1348 1360 static void
1349 1361 sample_mem_peak(void)
1350 1362 {
1351 1363 /*
1352 1364 * We want to find out the peak memory usage of the process, which is
1353 1365 * maintained by the kernel, but unforunately it gets lost when the
1354 1366 * process exits (it is not reported in struct rusage). Therefore we
1355 1367 * have to sample it whenever we suspect that the process is about
1356 1368 * to exit.
1357 1369 */
1358 1370 char buf[PROC_BUF_SIZE], *x;
1359 1371 static int proc_status_fd;
1360 1372 read_proc_file(buf, "status", &proc_status_fd);
1361 1373
1362 1374 x = buf;
1363 1375 while (*x)
1364 1376 {
1365 1377 char *key = x;
1366 1378 while (*x && *x != ':' && *x != '\n')
1367 1379 x++;
1368 1380 if (!*x || *x == '\n')
1369 1381 break;
1370 1382 *x++ = 0;
1371 1383 while (*x == ' ' || *x == '\t')
1372 1384 x++;
1373 1385
1374 1386 char *val = x;
1375 1387 while (*x && *x != '\n')
1376 1388 x++;
1377 1389 if (!*x)
1378 1390 break;
1379 1391 *x++ = 0;
1380 1392
1381 1393 if (!strcmp(key, "VmPeak"))
1382 1394 {
1383 1395 int peak = atoi(val);
1384 1396 if (peak > mem_peak_kb)
1385 1397 mem_peak_kb = peak;
1386 1398 }
1387 1399 }
1388 1400
1389 1401 if (verbose > 1)
1390 1402 msg("[mem-peak: %u KB]\n", mem_peak_kb);
1391 1403 }
1392 1404
1393 1405 static void
1394 1406 boxkeeper(void)
1395 1407 {
1396 1408 int syscall_count = (filter_syscalls ? 0 : 1);
1397 1409 struct sigaction sa;
1398 1410
1399 1411 is_ptraced = 1;
1400 1412
1401 1413 bzero(&sa, sizeof(sa));
1402 1414 sa.sa_handler = signal_int;
1403 1415 sigaction(SIGINT, &sa, NULL);
1404 1416
1405 1417 gettimeofday(&start_time, NULL);
1406 1418 ticks_per_sec = sysconf(_SC_CLK_TCK);
1407 1419 if (ticks_per_sec <= 0)
1408 1420 die("Invalid ticks_per_sec!");
1409 1421
1410 1422 if (timeout || wall_timeout)
1411 1423 {
1412 1424 sa.sa_handler = signal_alarm;
1413 1425 sigaction(SIGALRM, &sa, NULL);
1414 1426 alarm(1);
1415 1427 }
1416 1428
1417 1429 for(;;)
1418 1430 {
1419 1431 struct rusage rus;
1420 1432 int stat;
1421 1433 pid_t p;
1422 1434 if (timer_tick)
1423 1435 {
1424 1436 check_timeout();
1425 1437 timer_tick = 0;
1426 1438 }
1427 1439 p = wait4(box_pid, &stat, WUNTRACED, &rus);
1428 1440 if (p < 0)
1429 1441 {
1430 1442 if (errno == EINTR)
1431 1443 continue;
1432 1444 die("wait4: %m");
1433 1445 }
1434 1446 if (p != box_pid)
1435 1447 die("wait4: unknown pid %d exited!", p);
1436 1448 if (WIFEXITED(stat))
1437 1449 {
1438 1450 box_pid = 0;
1439 1451 final_stats(&rus);
1440 1452 if (WEXITSTATUS(stat))
1441 1453 {
1442 1454 if (syscall_count)
1443 1455 {
1444 1456 meta_printf("exitcode:%d\n", WEXITSTATUS(stat));
1445 1457 err("RE: Exited with error status %d", WEXITSTATUS(stat));
1446 1458 }
1447 1459 else
1448 1460 {
1449 1461 // Internal error happened inside the child process and it has been already reported.
1450 1462 box_exit(2);
1451 1463 }
1452 1464 }
1453 1465 if (timeout && total_ms > timeout)
1454 1466 err("TO: Time limit exceeded");
1455 1467 if (wall_timeout && wall_ms > wall_timeout)
1456 1468 err("TO: Time limit exceeded (wall clock)");
1457 1469 flush_line();
1458 1470 fprintf(stderr,"OK\n");
1459 1471 box_exit(0);
1460 1472 }
1461 1473 if (WIFSIGNALED(stat))
1462 1474 {
1463 1475 box_pid = 0;
1464 1476 meta_printf("exitsig:%d\n", WTERMSIG(stat));
1465 1477 final_stats(&rus);
1466 1478 err("SG: Caught fatal signal %d%s", WTERMSIG(stat), (syscall_count ? "" : " during startup"));
1467 1479 }
1468 1480 if (WIFSTOPPED(stat))
1469 1481 {
1470 1482 int sig = WSTOPSIG(stat);
1471 1483 if (sig == SIGTRAP)
1472 1484 {
1473 1485 if (verbose > 2)
1474 1486 msg("[ptrace status %08x] ", stat);
1475 1487 static int stop_count;
1476 1488 if (!stop_count++) /* Traceme request */
1477 1489 msg(">> Traceme request caught\n");
1478 1490 else
1479 1491 err("SG: Breakpoint");
1480 1492 ptrace(PTRACE_SYSCALL, box_pid, 0, 0);
1481 1493 }
1482 1494 else if (sig == (SIGTRAP | 0x80))
1483 1495 {
1484 1496 if (verbose > 2)
1485 1497 msg("[ptrace status %08x] ", stat);
1486 1498 struct syscall_args a;
1487 1499 static unsigned int sys_tick, last_act;
1488 1500 static arg_t last_sys;
1489 1501 if (++sys_tick & 1) /* Syscall entry */
1490 1502 {
1491 1503 char namebuf[32];
1492 1504 int act;
1493 1505
1494 1506 get_syscall_args(&a, 0);
1495 1507 arg_t sys = a.sys;
1496 1508 msg(">> Syscall %-12s (%08jx,%08jx,%08jx) ", syscall_name(sys, namebuf), (intmax_t) a.arg1, (intmax_t) a.arg2, (intmax_t) a.arg3);
1497 1509 if (!exec_seen)
1498 1510 {
1499 1511 msg("[master] ");
1500 1512 if (sys == NATIVE_NR_execve)
1501 1513 {
1502 1514 exec_seen = 1;
1503 1515 close_user_mem();
1504 1516 }
1505 1517 }
1506 1518 else if ((act = valid_syscall(&a)) >= 0)
1507 1519 {
1508 1520 last_act = act;
1509 1521 syscall_count++;
1510 1522 if (act & A_SAMPLE_MEM)
1511 1523 sample_mem_peak();
1512 1524 }
1513 1525 else
1514 1526 {
1515 1527 /*
1516 1528 * Unfortunately, PTRACE_KILL kills _after_ the syscall completes,
1517 1529 * so we have to change it to something harmless (e.g., an undefined
1518 1530 * syscall) and make the program continue.
1519 1531 */
1520 1532 set_syscall_nr(&a, ~(arg_t)0);
1521 1533 err("FO: Forbidden syscall %s", syscall_name(sys, namebuf));
1522 1534 }
1523 1535 last_sys = sys;
1524 1536 }
1525 1537 else /* Syscall return */
1526 1538 {
1527 1539 get_syscall_args(&a, 1);
1528 1540 if (a.sys == ~(arg_t)0)
1529 1541 {
1530 1542 /* Some syscalls (sigreturn et al.) do not return a value */
1531 1543 if (!(last_act & A_NO_RETVAL))
1532 1544 err("XX: Syscall does not return, but it should");
1533 1545 }
1534 1546 else
1535 1547 {
1536 1548 if (a.sys != last_sys)
1537 1549 err("XX: Mismatched syscall entry/exit");
1538 1550 }
1539 1551 if (last_act & A_NO_RETVAL)
1540 1552 msg("= ?\n");
1541 1553 else
1542 1554 msg("= %jd\n", (intmax_t) a.result);
1543 1555 }
1544 1556 ptrace(PTRACE_SYSCALL, box_pid, 0, 0);
1545 1557 }
1546 1558 else if (sig == SIGSTOP)
1547 1559 {
1548 1560 msg(">> SIGSTOP\n");
1549 1561 if (ptrace(PTRACE_SETOPTIONS, box_pid, NULL, (void *) PTRACE_O_TRACESYSGOOD) < 0)
1550 1562 die("ptrace(PTRACE_SETOPTIONS): %m");
1551 1563 ptrace(PTRACE_SYSCALL, box_pid, 0, 0);
1552 1564 }
1553 1565 else if (sig != SIGXCPU && sig != SIGXFSZ)
1554 1566 {
1555 1567 msg(">> Signal %d\n", sig);
1556 1568 sample_mem_peak(); /* Signal might be fatal, so update mem-peak */
1557 1569 ptrace(PTRACE_SYSCALL, box_pid, 0, sig);
1558 1570 }
1559 1571 else
1560 1572 {
1561 1573 meta_printf("exitsig:%d", sig);
1562 1574 err("SG: Received signal %d", sig);
1563 1575 }
1564 1576 }
1565 1577 else
1566 1578 die("wait4: unknown status %x, giving up!", stat);
1567 1579 }
1568 1580 }
1569 1581
1570 1582 static void
1571 1583 box_inside(int argc, char **argv)
1572 1584 {
1573 1585 struct rlimit rl;
1574 1586 char *args[argc+1];
1575 1587
1576 1588 memcpy(args, argv, argc * sizeof(char *));
1577 1589 args[argc] = NULL;
1578 1590 if (set_cwd && chdir(set_cwd))
1579 1591 die("chdir: %m");
1580 1592 if (redir_stdin)
1581 1593 {
1582 1594 close(0);
1583 1595 if (open(redir_stdin, O_RDONLY) != 0)
1584 1596 die("open(\"%s\"): %m", redir_stdin);
1585 1597 }
1586 1598 if (redir_stdout)
1587 1599 {
1588 1600 close(1);
1589 1601 if (open(redir_stdout, O_WRONLY | O_CREAT | O_TRUNC, 0666) != 1)
1590 1602 die("open(\"%s\"): %m", redir_stdout);
1591 1603 }
1592 1604 if (redir_stderr)
1593 1605 {
1594 1606 close(2);
1595 1607 if (open(redir_stderr, O_WRONLY | O_CREAT | O_TRUNC, 0666) != 2)
1596 1608 die("open(\"%s\"): %m", redir_stderr);
1597 1609 }
1598 1610 else
1599 1611 dup2(1, 2);
1600 1612 setpgrp();
1601 1613
1602 1614 if (memory_limit)
1603 1615 {
1604 1616 rl.rlim_cur = rl.rlim_max = memory_limit * 1024;
1605 1617 if (setrlimit(RLIMIT_AS, &rl) < 0)
1606 1618 die("setrlimit(RLIMIT_AS): %m");
1607 1619 }
1608 1620
1609 1621 rl.rlim_cur = rl.rlim_max = (stack_limit ? (rlim_t)stack_limit * 1024 : RLIM_INFINITY);
1610 1622 if (setrlimit(RLIMIT_STACK, &rl) < 0)
1611 1623 die("setrlimit(RLIMIT_STACK): %m");
1612 1624
1613 1625 rl.rlim_cur = rl.rlim_max = 64;
1614 1626 if (setrlimit(RLIMIT_NOFILE, &rl) < 0)
1615 1627 die("setrlimit(RLIMIT_NOFILE): %m");
1616 1628
1617 1629 char **env = setup_environment();
1618 1630 if (filter_syscalls)
1619 1631 {
1620 1632 if (ptrace(PTRACE_TRACEME) < 0)
1621 1633 die("ptrace(PTRACE_TRACEME): %m");
1622 1634 /* Trick: Make sure that we are stopped until the boxkeeper wakes up. */
1623 1635 raise(SIGSTOP);
1624 1636 }
1625 1637 execve(args[0], args, env);
1626 1638 die("execve(\"%s\"): %m", args[0]);
1627 1639 }
1628 1640
1629 1641 static void
1630 1642 usage(void)
1631 1643 {
1632 1644 fprintf(stderr, "Invalid arguments!\n");
1633 1645 printf("\
1634 1646 Usage: box [<options>] -- <command> <arguments>\n\
1635 1647 \n\
1636 1648 Options:\n\
1637 1649 -a <level>\tSet file access level (0=none, 1=cwd, 2=/etc,/lib,..., 3=whole fs, 9=no checks; needs -f)\n\
1638 1650 -c <dir>\tChange directory to <dir> first\n\
1639 1651 -e\t\tInherit full environment of the parent process\n\
1640 1652 -E <var>\tInherit the environment variable <var> from the parent process\n\
1641 1653 -E <var>=<val>\tSet the environment variable <var> to <val>; unset it if <var> is empty\n\
1642 1654 -f\t\tFilter system calls (-ff=very restricted)\n\
1643 1655 -i <file>\tRedirect stdin from <file>\n\
1644 1656 -k <size>\tLimit stack size to <size> KB (default: 0=unlimited)\n\
1645 1657 -m <size>\tLimit address space to <size> KB\n\
1646 1658 -M <file>\tOutput process information to <file> (name:value)\n\
1647 1659 -o <file>\tRedirect stdout to <file>\n\
1648 1660 -p <path>\tPermit access to the specified path (or subtree if it ends with a `/')\n\
1649 1661 -p <path>=<act>\tDefine action for the specified path (<act>=yes/no)\n\
1650 1662 -r <file>\tRedirect stderr to <file>\n\
1651 1663 -s <sys>\tPermit the specified syscall (be careful)\n\
1652 1664 -s <sys>=<act>\tDefine action for the specified syscall (<act>=yes/no/file)\n\
1653 1665 -t <time>\tSet run time limit (seconds, fractions allowed)\n\
1654 1666 -T\t\tAllow syscalls for measuring run time\n\
1655 1667 -v\t\tBe verbose (use multiple times for even more verbosity)\n\
1656 1668 -w <time>\tSet wall clock time limit (seconds, fractions allowed)\n\
1657 1669 -x <time>\tSet extra timeout, before which a timing-out program is not yet killed,\n\
1658 1670 \t\tso that its real execution time is reported (seconds, fractions allowed)\n\
1659 1671 -A <opt>\tPass <opt> as additional argument to the <command>\n\
1660 1672 \t\tBe noted that this option will be appended after <arguments> respectively\n\
1661 1673 ");
1662 1674 exit(2);
1663 1675 }
1664 1676
1665 1677 int
1666 1678 main(int argc, char **argv)
1667 1679 {
1668 1680 int c;
1669 1681 uid_t uid;
1670 1682 char **prog_argv = xmalloc(sizeof(char*) * argc);
1671 1683 int prog_argc = 0;
1672 1684
1673 1685 while ((c = getopt(argc, argv, "a:c:eE:fi:k:m:M:o:p:r:s:t:Tvw:x:A:")) >= 0)
1674 1686 switch (c)
1675 1687 {
1676 1688 case 'a':
1677 1689 file_access = atol(optarg);
1678 1690 break;
1679 1691 case 'c':
1680 1692 set_cwd = optarg;
1681 1693 break;
1682 1694 case 'e':
1683 1695 pass_environ = 1;
1684 1696 break;
1685 1697 case 'E':
1686 1698 if (!set_env_action(optarg))
1687 1699 usage();
1688 1700 break;
1689 1701 case 'f':
1690 1702 filter_syscalls++;
1691 1703 break;
1692 1704 case 'k':
1693 1705 stack_limit = atol(optarg);
1694 1706 break;
1695 1707 case 'i':
1696 1708 redir_stdin = optarg;
1697 1709 break;
1698 1710 case 'm':
1699 1711 memory_limit = atol(optarg);
1700 1712 break;
1701 1713 case 'M':
1702 1714 meta_open(optarg);
1703 1715 break;
1704 1716 case 'o':
1705 1717 redir_stdout = optarg;
1706 1718 break;
1707 1719 case 'p':
1708 1720 if (!set_path_action(optarg))
1709 1721 usage();
1710 1722 break;
1711 1723 case 'r':
1712 1724 redir_stderr = optarg;
1713 1725 break;
1714 1726 case 's':
1715 1727 if (!set_syscall_action(optarg))
1716 1728 usage();
1717 1729 break;
1718 1730 case 't':
1719 1731 timeout = 1000*atof(optarg);
1720 1732 break;
1721 1733 case 'T':
1722 1734 syscall_action[__NR_times] = A_YES;
1723 1735 break;
1724 1736 case 'v':
1725 1737 verbose++;
1726 1738 break;
1727 1739 case 'w':
1728 1740 wall_timeout = 1000*atof(optarg);
1729 1741 break;
1730 1742 case 'x':
1731 1743 extra_timeout = 1000*atof(optarg);
1732 1744 case 'A':
1733 1745 prog_argv[prog_argc++] = strdup(optarg);
1734 1746 break;
1735 1747 break;
1736 1748 default:
1737 1749 usage();
1738 1750 }
1739 1751 if (optind >= argc)
1740 1752 usage();
1741 1753
1742 1754 sanity_check();
1743 1755 uid = geteuid();
1744 1756 if (setreuid(uid, uid) < 0)
1745 1757 die("setreuid: %m");
1746 1758 box_pid = fork();
1747 1759 if (box_pid < 0)
1748 1760 die("fork: %m");
1749 1761 if (!box_pid) {
1750 1762 int real_argc = prog_argc + argc - optind;
1751 1763 char **real_argv = xmalloc(sizeof(char*) * (real_argc));
1752 1764 for (int i = 0;i < argc-optind;i++)
1753 1765 real_argv[i] = strdup(argv[i+optind]);
1754 1766 for (int i = 0;i < prog_argc;i++)
1755 1767 real_argv[argc - optind + i] = strdup(prog_argv[i]);
1756 1768 box_inside(real_argc, real_argv);
1757 1769 } else
1758 1770 boxkeeper();
1759 1771 die("Internal error: fell over edge of the world");
1760 1772 }
@@ -1,188 +1,196
1 1 #!/usr/bin/env ruby
2 2
3 3 require 'fileutils'
4 4
5 5 ##############################
6 6 #
7 7 # Standard Compile Script
8 8 #
9 9 # Supported compilers:
10 10 # gcc, g++, and fpc.
11 11 #
12 12 ##############################
13 13
14 14 def talk(str='')
15 15 if ENV['TALKATIVE']!=nil
16 16 puts str
17 17 end
18 18 if ENV['GRADER_LOGGING']!=nil
19 19 log_fname = ENV['GRADER_LOGGING']
20 20 fp = File.open(log_fname,"a")
21 21 fp.puts("run: #{Time.new.strftime("%H:%M")} #{str}")
22 22 fp.close
23 23 end
24 24 end
25 25
26 26 C_COMPILER = "/usr/bin/gcc"
27 27 CPLUSPLUS_COMPILER = "/usr/bin/g++"
28 28 PASCAL_COMPILER = "/usr/bin/fpc"
29 29 JAVA_COMPILER = "/usr/bin/javac"
30 30 RUBY_INTERPRETER = "/usr/bin/ruby"
31 - PYTHON_INTERPRETER = "/usr/bin/python"
31 + PYTHON_INTERPRETER = "/usr/bin/python3"
32 32 PYTHON_CHECKER = "/usr/bin/pyflakes"
33 33 PHP_INTERPRETER = "/usr/bin/php"
34 + HASKELL_COMPILER = "/usr/bin/ghc"
34 35
35 36 C_OPTIONS = "-O2 -s -static -std=c99 -DCONTEST -lm -Wall"
36 37 CPLUSPLUS_OPTIONS = "-O2 -s -std=c++11 -static -DCONTEST -lm -Wall"
37 38 PASCAL_OPTIONS = "-O1 -XS -dCONTEST"
38 39 JAVA_OPTIONS = ""
39 40 PYTHON_OPTIONS = ""
40 41 PHP_OPTIONS = "-l"
42 + HASKELL_OPTIONS = ""
41 43
42 44 # Check for the correct number of arguments. Otherwise, print usage.
43 45 if ARGV.length == 0 or ARGV.length > 4
44 46 puts "Usage: compile <language> [<source-file>] [<output-file>] [<message-file>]"
45 47 puts
46 48 puts "<source-file> is defaulted to \"source\"."
47 49 puts "<output-file> is defaulted to \"a.out\"."
48 50 puts "<message-file> is defaulted to \"compiler_message\"."
49 51 puts
50 52 exit(127)
51 53 end
52 54
53 55 PARAMS = {
54 56 :source_file => [1,'source'],
55 57 :output_file => [2,'a.out'],
56 58 :message_file => [3,'compiler_message']
57 59 }
58 60
59 61 params = {}
60 62 params[:prog_lang] = ARGV[0]
61 63 PARAMS.each_key do |param_name|
62 64 index, default = PARAMS[param_name]
63 65 if ARGV.length > index
64 66 params[param_name] = ARGV[index]
65 67 else
66 68 params[param_name] = default
67 69 end
68 70 talk "#{param_name}: #{params[param_name]}"
69 71 end
70 72
71 73 # Remove any remaining output files or message files.
72 74 if FileTest.exists? params[:output_file]
73 75 FileUtils.rm(params[:output_file])
74 76 end
75 77 if FileTest.exists? params[:message_file]
76 78 FileUtils.rm(params[:message_file])
77 79 end
78 80
79 81 # Check if the source file exists before attempt compiling.
80 82 if !FileTest.exists? params[:source_file]
81 83 talk("ERROR: The source file does not exist!")
82 84 open(params[:message_file],"w") do |f|
83 85 f.puts "ERROR: The source file did not exist."
84 86 end
85 87 exit(127)
86 88 end
87 89
88 90 if params[:prog_lang]=='cpp'
89 91 params[:prog_lang] = 'c++'
90 92 end
91 93
92 94 # Compile.
93 95 case params[:prog_lang]
94 96
95 97 when "c"
96 98 command = "#{C_COMPILER} #{params[:source_file]} -o #{params[:output_file]} #{C_OPTIONS}"
97 99 system(command, err: params[:message_file])
98 100
99 101 when "c++"
100 102 command = "#{CPLUSPLUS_COMPILER} #{params[:source_file]} -o #{params[:output_file]} #{CPLUSPLUS_OPTIONS}"
101 103 system(command, err: params[:message_file])
102 104
103 105 when "pas"
104 106 command = "#{PASCAL_COMPILER} #{params[:source_file]} -ooutpas #{PASCAL_OPTIONS}"
105 107 system(command,out: params[:message_file])
106 108 FileUtils.mv("output", params[:output_file])
107 109
108 110 when "java"
109 111 #rename the file to the public class name
110 112
111 113 #get the class name
112 114 classname = 'DUMMY'
113 115 source = Array.new
114 116 File.foreach(params[:source_file],'r:UTF-8') do |line|
115 117 line.encode!('UTF-8','UTF-8',invalid: :replace, replace: '')
116 118 md = /\s*public\s*class\s*(\w*)/.match(line)
117 119 classname=md[1] if md
118 120 source << line unless line =~ /\s*package\s*\w+\s*\;/
119 121 end
120 122 File.open("#{classname}.java","w") do |file|
121 123 source.each do |s|
122 124 file.puts s
123 125 end
124 126 end
125 127 #system("cp #{params[:source_file]} #{classname}.java")
126 128 command = "#{JAVA_COMPILER} -encoding utf8 #{classname}.java"
127 129 system(command, err: params[:message_file])
128 130 if File.exists?(classname + ".class")
129 131 File.open(params[:output_file],"w") {|file| file.write("#{classname}")}
130 132 end
131 133 if classname == 'DUMMY'
132 134 File.open(params[:message_file],"w") {|file| file.write("Cannot find any public class in the source code\n")}
133 135 end
134 136
135 137 when "ruby"
136 138 command = "#{RUBY_INTERPRETER} -c #{params[:source_file]}"
137 139 if system(command, err: params[:message_file])
138 140 File.open(params[:output_file],"w") do |out_file|
139 141 out_file.puts "#!#{RUBY_INTERPRETER}"
140 142 File.open(params[:source_file],"r").each do |line|
141 143 out_file.print line
142 144 end
143 145 end
144 146 File.chmod(0755, params[:output_file])
145 147 end
146 148
147 149 when "python"
148 - command = "#{PYTHON_CHECKER} #{params[:source_file]}"
149 - if system(command, out: params[:message_file])
150 + #command = "#{PYTHON_CHECKER} #{params[:source_file]}"
151 + #if system(command, out: params[:message_file])
150 152 #compile to python bytecode
151 - command = "#{PYTHON_INTERPRETER} -m py_compile #{params[:source_file]}"
153 + command = "#{PYTHON_INTERPRETER} -c \"import py_compile; py_compile.compile('#{params[:source_file]}','#{params[:source_file]}c');\""
152 154 puts "compile: #{command}"
153 - system(command)
154 - puts "pwd: " + Dir.pwd
155 - Dir.new('.').each {|file| puts file}
156 - File.open(params[:output_file],"w") do |out_file|
157 - out_file.puts "#!#{PYTHON_INTERPRETER} #{params[:source_file]}c"
155 + system(command, err: params[:message_file])
156 + if FileTest.exists?("#{params[:source_file]}c")
157 + puts "pwd: " + Dir.pwd
158 + Dir.new('.').each {|file| puts file}
159 + File.open(params[:output_file],"w") do |out_file|
160 + out_file.puts "#!#{PYTHON_INTERPRETER} #{params[:source_file]}c"
161 + end
162 + File.chmod(0755, params[:output_file])
163 + FileUtils.cp("#{params[:source_file]}c",params[:output_file])
158 164 end
159 - File.chmod(0755, params[:output_file])
160 - FileUtils.cp("#{params[:source_file]}c",params[:output_file])
161 - end
165 + #end
162 166
163 167 when "php"
164 168 command = "#{PHP_INTERPRETER} #{PHP_OPTIONS} #{params[:source_file]}"
165 169 if system(command, err: params[:message_file])
166 170 File.open(params[:output_file],"w") do |out_file|
167 171 out_file.puts "#!#{PHP_INTERPRETER}"
168 172 File.open(params[:source_file],"r").each do |line|
169 173 out_file.print line
170 174 end
171 175 end
172 176 File.chmod(0755, params[:output_file])
173 177 end
174 178
179 + when "haskell"
180 + command = "#{HASKELL_COMPILER} #{params[:source_file]} -o #{params[:output_file]} #{HASKELL_OPTIONS}"
181 + system(command, err: params[:message_file])
182 +
175 183 else
176 184 talk("ERROR: Invalid language specified!")
177 185 open(params[:message_file],"w") do |f|
178 186 f.puts "ERROR: Invalid language specified!"
179 187 end
180 188 exit(127)
181 189 end
182 190
183 191 # Report success or failure.
184 192 if FileTest.exists? params[:output_file]
185 193 talk "Compilation was successful!"
186 194 else
187 195 talk "ERROR: Something was wrong during the compilation!"
188 196 end
@@ -1,180 +1,186
1 1 #!/usr/bin/env ruby
2 2
3 3 require 'fileutils'
4 4
5 5 def log(str='')
6 6 if ENV['TALKATIVE']!=nil
7 7 puts str
8 8 end
9 9 if ENV['GRADER_LOGGING']!=nil
10 10 log_fname = ENV['GRADER_LOGGING']
11 11 fp = File.open(log_fname,"a")
12 12 fp.puts("judge: #{Time.new.strftime("%H:%M")} #{str}")
13 13 fp.close
14 14 end
15 15 end
16 16
17 17 problem_home = ENV['PROBLEM_HOME']
18 18
19 19 def execute(command, error_message="")
20 20 if not system(command)
21 21 msg = "ERROR: #{error_message}"
22 22 log msg
23 23 raise(msg)
24 24 end
25 25 end
26 26
27 27 def call_and_log(error_message)
28 28 begin
29 29 yield
30 30 rescue
31 31 msg = "ERROR: #{error_message}"
32 32 log msg
33 33 raise msg
34 34 end
35 35 end
36 36
37 37 def clear_and_create_empty_dir(dir)
38 38 FileUtils.rm_rf(dir, :secure => true)
39 39 call_and_log("Cannot make directory #{dir}.") { FileUtils.mkdir(dir) }
40 40 end
41 41
42 42 # ARGV[0] --- language
43 43 # ARGV[1] --- program source file
44 44 # ARGV[2] --- test result directory
45 45 # ARGV[3] --- sandbox directory
46 46
47 47 if ARGV.length < 2 || ARGV.length > 4
48 48 puts "Usage: judge <language> <program-source> [<test-result-directory>] [<sandbox-directory>]"
49 49 puts " <sandbox-directory> is defaulted to ./sandbox"
50 50 puts " <test-result-directory> is defaulted to ./test-result"
51 51 puts "WARNING: The judge script will forcefully create the (implicitly and explicitly) specified directories and remove anything inside it."
52 52 exit(127)
53 53 end
54 54
55 55 language = ARGV[0]
56 - if language != "c" && language != "c++" && language != "pas" && language != "java" && language != "ruby" && language != "python" && language != "php"
56 + if language != "c" && language != "c++" && language != "pas" && language != "java" && language != "ruby" && language != "python" && language != "php" && language != "haskell"
57 57 log "You specified a language that is not supported: #{language}."
58 58 exit(127)
59 59 end
60 60
61 61 source_file = ARGV[1]
62 62 ENV['SOURCE_NAME'] = source_file
63 63 if File.exist?(source_file) == false
64 64 log "The source file does not exist."
65 65 exit(127)
66 66 end
67 67
68 68 log "Making test result and sandbox directories..."
69 69
70 70 current_dir = FileUtils.pwd
71 71 current_dir.strip!
72 72
73 73 if ARGV.length >= 3
74 74 test_result_dir = ARGV[2]
75 75 else
76 76 test_result_dir = "#{current_dir}/test-result"
77 77 end
78 78
79 79 log "Test result directory: #{test_result_dir}"
80 80 clear_and_create_empty_dir(test_result_dir)
81 81
82 82 if ARGV.length >= 4
83 83 sandbox_dir = ARGV[3]
84 84 else
85 85 sandbox_dir = "#{current_dir}/sandbox"
86 86 end
87 87 log "Sandbox directory: #{sandbox_dir}"
88 88 clear_and_create_empty_dir(sandbox_dir)
89 89
90 90 # Compile
91 91 log
92 92 log "Compiling..."
93 93 call_and_log("Cannot copy the source file to #{sandbox_dir}") {
94 94 FileUtils.cp(source_file, sandbox_dir)
95 95 }
96 96 begin
97 97 Dir.chdir sandbox_dir
98 98 rescue
99 99 log "ERROR: Cannot change directory to #{sandbox_dir}."
100 100 exit(127)
101 101 end
102 102 execute("#{problem_home}/script/compile #{language} #{source_file}", "Compilation error!")
103 103 compile_message = open("compiler_message").read
104 104 compile_message.strip!
105 105 call_and_log("Cannot move the compiler message to #{test_result_dir}.") {
106 106 FileUtils.mv("compiler_message", test_result_dir)
107 107 }
108 108 if !FileTest.exist?("a.out")
109 109 log "Cannot compile the source code. See message in #{test_result_dir}/compile_message"
110 110 exit(127)
111 111 else
112 112 call_and_log("Cannot move the compiled program to #{test_result_dir}") {
113 113 FileUtils.mv("a.out",test_result_dir)
114 114 if language == "java" then Dir["*.class"].each { |file| FileUtils.mv(file,test_result_dir)} end
115 115 if language == "python" then Dir["*.pyc"].each { |file| FileUtils.mv(file,test_result_dir)} end
116 116 }
117 117 FileUtils.rm_rf("#{sandbox_dir}/.")
118 118 end
119 119
120 120 require "#{problem_home}/script/test_dsl.rb"
121 121 load "#{problem_home}/test_cases/all_tests.cfg"
122 122 problem = Problem.get_instance
123 123
124 124 if problem.well_formed? == false
125 125 log "The problem specification is not well formed."
126 126 exit(127)
127 127 end
128 128
129 129 # Doing the testing.
130 130 (1..(problem.num_tests)).each do |test_num|
131 131
132 132 $stdout.print "[#{test_num}]"
133 133 $stdout.flush
134 134
135 135 log "Test number: #{test_num}"
136 136
137 137 call_and_log("Cannot copy the compiled program into #{sandbox_dir}") {
138 138 FileUtils.cp("#{test_result_dir}/a.out", sandbox_dir, :preserve => true)
139 139 if language == "java" then Dir["#{test_result_dir}/*.class"].each { |file| FileUtils.cp(file,sandbox_dir)} end
140 140 if language == "python" then Dir["#{test_result_dir}/*.pyc"].each { |file| FileUtils.cp(file,sandbox_dir)} end
141 141 }
142 142
143 + #additionally copy any extra .txt file
144 + data_files = Dir[problem_home + '/*.txt']
145 + data_files.each do |file|
146 + FileUtils.cp(file,sandbox_dir)
147 + end
148 +
143 149 begin
144 150 execute("#{problem_home}/script/run #{language} #{test_num} ", "Error occured during execution of the run script")
145 151 rescue
146 152 # do nothing
147 153 end
148 154
149 155 call_and_log("Cannot create directory #{test_result_dir}/#{test_num}") {
150 156 FileUtils.mkdir "#{test_result_dir}/#{test_num}"
151 157 }
152 158 call_and_log("Cannot copy the result file into #{test_result_dir}/#{test_num}") {
153 159 FileUtils.mv "#{sandbox_dir}/result", "#{test_result_dir}/#{test_num}"
154 160 }
155 161 call_and_log("Cannot copy the comment file into #{test_result_dir}/#{test_num}") {
156 162 FileUtils.mv "#{sandbox_dir}/comment", "#{test_result_dir}/#{test_num}"
157 163 }
158 164 call_and_log("Cannot copy the output file into #{test_result_dir}/#{test_num}") {
159 165 FileUtils.mv "#{sandbox_dir}/output.txt", "#{test_result_dir}/#{test_num}"
160 166 }
161 167 call_and_log("Cannot clear #{sandbox_dir}") {
162 168 FileUtils.rm_rf(Dir.glob("#{sandbox_dir}/*"), :secure => true)
163 169 }
164 170 end
165 171
166 172 $stdout.print "[done]\n"
167 173
168 174 # Grade
169 175 log
170 176 log "Grading..."
171 177 begin
172 178 Dir.chdir test_result_dir
173 179 rescue
174 180 log "ERROR: Cannot change directory to #{test_result_dir}."
175 181 exit(127)
176 182 end
177 183 execute("#{problem_home}/script/grade", "An error occured during grading!")
178 184
179 185 log
180 186 log "All done!"
@@ -1,189 +1,192
1 1 #!/usr/bin/env ruby
2 2
3 3 require 'fileutils'
4 4
5 5 def log(str='')
6 6 if ENV['TALKATIVE']!=nil
7 7 puts str
8 8 end
9 9 if ENV['GRADER_LOGGING']!=nil
10 10 log_fname = ENV['GRADER_LOGGING']
11 11 fp = File.open(log_fname,"a")
12 12 fp.puts("run: #{Time.new.strftime("%H:%M")} #{str}")
13 13 fp.close
14 14 end
15 15 end
16 16
17 17 def extract_time(t)
18 18 # puts "TIME: #{t}"
19 19 if (result=/^(.*)r(.*)u(.*)s/.match(t))
20 20 {:real => result[1], :user => result[2], :sys => result[3]}
21 21 else
22 22 #{:real => 0, :user => 0, :sys => 0}
23 23 #puts "ERROR READING RUNNING TIME: #{t}"
24 24 raise "Error reading running time: #{t}"
25 25 end
26 26 end
27 27
28 28 def compile_box(source,bin)
29 29 system("g++ #{source} -o #{bin}")
30 30 end
31 31
32 32 if ARGV.length < 2 || ARGV.length > 3
33 33 puts "Usage: run <language> <test-num> [<program-name>]"
34 34 exit(127)
35 35 end
36 36
37 37 language = ARGV[0]
38 38 test_num = ARGV[1].to_i
39 39 if ARGV.length > 2
40 40 program_name = ARGV[2]
41 41 else
42 42 program_name = "a.out"
43 43 end
44 44
45 45 problem_home = ENV['PROBLEM_HOME']
46 46 source_name = ENV['SOURCE_NAME']
47 47 require "#{problem_home}/script/test_dsl.rb"
48 48 load "#{problem_home}/test_cases/all_tests.cfg"
49 49 problem = Problem.get_instance
50 50
51 51 sandbox_dir = Dir.getwd
52 52
53 53 if problem.well_formed? == false
54 54 log "The problem specification is not well formed."
55 55 exit(127)
56 56 end
57 57
58 58 # Check if the test number is okay.
59 59 if test_num <= 0 || test_num > problem.num_tests
60 60 log "You have specified a wrong test number."
61 61 exit(127)
62 62 end
63 63
64 64 #####################################
65 65 # Set the relavant file names here. #
66 66 #####################################
67 67
68 68 input_file_name = "#{problem_home}/test_cases/#{test_num}/input-#{test_num}.txt"
69 69
70 70 #####################################
71 71
72 72 time_limit = problem.get_time_limit test_num
73 73 mem_limit = problem.get_mem_limit(test_num) * 1024
74 74
75 75 # Copy the input file.
76 76 #`cp #{problem_home}/test_cases/#{test_num}/#{input_file_name} .`
77 77
78 78 # check if box is there, if not, compile it!
79 79 if !File.exists?("#{problem_home}/script/box")
80 80 log "WARNING: Compiling box: to increase efficiency, it should be compile manually"
81 81 compile_box("#{problem_home}/script/box.cc",
82 82 "#{problem_home}/script/box")
83 83 end
84 84
85 85 # Hide PROBLEM_HOME
86 86 ENV['PROBLEM_HOME'] = nil
87 87 ENV['SOURCE_NAME'] = nil
88 88
89 89 # Run the program.
90 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 93 JAVA_OPTION = "-s set_robust_list -s futex -s clone -s getppid -s clone -s wait4 -p /usr/bin/ -p ./"
94 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 /dev/urandom -p /usr/ -p #{sandbox_dir}/#{program_name} -p ./#{program_name} -p #{sandbox_dir}/#{source_name} -p /proc/sys/crypto/fips_enabled -p /proc/self/status -p /proc/mounts -p /var/lib/dpkg/status -s statfs -s set_robust_list -s openat -s sysinfo -s recvmsg -s connect -s socket -s sendto -s futex -s sigaltstack -s getrandom -E PYTHONNOUSERSITE=yes"
96 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 + HASKELL_OPTION = "-s set_robust_list -s clock_gettime -s sysinfo -s timer_create -s timer_settime -s futex -s timer_delete"
97 98
98 99 case language
99 100 when "java"
100 101 # for java, extract the classname
101 102 # wne have to add additional systemcall and we don't check the mem limit (dunno how to fix...)
102 103 classname = 'DUMMY'
103 104 File.open(program_name,"r").each do |line|
104 105 classname = line
105 106 end
106 107 #for java, we cannot really check the memory limit...
107 108 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 109 when "ruby"
109 110 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 111 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} "
112 + run_command = "#{problem_home}/script/box -a 2 -f -T -t #{time_limit*=2} -m #{[128 * 1024,mem_limit].max} #{PYTHON_OPTION} -i #{input_file_name} -o output.txt /usr/bin/python3 #{program_name} "
113 + when "haskell"
114 + run_command = "#{problem_home}/script/box -a 2 -f -T -t #{time_limit} -m #{[512 * 1024,mem_limit].max} #{HASKELL_OPTION} -i #{input_file_name} -o output.txt #{program_name} "
112 115 when "php"
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} "
116 + 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 117 else # for c++, pascal, we do the normal checking
115 118 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 119 end
117 120
118 121
119 122 log "Running test #{test_num}..."
120 123 log run_command
121 124 log
122 125 system(run_command,err: 'run_result')
123 126
124 127 # Restore PROBLEM_HOME
125 128 ENV['PROBLEM_HOME'] = problem_home
126 129
127 130 # Create the result file.
128 131 result_file = File.new("result", "w")
129 132 comment_file = File.new("comment", "w")
130 133
131 134 # Check if the program actually produced any output.
132 135 run_result_file = File.new("run_result", "r")
133 136 run_result = run_result_file.readlines
134 137 run_result_file.close
135 138
136 139 run_stat = run_result[run_result.length-1]
137 140 running_time = extract_time(run_stat)
138 141
139 142 report = lambda{ |status, points, comment|
140 143 result_file.write status.strip
141 144 result_file.write "\n"
142 145 result_file.write points.to_s.strip
143 146 result_file.write "\n"
144 147 result_file.write run_stat.strip
145 148 result_file.write "\n"
146 149 result_file.close
147 150 FileUtils.rm "run_result"
148 151 # `rm output.txt` --- keep the output
149 152
150 153 comment_file.write comment
151 154
152 155 # added for debuggin --- jittat
153 156 comment_file.write "--run-result--\n"
154 157 run_result.each do |l|
155 158 comment_file.write l
156 159 end
157 160
158 161 comment_file.close
159 162
160 163 log "Done!"
161 164 exit(0)
162 165 }
163 166
164 167
165 168 if run_result[0][0,2] != "OK"
166 169 log "There was a runtime error."
167 170 report.call(run_result[0], 0, "No comment.\n")
168 171 end
169 172
170 173 if running_time[:user].to_f > time_limit
171 174 log "Time limit exceeded."
172 175 report.call("Time limit exceeded", 0, "No comment.\n")
173 176 end
174 177
175 178 # Run 'check' to evaluate the output.
176 179 #puts "There was no runtime error. Proceed to checking the output."
177 180 check_command = "#{problem_home}/script/check #{language} #{test_num}"
178 181 log "Checking the output..."
179 182 log check_command
180 183 if not system(check_command)
181 184 log "Problem with check script"
182 185 report.call("Incorrect",0,"Check script error.\n")
183 186 exit(127)
184 187 end
185 188
186 189 check_file = File.new("check_result", "r")
187 190 check_file_lines = check_file.readlines
188 191
189 192 report.call(check_file_lines[0], check_file_lines[1], "No comment.\n")
@@ -1,66 +1,75
1 1 #!/usr/bin/env ruby
2 2
3 3 problem_home = ENV['PROBLEM_HOME']
4 4 require "#{problem_home}/script/test_dsl.rb"
5 5
6 6 if ARGV.length < 2
7 7 puts "Usage: check <language> <test-number> [<output-file>]"
8 8 exit(0)
9 9 end
10 10
11 11 language = ARGV[0]
12 12 test_num = ARGV[1].to_i
13 13 if ARGV.length >= 3
14 14 output_file_name = ARGV[2]
15 15 else
16 16 output_file_name = "output.txt"
17 17 end
18 18
19 19 load "#{problem_home}/test_cases/all_tests.cfg"
20 20 problem = Problem.get_instance
21 21
22 22 output_file = File.new(output_file_name, "r")
23 23 answer_file = File.new("#{problem_home}/test_cases/#{test_num}/answer-#{test_num}.txt")
24 24 result_file = File.new("check_result", "w")
25 25
26 26 output_file_content = output_file.read
27 27 answer_file_content = answer_file.read
28 28
29 29 report_correct = lambda {
30 30 result_file.write "Correct\n"
31 31 result_file.write problem.get_score(test_num)
32 32 result_file.write "\n"
33 33 result_file.close
34 34 exit(0)
35 35 }
36 36
37 37 report_wrong = lambda {
38 38 result_file.write "Incorrect\n"
39 39 result_file.write "0\n"
40 40 result_file.close
41 41 exit(0)
42 42 }
43 43
44 44 ##################
45 45 # Your code here #
46 46 ##################
47 47
48 48 ########### THIS IS FOR CHECKING FLOAT with EPSILON error ##########
49 49
50 +
51 + def is_float?(fl)
52 + !!Float(fl) rescue false
53 + end
54 +
50 55 EPSILON = 0.000001
51 56
52 57 out_items = output_file_content.split
53 58 ans_items = answer_file_content.split
54 59
55 60 if out_items.length != ans_items.length
56 61 report_wrong.call
57 62 else
58 63 out_items.length.times do |i|
59 - out_value = out_items[i].to_f
60 - ans_value = ans_items[i].to_f
61 - if (out_value - ans_value).abs > EPSILON * [out_value.abs,ans_value.abs].max
62 - report_wrong.call
64 + if is_float?(out_items[i]) && is_float?(ans_items[i])
65 + out_value = out_items[i].to_f
66 + ans_value = ans_items[i].to_f
67 + if (out_value - ans_value).abs > EPSILON * [out_value.abs,ans_value.abs].max
68 + report_wrong.call
69 + end
70 + else
71 + report_wrong.call if out_items[i] != ans_items[i]
63 72 end
64 73 end
65 74 report_correct.call
66 75 end
You need to be logged in to leave comments. Login now