S2OJ/uoj/1/app/libs/uoj-svn-lib.php
Masco Skray 2dc8e74fc1 feat(uoj/1/app): move data to the root when subfolder exists in the uploaded data zip
There is subfolder in the uploaded data archive file, we can now process it.
We will move files in the subfolder out to the data rootdir of current problem.
If you have some data archives with subfolder, don't need to repack them manually.
2019-04-16 22:48:13 +08:00

413 lines
16 KiB
PHP

<?php
// Actually, these things should be done by main_judger so that the code would be much simpler.
// However, this lib exists due to some history issues.
function svnNewProblem($id) {
exec("/var/svn/problem/new_problem.sh $id");
svnRefreshPasswordOfProblem($id);
exec("cd /var/uoj_data; rm $id.zip; zip $id.zip $id -r -q");
}
function svnRefreshPasswordOfProblem($id) {
$result = DB::query("select user_info.username, svn_password from problems_permissions, user_info where problem_id = $id and user_info.username = problems_permissions.username");
$content = "[users]\n";
$content .= UOJConfig::$data['svn']['our-root']['username']." = ".UOJConfig::$data['svn']['our-root']['password']."\n";
while ($row = DB::fetch($result, MYSQLI_NUM)) {
$content .= "${row[0]} = ${row[1]}\n";
}
file_put_contents("/var/svn/problem/$id/conf/passwd", $content);
}
class UOJProblemConfException extends Exception {
public function __construct($message) {
parent::__construct("<strong>problem.conf</strong> : $message");
}
}
class UOJFileNotFoundException extends Exception {
public function __construct($file_name) {
parent::__construct("file <strong>" . htmlspecialchars($file_name) . '</strong> not found');
}
}
function svnClearProblemData($problem) {
$id = $problem['id'];
if (!validateUInt($id)) {
error_log("svnClearProblemData: hacker detected");
return "invalid problem id";
}
exec("rm /var/svn/problem/$id -r");
exec("rm /var/uoj_data/$id -r");
svnNewProblem($id);
}
class SvnSyncProblemDataHandler {
private $problem, $user;
private $svn_data_dir, $data_dir, $prepare_dir;
private $requirement, $problem_extra_config;
private $problem_conf, $final_problem_conf;
private $allow_files;
public function __construct($problem, $user) {
$this->problem = $problem;
$this->user = $user;
}
private function check_conf_on($name) {
return isset($this->problem_conf[$name]) && $this->problem_conf[$name] == 'on';
}
private function copy_to_prepare($file_name) {
global $uojMainJudgerWorkPath;
if (!isset($this->allow_files[$file_name])) {
throw new UOJFileNotFoundException($file_name);
}
$src = escapeshellarg("{$this->svn_data_dir}/$file_name");
$dest = escapeshellarg("{$this->prepare_dir}/$file_name");
if (isset($this->problem_extra_config['dont_use_formatter']) || !is_file("{$this->svn_data_dir}/$file_name")) {
exec("cp $src $dest -r", $output, $ret);
} else {
exec("$uojMainJudgerWorkPath/run/formatter <$src >$dest", $output, $ret);
}
if ($ret) {
throw new UOJFileNotFoundException($file_name);
}
}
private function copy_file_to_prepare($file_name) {
global $uojMainJudgerWorkPath;
if (!isset($this->allow_files[$file_name]) || !is_file("{$this->svn_data_dir}/$file_name")) {
throw new UOJFileNotFoundException($file_name);
}
$this->copy_to_prepare($file_name);
}
private function compile_at_prepare($name, $config = array()) {
global $uojMainJudgerWorkPath;
$include_path = "$uojMainJudgerWorkPath/include";
if (!isset($config['src'])) {
$config['src'] = "$name.cpp";
}
if (isset($config['path'])) {
exec("mv {$this->prepare_dir}/$name.cpp {$this->prepare_dir}/{$config['path']}/$name.cpp");
$work_path = "{$this->prepare_dir}/{$config['path']}";
} else {
$work_path = $this->prepare_dir;
}
$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}";
if (isset($config['need_include_header']) && $config['need_include_header']) {
exec("$cmd_prefix --add-readable-raw=$include_path/ /usr/bin/g++ -o $name {$config['src']} -I$include_path -lm -O2 -DONLINE_JUDGE");
} else {
exec("$cmd_prefix /usr/bin/g++ -o $name {$config['src']} -lm -O2 -DONLINE_JUDGE");
}
$fp = fopen("{$this->prepare_dir}/run_compiler_result.txt", "r");
if (fscanf($fp, '%d %d %d %d', $rs, $used_time, $used_memory, $exit_code) != 4) {
$rs = 7;
}
fclose($fp);
unlink("{$this->prepare_dir}/run_compiler_result.txt");
if ($rs != 0 || $exit_code != 0) {
if ($rs == 0) {
throw new Exception("<strong>$name</strong> : compile error<pre>\n" . uojFilePreview("{$this->prepare_dir}/compiler_result.txt", 100) . "\n</pre>");
} elseif ($rs == 7) {
throw new Exception("<strong>$name</strong> : compile error. No comment");
} else {
throw new Exception("<strong>$name</strong> : compile error. Compiler " . judgerCodeStr($rs));
}
}
unlink("{$this->prepare_dir}/compiler_result.txt");
if (isset($config['path'])) {
exec("mv {$this->prepare_dir}/{$config['path']}/$name.cpp {$this->prepare_dir}/$name.cpp");
exec("mv {$this->prepare_dir}/{$config['path']}/$name {$this->prepare_dir}/$name");
}
}
private function makefile_at_prepare() {
global $uojMainJudgerWorkPath;
$include_path = "$uojMainJudgerWorkPath/include";
$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}";
exec("$cmd_prefix --add-readable-raw=$include_path/ /usr/bin/make INCLUDE_PATH=$include_path");
$fp = fopen("{$this->prepare_dir}/run_makefile_result.txt", "r");
if (fscanf($fp, '%d %d %d %d', $rs, $used_time, $used_memory, $exit_code) != 4) {
$rs = 7;
}
fclose($fp);
unlink("{$this->prepare_dir}/run_makefile_result.txt");
if ($rs != 0 || $exit_code != 0) {
if ($rs == 0) {
throw new Exception("<strong>Makefile</strong> : compile error<pre>\n" . uojFilePreview("{$this->prepare_dir}/makefile_result.txt", 100) . "\n</pre>");
} elseif ($rs == 7) {
throw new Exception("<strong>Makefile</strong> : compile error. No comment");
} else {
throw new Exception("<strong>Makefile</strong> : compile error. Compiler " . judgerCodeStr($rs));
}
}
unlink("{$this->prepare_dir}/makefile_result.txt");
}
public function handle() {
$id = $this->problem['id'];
if (!validateUInt($id)) {
error_log("svnSyncProblemData: hacker detected");
return "invalid problem id";
}
$this->svn_data_dir = "/var/svn/problem/$id/cur/$id/1";
$this->data_dir = "/var/uoj_data/$id";
$this->prepare_dir = "/var/uoj_data/prepare_$id";
if (file_exists($this->prepare_dir)) {
return "please wait until the last sync finish";
}
try {
$this->requirement = array();
$this->problem_extra_config = json_decode($this->problem['extra_config'], true);
mkdir($this->prepare_dir, 0755);
if (!is_file("{$this->svn_data_dir}/problem.conf")) {
throw new UOJFileNotFoundException("problem.conf");
}
$this->problem_conf = getUOJConf("{$this->svn_data_dir}/problem.conf");
$this->final_problem_conf = $this->problem_conf;
if ($this->problem_conf === -1) {
throw new UOJFileNotFoundException("problem.conf");
} elseif ($this->problem_conf === -2) {
throw new UOJProblemConfException("syntax error");
}
$this->allow_files = array_flip(array_filter(scandir($this->svn_data_dir), function($x){return $x !== '.' && $x !== '..';}));
$zip_file = new ZipArchive();
if ($zip_file->open("{$this->prepare_dir}/download.zip", ZipArchive::CREATE) !== true) {
throw new Exception("<strong>download.zip</strong> : failed to create the zip file");
}
if (isset($this->allow_files['require']) && is_dir("{$this->svn_data_dir}/require")) {
$this->copy_to_prepare('require');
}
if ($this->check_conf_on('use_builtin_judger')) {
$n_tests = getUOJConfVal($this->problem_conf, 'n_tests', 10);
if (!validateUInt($n_tests) || $n_tests <= 0) {
throw new UOJProblemConfException("n_tests must be a positive integer");
}
for ($num = 1; $num <= $n_tests; $num++) {
$input_file_name = getUOJProblemInputFileName($this->problem_conf, $num);
$output_file_name = getUOJProblemOutputFileName($this->problem_conf, $num);
$this->copy_file_to_prepare($input_file_name);
$this->copy_file_to_prepare($output_file_name);
}
if (!$this->check_conf_on('interaction_mode')) {
if (isset($this->problem_conf['use_builtin_checker'])) {
if (!preg_match('/^[a-zA-Z0-9_]{1,20}$/', $this->problem_conf['use_builtin_checker'])) {
throw new Exception("<strong>" . htmlspecialchars($this->problem_conf['use_builtin_checker']) . "</strong> is not a valid checker");
}
} else {
$this->copy_file_to_prepare('chk.cpp');
$this->compile_at_prepare('chk', array('need_include_header' => true));
}
}
if ($this->check_conf_on('submit_answer')) {
if ($this->problem['hackable']) {
throw new UOJProblemConfException("the problem can't be hackable if submit_answer is on");
}
for ($num = 1; $num <= $n_tests; $num++) {
$input_file_name = getUOJProblemInputFileName($this->problem_conf, $num);
$output_file_name = getUOJProblemOutputFileName($this->problem_conf, $num);
if (!isset($this->problem_extra_config['dont_download_input'])) {
$zip_file->addFile("{$this->prepare_dir}/$input_file_name", "$input_file_name");
}
$this->requirement[] = array('name' => "output$num", 'type' => 'text', 'file_name' => $output_file_name);
}
} else {
$n_ex_tests = getUOJConfVal($this->problem_conf, 'n_ex_tests', 0);
if (!validateUInt($n_ex_tests) || $n_ex_tests < 0) {
throw new UOJProblemConfException("n_ex_tests must be a non-nagative integer");
}
for ($num = 1; $num <= $n_ex_tests; $num++) {
$input_file_name = getUOJProblemExtraInputFileName($this->problem_conf, $num);
$output_file_name = getUOJProblemExtraOutputFileName($this->problem_conf, $num);
$this->copy_file_to_prepare($input_file_name);
$this->copy_file_to_prepare($output_file_name);
}
if ($this->problem['hackable']) {
$this->copy_file_to_prepare('std.cpp');
if (isset($this->problem_conf['with_implementer']) && $this->problem_conf['with_implementer'] == 'on') {
$this->compile_at_prepare('std',
array(
'src' => 'implementer.cpp std.cpp',
'path' => 'require'
)
);
} else {
$this->compile_at_prepare('std');
}
$this->copy_file_to_prepare('val.cpp');
$this->compile_at_prepare('val', array('need_include_header' => true));
}
if ($this->check_conf_on('interaction_mode')) {
$this->copy_file_to_prepare('interactor.cpp');
$this->compile_at_prepare('interactor', array('need_include_header' => true));
}
$n_sample_tests = getUOJConfVal($this->problem_conf, 'n_sample_tests', $n_tests);
if (!validateUInt($n_sample_tests) || $n_sample_tests < 0) {
throw new UOJProblemConfException("n_sample_tests must be a non-nagative integer");
}
if ($n_sample_tests > $n_ex_tests) {
throw new UOJProblemConfException("n_sample_tests can't be greater than n_ex_tests");
}
if (!isset($this->problem_extra_config['dont_download_sample'])) {
for ($num = 1; $num <= $n_sample_tests; $num++) {
$input_file_name = getUOJProblemExtraInputFileName($this->problem_conf, $num);
$output_file_name = getUOJProblemExtraOutputFileName($this->problem_conf, $num);
$zip_file->addFile("{$this->prepare_dir}/{$input_file_name}", "$input_file_name");
if (!isset($this->problem_extra_config['dont_download_sample_output'])) {
$zip_file->addFile("{$this->prepare_dir}/{$output_file_name}", "$output_file_name");
}
}
}
$this->requirement[] = array('name' => 'answer', 'type' => 'source code', 'file_name' => 'answer.code');
}
} else {
if (!isSuperUser($this->user)) {
throw new UOJProblemConfException("use_builtin_judger must be on.");
} else {
foreach ($this->allow_files as $file_name => $file_num) {
$this->copy_to_prepare($file_name);
}
$this->makefile_at_prepare();
$this->requirement[] = array('name' => 'answer', 'type' => 'source code', 'file_name' => 'answer.code');
}
}
putUOJConf("{$this->prepare_dir}/problem.conf", $this->final_problem_conf);
if (isset($this->allow_files['download']) && is_dir("{$this->svn_data_dir}/download")) {
foreach (scandir("{$this->svn_data_dir}/download") as $file_name) {
if (is_file("{$this->svn_data_dir}/download/{$file_name}")) {
$zip_file->addFile("{$this->svn_data_dir}/download/{$file_name}", $file_name);
}
}
}
$zip_file->close();
$orig_requirement = json_decode($this->problem['submission_requirement'], true);
if (!$orig_requirement) {
$esc_requirement = DB::escape(json_encode($this->requirement));
DB::update("update problems set submission_requirement = '$esc_requirement' where id = $id");
}
} catch (Exception $e) {
exec("rm {$this->prepare_dir} -r");
return $e->getMessage();
}
exec("rm {$this->data_dir} -r");
rename($this->prepare_dir, $this->data_dir);
exec("cd /var/uoj_data; rm $id.zip; zip $id.zip $id -r -q");
return '';
}
}
function svnSyncProblemData($problem, $user = null) {
return (new SvnSyncProblemDataHandler($problem, $user))->handle();
}
function svnAddExtraTest($problem, $input_file_name, $output_file_name) {
$id = $problem['id'];
$svnusr = UOJConfig::$data['svn']['our-root']['username'];
$svnpwd = UOJConfig::$data['svn']['our-root']['password'];
$cur_dir = "/var/svn/problem/$id/cur/$id";
$problem_conf = getUOJConf("{$cur_dir}/1/problem.conf");
if ($problem_conf == -1 || $problem_conf == -2) {
return $problem_conf;
}
$problem_conf['n_ex_tests'] = getUOJConfVal($problem_conf, 'n_ex_tests', 0) + 1;
$new_input_name = getUOJProblemExtraInputFileName($problem_conf, $problem_conf['n_ex_tests']);
$new_output_name = getUOJProblemExtraOutputFileName($problem_conf, $problem_conf['n_ex_tests']);
putUOJConf("$cur_dir/1/problem.conf", $problem_conf);
move_uploaded_file($input_file_name, "$cur_dir/1/$new_input_name");
move_uploaded_file($output_file_name, "$cur_dir/1/$new_output_name");
exec(
<<<EOD
cd $cur_dir
svn add 1/$new_input_name --username $svnusr --password $svnpwd
svn add 1/$new_output_name --username $svnusr --password $svnpwd
svn commit -m "add new extra test." --username $svnusr --password $svnpwd
EOD
);
if (svnSyncProblemData($problem) === '') {
rejudgeProblemAC($problem);
} else {
error_log('hack successfully but sync failed.');
}
}
function svnCommitZipData($problem, $type) {
$id = $problem['id'];
$cur_dir = "/var/svn/problem/$id/cur/$id";
$svnusr = UOJConfig::$data['svn']['our-root']['username'];
$svnpwd = UOJConfig::$data['svn']['our-root']['password'];
if($type=='conf'){
exec(
<<<EOD
cd $cur_dir
svn add 1/problem.conf --username $svnusr --password $svnpwd
svn commit -m "update problem.conf by online conf editor." --username $svnusr --password $svnpwd
EOD
);
}
else if($type=='data'){
exec(
<<<EOD
cd $cur_dir/1
for sub_dir in `find -maxdepth 1 -type d ! -name .`; do
mv -f \$sub_dir/* . && rm -rf \$sub_dir
done
cd $cur_dir
svn add * --username $svnusr --password $svnpwd
svn commit -m "add testdata from zip file online." --username $svnusr --password $svnpwd
EOD
);
}
else{
error_log("svnCommitZipData: invalid argument");
return "invalid argument";
}
}
?>