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

r173:aa6e7ff3942c - - 9 files changed: 117 inserted, 27 deleted

@@ -1,443 +1,459
1 1 #!/usr/bin/env ruby
2 2
3 3 def stop_grader(id)
4 4 if id==:all
5 5 File.open(File.dirname(__FILE__) + "/stop.all",'w').close
6 6 else
7 7 File.open(File.dirname(__FILE__) + "/stop.#{id}",'w').close
8 8 end
9 9 end
10 10
11 11 def check_stopfile
12 12 FileTest.exist?(File.dirname(__FILE__) + "/stop.all") or
13 13 FileTest.exist?(File.dirname(__FILE__) + "/stop.#{Process.pid}")
14 14 end
15 15
16 16 def clear_stopfile
17 17 if FileTest.exist?(File.dirname(__FILE__) + "/stop.#{Process.pid}")
18 18 File.delete(File.dirname(__FILE__) + "/stop.#{Process.pid}")
19 19 end
20 20 end
21 21
22 22 def config
23 23 Grader::Configuration.get_instance
24 24 end
25 25
26 26 def log_file_name
27 27 if !File.exists?(config.log_dir)
28 28 raise "Log directory does not exist: #{config.log_dir}"
29 29 end
30 30 config.log_dir +
31 31 "/#{GRADER_ENV}_#{config.grader_mode}.#{Process.pid}"
32 32 end
33 33
34 34 def log(str)
35 35 if config.talkative
36 36 puts str
37 37 end
38 38 if config.logging
39 39 fp = File.open(log_file_name,"a")
40 40 fp.puts("GRADER: #{Time.new.strftime("%H:%M")} #{str}")
41 41 fp.close
42 42 end
43 43 end
44 44
45 45 def display_manual
46 46 puts <<USAGE
47 47 Grader.
48 48 using: (1) grader
49 - (2) grader environment [mode]
49 + (2) grader environment [mode] [options]
50 50 (3) grader stop [all|pids-list]
51 51 (4) grader --help
52 52 (1) call grader with environment = 'exam', mode = 'queue'
53 53 (2) possible modes are: 'queue', 'test_request', 'prob', 'sub', 'contest', and 'autonew'
54 + queue: repeatedly check the task queue and grade any available tasks
55 +
56 + prob: re-grade every user latest submission of the specific problem.
57 + the problem name must be specified by the next argument.
58 +
59 + additional options:
60 +
61 + --all-sub re-grade every submissions instead of just the latest submission of each user.
62 + sub: re-grader the specified submission.
63 + The submission ID to be re-graded must be specified by the next argument.
64 +
54 65 (3) create stop-file to stop running grader in queue mode
55 66 (4) You are here.
56 67 USAGE
57 68 end
58 69
59 70 def process_options_and_stop_file
60 71 # The list of options are:
61 72 # - stop [all|process ids]
62 73 # -
63 74
64 75 # Process 'help' option
65 76 if (ARGV.length==1) and (/help/.match(ARGV[0]))
66 77 display_manual
67 78 exit(0)
68 79 end
69 80
70 81 # Process 'stop' option.
71 82 if (ARGV.length >= 1) and (ARGV[0]=='stop')
72 83 if ARGV.length==1
73 84 puts "you should specify pid-list or 'all'"
74 85 display_manual
75 86 elsif (ARGV.length==2) and (ARGV[1]=='all')
76 87 stop_grader(:all)
77 88 puts "A global stop file ('stop.all') created."
78 89 puts "You should remove it manually later."
79 90 else
80 91 (1..ARGV.length-1).each do |i|
81 92 stop_grader(ARGV[i])
82 93 end
83 94 puts "stop file(s) created"
84 95 end
85 96 exit(0)
86 97 end
87 98
88 99 # Check stop file.
89 100 if check_stopfile
90 101 puts "Stop file exists. Terminated."
91 102 clear_stopfile
92 103 exit(0)
93 104 end
94 105
95 106 #default options
96 107 options = {
97 108 :mode => 'queue',
98 109 :environment => 'exam',
99 110 :dry_run => false,
100 111 }
101 112
102 113 # Process mode and environment option
103 114 if ARGV.length >= 1
104 115 options[:environment] = ARGV.shift
105 116 if ARGV.length >=1
106 117 options[:mode] = ARGV.shift
107 118 end
119 + else
120 + puts 'no argument specified, using default mode and environment.'
108 121 end
109 122
110 123 options[:dry_run] = (ARGV.delete('--dry') != nil)
111 124 if options[:dry_run] and (not ['prob','contest','autonew'].include? options[:mode])
112 125 puts "Dry run currently works only for 'prob' or 'contest' modes."
113 126 exit(0)
114 127 end
115 128
116 129 options[:report] = (ARGV.delete('--report') != nil)
117 130 if options[:report] and (not ['prob','contest','autonew'].include? options[:mode])
118 131 puts "Report currently works only for 'prob' or 'contest' modes."
119 132 exit(0)
120 133 end
121 134
135 + options[:all_sub] = (ARGV.delete('--all-sub') != nil)
136 +
122 137 return options
123 138 end
124 139
125 140 class ResultCollector
126 141 def initialize
127 142 @results = {}
128 143 @problems = {}
129 144 @users = {}
130 145 end
131 146
132 147 def after_save_hook(submission, grading_result)
133 148 end
134 149
135 150 def save(submission, grading_result)
136 151 user = submission.user
137 152 problem = submission.problem
138 153 if not @problems.has_key? problem.id
139 154 @problems[problem.id] = problem
140 155 end
141 156 if not @users.has_key? user.id
142 157 @users[user.id] = user
143 158 end
144 159 @results[[user.id, problem.id]] = grading_result
145 160
146 161 after_save_hook(submission, grading_result)
147 162 end
148 163
149 164 def print_report_by_user
150 165 puts "---------------------"
151 166 puts " REPORT"
152 167 puts "---------------------"
153 168
154 169 print "login,email"
155 170 @problems.each_value do |problem|
156 171 print ",#{problem.name}"
157 172 end
158 173 print "\n"
159 174
160 175 @users.each_value do |user|
161 176 print "#{user.login},#{user.email}"
162 177 @problems.each_value do |problem|
163 178 if @results.has_key? [user.id, problem.id]
164 179 print ",#{@results[[user.id,problem.id]][:points]}"
165 180 else
166 181 print ","
167 182 end
168 183 end
169 184 print "\n"
170 185 end
171 186 end
172 187 end
173 188
174 189 def grader_general_loop(engine, grader_proc, options)
175 190 runner = Grader::Runner.new(engine, grader_proc)
176 191 while true
177 192
178 193 if check_stopfile # created by calling grader stop
179 194 clear_stopfile
180 195 log "stopped (with stop file)"
181 196 break
182 197 end
183 198
184 199 task = yield(runner)
185 200
186 201 if task==nil
187 202 sleep(1)
188 203 end
189 204 end
190 205 end
191 206
192 207 def grader_queue_loop(grader_proc, options)
193 208 log "Grader: queue"
194 209 engine = Grader::Engine.new
195 210 grader_general_loop(engine, grader_proc, options) do |runner|
196 211 runner.grade_oldest_task
197 212 end
198 213 end
199 214
200 215 def grader_test_request_loop(grader_proc, options)
201 216 log "Grader: test_request"
202 217 engine = Grader::Engine.new(:room_maker => Grader::TestRequestRoomMaker.new,
203 218 :reporter => Grader::TestRequestReporter.new)
204 219 grader_general_loop(engine, grader_proc, options) do |runner|
205 220 runner.grade_oldest_test_request
206 221 end
207 222 end
208 223
209 224 def grader_autonew_loop(grader_proc, options)
210 225 log "Grader: autonew"
211 226
212 227 if options[:report]
213 228 result_collector = ResultCollector.new
214 229 else
215 230 result_collector = nil
216 231 end
217 232
218 233 if options[:dry_run]
219 234 puts "Running in dry mode"
220 235 end
221 236
222 237 prob_reporter = Grader::SubmissionReporter.new(:dry_run => options[:dry_run],
223 238 :result_collector => result_collector)
224 239
225 240 engine = Grader::Engine.new(:reporter => prob_reporter)
226 241 runner = Grader::Runner.new(engine, grader_proc)
227 242
228 243 grader_proc.report_active if grader_proc!=nil
229 244
230 245 latest_submitted_at = nil
231 246 graded_submission_ids = {}
232 247
233 248 while true
234 249
235 250 if check_stopfile # created by calling grader stop
236 251 clear_stopfile
237 252 log "stopped (with stop file)"
238 253 break
239 254 end
240 255
241 256 if latest_submitted_at==nil
242 257 submissions = Submission.all
243 258 else
244 259 submissions = Submission.all(:conditions => ["submitted_at >= :latest",
245 260 {:latest => latest_submitted_at}])
246 261 end
247 262
248 263 graded_any = false
249 264
250 265 if submissions.length != 0
251 266 submissions.each do |submission|
252 267 if (submission.problem == nil) or (!submission.problem.available)
253 268 next
254 269 end
255 270 if ! graded_submission_ids[submission.id]
256 271 runner.grade_submission(submission)
257 272 graded_submission_ids[submission.id] = true
258 273 if (!latest_submitted_at or
259 274 latest_submitted_at < submission.submitted_at)
260 275 latest_submitted_at = submission.submitted_at
261 276 end
262 277 puts "graded: #{submission.id}"
263 278 puts "latest: #{latest_submitted_at}"
264 279 graded_any = true
265 280 end
266 281 end
267 282 end
268 283
269 284 if ! graded_any
270 285 sleep(1)
271 286 end
272 287 end
273 288 end
274 289
275 290 def grader_grade_problems(grader_proc, options)
276 291 if options[:report]
277 292 result_collector = ResultCollector.new
278 293 else
279 294 result_collector = nil
280 295 end
281 296
282 297 if options[:dry_run]
283 298 puts "Running in dry mode"
284 299 end
285 300
286 301 prob_reporter = Grader::SubmissionReporter.new(:dry_run => options[:dry_run],
287 302 :result_collector => result_collector)
288 303 engine = Grader::Engine.new(:reporter => prob_reporter)
289 304 runner = Grader::Runner.new(engine, grader_proc)
290 305
291 306 grader_proc.report_active if grader_proc!=nil
292 307
293 308 ARGV.each do |prob_name|
294 309 prob = Problem.find_by_name(prob_name)
295 310 if prob==nil
296 311 puts "cannot find problem: #{prob_name}"
297 312 else
298 - runner.grade_problem(prob)
313 + runner.grade_problem(prob,options)
299 314 end
300 315 end
301 316
302 317 if options[:report]
303 318 result_collector.print_report_by_user
304 319 end
305 320 end
306 321
307 322 def grader_grade_contests(grader_proc, options)
308 323 # always use dry run when grading during contest
309 324 dry_run = options[:dry_run] = true
310 325
311 326 contest_name = ARGV.shift
312 327
313 328 contest = Contest.find_by_name(contest_name)
314 329 if contest==nil
315 330 puts "cannot find contest: #{contest_name}"
316 331 exit(0)
317 332 end
318 333
319 334 if options[:report]
320 335 result_collector = ResultCollector.new
321 336 else
322 337 result_collector = nil
323 338 end
324 339
325 340 if options[:dry_run]
326 341 puts "Running in dry mode"
327 342 end
328 343
329 344 prob_reporter = Grader::SubmissionReporter.new(:dry_run => dry_run,
330 345 :result_collector => result_collector)
331 346 engine = Grader::Engine.new(:reporter => prob_reporter)
332 347 runner = Grader::Runner.new(engine, grader_proc)
333 348
334 349 grader_proc.report_active if grader_proc!=nil
335 350
336 351 contest.problems.each do |problem|
337 352 puts "Grading: #{problem.name}"
338 353 runner.grade_problem(problem,
339 354 :user_conditions => lambda do |u|
340 355 u.contest_finished? and
341 356 u.contest_ids.include?(contest.id)
342 357 end)
343 358 end
344 359
345 360 if options[:report]
346 361 result_collector.print_report_by_user
347 362 end
348 363 end
349 364
350 365 def grader_grade_submissions(grader_proc, options)
351 366 engine = Grader::Engine.new
352 367 runner = Grader::Runner.new(engine, grader_proc)
353 368
354 369 grader_proc.report_active if grader_proc!=nil
355 370
356 371 ARGV.each do |sub_id|
357 372 puts "Grading #{sub_id}"
358 373 begin
359 374 submission = Submission.find(sub_id.to_i)
360 375 rescue ActiveRecord::RecordNotFound
361 376 puts "Submission #{sub_id} not found"
362 377 submission = nil
363 378 end
364 379
365 380 if submission!=nil
366 381 runner.grade_submission(submission)
367 382 end
368 383 end
369 384 end
370 385
371 386 #########################################
372 387 # main program
373 388 #########################################
374 389
375 390 options = process_options_and_stop_file
376 391 GRADER_ENV = options[:environment]
377 392 grader_mode = options[:mode]
378 393 dry_run = options[:dry_run]
379 394
380 395 puts "environment: #{GRADER_ENV}"
396 + puts "grader mode: #{grader_mode}"
381 397 require File.join(File.dirname(__FILE__),'config/environment')
382 398
383 399 # add grader_mode to config
384 400 # this is needed because method log needs it. TODO: clean this up
385 401 class << config
386 402 attr_accessor :grader_mode
387 403 end
388 404 config.grader_mode = grader_mode
389 405
390 406 # reading rails environment
391 407 log 'Reading rails environment'
392 408
393 409 RAILS_ENV = config.rails_env
394 410 require RAILS_ROOT + '/config/environment'
395 411
396 412 # register grader process
397 413 if config.report_grader
398 414 grader_proc = GraderProcess.register(config.grader_hostname,
399 415 Process.pid,
400 416 grader_mode)
401 417 else
402 418 grader_proc = nil
403 419 end
404 420
405 421 #set loggin environment
406 422 ENV['GRADER_LOGGING'] = log_file_name
407 423
408 424 # register exit handler to report inactive, and terminated
409 425 at_exit do
410 426 if grader_proc!=nil
411 427 grader_proc.report_inactive
412 428 grader_proc.terminate
413 429 end
414 430 end
415 431
416 432 #
417 433 # MAIN LOOP
418 434 #
419 435
420 436 case grader_mode
421 437 when "queue"
422 438 grader_queue_loop(grader_proc, options)
423 439
424 440 when "test_request"
425 441 grader_test_request_loop(grader_proc, options)
426 442
427 443 when "prob"
428 444 grader_grade_problems(grader_proc, options)
429 445
430 446 when "contest"
431 447 grader_grade_contests(grader_proc, options)
432 448
433 449 when "sub"
434 450 grader_grade_submissions(grader_proc, options)
435 451
436 452 when "autonew"
437 453 grader_autonew_loop(grader_proc, options)
438 454
439 455 else
440 456 display_manual
441 457 exit(0)
442 458 end
443 459
@@ -1,62 +1,67
1 1 #
2 2 # A runner drives the engine into various tasks.
3 3 #
4 4
5 5 module Grader
6 6
7 7 class Runner
8 8
9 9 def initialize(engine, grader_process=nil)
10 10 @engine = engine
11 11 @grader_process = grader_process
12 12 end
13 13
14 14 def grade_oldest_task
15 15 task = Task.get_inqueue_and_change_status(Task::STATUS_GRADING)
16 16 if task!=nil
17 17 @grader_process.report_active(task) if @grader_process!=nil
18 18
19 19 submission = Submission.find(task.submission_id)
20 20 @engine.grade(submission)
21 21 task.status_complete!
22 22 @grader_process.report_inactive(task) if @grader_process!=nil
23 23 end
24 24 return task
25 25 end
26 26
27 27 def grade_problem(problem, options={})
28 - users = User.find(:all)
29 - users.each do |u|
28 + User.find_each do |u|
30 29 puts "user: #{u.login}"
31 30 if options[:user_conditions]!=nil
32 31 con_proc = options[:user_conditions]
33 32 next if not con_proc.call(u)
34 33 end
34 + if options[:all_sub]
35 + Submission.where(user_id: u.id,problem_id: problem.id).find_each do |sub|
36 + @engine.grade(sub)
37 + end
38 + else
35 39 last_sub = Submission.find_last_by_user_and_problem(u.id,problem.id)
36 40 if last_sub!=nil
37 41 @engine.grade(last_sub)
38 42 end
39 43 end
40 44 end
45 + end
41 46
42 47 def grade_submission(submission)
43 48 puts "Submission: #{submission.id} by #{submission.user.full_name}"
44 49 @engine.grade(submission)
45 50 end
46 51
47 52 def grade_oldest_test_request
48 53 test_request = TestRequest.get_inqueue_and_change_status(Task::STATUS_GRADING)
49 54 if test_request!=nil
50 55 @grader_process.report_active(test_request) if @grader_process!=nil
51 56
52 57 @engine.grade(test_request)
53 58 test_request.status_complete!
54 59 @grader_process.report_inactive(test_request) if @grader_process!=nil
55 60 end
56 61 return test_request
57 62 end
58 63
59 64 end
60 65
61 66 end
62 67
@@ -1,134 +1,152
1 1 module Grader
2 2
3 3 class SubmissionRoomMaker
4 4 def initialize
5 5 @config = Grader::Configuration.get_instance
6 6 end
7 7
8 8 def produce_grading_room(submission)
9 9 user = submission.user
10 10 problem = submission.problem
11 11 grading_room = "#{@config.user_result_dir}/" +
12 12 "#{user.login}/#{problem.name}/#{submission.id}"
13 13
14 14 FileUtils.mkdir_p(grading_room)
15 15 grading_room
16 16 end
17 17
18 18 def find_problem_home(submission)
19 19 problem = submission.problem
20 20 "#{@config.problems_dir}/#{problem.name}"
21 21 end
22 22
23 23 def save_source(submission,source_name)
24 24 dir = self.produce_grading_room(submission)
25 25 f = File.open("#{dir}/#{source_name}","w")
26 26 f.write(submission.source)
27 27 f.close
28 28 end
29 29
30 30 def clean_up(submission)
31 31 end
32 32 end
33 33
34 34 class SubmissionReporter
35 35 def initialize(options={})
36 36 options = {:dry_run => false, :result_collector => nil}.merge(options)
37 37 @config = Grader::Configuration.get_instance
38 38 @dry_run = options[:dry_run]
39 39 @result_collector = options[:result_collector]
40 40 end
41 41
42 42 def report(sub,test_result_dir)
43 43 result = read_result(test_result_dir)
44 44 if @result_collector
45 45 @result_collector.save(sub,
46 46 result)
47 47 end
48 48 save_result(sub,result)
49 49 end
50 50
51 51 def report_error(sub,msg)
52 52 save_result(sub,{:points => 0,
53 53 :comment => "Grading error: #{msg}" })
54 54 end
55 55
56 56 protected
57 57 def read_result(test_result_dir)
58 58 cmp_msg_fname = "#{test_result_dir}/compiler_message"
59 59 if FileTest.exist?(cmp_msg_fname)
60 60 cmp_file = File.open(cmp_msg_fname)
61 61 cmp_msg = cmp_file.read
62 62 cmp_file.close
63 63 else
64 64 cmp_msg = ""
65 65 end
66 66
67 67 result_fname = "#{test_result_dir}/result"
68 68 comment_fname = "#{test_result_dir}/comment"
69 + runstat_fname = "#{test_result_dir}/run_stat"
69 70 if FileTest.exist?(result_fname)
70 71 comment = ""
71 72 begin
72 73 result_file = File.open(result_fname)
73 74 result = result_file.readline.to_i
74 75 result_file.close
75 76 rescue
76 77 result = 0
77 78 comment = "error reading result file."
78 79 end
79 80
80 81 begin
81 82 comment_file = File.open(comment_fname)
82 83 comment += comment_file.readline.chomp
83 84 comment_file.close
84 85 rescue
85 86 comment += ""
86 87 end
87 88
88 - return {:points => result,
89 - :comment => comment,
90 - :cmp_msg => cmp_msg}
89 + begin
90 + runstat_file = File.open(runstat_fname)
91 + max_runtime = runstat_file.readline.to_f
92 + peak_memory = runstat_file.readline.to_i
93 + rescue
94 + max_runtime = -1
95 + peak_memory = -1
96 + end
97 +
98 +
99 + return {points: result,
100 + comment: comment,
101 + cmp_msg: cmp_msg,
102 + max_runtime: max_runtime,
103 + peak_memory: peak_memory
104 + }
91 105 else
92 106 if FileTest.exist?("#{test_result_dir}/a.out")
93 107 return {:points => 0,
94 108 :comment => 'error during grading',
95 109 :cmp_msg => cmp_msg}
96 110 else
97 111 return {:points => 0,
98 112 :comment => 'compilation error',
99 113 :cmp_msg => cmp_msg}
100 114 end
101 115 end
102 116 end
103 117
104 118 def save_result(submission,result)
105 119 problem = submission.problem
106 120 submission.graded_at = Time.now.gmtime
107 121 points = result[:points]
108 122 submission.points = points
109 123 comment = @config.report_comment(result[:comment])
110 124
125 + submission.peak_memory = result[:peak_memory]
126 + submission.max_runtime = result[:max_runtime]
127 + submission.effective_code_length =submission.source.length
128 +
111 129 #
112 130 # TODO: FIX THIS MESSAGE
113 131 #
114 132 if problem == nil
115 133 submission.grader_comment = 'PASSED: ' + comment + '(problem is nil)'
116 134 elsif points == problem.full_score
117 135 #submission.grader_comment = 'PASSED: ' + comment
118 136 submission.grader_comment = comment
119 137 elsif result[:comment].chomp =~ /^[\[\]P]+$/
120 138 submission.grader_comment = 'PASSED: ' + comment + '(inconsistent score)'
121 139 else
122 140 #submission.grader_comment = 'FAILED: ' + comment
123 141 submission.grader_comment = comment
124 142 end
125 143 submission.compiler_message = result[:cmp_msg] or ''
126 144
127 145 if not @dry_run
128 146 submission.save
129 147 end
130 148 end
131 149
132 150 end
133 151
134 152 end
@@ -9,246 +9,246
9 9 begin
10 10 FileUtils.ln_s(src, des)
11 11 rescue NotImplementedError
12 12 FileUtils.cp(src,des)
13 13 end
14 14 end
15 15
16 16 def self.call_and_log(error_message)
17 17 begin
18 18 yield
19 19 rescue
20 20 msg = "ERROR: #{error_message}"
21 21 raise msg
22 22 end
23 23 end
24 24
25 25 #
26 26 # A TestRequestRoomMaker is a helper object for Engine
27 27 # - finds grading room: in user_result_dir/(user)/test_request/ ...
28 28 # - prepare problem configuration for grading --- basically it copy
29 29 # all config files, and copy user's input into the testcase
30 30 # directory. First, it finds the template from problem template
31 31 # directory; if it can't find a template, it'll use the template
32 32 # from default template.
33 33 class TestRequestRoomMaker
34 34 def initialize
35 35 @config = Grader::Configuration.get_instance
36 36 end
37 37
38 38 def produce_grading_room(test_request)
39 39 grading_room = grading_room_dir(test_request)
40 40 FileUtils.mkdir_p(grading_room)
41 41
42 42 #
43 43 # Also copy additional submitted file to this directory as well.
44 44 # The program would see this file only if it is copied
45 45 # to the sandbox directory later. The run script should do it.
46 46 #
47 47 if FileTest.exists?("#{test_request.input_file_name}.files")
48 48 FileUtils.cp_r("#{test_request.input_file_name}.files/.",
49 49 "#{grading_room}")
50 50 end
51 51
52 52 grading_room
53 53 end
54 54
55 55 def find_problem_home(test_request)
56 56 problem_name = test_request.problem_name
57 57
58 58 template_dir = "#{@config.test_request_problem_templates_dir}/" + problem_name
59 59
60 60 raise "Test Request: error template not found" if !File.exists?(template_dir)
61 61
62 62 problem_home = problem_home_dir(test_request)
63 63 FileUtils.mkdir_p(problem_home)
64 64
65 65 copy_problem_template(template_dir,problem_home)
66 66 link_input_file(test_request,problem_home)
67 67
68 68 problem_home
69 69 end
70 70
71 71 def save_source(test_request,source_name)
72 72 dir = self.produce_grading_room(test_request)
73 73 submission = test_request.submission
74 74 f = File.open("#{dir}/#{source_name}","w")
75 75 f.write(submission.source)
76 76 f.close
77 77 end
78 78
79 79 def clean_up(test_request)
80 80 problem_home = problem_home_dir(test_request)
81 81 remove_data_files(problem_home)
82 82 end
83 83
84 84 protected
85 85 def grading_room_dir(test_request)
86 86 problem_name = test_request.problem_name
87 87 user = test_request.user
88 88 grading_room = "#{@config.user_result_dir}" +
89 89 "/#{user.login}/test_request" +
90 90 "/#{problem_name}/#{test_request.id}"
91 91 grading_room
92 92 end
93 93
94 94 def problem_home_dir(test_request)
95 95 problem_name = test_request.problem_name
96 96 user = test_request.user
97 97 "#{@config.user_result_dir}" +
98 98 "/#{user.login}/test_request/#{problem_name}"
99 99 end
100 100
101 101 def copy_problem_template(template_dir,problem_home)
102 102 Grader::call_and_log("Test Request: cannot copy problem template") {
103 103 FileUtils.cp_r("#{template_dir}/.","#{problem_home}")
104 104 }
105 105 end
106 106
107 107 def link_input_file(test_request, problem_home)
108 108 input_fname = "#{test_request.input_file_name}"
109 109 if !File.exists?(input_fname)
110 110 raise "Test Request: input file not found."
111 111 end
112 112
113 113 input_fname_problem_home = "#{problem_home}/test_cases/1/input-1.txt"
114 114 if File.exists?(input_fname_problem_home)
115 115 FileUtils.rm([input_fname_problem_home], :force => true)
116 116 end
117 117
118 118 Grader::link_or_copy("#{input_fname}", "#{input_fname_problem_home}")
119 119 end
120 120
121 121 def remove_data_files(problem_home)
122 122 if File.exists?("#{problem_home}/test_cases/1/input-1.txt")
123 123 Grader::call_and_log("Test Request: cannot remove data files") {
124 124 FileUtils.rm Dir.glob("#{problem_home}/test_cases/1/*")
125 125 }
126 126 end
127 127 end
128 128
129 129 end
130 130
131 131 class TestRequestReporter
132 132 def initialize
133 133 @config = Grader::Configuration.get_instance
134 134 end
135 135
136 136 def report(test_request,test_result_dir)
137 137 save_result(test_request,read_result(test_result_dir))
138 138 end
139 139
140 140 def report_error(test_request, msg)
141 141 save_result(test_request, {:running_stat => {
142 142 :msg => "#{msg}",
143 143 :running_time => nil,
144 144 :exit_status => "Some error occured. Program did not run",
145 145 :memory_usage => nil
146 146 }})
147 147 end
148 148
149 149 protected
150 150 def read_result(test_result_dir)
151 151 # TODO:
152 152 cmp_msg_fname = "#{test_result_dir}/compiler_message"
153 153 cmp_file = File.open(cmp_msg_fname)
154 154 cmp_msg = cmp_file.read
155 155 cmp_file.close
156 156
157 157 result_file_name = "#{test_result_dir}/1/result"
158 158
159 159 if File.exists?(result_file_name)
160 160 output_file_name = "#{test_result_dir}/1/output.txt"
161 161 results = []
162 162 File.open("#{test_result_dir}/1/result") do |f|
163 163 results = f.readlines
164 164 end
165 165 stat = extract_running_stat(results)
166 166
167 167 return {
168 168 :output_file_name => output_file_name,
169 169 :running_stat => stat,
170 170 :comment => "",
171 171 :cmp_msg => cmp_msg}
172 172 else
173 173 return {
174 174 :running_stat => nil,
175 175 :comment => "Compilation error",
176 176 :cmp_msg => cmp_msg}
177 177 end
178 178 end
179 179
180 180 def extract_running_stat(results)
181 181 running_stat_line = results[-1]
182 182
183 183 # extract exit status line
184 184 run_stat = ""
185 185 if !(/[Cc]orrect/.match(results[0]))
186 186 run_stat = results[0].chomp
187 187 else
188 188 run_stat = 'Program exited normally'
189 189 end
190 190
191 191 # extract running time
192 192 if res = /r(.*)u(.*)s/.match(running_stat_line)
193 193 seconds = (res[1].to_f + res[2].to_f)
194 194 time_stat = "Time used: #{seconds} sec."
195 195 else
196 196 seconds = nil
197 197 time_stat = "Time used: n/a sec."
198 198 end
199 199
200 200 # extract memory usage
201 - if res = /s(.*)m/.match(running_stat_line)
201 + if res = /s(.*)kbytes/.match(running_stat_line)
202 202 memory_used = res[1].to_i
203 203 else
204 204 memory_used = -1
205 205 end
206 206
207 207 return {
208 208 :msg => "#{run_stat}\n#{time_stat}",
209 209 :running_time => seconds,
210 210 :exit_status => run_stat,
211 211 :memory_usage => memory_used
212 212 }
213 213 end
214 214
215 215 def save_result(test_request,result)
216 216 if result[:output_file_name]!=nil
217 217 test_request.output_file_name = link_output_file(test_request,
218 218 result[:output_file_name])
219 219 end
220 220 test_request.graded_at = Time.now
221 221 test_request.compiler_message = (result[:cmp_msg] or '')
222 222 test_request.grader_comment = (result[:comment] or '')
223 223 if result[:running_stat]!=nil
224 224 test_request.running_stat = (result[:running_stat][:msg] or '')
225 225 test_request.running_time = (result[:running_stat][:running_time] or nil)
226 226 test_request.exit_status = result[:running_stat][:exit_status]
227 227 test_request.memory_usage = result[:running_stat][:memory_usage]
228 228 else
229 229 test_request.running_stat = ''
230 230 end
231 231 test_request.save
232 232 end
233 233
234 234 protected
235 235 def link_output_file(test_request, fname)
236 236 target_file_name = random_output_file_name(test_request.user,
237 237 test_request.problem)
238 238 FileUtils.mkdir_p(File.dirname(target_file_name))
239 239 Grader::link_or_copy("#{fname}", "#{target_file_name}")
240 240 return target_file_name
241 241 end
242 242
243 243 def random_output_file_name(user,problem)
244 244 problem_name = TestRequest.name_of(problem)
245 245 begin
246 246 tmpname = "#{@config.test_request_output_base_dir}" +
247 247 "/#{user.login}/#{problem_name}/#{rand(10000)}"
248 248 end while File.exists?(tmpname)
249 249 tmpname
250 250 end
251 251
252 252 end
253 253
254 254 end
@@ -1,404 +1,404
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 - fprintf(stderr,"%.4lfr%.4lfu%.4lfs%dm\n",
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 - (mem_peak_kb + 1023) / 1024);
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",
@@ -1267,389 +1267,384
1267 1267 }
1268 1268
1269 1269 static void
1270 1270 signal_alarm(int unused UNUSED)
1271 1271 {
1272 1272 /* Time limit checks are synchronous, so we only schedule them there. */
1273 1273 timer_tick = 1;
1274 1274 alarm(1);
1275 1275 }
1276 1276
1277 1277 static void
1278 1278 signal_int(int unused UNUSED)
1279 1279 {
1280 1280 /* Interrupts are fatal, so no synchronization requirements. */
1281 1281 meta_printf("exitsig:%d\n", SIGINT);
1282 1282 err("SG: Interrupted");
1283 1283 }
1284 1284
1285 1285 #define PROC_BUF_SIZE 4096
1286 1286 static void
1287 1287 read_proc_file(char *buf, char *name, int *fdp)
1288 1288 {
1289 1289 int c;
1290 1290
1291 1291 if (!*fdp)
1292 1292 {
1293 1293 sprintf(buf, "/proc/%d/%s", (int) box_pid, name);
1294 1294 *fdp = open(buf, O_RDONLY);
1295 1295 if (*fdp < 0)
1296 1296 die("open(%s): %m", buf);
1297 1297 }
1298 1298 lseek(*fdp, 0, SEEK_SET);
1299 1299 if ((c = read(*fdp, buf, PROC_BUF_SIZE-1)) < 0)
1300 1300 die("read on /proc/$pid/%s: %m", name);
1301 1301 if (c >= PROC_BUF_SIZE-1)
1302 1302 die("/proc/$pid/%s too long", name);
1303 1303 buf[c] = 0;
1304 1304 }
1305 1305
1306 1306 static void
1307 1307 check_timeout(void)
1308 1308 {
1309 1309 if (wall_timeout)
1310 1310 {
1311 1311 struct timeval now, wall;
1312 1312 int wall_ms;
1313 1313 gettimeofday(&now, NULL);
1314 1314 timersub(&now, &start_time, &wall);
1315 1315 wall_ms = wall.tv_sec*1000 + wall.tv_usec/1000;
1316 1316 if (wall_ms > wall_timeout)
1317 1317 err("TO: Time limit exceeded (wall clock)");
1318 1318 if (verbose > 1)
1319 1319 fprintf(stderr, "[wall time check: %d msec]\n", wall_ms);
1320 1320 }
1321 1321 if (timeout)
1322 1322 {
1323 1323 char buf[PROC_BUF_SIZE], *x;
1324 1324 int utime, stime, ms;
1325 1325 static int proc_stat_fd;
1326 1326 read_proc_file(buf, "stat", &proc_stat_fd);
1327 1327 x = buf;
1328 1328 while (*x && *x != ' ')
1329 1329 x++;
1330 1330 while (*x == ' ')
1331 1331 x++;
1332 1332 if (*x++ != '(')
1333 1333 die("proc stat syntax error 1");
1334 1334 while (*x && (*x != ')' || x[1] != ' '))
1335 1335 x++;
1336 1336 while (*x == ')' || *x == ' ')
1337 1337 x++;
1338 1338 if (sscanf(x, "%*c %*d %*d %*d %*d %*d %*d %*d %*d %*d %*d %d %d", &utime, &stime) != 2)
1339 1339 die("proc stat syntax error 2");
1340 1340 ms = (utime + stime) * 1000 / ticks_per_sec;
1341 1341 if (verbose > 1)
1342 1342 fprintf(stderr, "[time check: %d msec]\n", ms);
1343 1343 if (ms > timeout && ms > extra_timeout)
1344 1344 err("TO: Time limit exceeded");
1345 1345 }
1346 1346 }
1347 1347
1348 1348 static void
1349 1349 sample_mem_peak(void)
1350 1350 {
1351 1351 /*
1352 1352 * We want to find out the peak memory usage of the process, which is
1353 1353 * maintained by the kernel, but unforunately it gets lost when the
1354 1354 * process exits (it is not reported in struct rusage). Therefore we
1355 1355 * have to sample it whenever we suspect that the process is about
1356 1356 * to exit.
1357 1357 */
1358 1358 char buf[PROC_BUF_SIZE], *x;
1359 1359 static int proc_status_fd;
1360 1360 read_proc_file(buf, "status", &proc_status_fd);
1361 1361
1362 1362 x = buf;
1363 1363 while (*x)
1364 1364 {
1365 1365 char *key = x;
1366 1366 while (*x && *x != ':' && *x != '\n')
1367 1367 x++;
1368 1368 if (!*x || *x == '\n')
1369 1369 break;
1370 1370 *x++ = 0;
1371 1371 while (*x == ' ' || *x == '\t')
1372 1372 x++;
1373 1373
1374 1374 char *val = x;
1375 1375 while (*x && *x != '\n')
1376 1376 x++;
1377 1377 if (!*x)
1378 1378 break;
1379 1379 *x++ = 0;
1380 1380
1381 1381 if (!strcmp(key, "VmPeak"))
1382 1382 {
1383 1383 int peak = atoi(val);
1384 1384 if (peak > mem_peak_kb)
1385 1385 mem_peak_kb = peak;
1386 1386 }
1387 1387 }
1388 1388
1389 1389 if (verbose > 1)
1390 1390 msg("[mem-peak: %u KB]\n", mem_peak_kb);
1391 1391 }
1392 1392
1393 1393 static void
1394 1394 boxkeeper(void)
1395 1395 {
1396 1396 int syscall_count = (filter_syscalls ? 0 : 1);
1397 1397 struct sigaction sa;
1398 1398
1399 1399 is_ptraced = 1;
1400 1400
1401 1401 bzero(&sa, sizeof(sa));
1402 1402 sa.sa_handler = signal_int;
1403 1403 sigaction(SIGINT, &sa, NULL);
1404 1404
1405 1405 gettimeofday(&start_time, NULL);
1406 1406 ticks_per_sec = sysconf(_SC_CLK_TCK);
1407 1407 if (ticks_per_sec <= 0)
1408 1408 die("Invalid ticks_per_sec!");
1409 1409
1410 1410 if (timeout || wall_timeout)
1411 1411 {
1412 1412 sa.sa_handler = signal_alarm;
1413 1413 sigaction(SIGALRM, &sa, NULL);
1414 1414 alarm(1);
1415 1415 }
1416 1416
1417 1417 for(;;)
1418 1418 {
1419 1419 struct rusage rus;
1420 1420 int stat;
1421 1421 pid_t p;
1422 1422 if (timer_tick)
1423 1423 {
1424 1424 check_timeout();
1425 1425 timer_tick = 0;
1426 1426 }
1427 1427 p = wait4(box_pid, &stat, WUNTRACED, &rus);
1428 1428 if (p < 0)
1429 1429 {
1430 1430 if (errno == EINTR)
1431 1431 continue;
1432 1432 die("wait4: %m");
1433 1433 }
1434 1434 if (p != box_pid)
1435 1435 die("wait4: unknown pid %d exited!", p);
1436 1436 if (WIFEXITED(stat))
1437 1437 {
1438 1438 box_pid = 0;
1439 1439 final_stats(&rus);
1440 1440 if (WEXITSTATUS(stat))
1441 1441 {
1442 1442 if (syscall_count)
1443 1443 {
1444 1444 meta_printf("exitcode:%d\n", WEXITSTATUS(stat));
1445 1445 err("RE: Exited with error status %d", WEXITSTATUS(stat));
1446 1446 }
1447 1447 else
1448 1448 {
1449 1449 // Internal error happened inside the child process and it has been already reported.
1450 1450 box_exit(2);
1451 1451 }
1452 1452 }
1453 1453 if (timeout && total_ms > timeout)
1454 1454 err("TO: Time limit exceeded");
1455 1455 if (wall_timeout && wall_ms > wall_timeout)
1456 1456 err("TO: Time limit exceeded (wall clock)");
1457 1457 flush_line();
1458 1458 fprintf(stderr,"OK\n");
1459 - print_running_stat(
1460 - (double)wall_ms/1000,
1461 - (double)total_ms/1000,
1462 - (double)sys_ms/1000,
1463 - (mem_peak_kb + 1023) / 1024);
1464 1459 box_exit(0);
1465 1460 }
1466 1461 if (WIFSIGNALED(stat))
1467 1462 {
1468 1463 box_pid = 0;
1469 1464 meta_printf("exitsig:%d\n", WTERMSIG(stat));
1470 1465 final_stats(&rus);
1471 1466 err("SG: Caught fatal signal %d%s", WTERMSIG(stat), (syscall_count ? "" : " during startup"));
1472 1467 }
1473 1468 if (WIFSTOPPED(stat))
1474 1469 {
1475 1470 int sig = WSTOPSIG(stat);
1476 1471 if (sig == SIGTRAP)
1477 1472 {
1478 1473 if (verbose > 2)
1479 1474 msg("[ptrace status %08x] ", stat);
1480 1475 static int stop_count;
1481 1476 if (!stop_count++) /* Traceme request */
1482 1477 msg(">> Traceme request caught\n");
1483 1478 else
1484 1479 err("SG: Breakpoint");
1485 1480 ptrace(PTRACE_SYSCALL, box_pid, 0, 0);
1486 1481 }
1487 1482 else if (sig == (SIGTRAP | 0x80))
1488 1483 {
1489 1484 if (verbose > 2)
1490 1485 msg("[ptrace status %08x] ", stat);
1491 1486 struct syscall_args a;
1492 1487 static unsigned int sys_tick, last_act;
1493 1488 static arg_t last_sys;
1494 1489 if (++sys_tick & 1) /* Syscall entry */
1495 1490 {
1496 1491 char namebuf[32];
1497 1492 int act;
1498 1493
1499 1494 get_syscall_args(&a, 0);
1500 1495 arg_t sys = a.sys;
1501 1496 msg(">> Syscall %-12s (%08jx,%08jx,%08jx) ", syscall_name(sys, namebuf), (intmax_t) a.arg1, (intmax_t) a.arg2, (intmax_t) a.arg3);
1502 1497 if (!exec_seen)
1503 1498 {
1504 1499 msg("[master] ");
1505 1500 if (sys == NATIVE_NR_execve)
1506 1501 {
1507 1502 exec_seen = 1;
1508 1503 close_user_mem();
1509 1504 }
1510 1505 }
1511 1506 else if ((act = valid_syscall(&a)) >= 0)
1512 1507 {
1513 1508 last_act = act;
1514 1509 syscall_count++;
1515 1510 if (act & A_SAMPLE_MEM)
1516 1511 sample_mem_peak();
1517 1512 }
1518 1513 else
1519 1514 {
1520 1515 /*
1521 1516 * Unfortunately, PTRACE_KILL kills _after_ the syscall completes,
1522 1517 * so we have to change it to something harmless (e.g., an undefined
1523 1518 * syscall) and make the program continue.
1524 1519 */
1525 1520 set_syscall_nr(&a, ~(arg_t)0);
1526 1521 err("FO: Forbidden syscall %s", syscall_name(sys, namebuf));
1527 1522 }
1528 1523 last_sys = sys;
1529 1524 }
1530 1525 else /* Syscall return */
1531 1526 {
1532 1527 get_syscall_args(&a, 1);
1533 1528 if (a.sys == ~(arg_t)0)
1534 1529 {
1535 1530 /* Some syscalls (sigreturn et al.) do not return a value */
1536 1531 if (!(last_act & A_NO_RETVAL))
1537 1532 err("XX: Syscall does not return, but it should");
1538 1533 }
1539 1534 else
1540 1535 {
1541 1536 if (a.sys != last_sys)
1542 1537 err("XX: Mismatched syscall entry/exit");
1543 1538 }
1544 1539 if (last_act & A_NO_RETVAL)
1545 1540 msg("= ?\n");
1546 1541 else
1547 1542 msg("= %jd\n", (intmax_t) a.result);
1548 1543 }
1549 1544 ptrace(PTRACE_SYSCALL, box_pid, 0, 0);
1550 1545 }
1551 1546 else if (sig == SIGSTOP)
1552 1547 {
1553 1548 msg(">> SIGSTOP\n");
1554 1549 if (ptrace(PTRACE_SETOPTIONS, box_pid, NULL, (void *) PTRACE_O_TRACESYSGOOD) < 0)
1555 1550 die("ptrace(PTRACE_SETOPTIONS): %m");
1556 1551 ptrace(PTRACE_SYSCALL, box_pid, 0, 0);
1557 1552 }
1558 1553 else if (sig != SIGXCPU && sig != SIGXFSZ)
1559 1554 {
1560 1555 msg(">> Signal %d\n", sig);
1561 1556 sample_mem_peak(); /* Signal might be fatal, so update mem-peak */
1562 1557 ptrace(PTRACE_SYSCALL, box_pid, 0, sig);
1563 1558 }
1564 1559 else
1565 1560 {
1566 1561 meta_printf("exitsig:%d", sig);
1567 1562 err("SG: Received signal %d", sig);
1568 1563 }
1569 1564 }
1570 1565 else
1571 1566 die("wait4: unknown status %x, giving up!", stat);
1572 1567 }
1573 1568 }
1574 1569
1575 1570 static void
1576 1571 box_inside(int argc, char **argv)
1577 1572 {
1578 1573 struct rlimit rl;
1579 1574 char *args[argc+1];
1580 1575
1581 1576 memcpy(args, argv, argc * sizeof(char *));
1582 1577 args[argc] = NULL;
1583 1578 if (set_cwd && chdir(set_cwd))
1584 1579 die("chdir: %m");
1585 1580 if (redir_stdin)
1586 1581 {
1587 1582 close(0);
1588 1583 if (open(redir_stdin, O_RDONLY) != 0)
1589 1584 die("open(\"%s\"): %m", redir_stdin);
1590 1585 }
1591 1586 if (redir_stdout)
1592 1587 {
1593 1588 close(1);
1594 1589 if (open(redir_stdout, O_WRONLY | O_CREAT | O_TRUNC, 0666) != 1)
1595 1590 die("open(\"%s\"): %m", redir_stdout);
1596 1591 }
1597 1592 if (redir_stderr)
1598 1593 {
1599 1594 close(2);
1600 1595 if (open(redir_stderr, O_WRONLY | O_CREAT | O_TRUNC, 0666) != 2)
1601 1596 die("open(\"%s\"): %m", redir_stderr);
1602 1597 }
1603 1598 else
1604 1599 dup2(1, 2);
1605 1600 setpgrp();
1606 1601
1607 1602 if (memory_limit)
1608 1603 {
1609 1604 rl.rlim_cur = rl.rlim_max = memory_limit * 1024;
1610 1605 if (setrlimit(RLIMIT_AS, &rl) < 0)
1611 1606 die("setrlimit(RLIMIT_AS): %m");
1612 1607 }
1613 1608
1614 1609 rl.rlim_cur = rl.rlim_max = (stack_limit ? (rlim_t)stack_limit * 1024 : RLIM_INFINITY);
1615 1610 if (setrlimit(RLIMIT_STACK, &rl) < 0)
1616 1611 die("setrlimit(RLIMIT_STACK): %m");
1617 1612
1618 1613 rl.rlim_cur = rl.rlim_max = 64;
1619 1614 if (setrlimit(RLIMIT_NOFILE, &rl) < 0)
1620 1615 die("setrlimit(RLIMIT_NOFILE): %m");
1621 1616
1622 1617 char **env = setup_environment();
1623 1618 if (filter_syscalls)
1624 1619 {
1625 1620 if (ptrace(PTRACE_TRACEME) < 0)
1626 1621 die("ptrace(PTRACE_TRACEME): %m");
1627 1622 /* Trick: Make sure that we are stopped until the boxkeeper wakes up. */
1628 1623 raise(SIGSTOP);
1629 1624 }
1630 1625 execve(args[0], args, env);
1631 1626 die("execve(\"%s\"): %m", args[0]);
1632 1627 }
1633 1628
1634 1629 static void
1635 1630 usage(void)
1636 1631 {
1637 1632 fprintf(stderr, "Invalid arguments!\n");
1638 1633 printf("\
1639 1634 Usage: box [<options>] -- <command> <arguments>\n\
1640 1635 \n\
1641 1636 Options:\n\
1642 1637 -a <level>\tSet file access level (0=none, 1=cwd, 2=/etc,/lib,..., 3=whole fs, 9=no checks; needs -f)\n\
1643 1638 -c <dir>\tChange directory to <dir> first\n\
1644 1639 -e\t\tInherit full environment of the parent process\n\
1645 1640 -E <var>\tInherit the environment variable <var> from the parent process\n\
1646 1641 -E <var>=<val>\tSet the environment variable <var> to <val>; unset it if <var> is empty\n\
1647 1642 -f\t\tFilter system calls (-ff=very restricted)\n\
1648 1643 -i <file>\tRedirect stdin from <file>\n\
1649 1644 -k <size>\tLimit stack size to <size> KB (default: 0=unlimited)\n\
1650 1645 -m <size>\tLimit address space to <size> KB\n\
1651 1646 -M <file>\tOutput process information to <file> (name:value)\n\
1652 1647 -o <file>\tRedirect stdout to <file>\n\
1653 1648 -p <path>\tPermit access to the specified path (or subtree if it ends with a `/')\n\
1654 1649 -p <path>=<act>\tDefine action for the specified path (<act>=yes/no)\n\
1655 1650 -r <file>\tRedirect stderr to <file>\n\
@@ -1,165 +1,187
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 31 PYTHON_INTERPRETER = "/usr/bin/python"
32 32 PYTHON_CHECKER = "/usr/bin/pyflakes"
33 + PHP_INTERPRETER = "/usr/bin/php"
33 34
34 35 C_OPTIONS = "-O2 -s -static -std=c99 -DCONTEST -lm -Wall"
35 - CPLUSPLUS_OPTIONS = "-O2 -s -static -DCONTEST -lm -Wall"
36 + CPLUSPLUS_OPTIONS = "-O2 -s -std=c++11 -static -DCONTEST -lm -Wall"
36 37 PASCAL_OPTIONS = "-O1 -XS -dCONTEST"
37 38 JAVA_OPTIONS = ""
38 39 PYTHON_OPTIONS = ""
40 + PHP_OPTIONS = "-l"
39 41
40 42 # Check for the correct number of arguments. Otherwise, print usage.
41 43 if ARGV.length == 0 or ARGV.length > 4
42 44 puts "Usage: compile <language> [<source-file>] [<output-file>] [<message-file>]"
43 45 puts
44 46 puts "<source-file> is defaulted to \"source\"."
45 47 puts "<output-file> is defaulted to \"a.out\"."
46 48 puts "<message-file> is defaulted to \"compiler_message\"."
47 49 puts
48 50 exit(127)
49 51 end
50 52
51 53 PARAMS = {
52 54 :source_file => [1,'source'],
53 55 :output_file => [2,'a.out'],
54 56 :message_file => [3,'compiler_message']
55 57 }
56 58
57 59 params = {}
58 60 params[:prog_lang] = ARGV[0]
59 61 PARAMS.each_key do |param_name|
60 62 index, default = PARAMS[param_name]
61 63 if ARGV.length > index
62 64 params[param_name] = ARGV[index]
63 65 else
64 66 params[param_name] = default
65 67 end
66 68 talk "#{param_name}: #{params[param_name]}"
67 69 end
68 70
69 71 # Remove any remaining output files or message files.
70 72 if FileTest.exists? params[:output_file]
71 73 FileUtils.rm(params[:output_file])
72 74 end
73 75 if FileTest.exists? params[:message_file]
74 76 FileUtils.rm(params[:message_file])
75 77 end
76 78
77 79 # Check if the source file exists before attempt compiling.
78 80 if !FileTest.exists? params[:source_file]
79 81 talk("ERROR: The source file does not exist!")
80 82 open(params[:message_file],"w") do |f|
81 83 f.puts "ERROR: The source file did not exist."
82 84 end
83 85 exit(127)
84 86 end
85 87
86 88 if params[:prog_lang]=='cpp'
87 89 params[:prog_lang] = 'c++'
88 90 end
89 91
90 92 # Compile.
91 93 case params[:prog_lang]
92 94
93 95 when "c"
94 96 command = "#{C_COMPILER} #{params[:source_file]} -o #{params[:output_file]} #{C_OPTIONS} 2> #{params[:message_file]}"
95 97 system(command)
96 98
97 99 when "c++"
98 100 command = "#{CPLUSPLUS_COMPILER} #{params[:source_file]} -o #{params[:output_file]} #{CPLUSPLUS_OPTIONS} 2> #{params[:message_file]}"
99 101 system(command)
100 102
101 103 when "pas"
102 104 command = "#{PASCAL_COMPILER} #{params[:source_file]} -ooutpas #{PASCAL_OPTIONS} > #{params[:message_file]}"
103 105 system(command)
104 106 FileUtils.mv("output", params[:output_file])
105 107
106 108 when "java"
107 109 #rename the file to the public class name
108 110
109 111 #get the class name
110 112 classname = 'DUMMY'
113 + source = Array.new
111 114 File.foreach(params[:source_file]) do |line|
112 115 md = /\s*public\s*class\s*(\w*)/.match(line)
113 116 classname=md[1] if md
117 + source << line unless line =~ /\s*package\s*\w+\s*\;/
114 118 end
115 - system("cp #{params[:source_file]} #{classname}.java")
119 + File.open("#{classname}.java","w") do |file|
120 + source.each do |s|
121 + file.puts s
122 + end
123 + end
124 + #system("cp #{params[:source_file]} #{classname}.java")
116 125 command = "#{JAVA_COMPILER} #{classname}.java 2> #{params[:message_file]}"
117 126 system(command)
118 127 if File.exists?(classname + ".class")
119 128 File.open(params[:output_file],"w") {|file| file.write("#{classname}")}
120 129 end
121 130 if classname == 'DUMMY'
122 131 File.open(params[:message_file],"w") {|file| file.write("Cannot find any public class in the source code\n")}
123 132 end
124 133
125 134 when "ruby"
126 135 command = "#{RUBY_INTERPRETER} -c #{params[:source_file]} 2> #{params[:message_file]}"
127 136 if system(command)
128 137 File.open(params[:output_file],"w") do |out_file|
129 138 out_file.puts "#!#{RUBY_INTERPRETER}"
130 139 File.open(params[:source_file],"r").each do |line|
131 140 out_file.print line
132 141 end
133 142 end
134 143 File.chmod(0755, params[:output_file])
135 144 end
136 145
137 146 when "python"
138 147 command = "#{PYTHON_CHECKER} #{params[:source_file]} > #{params[:message_file]}"
139 148 if system(command)
140 149 #compile to python bytecode
141 150 command = "#{PYTHON_INTERPRETER} -m py_compile #{params[:source_file]}"
142 151 puts "compile: #{command}"
143 152 system(command)
144 153 puts "pwd: " + Dir.pwd
145 154 Dir.new('.').each {|file| puts file}
146 155 File.open(params[:output_file],"w") do |out_file|
147 156 out_file.puts "#!#{PYTHON_INTERPRETER} #{params[:source_file]}c"
148 157 end
149 158 File.chmod(0755, params[:output_file])
159 + FileUtils.cp("#{params[:source_file]}c",params[:output_file])
160 + end
161 +
162 + when "php"
163 + command = "#{PHP_INTERPRETER} #{PHP_OPTIONS} #{params[:source_file]} 2> #{params[:message_file]}"
164 + if system(command)
165 + File.open(params[:output_file],"w") do |out_file|
166 + out_file.puts "#!#{PHP_INTERPRETER}"
167 + File.open(params[:source_file],"r").each do |line|
168 + out_file.print line
169 + end
170 + end
171 + File.chmod(0755, params[:output_file])
150 172 end
151 173
152 174 else
153 175 talk("ERROR: Invalid language specified!")
154 176 open(params[:message_file],"w") do |f|
155 177 f.puts "ERROR: Invalid language specified!"
156 178 end
157 179 exit(127)
158 180 end
159 181
160 182 # Report success or failure.
161 183 if FileTest.exists? params[:output_file]
162 184 talk "Compilation was successful!"
163 185 else
164 186 talk "ERROR: Something was wrong during the compilation!"
165 187 end
@@ -1,108 +1,133
1 1 #!/usr/bin/env ruby
2 2
3 3 CORRECT_MARK = 'P'
4 4 INCORRECT_MARK = '-'
5 5 TIMEOUT_MARK = 'T'
6 6 RUN_ERROR_MARK = 'x'
7 7
8 8 def log(str='')
9 9 if ENV['TALKATIVE']!=nil
10 10 puts str
11 11 end
12 12 if ENV['GRADER_LOGGING']!=nil
13 13 log_fname = ENV['GRADER_LOGGING']
14 14 fp = File.open(log_fname,"a")
15 15 fp.puts("grade: #{Time.new.strftime("%H:%M")} #{str}")
16 16 fp.close
17 17 end
18 18 end
19 19
20 20 def char_comment(comment)
21 21 if comment =~ /[Ii]ncorrect/
22 22 INCORRECT_MARK
23 23 elsif comment =~ /[Cc]orrect/
24 24 CORRECT_MARK
25 25 elsif comment =~ /[Tt]ime/
26 26 TIMEOUT_MARK
27 27 elsif res = /^[Cc]omment:(.*)$/.match(comment)
28 28 res[1]
29 29 else
30 30 RUN_ERROR_MARK # these are run time errors
31 31 end
32 32 end
33 33
34 + def extract_time(t)
35 + #puts "TIME: #{t}"
36 + if (result=/^(.*)r(.*)u(.*)s(.*)kbytes/.match(t))
37 + {:real => result[1], :user => result[2], :sys => result[3], :mem => result[4]}
38 + else
39 + #{:real => 0, :user => 0, :sys => 0}
40 + #puts "ERROR READING RUNNING TIME: #{t}"
41 + raise "Error reading running time: #{t}"
42 + end
43 + end
44 +
34 45 problem_home = ENV['PROBLEM_HOME']
35 46 require "#{problem_home}/script/test_dsl.rb"
36 47 load "#{problem_home}/test_cases/all_tests.cfg"
37 48 problem = Problem.get_instance
38 49
39 50 if problem.well_formed? == false
40 51 log "The problem specification is not well formed."
41 52 exit(127)
42 53 end
43 54
44 55 all_score = 0
45 56 all_comment = ''
57 + peak_memory = -1
58 + max_runtime = -1
46 59 (1..(problem.runs.length-1)).each do |k|
47 60 log "grade run #{k}"
48 61 run = problem.runs[k]
49 62 run_score = nil
50 63 run_comment = ''
51 64 run_comment_short = ''
52 65 run.tests.each do |test_num|
53 66 result_file_name = "#{test_num}/result"
54 67 if not File.exists?(result_file_name)
55 68 run_comment += "result file for test #{test_num} not found\n"
56 69 run_comment_short += RUN_ERROR_MARK
57 70 log "Cannot find the file #{test_num}/result!"
58 71 else
59 72 result_file = File.new(result_file_name, "r")
60 73 result_file_lines = result_file.readlines
61 - if result_file_lines.length>=2
74 + if result_file_lines.length>=3
62 75 current_run_score = result_file_lines[1].to_i
63 76 run_comment += result_file_lines[0]
64 77 run_comment_short += char_comment(result_file_lines[0].chomp)
78 +
79 + #update max runtime & memory
80 + run_stat = extract_time result_file_lines[2]
81 + peak_memory = [peak_memory,run_stat[:mem].to_i].max
82 + max_runtime = [max_runtime,run_stat[:user].to_f + run_stat[:sys].to_f].max
65 83 else
66 84 current_run_score = 0
67 85 run_comment += "result file for test #{test_num} error\n"
68 86 run_comment_short += RUN_ERROR_MARK
69 87 log "Error in #{test_num}/result!"
70 88 end
71 89
72 90 # the score of this run should be the minimum of the score for
73 91 # each test case
74 92 if (run_score==nil) or (run_score>current_run_score)
75 93 run_score = current_run_score
76 94 end
77 95 result_file.close
78 96 end
79 97 end
80 98
81 99 run_result_file = File.new("result-#{k}", "w")
82 100 run_result_file.write run_score
83 101 run_result_file.write "\n"
84 102 run_result_file.close
85 103
86 104 run_comment_file = File.new("comment-#{k}", "w")
87 105 run_comment_file.write "#{run_comment}\n"
88 106 run_comment_file.close
89 107
90 108 all_score = all_score + run_score
91 109
92 110 # append comment for test run with many test cases
93 111 if run.tests.length > 1
94 112 run_comment_short = '[' + run_comment_short + ']'
95 113 end
96 114 all_comment += run_comment_short
97 115 end
98 116
99 117 result_file = File.new("result", "w")
100 118 result_file.write all_score
101 119 result_file.write "\n"
102 120 result_file.close
103 121
104 122 comment_file = File.new("comment", "w")
105 123 comment_file.write "#{all_comment}\n"
106 124 comment_file.close
107 125
108 - log "score = #{all_score} comment = #{all_comment}"
126 +
127 + File.open("run_stat","w") do |file|
128 + file.puts max_runtime
129 + file.puts peak_memory
130 + end
131 +
132 + log "score = #{all_score}\ncomment = #{all_comment}"
133 + log "max_runtime = #{max_runtime}\npeak_memory = #{peak_memory}"
@@ -1,179 +1,180
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"
56 + if language != "c" && language != "c++" && language != "pas" && language != "java" && language != "ruby" && language != "python" && language != "php"
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 + ENV['SOURCE_NAME'] = source_file
62 63 if File.exist?(source_file) == false
63 64 log "The source file does not exist."
64 65 exit(127)
65 66 end
66 67
67 68 log "Making test result and sandbox directories..."
68 69
69 70 current_dir = FileUtils.pwd
70 71 current_dir.strip!
71 72
72 73 if ARGV.length >= 3
73 74 test_result_dir = ARGV[2]
74 75 else
75 76 test_result_dir = "#{current_dir}/test-result"
76 77 end
77 78
78 79 log "Test result directory: #{test_result_dir}"
79 80 clear_and_create_empty_dir(test_result_dir)
80 81
81 82 if ARGV.length >= 4
82 83 sandbox_dir = ARGV[3]
83 84 else
84 85 sandbox_dir = "#{current_dir}/sandbox"
85 86 end
86 87 log "Sandbox directory: #{sandbox_dir}"
87 88 clear_and_create_empty_dir(sandbox_dir)
88 89
89 90 # Compile
90 91 log
91 92 log "Compiling..."
92 93 call_and_log("Cannot copy the source file to #{sandbox_dir}") {
93 94 FileUtils.cp(source_file, sandbox_dir)
94 95 }
95 96 begin
96 97 Dir.chdir sandbox_dir
97 98 rescue
98 99 log "ERROR: Cannot change directory to #{sandbox_dir}."
99 100 exit(127)
100 101 end
101 102 execute("#{problem_home}/script/compile #{language} #{source_file}", "Compilation error!")
102 103 compile_message = open("compiler_message").read
103 104 compile_message.strip!
104 105 call_and_log("Cannot move the compiler message to #{test_result_dir}.") {
105 106 FileUtils.mv("compiler_message", test_result_dir)
106 107 }
107 108 if !FileTest.exist?("a.out")
108 109 log "Cannot compile the source code. See message in #{test_result_dir}/compile_message"
109 110 exit(127)
110 111 else
111 112 call_and_log("Cannot move the compiled program to #{test_result_dir}") {
112 113 FileUtils.mv("a.out",test_result_dir)
113 114 if language == "java" then Dir["*.class"].each { |file| FileUtils.mv(file,test_result_dir)} end
114 115 if language == "python" then Dir["*.pyc"].each { |file| FileUtils.mv(file,test_result_dir)} end
115 116 }
116 117 FileUtils.rm_rf("#{sandbox_dir}/.")
117 118 end
118 119
119 120 require "#{problem_home}/script/test_dsl.rb"
120 121 load "#{problem_home}/test_cases/all_tests.cfg"
121 122 problem = Problem.get_instance
122 123
123 124 if problem.well_formed? == false
124 125 log "The problem specification is not well formed."
125 126 exit(127)
126 127 end
127 128
128 129 # Doing the testing.
129 130 (1..(problem.num_tests)).each do |test_num|
130 131
131 132 $stdout.print "[#{test_num}]"
132 133 $stdout.flush
133 134
134 135 log "Test number: #{test_num}"
135 136
136 137 call_and_log("Cannot copy the compiled program into #{sandbox_dir}") {
137 138 FileUtils.cp("#{test_result_dir}/a.out", sandbox_dir, :preserve => true)
138 139 if language == "java" then Dir["#{test_result_dir}/*.class"].each { |file| FileUtils.cp(file,sandbox_dir)} end
139 140 if language == "python" then Dir["#{test_result_dir}/*.pyc"].each { |file| FileUtils.cp(file,sandbox_dir)} end
140 141 }
141 142
142 143 begin
143 144 execute("#{problem_home}/script/run #{language} #{test_num}", "Error occured during execution of the run script")
144 145 rescue
145 146 # do nothing
146 147 end
147 148
148 149 call_and_log("Cannot create directory #{test_result_dir}/#{test_num}") {
149 150 FileUtils.mkdir "#{test_result_dir}/#{test_num}"
150 151 }
151 152 call_and_log("Cannot copy the result file into #{test_result_dir}/#{test_num}") {
152 153 FileUtils.mv "#{sandbox_dir}/result", "#{test_result_dir}/#{test_num}"
153 154 }
154 155 call_and_log("Cannot copy the comment file into #{test_result_dir}/#{test_num}") {
155 156 FileUtils.mv "#{sandbox_dir}/comment", "#{test_result_dir}/#{test_num}"
156 157 }
157 158 call_and_log("Cannot copy the output file into #{test_result_dir}/#{test_num}") {
158 159 FileUtils.mv "#{sandbox_dir}/output.txt", "#{test_result_dir}/#{test_num}"
159 160 }
160 161 call_and_log("Cannot clear #{sandbox_dir}") {
161 162 FileUtils.rm_rf(Dir.glob("#{sandbox_dir}/*"), :secure => true)
162 163 }
163 164 end
164 165
165 166 $stdout.print "[done]\n"
166 167
167 168 # Grade
168 169 log
169 170 log "Grading..."
170 171 begin
171 172 Dir.chdir test_result_dir
172 173 rescue
173 174 log "ERROR: Cannot change directory to #{test_result_dir}."
174 175 exit(127)
175 176 end
176 177 execute("#{problem_home}/script/grade", "An error occured during grading!")
177 178
178 179 log
179 180 log "All done!"
@@ -1,181 +1,189
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 + source_name = ENV['SOURCE_NAME']
46 47 require "#{problem_home}/script/test_dsl.rb"
47 48 load "#{problem_home}/test_cases/all_tests.cfg"
48 49 problem = Problem.get_instance
49 50
51 + sandbox_dir = Dir.getwd
52 +
50 53 if problem.well_formed? == false
51 54 log "The problem specification is not well formed."
52 55 exit(127)
53 56 end
54 57
55 58 # Check if the test number is okay.
56 59 if test_num <= 0 || test_num > problem.num_tests
57 60 log "You have specified a wrong test number."
58 61 exit(127)
59 62 end
60 63
61 64 #####################################
62 65 # Set the relavant file names here. #
63 66 #####################################
64 67
65 68 input_file_name = "#{problem_home}/test_cases/#{test_num}/input-#{test_num}.txt"
66 69
67 70 #####################################
68 71
69 72 time_limit = problem.get_time_limit test_num
70 73 mem_limit = problem.get_mem_limit(test_num) * 1024
71 74
72 75 # Copy the input file.
73 76 #`cp #{problem_home}/test_cases/#{test_num}/#{input_file_name} .`
74 77
75 78 # check if box is there, if not, compile it!
76 79 if !File.exists?("#{problem_home}/script/box")
77 80 log "WARNING: Compiling box: to increase efficiency, it should be compile manually"
78 81 compile_box("#{problem_home}/script/box.cc",
79 82 "#{problem_home}/script/box")
80 83 end
81 84
82 85 # Hide PROBLEM_HOME
83 86 ENV['PROBLEM_HOME'] = nil
87 + ENV['SOURCE_NAME'] = nil
84 88
85 89 # Run the program.
86 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}"
87 91 #
88 92
89 -
90 -
93 + JAVA_OPTION = "-s set_robust_list -s futex -s clone -s getppid -s clone -s wait4 -p /usr/bin/ -p ./"
94 + RUBY_OPTION = "-p /usr/lib64/ -p /usr/local/lib64/ -p /usr/local/lib/ -p /lib64/ -p /dev/urandom -p #{sandbox_dir}/#{program_name} -p #{sandbox_dir}/ -s set_robust_list -s sched_getaffinity -s clock_gettime -s sigaltstack -s pipe2 -s clone -s futex -s openat -s pipe"
95 + PYTHON_OPTION = "-p /usr/lib64/ -p /usr/local/lib64/ -p /usr/local/lib/ -p /usr/bin/ -p /lib64/ -p #{sandbox_dir}/#{program_name} -p ./#{program_name} -p #{sandbox_dir}/#{source_name} -s set_robust_list -s openat -s recvmsg -s connect -s socket -s sendto -s futex -E PYTHONNOUSERSITE=yes"
96 + PHP_OPTION = "-p /usr/lib64/ -p/lib64/ -p /usr/bin/ -p #{sandbox_dir}/#{program_name} -p ./#{program_name} -p /usr/share/ -s setfsuid -s setfsgid -s openat -s set_robust_list -s futex -s clone -s socket -s connect"
91 97
92 98 case language
93 99 when "java"
94 -
95 100 # for java, extract the classname
96 101 # wne have to add additional systemcall and we don't check the mem limit (dunno how to fix...)
97 102 classname = 'DUMMY'
98 103 File.open(program_name,"r").each do |line|
99 104 classname = line
100 105 end
101 - run_command = "#{problem_home}/script/box -T -t #{time_limit} -s getppid -s clone -s wait4 -p /usr/bin/ -p ./ -i #{input_file_name} -o output.txt /usr/bin/java #{classname} 2>run_result"
106 + #for java, we cannot really check the memory limit...
107 + run_command = "#{problem_home}/script/box -a 3 -f -T -t #{time_limit} #{JAVA_OPTION} -i #{input_file_name} -o output.txt /usr/bin/java #{classname} 2>run_result"
102 108 when "ruby"
103 - run_command = "#{problem_home}/script/box -T -t #{time_limit} -s getppid -s wait4 -s clone -s set_robust_list -s futex -s sigaltstack -p /dev/urandom -p ./ -p /home/dae/.rvm/rubies/ruby-1.9.2-p320/ -p #{problem_home}/ -i #{input_file_name} -o output.txt #{program_name} 2>run_result"
109 + run_command = "#{problem_home}/script/box -a 2 -f -T -t #{time_limit*=2} -m #{mem_limit} #{RUBY_OPTION} -i #{input_file_name} -o output.txt /usr/bin/ruby #{program_name} 2>run_result"
104 110 when "python"
105 - #this code just run without any checking
106 - run_command = "#{problem_home}/script/box -T -t #{time_limit} -p #{problem_home}/ -i #{input_file_name} -o output.txt #{program_name} 2>run_result"
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} 2>run_result"
112 + when "php"
113 + run_command = "#{problem_home}/script/box -a 2 -f -T -t #{time_limit*=2} #{PHP_OPTION} -i #{input_file_name} -o output.txt /usr/bin/php #{program_name} 2>run_result"
107 114 else # for c++, pascal, we do the normal checking
108 115 run_command = "#{problem_home}/script/box -a 2 -f -T -t #{time_limit} -m #{mem_limit} -i #{input_file_name} -o output.txt #{program_name} 2>run_result"
109 116 end
110 117
111 118
112 119 log "Running test #{test_num}..."
113 120 log run_command
114 121 log
115 122 system(run_command)
116 123
117 124 # Restore PROBLEM_HOME
118 125 ENV['PROBLEM_HOME'] = problem_home
119 126
120 127 # Create the result file.
121 128 result_file = File.new("result", "w")
122 129 comment_file = File.new("comment", "w")
123 130
124 131 # Check if the program actually produced any output.
125 132 run_result_file = File.new("run_result", "r")
126 133 run_result = run_result_file.readlines
127 134 run_result_file.close
128 135
129 136 run_stat = run_result[run_result.length-1]
130 137 running_time = extract_time(run_stat)
131 138
132 139 report = lambda{ |status, points, comment|
133 140 result_file.write status.strip
134 141 result_file.write "\n"
135 142 result_file.write points.to_s.strip
136 143 result_file.write "\n"
137 144 result_file.write run_stat.strip
138 145 result_file.write "\n"
139 146 result_file.close
140 147 FileUtils.rm "run_result"
141 148 # `rm output.txt` --- keep the output
142 149
143 150 comment_file.write comment
144 151
145 152 # added for debuggin --- jittat
146 153 comment_file.write "--run-result--\n"
147 154 run_result.each do |l|
148 155 comment_file.write l
149 156 end
150 157
151 158 comment_file.close
152 159
153 160 log "Done!"
154 161 exit(0)
155 162 }
156 163
164 +
157 165 if run_result[0][0,2] != "OK"
158 166 log "There was a runtime error."
159 167 report.call(run_result[0], 0, "No comment.\n")
160 168 end
161 169
162 170 if running_time[:user].to_f > time_limit
163 171 log "Time limit exceeded."
164 172 report.call("Time limit exceeded", 0, "No comment.\n")
165 173 end
166 174
167 175 # Run 'check' to evaluate the output.
168 176 #puts "There was no runtime error. Proceed to checking the output."
169 177 check_command = "#{problem_home}/script/check #{language} #{test_num}"
170 178 log "Checking the output..."
171 179 log check_command
172 180 if not system(check_command)
173 181 log "Problem with check script"
174 182 report.call("Incorrect",0,"Check script error.\n")
175 183 exit(127)
176 184 end
177 185
178 186 check_file = File.new("check_result", "r")
179 187 check_file_lines = check_file.readlines
180 188
181 189 report.call(check_file_lines[0], check_file_lines[1], "No comment.\n")
You need to be logged in to leave comments. Login now