Description:
[grader] added fractional timelimit git-svn-id: http://theory.cpe.ku.ac.th/grader/judge/trunk/scripts@191 6386c4cd-e34a-4fa8-8920-d93eb39b512e
Commit status:
[Not Reviewed]
References:
Comments:
0 Commit comments 0 Inline Comments
Unresolved TODOs:
There are no unresolved TODOs
Add another comment

r51:3618759afb50 - - 6 files changed: 62 inserted, 53 deleted

@@ -0,0 +1,38
1 + #include <stdio.h>
2 + #include <stdlib.h>
3 + #include <unistd.h>
4 + #include <sys/time.h>
5 + #include <time.h>
6 + #include <sys/resource.h>
7 +
8 + // run it for 1.5 s
9 +
10 + int main()
11 + {
12 + int a,b;
13 +
14 + int c=0;
15 +
16 + scanf("%d %d",&a,&b);
17 + printf("%d\n",a+b);
18 +
19 + struct rusage ru;
20 +
21 + while(1) {
22 + c++;
23 + b+=c;
24 + while(c<100000) {
25 + c++;
26 + b+=c;
27 + }
28 + getrusage(RUSAGE_SELF,&ru);
29 + double rtime = ru.ru_utime.tv_sec + ru.ru_stime.tv_sec;
30 + rtime += (double)ru.ru_utime.tv_usec / 1000000.0;
31 + rtime += (double)ru.ru_stime.tv_usec / 1000000.0;
32 + if(rtime > 0.5)
33 + break;
34 + }
35 + printf("%d\n",b);
36 + exit(0);
37 + }
38 +
@@ -1,184 +1,189
1 1 #!/usr/bin/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 system("rm " + 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 49 (2) grader environment [mode]
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', 'prob', 'test_request'
54 54 (3) create stop-file to stop running grader in queue mode
55 55 (4) You are here.
56 56 USAGE
57 57 end
58 58
59 59 #########################################
60 60 # main program
61 61 #########################################
62 62
63 63 # with --help
64 64 if (ARGV.length==1) and (/help/.match(ARGV[0]))
65 65 display_manual
66 66 exit(0)
67 67 end
68 68
69 69 # reading environment and options
70 70 if (ARGV.length >= 1) and (ARGV[0]=='stop')
71 71 if ARGV.length==1
72 72 puts "you should specify pid-list or 'all'"
73 73 display_manual
74 74 elsif (ARGV.length==2) and (ARGV[1]=='all')
75 75 stop_grader(:all)
76 76 puts "A global stop file ('stop.all') created."
77 77 puts "You should remove it manually later."
78 78 else
79 79 (1..ARGV.length-1).each do |i|
80 80 stop_grader(ARGV[i])
81 81 end
82 82 puts "stop file(s) created"
83 83 end
84 84 exit(0)
85 85 end
86 86
87 87 if check_stopfile
88 88 puts "Stop file exists. Terminated."
89 89 clear_stopfile
90 90 exit(0)
91 91 end
92 92
93 93 grader_mode = 'queue'
94 94 if ARGV.length >= 1
95 95 GRADER_ENV = ARGV[0]
96 96 if ARGV.length >=2
97 97 grader_mode = ARGV[1]
98 98 end
99 99 else
100 100 GRADER_ENV = 'exam'
101 101 end
102 102
103 103 puts "environment: #{GRADER_ENV}"
104 104 require File.join(File.dirname(__FILE__),'config/environment')
105 105
106 106 # add grader_mode to config
107 107 # this is needed because method log needs it. TODO: clean this up
108 108 class << config
109 109 attr_accessor :grader_mode
110 110 end
111 111 config.grader_mode = grader_mode
112 112
113 113 # reading rails environment
114 114 log 'Reading rails environment'
115 115
116 116 RAILS_ENV = config.rails_env
117 117 require RAILS_ROOT + '/config/environment'
118 118
119 119 # register grader process
120 120 if config.report_grader
121 121 grader_proc = GraderProcess.register(config.grader_hostname,
122 122 Process.pid,
123 123 grader_mode)
124 124 else
125 125 grader_proc = nil
126 126 end
127 127
128 128 #set loggin environment
129 129 ENV['GRADER_LOGGING'] = log_file_name
130 130
131 131 #
132 132 # MAIN LOOP
133 133 #
134 134
135 135 case grader_mode
136 136 when "queue", "test_request"
137 137 log "Grader: #{grader_mode}"
138 138 if grader_mode=="queue"
139 139 engine = Grader::Engine.new
140 140 else
141 141 engine = Grader::Engine.new(Grader::TestRequestRoomMaker.new,
142 142 Grader::TestRequestReporter.new)
143 143 end
144 144
145 145 runner = Grader::Runner.new(engine, grader_proc)
146 146 while true
147 147
148 148 if check_stopfile # created by calling grader stop
149 149 clear_stopfile
150 150 log "stopped (with stop file)"
151 151 break
152 152 end
153 153
154 154 if grader_mode=="queue"
155 155 task = runner.grade_oldest_task
156 156 else
157 157 task = runner.grade_oldest_test_request
158 158 end
159 159 if task==nil
160 160 sleep(1)
161 161 end
162 162 end
163 163
164 164 when "prob"
165 165 engine = Grader::Engine.new
166 166 runner = Grader::Runner.new(engine, grader_proc)
167 167
168 168 grader_proc.report_active if grader_proc!=nil
169 169
170 - prob = Problem.find_by_name(ARGV[2])
170 + ARGV.shift
171 + ARGV.shift
172 +
173 + ARGV.each do |prob_name|
174 + prob = Problem.find_by_name(prob_name)
171 175 if prob==nil
172 - puts "cannot find problem: #{ARGV[2]}"
176 + puts "cannot find problem: #{prob_name}"
173 177 else
174 178 runner.grade_problem(prob)
175 179 end
180 + end
176 181
177 182 else
178 183 display_manual
179 184 exit(0)
180 185 end
181 186
182 187 # report inactive
183 188 grader_proc.report_inactive if grader_proc!=nil
184 189
@@ -1,677 +1,680
1 1 /*
2 2 * A Simple Testing Sandbox
3 3 *
4 4 * (c) 2001--2004 Martin Mares <mj@ucw.cz>
5 5 */
6 6
7 7 #define _LARGEFILE64_SOURCE
8 8 //#define _GNU_SOURCE
9 9
10 10 #include <errno.h>
11 11 #include <stdio.h>
12 12 #include <fcntl.h>
13 13 #include <stdlib.h>
14 14 #include <string.h>
15 15 #include <stdarg.h>
16 16 #include <unistd.h>
17 17 #include <getopt.h>
18 18 #include <time.h>
19 19 #include <sys/wait.h>
20 20 #include <sys/user.h>
21 21 #include <sys/time.h>
22 22 #include <sys/ptrace.h>
23 23 #include <sys/signal.h>
24 24 #include <sys/sysinfo.h>
25 25 #include <sys/syscall.h>
26 26 #include <sys/resource.h>
27 27
28 28 #define NONRET __attribute__((noreturn))
29 29 #define UNUSED __attribute__((unused))
30 30
31 31 static int filter_syscalls; /* 0=off, 1=liberal, 2=totalitarian */
32 - static int timeout;
32 + static double timeout;
33 33 static int pass_environ;
34 34 static int use_wall_clock;
35 35 static int file_access;
36 36 static int verbose;
37 37 static int memory_limit;
38 38 static int allow_times;
39 39 static char *redir_stdin, *redir_stdout;
40 40 static char *set_cwd;
41 41
42 42 static pid_t box_pid;
43 43 static int is_ptraced;
44 44 static volatile int timer_tick;
45 45 static time_t start_time;
46 46 static int ticks_per_sec;
47 47 static int page_size;
48 48
49 49 #if defined(__GLIBC__) && __GLIBC__ == 2 && __GLIBC_MINOR__ > 0
50 50 /* glibc 2.1 or newer -> has lseek64 */
51 51 #define long_seek(f,o,w) lseek64(f,o,w)
52 52 #else
53 53 /* Touching clandestine places in glibc */
54 54 extern loff_t llseek(int fd, loff_t pos, int whence);
55 55 #define long_seek(f,o,w) llseek(f,o,w)
56 56 #endif
57 57
58 58 int max_mem_used = 0;
59 59
60 60 void print_running_stat(double wall_time,
61 61 double user_time,
62 62 double system_time,
63 63 int mem_usage)
64 64 {
65 65 fprintf(stderr,"%.4lfr%.4lfu%.4lfs%dm\n",
66 66 wall_time, user_time, system_time, mem_usage);
67 67 }
68 68
69 69 static void NONRET
70 70 box_exit(void)
71 71 {
72 72 if (box_pid > 0) {
73 73 if (is_ptraced)
74 74 ptrace(PTRACE_KILL, box_pid);
75 75 kill(-box_pid, SIGKILL);
76 76 kill(box_pid, SIGKILL);
77 77 }
78 78
79 79 struct timeval total;
80 80 int wall;
81 81 struct rusage rus;
82 82 int stat;
83 83 pid_t p;
84 84
85 85 // wait so that we can get information
86 86 p = wait4(box_pid, &stat, WUNTRACED, &rus);
87 87 if (p < 0) {
88 88 fprintf(stderr,"wait4: error\n");
89 89 print_running_stat(0,0,0,max_mem_used);
90 90 } else if (p != box_pid) {
91 91 fprintf(stderr,"wait4: unknown pid %d exited!\n", p);
92 92 print_running_stat(0,0,0,max_mem_used);
93 93 } else {
94 94 if (!WIFEXITED(stat))
95 95 fprintf(stderr,"wait4: unknown status\n");
96 96 struct timeval total;
97 97 int wall;
98 98 wall = time(NULL) - start_time;
99 99 timeradd(&rus.ru_utime, &rus.ru_stime, &total);
100 100
101 101 print_running_stat((double)wall,
102 102 (double) rus.ru_utime.tv_sec +
103 103 ((double) rus.ru_utime.tv_usec/1000000.0),
104 104 (double) rus.ru_stime.tv_sec +
105 105 ((double) rus.ru_stime.tv_usec/1000000.0),
106 106 max_mem_used);
107 107 }
108 108 exit(1);
109 109 }
110 110
111 111 static void NONRET __attribute__((format(printf,1,2)))
112 112 die(char *msg, ...)
113 113 {
114 114 va_list args;
115 115 va_start(args, msg);
116 116 vfprintf(stderr, msg, args);
117 117 fputc('\n', stderr);
118 118 box_exit();
119 119 }
120 120
121 121 static void __attribute__((format(printf,1,2)))
122 122 log(char *msg, ...)
123 123 {
124 124 va_list args;
125 125 va_start(args, msg);
126 126 if (verbose)
127 127 {
128 128 vfprintf(stderr, msg, args);
129 129 fflush(stderr);
130 130 }
131 131 va_end(args);
132 132 }
133 133
134 134 static void
135 135 valid_filename(unsigned long addr)
136 136 {
137 137 char namebuf[4096], *p, *end;
138 138 static int mem_fd;
139 139
140 140 if (!file_access)
141 141 die("File access forbidden.");
142 142 if (file_access >= 9)
143 143 return;
144 144
145 145 if (!mem_fd)
146 146 {
147 147 sprintf(namebuf, "/proc/%d/mem", (int) box_pid);
148 148 mem_fd = open(namebuf, O_RDONLY);
149 149 if (mem_fd < 0)
150 150 die("open(%s): %m", namebuf);
151 151 }
152 152 p = end = namebuf;
153 153 do
154 154 {
155 155 if (p >= end)
156 156 {
157 157 int remains = PAGE_SIZE - (addr & (PAGE_SIZE-1));
158 158 int l = namebuf + sizeof(namebuf) - end;
159 159 if (l > remains)
160 160 l = remains;
161 161 if (!l)
162 162 die("Access to file with name too long.");
163 163 if (long_seek(mem_fd, addr, SEEK_SET) < 0)
164 164 die("long_seek(mem): %m");
165 165 remains = read(mem_fd, end, l);
166 166 if (remains < 0)
167 167 die("read(mem): %m");
168 168 if (!remains)
169 169 die("Access to file with name out of memory.");
170 170 end += l;
171 171 addr += l;
172 172 }
173 173 }
174 174 while (*p++);
175 175
176 176 log("[%s] ", namebuf);
177 177 if (file_access >= 3)
178 178 return;
179 179 if (!strchr(namebuf, '/') && strcmp(namebuf, ".."))
180 180 return;
181 181 if (file_access >= 2)
182 182 {
183 183 if ((!strncmp(namebuf, "/etc/", 5) ||
184 184 !strncmp(namebuf, "/lib/", 5) ||
185 185 !strncmp(namebuf, "/usr/lib/", 9))
186 186 && !strstr(namebuf, ".."))
187 187 return;
188 188 if (!strcmp(namebuf, "/dev/null") ||
189 189 !strcmp(namebuf, "/dev/zero") ||
190 190 !strcmp(namebuf, "/proc/meminfo") ||
191 191 !strcmp(namebuf, "/proc/self/stat") ||
192 192 !strncmp(namebuf, "/usr/share/zoneinfo/", 20))
193 193 return;
194 194 }
195 195 die("Forbidden access to file `%s'.", namebuf);
196 196 }
197 197
198 198 static int
199 199 valid_syscall(struct user *u)
200 200 {
201 201 switch (u->regs.orig_eax)
202 202 {
203 203 case __NR_execve:
204 204 {
205 205 static int exec_counter;
206 206 return !exec_counter++;
207 207 }
208 208 case __NR_open:
209 209 case __NR_creat:
210 210 case __NR_unlink:
211 211 case __NR_oldstat:
212 212 case __NR_access:
213 213 case __NR_oldlstat:
214 214 case __NR_truncate:
215 215 case __NR_stat:
216 216 case __NR_lstat:
217 217 case __NR_truncate64:
218 218 case __NR_stat64:
219 219 case __NR_lstat64:
220 220 valid_filename(u->regs.ebx);
221 221 return 1;
222 222 case __NR_exit:
223 223 case __NR_read:
224 224 case __NR_write:
225 225 case __NR_close:
226 226 case __NR_lseek:
227 227 case __NR_getpid:
228 228 case __NR_getuid:
229 229 case __NR_oldfstat:
230 230 case __NR_dup:
231 231 case __NR_brk:
232 232 case __NR_getgid:
233 233 case __NR_geteuid:
234 234 case __NR_getegid:
235 235 case __NR_dup2:
236 236 case __NR_ftruncate:
237 237 case __NR_fstat:
238 238 case __NR_personality:
239 239 case __NR__llseek:
240 240 case __NR_readv:
241 241 case __NR_writev:
242 242 case __NR_getresuid:
243 243 #ifdef __NR_pread64
244 244 case __NR_pread64:
245 245 case __NR_pwrite64:
246 246 #else
247 247 case __NR_pread:
248 248 case __NR_pwrite:
249 249 #endif
250 250 case __NR_ftruncate64:
251 251 case __NR_fstat64:
252 252 case __NR_fcntl:
253 253 case __NR_fcntl64:
254 254 case __NR_mmap:
255 255 case __NR_munmap:
256 256 case __NR_ioctl:
257 257 case __NR_uname:
258 258 case 252:
259 259 case 243:
260 260 return 1;
261 261 // case __NR_time:
262 262 case __NR_alarm:
263 263 // case __NR_pause:
264 264 case __NR_signal:
265 265 case __NR_fchmod:
266 266 case __NR_sigaction:
267 267 case __NR_sgetmask:
268 268 case __NR_ssetmask:
269 269 case __NR_sigsuspend:
270 270 case __NR_sigpending:
271 271 case __NR_getrlimit:
272 272 case __NR_getrusage:
273 273 case __NR_gettimeofday:
274 274 case __NR_select:
275 275 case __NR_readdir:
276 276 case __NR_setitimer:
277 277 case __NR_getitimer:
278 278 case __NR_sigreturn:
279 279 case __NR_mprotect:
280 280 case __NR_sigprocmask:
281 281 case __NR_getdents:
282 282 case __NR_getdents64:
283 283 case __NR__newselect:
284 284 case __NR_fdatasync:
285 285 case __NR_mremap:
286 286 case __NR_poll:
287 287 case __NR_getcwd:
288 288 case __NR_nanosleep:
289 289 case __NR_rt_sigreturn:
290 290 case __NR_rt_sigaction:
291 291 case __NR_rt_sigprocmask:
292 292 case __NR_rt_sigpending:
293 293 case __NR_rt_sigtimedwait:
294 294 case __NR_rt_sigqueueinfo:
295 295 case __NR_rt_sigsuspend:
296 296 case __NR_mmap2:
297 297 case __NR__sysctl:
298 298 return (filter_syscalls == 1);
299 299 case __NR_times:
300 300 return allow_times;
301 301 case __NR_kill:
302 302 if (u->regs.ebx == box_pid)
303 303 die("Commited suicide by signal %d.", (int)u->regs.ecx);
304 304 return 0;
305 305 default:
306 306 return 0;
307 307 }
308 308 }
309 309
310 310 static void
311 311 signal_alarm(int unused UNUSED)
312 312 {
313 313 /* Time limit checks are synchronous, so we only schedule them there. */
314 314 timer_tick = 1;
315 315
316 316 //NOTE: do not use alarm, changed to setitimer for precision
317 317 // alarm(1);
318 318 }
319 319
320 320 static void
321 321 signal_int(int unused UNUSED)
322 322 {
323 323 /* Interrupts are fatal, so no synchronization requirements. */
324 324 die("Interrupted.");
325 325 }
326 326
327 327 static void
328 328 check_timeout(void)
329 329 {
330 - int sec;
330 + double sec;
331 331
332 332 if (use_wall_clock)
333 - sec = time(NULL) - start_time;
333 + sec = (double)(time(NULL) - start_time);
334 334 else
335 335 {
336 336 char buf[4096], *x;
337 337 int c, utime, stime;
338 338 static int proc_status_fd;
339 339 if (!proc_status_fd)
340 340 {
341 341 sprintf(buf, "/proc/%d/stat", (int) box_pid);
342 342 proc_status_fd = open(buf, O_RDONLY);
343 343 if (proc_status_fd < 0)
344 344 die("open(%s): %m", buf);
345 345 }
346 346 lseek(proc_status_fd, 0, SEEK_SET);
347 347 if ((c = read(proc_status_fd, buf, sizeof(buf)-1)) < 0)
348 348 die("read on /proc/$pid/stat: %m");
349 349 if (c >= (int) sizeof(buf) - 1)
350 350 die("/proc/$pid/stat too long");
351 351 buf[c] = 0;
352 352 x = buf;
353 353 while (*x && *x != ' ')
354 354 x++;
355 355 while (*x == ' ')
356 356 x++;
357 357 if (*x++ != '(')
358 358 die("proc syntax error 1");
359 359 while (*x && (*x != ')' || x[1] != ' '))
360 360 x++;
361 361 while (*x == ')' || *x == ' ')
362 362 x++;
363 363 if (sscanf(x, "%*c %*d %*d %*d %*d %*d %*d %*d %*d %*d %*d %d %d", &utime, &stime) != 2)
364 364 die("proc syntax error 2");
365 365 //printf("%s - %d\n",x,ticks_per_sec);
366 - sec = (utime + stime + ticks_per_sec-1)/ticks_per_sec;
366 + sec = ((double)(utime + stime))/(double)ticks_per_sec;
367 367 }
368 368 if (verbose > 1)
369 369 fprintf(stderr, "[timecheck: %d seconds]\n", sec);
370 370 if (sec > timeout) {
371 - die("Time limit exceeded.");
371 + die("Time limit exceeded.",sec,timeout);
372 372 }
373 373 }
374 374
375 375 static void
376 376 check_memory_usage()
377 377 {
378 378 char proc_fname[100];
379 379 sprintf(proc_fname,"/proc/%d/statm",box_pid);
380 380 //printf("proc fname: %s\n",proc_fname);
381 381 FILE *fp = fopen(proc_fname,"r");
382 382 if(fp!=NULL) {
383 383 char line[1000];
384 384 fgets(line,999,fp);
385 385 //printf("%s\n",line);
386 386 int m;
387 387
388 388 if(sscanf(line,"%d",&m)==1) {
389 389 m = (m*page_size+1023)/1024;
390 390 if(m>max_mem_used)
391 391 max_mem_used = m;
392 392 }
393 393
394 394 fclose(fp);
395 395 }
396 396 }
397 397
398 398 static void
399 399 boxkeeper(void)
400 400 {
401 401 int syscall_count = 0;
402 402 struct sigaction sa;
403 403
404 404 is_ptraced = 1;
405 405 bzero(&sa, sizeof(sa));
406 406 sa.sa_handler = signal_int;
407 407 sigaction(SIGINT, &sa, NULL);
408 408 start_time = time(NULL);
409 409 ticks_per_sec = sysconf(_SC_CLK_TCK);
410 410 page_size = getpagesize();
411 411 if (ticks_per_sec <= 0)
412 412 die("Invalid ticks_per_sec!");
413 413
414 414 check_memory_usage();
415 415
416 416 sa.sa_handler = signal_alarm;
417 417 sigaction(SIGALRM, &sa, NULL);
418 418 //alarm(1);
419 419
420 420 struct itimerval val;
421 421 val.it_interval.tv_sec = 0;
422 422 val.it_interval.tv_usec = 50000;
423 423 val.it_value.tv_sec = 0;
424 424 val.it_value.tv_usec = 50000;
425 425 setitimer(ITIMER_REAL,&val,NULL);
426 426
427 427 /*
428 428 --- add alarm handler no matter what..
429 429 if (timeout)
430 430 {
431 431 sa.sa_handler = signal_alarm;
432 432 sigaction(SIGALRM, &sa, NULL);
433 433 alarm(1);
434 434 }
435 435 */
436 436
437 437 for(;;)
438 438 {
439 439 struct rusage rus;
440 440 int stat;
441 441 pid_t p;
442 442
443 443 if (timer_tick)
444 444 {
445 445 check_timeout();
446 446 check_memory_usage();
447 447 timer_tick = 0;
448 448 }
449 449 p = wait4(box_pid, &stat, WUNTRACED, &rus);
450 450
451 451 if (p < 0)
452 452 {
453 453 if (errno == EINTR)
454 454 continue;
455 455 die("wait4: %m");
456 456 }
457 457 if (p != box_pid)
458 458 die("wait4: unknown pid %d exited!", p);
459 459 if (WIFEXITED(stat))
460 460 {
461 461 struct timeval total;
462 462 int wall;
463 463 wall = time(NULL) - start_time;
464 464 timeradd(&rus.ru_utime, &rus.ru_stime, &total);
465 465
466 466 box_pid = 0;
467 467 if (WEXITSTATUS(stat))
468 468 fprintf(stderr,"Exited with error status %d.\n", WEXITSTATUS(stat));
469 - else if ((use_wall_clock ? wall : total.tv_sec) > timeout)
469 + else if ((use_wall_clock ?
470 + wall :
471 + (double) total.tv_sec +
472 + ((double) total.tv_usec/1000000.0)) > timeout)
470 473 fprintf(stderr,"Time limit exceeded.\n");
471 474 else
472 475 // report OK and statistics
473 476 fprintf(stderr,"OK\n");
474 477
475 478 print_running_stat((double) wall,
476 479 (double) rus.ru_utime.tv_sec +
477 480 ((double) rus.ru_utime.tv_usec/1000000.0),
478 481 (double) rus.ru_stime.tv_sec +
479 482 ((double) rus.ru_stime.tv_usec/1000000.0),
480 483 max_mem_used);
481 484 /*
482 485 (%.4lf sec real (%d), %d sec wall, %d syscalls, %d kb)\n",
483 486 (double) total.tv_sec + ((double)total.tv_usec / 1000000.0),
484 487 (int) total.tv_usec,
485 488 wall,
486 489 syscall_count,
487 490 max_mem_used);
488 491 */
489 492 exit(0);
490 493 }
491 494 if (WIFSIGNALED(stat))
492 495 {
493 496 box_pid = 0;
494 497 fprintf(stderr,"Caught fatal signal %d.\n", WTERMSIG(stat));
495 498
496 499 struct timeval total;
497 500 int wall;
498 501 wall = time(NULL) - start_time;
499 502 timeradd(&rus.ru_utime, &rus.ru_stime, &total);
500 503 print_running_stat((double) wall,
501 504 (double) rus.ru_utime.tv_sec +
502 505 ((double) rus.ru_utime.tv_usec/1000000.0),
503 506 (double) rus.ru_stime.tv_sec +
504 507 ((double) rus.ru_stime.tv_usec/1000000.0),
505 508 max_mem_used);
506 509 exit(0);
507 510 }
508 511 if (WIFSTOPPED(stat))
509 512 {
510 513 int sig = WSTOPSIG(stat);
511 514 if (sig == SIGTRAP)
512 515 {
513 516 struct user u;
514 517 static int stop_count = -1;
515 518 if (ptrace(PTRACE_GETREGS, box_pid, NULL, &u) < 0)
516 519 die("ptrace(PTRACE_GETREGS): %m");
517 520 stop_count++;
518 521 if (!stop_count) /* Traceme request */
519 522 log(">> Traceme request caught\n");
520 523 else if (stop_count & 1) /* Syscall entry */
521 524 {
522 525 log(">> Syscall %3ld (%08lx,%08lx,%08lx) ", u.regs.orig_eax, u.regs.ebx, u.regs.ecx, u.regs.edx);
523 526 syscall_count++;
524 527 if (!valid_syscall(&u))
525 528 {
526 529 /*
527 530 * Unfortunately, PTRACE_KILL kills _after_ the syscall completes,
528 531 * so we have to change it to something harmless (e.g., an undefined
529 532 * syscall) and make the program continue.
530 533 */
531 534 unsigned int sys = u.regs.orig_eax;
532 535 u.regs.orig_eax = 0xffffffff;
533 536 if (ptrace(PTRACE_SETREGS, box_pid, NULL, &u) < 0)
534 537 die("ptrace(PTRACE_SETREGS): %m");
535 538 die("Forbidden syscall %d.", sys);
536 539 }
537 540 }
538 541 else /* Syscall return */
539 542 log("= %ld\n", u.regs.eax);
540 543 ptrace(PTRACE_SYSCALL, box_pid, 0, 0);
541 544 }
542 545 else if (sig != SIGSTOP && sig != SIGXCPU && sig != SIGXFSZ)
543 546 {
544 547 log(">> Signal %d\n", sig);
545 548 ptrace(PTRACE_SYSCALL, box_pid, 0, sig);
546 549 }
547 550 else
548 551 die("Received signal %d.", sig);
549 552 }
550 553 else
551 554 die("wait4: unknown status %x, giving up!", stat);
552 555 }
553 556 }
554 557
555 558 static void
556 559 box_inside(int argc, char **argv)
557 560 {
558 561 struct rlimit rl;
559 562 char *args[argc+1];
560 563 char *env[1] = { NULL };
561 564
562 565 memcpy(args, argv, argc * sizeof(char *));
563 566 args[argc] = NULL;
564 567 if (set_cwd && chdir(set_cwd))
565 568 die("chdir: %m");
566 569 if (redir_stdin)
567 570 {
568 571 close(0);
569 572 if (open(redir_stdin, O_RDONLY) != 0)
570 573 die("open(\"%s\"): %m", redir_stdin);
571 574 }
572 575 if (redir_stdout)
573 576 {
574 577 close(1);
575 578 if (open(redir_stdout, O_WRONLY | O_CREAT | O_TRUNC, 0666) != 1)
576 579 die("open(\"%s\"): %m", redir_stdout);
577 580 }
578 581 dup2(1, 2);
579 582 setpgrp();
580 583 if (memory_limit)
581 584 {
582 585 rl.rlim_cur = rl.rlim_max = memory_limit * 1024;
583 586 if (setrlimit(RLIMIT_AS, &rl) < 0)
584 587 die("setrlimit: %m");
585 588 }
586 589 rl.rlim_cur = rl.rlim_max = 64;
587 590 if (setrlimit(RLIMIT_NOFILE, &rl) < 0)
588 591 die("setrlimit: %m");
589 592 if (filter_syscalls && ptrace(PTRACE_TRACEME) < 0)
590 593 die("ptrace(PTRACE_TRACEME): %m");
591 594 execve(args[0], args, (pass_environ ? environ : env));
592 595 die("execve(\"%s\"): %m", args[0]);
593 596 }
594 597
595 598 static void
596 599 usage(void)
597 600 {
598 601 fprintf(stderr, "Invalid arguments!\n");
599 602 printf("\
600 603 Usage: box [<options>] -- <command> <arguments>\n\
601 604 \n\
602 605 Options:\n\
603 606 -a <level>\tSet file access level (0=none, 1=cwd, 2=/etc,/lib,..., 3=whole fs, 9=no checks; needs -f)\n\
604 607 -c <dir>\tChange directory to <dir> first\n\
605 608 -e\t\tPass full environment of parent process\n\
606 609 -f\t\tFilter system calls (-ff=very restricted)\n\
607 610 -i <file>\tRedirect stdin from <file>\n\
608 611 -m <size>\tLimit address space to <size> KB\n\
609 612 -o <file>\tRedirect stdout to <file>\n\
610 613 -t <time>\tStop after <time> seconds\n\
611 614 -T\t\tAllow syscalls for measuring run time\n\
612 615 -v\t\tBe verbose\n\
613 616 -w\t\tMeasure wall clock time instead of run time\n\
614 617 ");
615 618 exit(1);
616 619 }
617 620
618 621 int
619 622 main(int argc, char **argv)
620 623 {
621 624 int c;
622 625 uid_t uid;
623 626
624 627 while ((c = getopt(argc, argv, "a:c:efi:m:o:t:Tvw")) >= 0)
625 628 switch (c)
626 629 {
627 630 case 'a':
628 631 file_access = atol(optarg);
629 632 break;
630 633 case 'c':
631 634 set_cwd = optarg;
632 635 break;
633 636 case 'e':
634 637 pass_environ = 1;
635 638 break;
636 639 case 'f':
637 640 filter_syscalls++;
638 641 break;
639 642 case 'i':
640 643 redir_stdin = optarg;
641 644 break;
642 645 case 'm':
643 646 memory_limit = atol(optarg);
644 647 break;
645 648 case 'o':
646 649 redir_stdout = optarg;
647 650 break;
648 651 case 't':
649 - timeout = atol(optarg);
652 + timeout = atof(optarg);
650 653 break;
651 654 case 'T':
652 655 allow_times++;
653 656 break;
654 657 case 'v':
655 658 verbose++;
656 659 break;
657 660 case 'w':
658 661 use_wall_clock = 1;
659 662 break;
660 663 default:
661 664 usage();
662 665 }
663 666 if (optind >= argc)
664 667 usage();
665 668
666 669 uid = geteuid();
667 670 if (setreuid(uid, uid) < 0)
668 671 die("setreuid: %m");
669 672 box_pid = fork();
670 673 if (box_pid < 0)
671 674 die("fork: %m");
672 675 if (!box_pid)
673 676 box_inside(argc-optind, argv+optind);
674 677 else
675 678 boxkeeper();
676 679 die("Internal error: fell over edge of the world");
677 680 }
@@ -1,20 +1,24
1 1 problem do
2 2 num_tests 2
3 3 full_score 20
4 4 time_limit_each 1
5 5 mem_limit_each 16
6 6 score_each 10
7 7
8 + test 1 do
9 + time_limit 0.5
10 + end
11 +
8 12 run 1 do
9 13 tests 1
10 14 end
11 15
12 16 test 2 do
13 - time_limit 2
17 + time_limit 0.6
14 18 end
15 19
16 20 run 2 do
17 21 tests 2
18 22 end
19 23
20 24 end
@@ -1,247 +1,247
1 1 require File.join(File.dirname(__FILE__),'spec_helper')
2 2 require File.join(File.dirname(__FILE__),'engine_spec_helper')
3 3
4 4 describe "A grader engine, when grading submissions" do
5 5
6 6 include GraderEngineHelperMethods
7 7
8 8 before(:each) do
9 9 @config = Grader::Configuration.get_instance
10 10
11 11 # this test is from Pong
12 12 @problem_test_normal = stub(Problem,
13 13 :id => 1, :name => 'test_normal',
14 14 :full_score => 135)
15 15 @user_user1 = stub(User,
16 16 :id => 1, :login => 'user1')
17 17
18 18 @engine = Grader::Engine.new
19 19 init_sandbox
20 20 end
21 21
22 22 it "should grade normal submission" do
23 23 grader_should(:grade => "test1_correct.c",
24 24 :on => @problem_test_normal,
25 25 :and_report => {
26 26 :score => 135,
27 27 :comment => /^PASSED/})
28 28 end
29 29
30 30
31 31 it "should produce error message when submission cannot compile" do
32 32 grader_should(:grade => "test1_compile_error.c",
33 33 :on => @problem_test_normal,
34 34 :and_report => {
35 35 :score => 0,
36 36 :comment => 'FAILED: compilation error',
37 37 :compiler_message => /[Ee]rror/})
38 38 end
39 39
40 40 it "should produce timeout error when submission runs forever" do
41 41 @problem_test_timeout = stub(Problem,
42 42 :id => 1, :name => 'test_timeout',
43 43 :full_score => 10)
44 44 grader_should(:grade => "test2_timeout.c",
45 45 :on => @problem_test_timeout,
46 46 :and_report => {
47 47 :score => 0,
48 48 :comment => 'FAILED: TT'})
49 49 end
50 50
51 - it "should produce timeout error correctly when submission runs slower than expected in less than a second" do
51 + it "should produce timeout error correctly with fractional running time and fractional time limits" do
52 52 @problem_test_timeout = stub(Problem,
53 53 :id => 1, :name => 'test_timeout',
54 54 :full_score => 20)
55 - grader_should(:grade => "test2_1-5sec.c",
55 + grader_should(:grade => "test2_05sec.c",
56 56 :on => @problem_test_timeout,
57 57 :and_report => {
58 58 :score => 10,
59 59 :comment => 'FAILED: TP'})
60 60 end
61 61
62 62 it "should produce runtime error when submission uses too much static memory" do
63 63 @problem_test_memory = stub(Problem,
64 64 :id => 1, :name => 'test_memory',
65 65 :full_score => 20)
66 66 grader_should(:grade => "add_too_much_memory_static.c",
67 67 :on => @problem_test_memory,
68 68 :and_report => {
69 69 :score => 10,
70 70 :comment => /FAILED: [^P]P/})
71 71 end
72 72
73 73 it "should not allow submission to allocate too much dynamic memory" do
74 74 @problem_test_memory = stub(Problem,
75 75 :id => 1, :name => 'test_memory',
76 76 :full_score => 20)
77 77 grader_should(:grade => "add_too_much_memory_dynamic.c",
78 78 :on => @problem_test_memory,
79 79 :and_report => {
80 80 :score => 10,
81 81 :comment => /FAILED: [^P]P/})
82 82 end
83 83
84 84 it "should score test runs correctly when submission fails in some test case" do
85 85 grader_should(:grade => "add_fail_test_case_1.c",
86 86 :on => @problem_test_normal,
87 87 :and_report => {
88 88 :score => 105,
89 89 :comment => /^FAILED:/})
90 90 end
91 91
92 92 it "should fail submission with non-zero exit status" do
93 93 grader_should(:grade => "add_nonzero_exit_status.c",
94 94 :on => @problem_test_normal,
95 95 :and_report => {
96 96 :score => 0,
97 97 :comment => /^FAILED:/})
98 98 end
99 99
100 100 it "should not allow malicious submission to see PROBLEM_HOME" do
101 101 problem_test_yesno = stub(Problem,
102 102 :id => 1, :name => 'test_yesno',
103 103 :full_score => 10)
104 104 grader_should(:grade => "yesno_access_problem_home.c",
105 105 :on => problem_test_yesno,
106 106 :and_report => {
107 107 :score => 0,
108 108 :comment => /^FAILED:/})
109 109 end
110 110
111 111 it "should not allow malicious submission to open files" do
112 112 problem_test_yesno = stub(Problem,
113 113 :id => 1, :name => 'test_yesno',
114 114 :full_score => 10)
115 115 grader_should(:grade => "yesno_open_file.c",
116 116 :on => problem_test_yesno,
117 117 :and_report => {
118 118 :score => 0,
119 119 :comment => /^FAILED:/})
120 120 end
121 121
122 122 def grader_should(args)
123 123 @user1 = stub(User,
124 124 :id => 1, :login => 'user1')
125 125 submission =
126 126 create_submission_from_file(1, @user1, args[:on], args[:grade])
127 127 submission.should_receive(:graded_at=)
128 128
129 129 expected_score = args[:and_report][:score]
130 130 expected_comment = args[:and_report][:comment]
131 131 if args[:and_report][:compiler_message]!=nil
132 132 expected_compiler_message = args[:and_report][:compiler_message]
133 133 else
134 134 expected_compiler_message = ''
135 135 end
136 136
137 137 submission.should_receive(:points=).with(expected_score)
138 138 submission.should_receive(:grader_comment=).with(expected_comment)
139 139 submission.should_receive(:compiler_message=).with(expected_compiler_message)
140 140 submission.should_receive(:save)
141 141
142 142 @engine.grade(submission)
143 143 end
144 144
145 145 protected
146 146
147 147 def create_normal_submission_mock_from_file(source_fname)
148 148 create_submission_from_file(1, @user_user1, @problem_test_normal, source_fname)
149 149 end
150 150
151 151 end
152 152
153 153 describe "A grader engine, when grading test requests" do
154 154
155 155 include GraderEngineHelperMethods
156 156
157 157 before(:each) do
158 158 @config = Grader::Configuration.get_instance
159 159 @engine = Grader::Engine.new(Grader::TestRequestRoomMaker.new,
160 160 Grader::TestRequestReporter.new)
161 161 init_sandbox
162 162 end
163 163
164 164 it "should report error if there is no problem template" do
165 165 problem = stub(Problem,
166 166 :id => 1, :name => 'nothing')
167 167 grader_should(:grade => 'test1_correct.c',
168 168 :on => problem,
169 169 :with => 'in1.txt',
170 170 :and_report => {
171 171 :graded_at= => nil,
172 172 :compiler_message= => '',
173 173 :grader_comment= => '',
174 174 :running_stat= => /template not found/,
175 175 :running_time= => nil,
176 176 :exit_status= => nil,
177 177 :memory_usage= => nil,
178 178 :save => nil})
179 179 end
180 180
181 181 it "should run test request and produce output file" do
182 182 problem = stub(Problem,
183 183 :id => 1, :name => 'test_normal')
184 184 grader_should(:grade => 'test1_correct.c',
185 185 :on => problem,
186 186 :with => 'in1.txt',
187 187 :and_report => {
188 188 :graded_at= => nil,
189 189 :compiler_message= => '',
190 190 :grader_comment= => '',
191 191 :running_stat= => /0.0\d* sec./,
192 192 :output_file_name= => lambda { |fname|
193 193 File.exists?(fname).should be_true
194 194 },
195 195 :running_time= => nil,
196 196 :exit_status= => nil,
197 197 :memory_usage= => nil,
198 198 :save => nil})
199 199 end
200 200
201 201 it "should clean up problem directory after running test request" do
202 202 problem = stub(Problem,
203 203 :id => 1, :name => 'test_normal')
204 204 grader_should(:grade => 'test1_correct.c',
205 205 :on => problem,
206 206 :with => 'in1.txt',
207 207 :and_report => {
208 208 :graded_at= => nil,
209 209 :compiler_message= => '',
210 210 :grader_comment= => '',
211 211 :running_stat= => nil,
212 212 :output_file_name= => nil,
213 213 :running_time= => nil,
214 214 :exit_status= => nil,
215 215 :memory_usage= => nil,
216 216 :save => nil})
217 217 File.exists?(@config.user_result_dir + "/test_request/test_normal/test_cases/1/input-1.txt").should be_false
218 218 end
219 219
220 220 it "should compile test request with error and report compilation error" do
221 221 problem = stub(Problem,
222 222 :id => 1, :name => 'test_normal')
223 223 grader_should(:grade => 'test1_compile_error.c',
224 224 :on => problem,
225 225 :with => 'in1.txt',
226 226 :and_report => {
227 227 :graded_at= => nil,
228 228 :compiler_message= => /.+/,
229 229 :grader_comment= => /[Cc]ompil.*error/,
230 230 :running_stat= => '',
231 231 :save => nil})
232 232 end
233 233
234 234 it "should report exit status" do
235 235 problem = stub(Problem,
236 236 :id => 1, :name => 'test_normal')
237 237 grader_should(:grade => 'add_nonzero_exit_status.c',
238 238 :on => problem,
239 239 :with => 'in1.txt',
240 240 :and_report => {
241 241 :graded_at= => nil,
242 242 :compiler_message= => '',
243 243 :grader_comment= => '',
244 244 :running_stat= => /[Ee]xit.*status.*10.*0\.0\d* sec/m,
245 245 :output_file_name= => lambda { |fname|
246 246 File.exists?(fname).should be_true
247 247 },
deleted file
You need to be logged in to leave comments. Login now