Description:
add run_stat checking
Commit status:
[Not Reviewed]
References:
Comments:
0 Commit comments 0 Inline Comments
Unresolved TODOs:
There are no unresolved TODOs
Add another comment

r160:f0502be39e3b - - 3 files changed: 48 inserted, 10 deleted

@@ -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
@@ -1075,678 +1075,673
1075 1075 {
1076 1076 case 0x050f:
1077 1077 break;
1078 1078 case 0x80cd:
1079 1079 err("FO: Forbidden 32-bit syscall in 64-bit mode");
1080 1080 default:
1081 1081 err("XX: Unknown syscall instruction %04x", instr);
1082 1082 }
1083 1083 sys_type = 64;
1084 1084 break;
1085 1085 default:
1086 1086 err("XX: Unknown code segment %04jx", (intmax_t) a->user.regs.cs);
1087 1087 }
1088 1088
1089 1089 #ifdef CONFIG_BOX_USER_AMD64
1090 1090 if (sys_type != 64)
1091 1091 err("FO: Forbidden %d-bit mode syscall", sys_type);
1092 1092 #else
1093 1093 if (sys_type != (exec_seen ? 32 : 64))
1094 1094 err("FO: Forbidden %d-bit mode syscall", sys_type);
1095 1095 #endif
1096 1096
1097 1097 if (sys_type == 32)
1098 1098 {
1099 1099 a->arg1 = a->user.regs.rbx;
1100 1100 a->arg2 = a->user.regs.rcx;
1101 1101 a->arg3 = a->user.regs.rdx;
1102 1102 }
1103 1103 else
1104 1104 {
1105 1105 a->arg1 = a->user.regs.rdi;
1106 1106 a->arg2 = a->user.regs.rsi;
1107 1107 a->arg3 = a->user.regs.rdx;
1108 1108 }
1109 1109 }
1110 1110
1111 1111 static void
1112 1112 set_syscall_nr(struct syscall_args *a, arg_t sys)
1113 1113 {
1114 1114 a->sys = sys;
1115 1115 a->user.regs.orig_rax = sys;
1116 1116 if (ptrace(PTRACE_SETREGS, box_pid, NULL, &a->user) < 0)
1117 1117 die("ptrace(PTRACE_SETREGS): %m");
1118 1118 }
1119 1119
1120 1120 static void
1121 1121 sanity_check(void)
1122 1122 {
1123 1123 }
1124 1124
1125 1125 #else
1126 1126
1127 1127 static void
1128 1128 get_syscall_args(struct syscall_args *a, int is_exit UNUSED)
1129 1129 {
1130 1130 if (ptrace(PTRACE_GETREGS, box_pid, NULL, &a->user) < 0)
1131 1131 die("ptrace(PTRACE_GETREGS): %m");
1132 1132 a->sys = a->user.regs.orig_eax;
1133 1133 a->arg1 = a->user.regs.ebx;
1134 1134 a->arg2 = a->user.regs.ecx;
1135 1135 a->arg3 = a->user.regs.edx;
1136 1136 a->result = a->user.regs.eax;
1137 1137 }
1138 1138
1139 1139 static void
1140 1140 set_syscall_nr(struct syscall_args *a, arg_t sys)
1141 1141 {
1142 1142 a->sys = sys;
1143 1143 a->user.regs.orig_eax = sys;
1144 1144 if (ptrace(PTRACE_SETREGS, box_pid, NULL, &a->user) < 0)
1145 1145 die("ptrace(PTRACE_SETREGS): %m");
1146 1146 }
1147 1147
1148 1148 static void
1149 1149 sanity_check(void)
1150 1150 {
1151 1151 #if !defined(CONFIG_BOX_ALLOW_INSECURE)
1152 1152 struct utsname uts;
1153 1153 if (uname(&uts) < 0)
1154 1154 die("uname() failed: %m");
1155 1155
1156 1156 if (!strcmp(uts.machine, "x86_64"))
1157 1157 die("Running 32-bit sandbox on 64-bit kernels is inherently unsafe. Please get a 64-bit version.");
1158 1158 #endif
1159 1159 }
1160 1160
1161 1161 #endif
1162 1162
1163 1163 /*** Syscall checks ***/
1164 1164
1165 1165 static void
1166 1166 valid_filename(arg_t addr)
1167 1167 {
1168 1168 char namebuf[4096], *p, *end;
1169 1169
1170 1170 if (!file_access)
1171 1171 err("FA: File access forbidden");
1172 1172 if (file_access >= 9)
1173 1173 return;
1174 1174
1175 1175 p = end = namebuf;
1176 1176 do
1177 1177 {
1178 1178 if (p >= end)
1179 1179 {
1180 1180 int remains = PAGE_SIZE - (addr & (PAGE_SIZE-1));
1181 1181 int l = namebuf + sizeof(namebuf) - end;
1182 1182 if (l > remains)
1183 1183 l = remains;
1184 1184 if (!l)
1185 1185 err("FA: Access to file with name too long");
1186 1186 remains = read_user_mem(addr, end, l);
1187 1187 if (remains < 0)
1188 1188 die("read(mem): %m");
1189 1189 if (!remains)
1190 1190 err("FA: Access to file with name out of memory");
1191 1191 end += remains;
1192 1192 addr += remains;
1193 1193 }
1194 1194 }
1195 1195 while (*p++);
1196 1196
1197 1197 msg("[%s] ", namebuf);
1198 1198 if (file_access >= 3)
1199 1199 return;
1200 1200
1201 1201 // Everything in current directory is permitted
1202 1202 if (!strchr(namebuf, '/') && strcmp(namebuf, ".."))
1203 1203 return;
1204 1204
1205 1205 // ".." anywhere in the path is forbidden
1206 1206 enum action act = A_DEFAULT;
1207 1207 if (strstr(namebuf, ".."))
1208 1208 act = A_NO;
1209 1209
1210 1210 // Scan user rules
1211 1211 for (struct path_rule *r = user_path_rules; r && !act; r=r->next)
1212 1212 act = match_path_rule(r, namebuf);
1213 1213
1214 1214 // Scan built-in rules
1215 1215 if (file_access >= 2)
1216 1216 for (int i=0; i<ARRAY_SIZE(default_path_rules) && !act; i++)
1217 1217 act = match_path_rule(&default_path_rules[i], namebuf);
1218 1218
1219 1219 if (act != A_YES)
1220 1220 err("FA: Forbidden access to file `%s'", namebuf);
1221 1221 }
1222 1222
1223 1223 // Check syscall. If invalid, return -1, otherwise return the action mask.
1224 1224 static int
1225 1225 valid_syscall(struct syscall_args *a)
1226 1226 {
1227 1227 unsigned int sys = a->sys;
1228 1228 unsigned int act = (sys < NUM_ACTIONS) ? syscall_action[sys] : A_DEFAULT;
1229 1229
1230 1230 if (act & A_LIBERAL)
1231 1231 {
1232 1232 if (filter_syscalls != 1)
1233 1233 act = A_DEFAULT;
1234 1234 }
1235 1235
1236 1236 switch (act & A_ACTION_MASK)
1237 1237 {
1238 1238 case A_YES:
1239 1239 return act;
1240 1240 case A_NO:
1241 1241 return -1;
1242 1242 case A_FILENAME:
1243 1243 valid_filename(a->arg1);
1244 1244 return act;
1245 1245 default: ;
1246 1246 }
1247 1247
1248 1248 switch (sys)
1249 1249 {
1250 1250 case __NR_kill:
1251 1251 if (a->arg1 == (arg_t) box_pid)
1252 1252 {
1253 1253 meta_printf("exitsig:%d\n", (int) a->arg2);
1254 1254 err("SG: Committed suicide by signal %d", (int) a->arg2);
1255 1255 }
1256 1256 return -1;
1257 1257 case __NR_tgkill:
1258 1258 if (a->arg1 == (arg_t) box_pid && a->arg2 == (arg_t) box_pid)
1259 1259 {
1260 1260 meta_printf("exitsig:%d\n", (int) a->arg3);
1261 1261 err("SG: Committed suicide by signal %d", (int) a->arg3);
1262 1262 }
1263 1263 return -1;
1264 1264 default:
1265 1265 return -1;
1266 1266 }
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\
1656 1651 -s <sys>\tPermit the specified syscall (be careful)\n\
1657 1652 -s <sys>=<act>\tDefine action for the specified syscall (<act>=yes/no/file)\n\
1658 1653 -t <time>\tSet run time limit (seconds, fractions allowed)\n\
1659 1654 -T\t\tAllow syscalls for measuring run time\n\
1660 1655 -v\t\tBe verbose (use multiple times for even more verbosity)\n\
1661 1656 -w <time>\tSet wall clock time limit (seconds, fractions allowed)\n\
1662 1657 -x <time>\tSet extra timeout, before which a timing-out program is not yet killed,\n\
1663 1658 \t\tso that its real execution time is reported (seconds, fractions allowed)\n\
1664 1659 ");
1665 1660 exit(2);
1666 1661 }
1667 1662
1668 1663 int
1669 1664 main(int argc, char **argv)
1670 1665 {
1671 1666 int c;
1672 1667 uid_t uid;
1673 1668
1674 1669 while ((c = getopt(argc, argv, "a:c:eE:fi:k:m:M:o:p:r:s:t:Tvw:x:")) >= 0)
1675 1670 switch (c)
1676 1671 {
1677 1672 case 'a':
1678 1673 file_access = atol(optarg);
1679 1674 break;
1680 1675 case 'c':
1681 1676 set_cwd = optarg;
1682 1677 break;
1683 1678 case 'e':
1684 1679 pass_environ = 1;
1685 1680 break;
1686 1681 case 'E':
1687 1682 if (!set_env_action(optarg))
1688 1683 usage();
1689 1684 break;
1690 1685 case 'f':
1691 1686 filter_syscalls++;
1692 1687 break;
1693 1688 case 'k':
1694 1689 stack_limit = atol(optarg);
1695 1690 break;
1696 1691 case 'i':
1697 1692 redir_stdin = optarg;
1698 1693 break;
1699 1694 case 'm':
1700 1695 memory_limit = atol(optarg);
1701 1696 break;
1702 1697 case 'M':
1703 1698 meta_open(optarg);
1704 1699 break;
1705 1700 case 'o':
1706 1701 redir_stdout = optarg;
1707 1702 break;
1708 1703 case 'p':
1709 1704 if (!set_path_action(optarg))
1710 1705 usage();
1711 1706 break;
1712 1707 case 'r':
1713 1708 redir_stderr = optarg;
1714 1709 break;
1715 1710 case 's':
1716 1711 if (!set_syscall_action(optarg))
1717 1712 usage();
1718 1713 break;
1719 1714 case 't':
1720 1715 timeout = 1000*atof(optarg);
1721 1716 break;
1722 1717 case 'T':
1723 1718 syscall_action[__NR_times] = A_YES;
1724 1719 break;
1725 1720 case 'v':
1726 1721 verbose++;
1727 1722 break;
1728 1723 case 'w':
1729 1724 wall_timeout = 1000*atof(optarg);
1730 1725 break;
1731 1726 case 'x':
1732 1727 extra_timeout = 1000*atof(optarg);
1733 1728 break;
1734 1729 default:
1735 1730 usage();
1736 1731 }
1737 1732 if (optind >= argc)
1738 1733 usage();
1739 1734
1740 1735 sanity_check();
1741 1736 uid = geteuid();
1742 1737 if (setreuid(uid, uid) < 0)
1743 1738 die("setreuid: %m");
1744 1739 box_pid = fork();
1745 1740 if (box_pid < 0)
1746 1741 die("fork: %m");
1747 1742 if (!box_pid)
1748 1743 box_inside(argc-optind, argv+optind);
1749 1744 else
1750 1745 boxkeeper();
1751 1746 die("Internal error: fell over edge of the world");
1752 1747 }
@@ -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(.*)m/.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}"
You need to be logged in to leave comments. Login now