uoj-data-lib.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  1. <?php
  2. // Actually, these things should be done by main_judger so that the code would be much simpler.
  3. // However, this lib exists due to some history issues.
  4. function dataNewProblem($id) {
  5. mkdir("/var/uoj_data/upload/$id");
  6. mkdir("/var/uoj_data/$id");
  7. exec("cd /var/uoj_data; rm $id.zip; zip $id.zip $id -r -q");
  8. }
  9. class UOJProblemConfException extends Exception {
  10. public function __construct($message) {
  11. parent::__construct("<strong>problem.conf</strong> : $message");
  12. }
  13. }
  14. class UOJFileNotFoundException extends Exception {
  15. public function __construct($file_name) {
  16. parent::__construct("file <strong>" . htmlspecialchars($file_name) . '</strong> not found');
  17. }
  18. }
  19. function dataCopyProblemData($src, $dst) {
  20. exec("rm -r /var/uoj_data/upload/$dst");
  21. exec("cp -r /var/uoj_data/$src /var/uoj_data/$dst");
  22. exec("cd /var/uoj_data; zip $dst.zip $dst -r -q");
  23. }
  24. function dataClearProblemData($problem) {
  25. $id = $problem['id'];
  26. if (!validateUInt($id)) {
  27. error_log("dataClearProblemData: hacker detected");
  28. return "invalid problem id";
  29. }
  30. exec("rm /var/uoj_data/upload/$id -r");
  31. exec("rm /var/uoj_data/$id -r");
  32. dataNewProblem($id);
  33. }
  34. class SyncProblemDataHandler {
  35. private $problem, $user;
  36. private $upload_dir, $data_dir, $prepare_dir;
  37. private $requirement, $problem_extra_config;
  38. private $problem_conf, $final_problem_conf;
  39. private $allow_files;
  40. public function __construct($problem, $user) {
  41. $this->problem = $problem;
  42. $this->user = $user;
  43. }
  44. private function check_conf_on($name) {
  45. return isset($this->problem_conf[$name]) && $this->problem_conf[$name] == 'on';
  46. }
  47. private function copy_to_prepare($file_name) {
  48. global $uojMainJudgerWorkPath;
  49. if (!isset($this->allow_files[$file_name])) {
  50. throw new UOJFileNotFoundException($file_name);
  51. }
  52. $src = escapeshellarg("{$this->upload_dir}/$file_name");
  53. $dest = escapeshellarg("{$this->prepare_dir}/$file_name");
  54. if (isset($this->problem_extra_config['dont_use_formatter']) || !is_file("{$this->upload_dir}/$file_name")) {
  55. exec("cp $src $dest -r", $output, $ret);
  56. } else {
  57. exec("$uojMainJudgerWorkPath/run/formatter <$src >$dest", $output, $ret);
  58. }
  59. if ($ret) {
  60. throw new UOJFileNotFoundException($file_name);
  61. }
  62. }
  63. private function copy_file_to_prepare($file_name) {
  64. global $uojMainJudgerWorkPath;
  65. if (!isset($this->allow_files[$file_name]) || !is_file("{$this->upload_dir}/$file_name")) {
  66. throw new UOJFileNotFoundException($file_name);
  67. }
  68. $this->copy_to_prepare($file_name);
  69. }
  70. private function compile_at_prepare($name, $config = array()) {
  71. global $uojMainJudgerWorkPath;
  72. $include_path = "$uojMainJudgerWorkPath/include";
  73. if (!isset($config['src'])) {
  74. $config['src'] = "$name.cpp";
  75. }
  76. if (isset($config['path'])) {
  77. exec("mv {$this->prepare_dir}/$name.cpp {$this->prepare_dir}/{$config['path']}/$name.cpp");
  78. $work_path = "{$this->prepare_dir}/{$config['path']}";
  79. } else {
  80. $work_path = $this->prepare_dir;
  81. }
  82. $cmd_prefix = "$uojMainJudgerWorkPath/run/run_program >{$this->prepare_dir}/run_compiler_result.txt --in=/dev/null --out=stderr --err={$this->prepare_dir}/compiler_result.txt --tl=10 --ml=512 --ol=64 --type=compiler --work-path={$work_path}";
  83. if (isset($config['need_include_header']) && $config['need_include_header']) {
  84. exec("$cmd_prefix --add-readable-raw=$include_path/ /usr/bin/g++ -o $name {$config['src']} -I$include_path -lm -O2 -DONLINE_JUDGE");
  85. } else {
  86. exec("$cmd_prefix /usr/bin/g++ -o $name {$config['src']} -lm -O2 -DONLINE_JUDGE");
  87. }
  88. $fp = fopen("{$this->prepare_dir}/run_compiler_result.txt", "r");
  89. if (fscanf($fp, '%d %d %d %d', $rs, $used_time, $used_memory, $exit_code) != 4) {
  90. $rs = 7;
  91. }
  92. fclose($fp);
  93. unlink("{$this->prepare_dir}/run_compiler_result.txt");
  94. if ($rs != 0 || $exit_code != 0) {
  95. if ($rs == 0) {
  96. throw new Exception("<strong>$name</strong> : compile error<pre>\n" . uojFilePreview("{$this->prepare_dir}/compiler_result.txt", 100) . "\n</pre>");
  97. } elseif ($rs == 7) {
  98. throw new Exception("<strong>$name</strong> : compile error. No comment");
  99. } else {
  100. throw new Exception("<strong>$name</strong> : compile error. Compiler " . judgerCodeStr($rs));
  101. }
  102. }
  103. unlink("{$this->prepare_dir}/compiler_result.txt");
  104. if (isset($config['path'])) {
  105. exec("mv {$this->prepare_dir}/{$config['path']}/$name.cpp {$this->prepare_dir}/$name.cpp");
  106. exec("mv {$this->prepare_dir}/{$config['path']}/$name {$this->prepare_dir}/$name");
  107. }
  108. }
  109. private function makefile_at_prepare() {
  110. global $uojMainJudgerWorkPath;
  111. $include_path = "$uojMainJudgerWorkPath/include";
  112. $cmd_prefix = "$uojMainJudgerWorkPath/run/run_program >{$this->prepare_dir}/run_makefile_result.txt --in=/dev/null --out=stderr --err={$this->prepare_dir}/makefile_result.txt --tl=10 --ml=512 --ol=64 --type=compiler --work-path={$this->prepare_dir}";
  113. exec("$cmd_prefix --add-readable-raw=$include_path/ /usr/bin/make INCLUDE_PATH=$include_path");
  114. $fp = fopen("{$this->prepare_dir}/run_makefile_result.txt", "r");
  115. if (fscanf($fp, '%d %d %d %d', $rs, $used_time, $used_memory, $exit_code) != 4) {
  116. $rs = 7;
  117. }
  118. fclose($fp);
  119. unlink("{$this->prepare_dir}/run_makefile_result.txt");
  120. if ($rs != 0 || $exit_code != 0) {
  121. if ($rs == 0) {
  122. throw new Exception("<strong>Makefile</strong> : compile error<pre>\n" . uojFilePreview("{$this->prepare_dir}/makefile_result.txt", 100) . "\n</pre>");
  123. } elseif ($rs == 7) {
  124. throw new Exception("<strong>Makefile</strong> : compile error. No comment");
  125. } else {
  126. throw new Exception("<strong>Makefile</strong> : compile error. Compiler " . judgerCodeStr($rs));
  127. }
  128. }
  129. unlink("{$this->prepare_dir}/makefile_result.txt");
  130. }
  131. public function handle() {
  132. $id = $this->problem['id'];
  133. if (!validateUInt($id)) {
  134. error_log("dataSyncProblemData: hacker detected");
  135. return "invalid problem id";
  136. }
  137. $this->upload_dir = "/var/uoj_data/upload/$id";
  138. $this->data_dir = "/var/uoj_data/$id";
  139. $this->prepare_dir = "/var/uoj_data/prepare_$id";
  140. if (file_exists($this->prepare_dir)) {
  141. return "please wait until the last sync finish";
  142. }
  143. try {
  144. $this->requirement = array();
  145. $this->problem_extra_config = json_decode($this->problem['extra_config'], true);
  146. mkdir($this->prepare_dir, 0755);
  147. if (!is_file("{$this->upload_dir}/problem.conf")) {
  148. throw new UOJFileNotFoundException("problem.conf");
  149. }
  150. $this->problem_conf = getUOJConf("{$this->upload_dir}/problem.conf");
  151. $this->final_problem_conf = $this->problem_conf;
  152. if ($this->problem_conf === -1) {
  153. throw new UOJFileNotFoundException("problem.conf");
  154. } elseif ($this->problem_conf === -2) {
  155. throw new UOJProblemConfException("syntax error");
  156. }
  157. $this->allow_files = array_flip(array_filter(scandir($this->upload_dir), function($x) {
  158. return $x !== '.' && $x !== '..';
  159. }));
  160. $zip_file = new ZipArchive();
  161. if ($zip_file->open("{$this->prepare_dir}/download.zip", ZipArchive::CREATE) !== true) {
  162. throw new Exception("<strong>download.zip</strong> : failed to create the zip file");
  163. }
  164. if (isset($this->allow_files['require']) && is_dir("{$this->upload_dir}/require")) {
  165. $this->copy_to_prepare('require');
  166. }
  167. if ($this->check_conf_on('use_builtin_judger')) {
  168. $n_tests = getUOJConfVal($this->problem_conf, 'n_tests', 10);
  169. if (!validateUInt($n_tests) || $n_tests <= 0) {
  170. throw new UOJProblemConfException("n_tests must be a positive integer");
  171. }
  172. for ($num = 1; $num <= $n_tests; $num++) {
  173. $input_file_name = getUOJProblemInputFileName($this->problem_conf, $num);
  174. $output_file_name = getUOJProblemOutputFileName($this->problem_conf, $num);
  175. $this->copy_file_to_prepare($input_file_name);
  176. $this->copy_file_to_prepare($output_file_name);
  177. }
  178. if (!$this->check_conf_on('interaction_mode')) {
  179. if (isset($this->problem_conf['use_builtin_checker'])) {
  180. if (!preg_match('/^[a-zA-Z0-9_]{1,20}$/', $this->problem_conf['use_builtin_checker'])) {
  181. throw new Exception("<strong>" . htmlspecialchars($this->problem_conf['use_builtin_checker']) . "</strong> is not a valid checker");
  182. }
  183. } else {
  184. $this->copy_file_to_prepare('chk.cpp');
  185. $this->compile_at_prepare('chk', array('need_include_header' => true));
  186. }
  187. }
  188. if ($this->check_conf_on('submit_answer')) {
  189. if ($this->problem['hackable']) {
  190. throw new UOJProblemConfException("the problem can't be hackable if submit_answer is on");
  191. }
  192. for ($num = 1; $num <= $n_tests; $num++) {
  193. $input_file_name = getUOJProblemInputFileName($this->problem_conf, $num);
  194. $output_file_name = getUOJProblemOutputFileName($this->problem_conf, $num);
  195. if (!isset($this->problem_extra_config['dont_download_input'])) {
  196. $zip_file->addFile("{$this->prepare_dir}/$input_file_name", "$input_file_name");
  197. }
  198. $this->requirement[] = array('name' => "output$num", 'type' => 'text', 'file_name' => $output_file_name);
  199. }
  200. } else {
  201. $n_ex_tests = getUOJConfVal($this->problem_conf, 'n_ex_tests', 0);
  202. if (!validateUInt($n_ex_tests) || $n_ex_tests < 0) {
  203. throw new UOJProblemConfException("n_ex_tests must be a non-negative integer");
  204. }
  205. for ($num = 1; $num <= $n_ex_tests; $num++) {
  206. $input_file_name = getUOJProblemExtraInputFileName($this->problem_conf, $num);
  207. $output_file_name = getUOJProblemExtraOutputFileName($this->problem_conf, $num);
  208. $this->copy_file_to_prepare($input_file_name);
  209. $this->copy_file_to_prepare($output_file_name);
  210. }
  211. if ($this->problem['hackable']) {
  212. $this->copy_file_to_prepare('std.cpp');
  213. if (isset($this->problem_conf['with_implementer']) && $this->problem_conf['with_implementer'] == 'on') {
  214. $this->compile_at_prepare('std',
  215. array(
  216. 'src' => 'implementer.cpp std.cpp',
  217. 'path' => 'require'
  218. )
  219. );
  220. } else {
  221. $this->compile_at_prepare('std');
  222. }
  223. $this->copy_file_to_prepare('val.cpp');
  224. $this->compile_at_prepare('val', array('need_include_header' => true));
  225. }
  226. if ($this->check_conf_on('interaction_mode')) {
  227. $this->copy_file_to_prepare('interactor.cpp');
  228. $this->compile_at_prepare('interactor', array('need_include_header' => true));
  229. }
  230. $n_sample_tests = getUOJConfVal($this->problem_conf, 'n_sample_tests', $n_tests);
  231. if (!validateUInt($n_sample_tests) || $n_sample_tests < 0) {
  232. throw new UOJProblemConfException("n_sample_tests must be a non-negative integer");
  233. }
  234. if ($n_sample_tests > $n_ex_tests) {
  235. throw new UOJProblemConfException("n_sample_tests can't be greater than n_ex_tests");
  236. }
  237. if (!isset($this->problem_extra_config['dont_download_sample'])) {
  238. for ($num = 1; $num <= $n_sample_tests; $num++) {
  239. $input_file_name = getUOJProblemExtraInputFileName($this->problem_conf, $num);
  240. $output_file_name = getUOJProblemExtraOutputFileName($this->problem_conf, $num);
  241. $zip_file->addFile("{$this->prepare_dir}/{$input_file_name}", "$input_file_name");
  242. if (!isset($this->problem_extra_config['dont_download_sample_output'])) {
  243. $zip_file->addFile("{$this->prepare_dir}/{$output_file_name}", "$output_file_name");
  244. }
  245. }
  246. }
  247. $this->requirement[] = array('name' => 'answer', 'type' => 'source code', 'file_name' => 'answer.code');
  248. }
  249. } else {
  250. if (!isSuperUser($this->user)) {
  251. throw new UOJProblemConfException("use_builtin_judger must be on.");
  252. } else {
  253. foreach ($this->allow_files as $file_name => $file_num) {
  254. $this->copy_to_prepare($file_name);
  255. }
  256. $this->makefile_at_prepare();
  257. $this->requirement[] = array('name' => 'answer', 'type' => 'source code', 'file_name' => 'answer.code');
  258. }
  259. }
  260. putUOJConf("{$this->prepare_dir}/problem.conf", $this->final_problem_conf);
  261. if (isset($this->allow_files['download']) && is_dir("{$this->upload_dir}/download")) {
  262. foreach (scandir("{$this->upload_dir}/download") as $file_name) {
  263. if (is_file("{$this->upload_dir}/download/{$file_name}")) {
  264. $zip_file->addFile("{$this->upload_dir}/download/{$file_name}", $file_name);
  265. }
  266. }
  267. }
  268. $zip_file->close();
  269. $orig_requirement = json_decode($this->problem['submission_requirement'], true);
  270. if (!$orig_requirement) {
  271. $esc_requirement = DB::escape(json_encode($this->requirement));
  272. DB::update("update problems set submission_requirement = '$esc_requirement' where id = $id");
  273. }
  274. } catch (Exception $e) {
  275. exec("rm {$this->prepare_dir} -r");
  276. return $e->getMessage();
  277. }
  278. exec("rm {$this->data_dir} -r");
  279. rename($this->prepare_dir, $this->data_dir);
  280. exec("cd /var/uoj_data; rm $id.zip; zip $id.zip $id -r -q");
  281. return '';
  282. }
  283. }
  284. function dataSyncProblemData($problem, $user = null) {
  285. return (new SyncProblemDataHandler($problem, $user))->handle();
  286. }
  287. function dataAddExtraTest($problem, $input_file_name, $output_file_name) {
  288. $id = $problem['id'];
  289. $cur_dir = "/var/uoj_data/upload/$id";
  290. # BY ryp!!!!, 20250827
  291. exec ("ln -sf /var/uoj_data/$id /var/uoj_data/upload/$id");
  292. $problem_conf = getUOJConf("{$cur_dir}/problem.conf");
  293. if ($problem_conf == -1 || $problem_conf == -2) {
  294. return $problem_conf;
  295. }
  296. $problem_conf['n_ex_tests'] = getUOJConfVal($problem_conf, 'n_ex_tests', 0) + 1;
  297. $new_input_name = getUOJProblemExtraInputFileName($problem_conf, $problem_conf['n_ex_tests']);
  298. $new_output_name = getUOJProblemExtraOutputFileName($problem_conf, $problem_conf['n_ex_tests']);
  299. putUOJConf("$cur_dir/problem.conf", $problem_conf);
  300. move_uploaded_file($input_file_name, "$cur_dir/$new_input_name");
  301. move_uploaded_file($output_file_name, "$cur_dir/$new_output_name");
  302. if (dataSyncProblemData($problem) === '') {
  303. rejudgeProblemAC($problem);
  304. } else {
  305. error_log('hack successfully but sync failed.');
  306. }
  307. }
  308. ?>