uoj-data-lib.php 15 KB

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