@@ -290,6 +290,39 @@ function read_judgehostlog(int $numLines = 20) : string
290290 return trim (ob_get_clean ());
291291}
292292
293+ /**
294+ * Execute a shell command given an array of strings forming the command.
295+ * The command and all its arguments are automatically escaped.
296+ *
297+ * @param array $command_parts The command and its arguments (e.g., ['ls', '-l', '/tmp/']).
298+ * @param int|null $retval The variable to store the command's exit code.
299+ * @param bool $log_nonzero_exitcode Whether non-zero exit codes should be logged.
300+ *
301+ * @return bool Returns true on success (exit code 0), false otherwise.
302+ */
303+ function run_command_safe (array $ command_parts , ?int &$ retval = null , $ log_nonzero_exitcode = true ): bool
304+ {
305+ if (empty ($ command_parts )) {
306+ logmsg (LOG_WARNING , "Need at least the command that should be called. " );
307+ $ retval = -1 ;
308+ return false ;
309+ }
310+
311+ $ command = implode (' ' , array_map ('dj_escapeshellarg ' , $ command_parts ));
312+
313+ logmsg (LOG_DEBUG , "Executing command: $ command " );
314+ system ($ command , $ retval );
315+
316+ if ($ retval !== 0 ) {
317+ if ($ log_nonzero_exitcode ) {
318+ logmsg (LOG_WARNING , "Command failed with exit code $ retval: $ command " );
319+ }
320+ return false ;
321+ }
322+
323+ return true ;
324+ }
325+
293326// Fetches a new executable from database if not cached already, and runs build script to compile executable.
294327// Returns an array with
295328// - absolute path to run script
@@ -351,12 +384,10 @@ function fetch_executable_internal(
351384 $ execrunjurypath = $ execbuilddir . '/runjury ' ;
352385 if (!is_dir ($ execdir ) || !file_exists ($ execdeploypath ) ||
353386 ($ combined_run_compare && file_get_contents (LIBJUDGEDIR . '/run-interactive.sh ' )!==file_get_contents ($ execrunpath ))) {
354- system ('rm -rf ' . dj_escapeshellarg ($ execdir ) . ' ' . dj_escapeshellarg ($ execbuilddir ), $ retval );
355- if ($ retval !== 0 ) {
387+ if (!run_command_safe (['rm ' , '-rf ' , $ execdir , $ execbuilddir ])) {
356388 disable ('judgehost ' , 'hostname ' , $ myhost , "Deleting ' $ execdir' or ' $ execbuilddir' was unsuccessful. " );
357389 }
358- system ('mkdir -p ' . dj_escapeshellarg ($ execbuilddir ), $ retval );
359- if ($ retval !== 0 ) {
390+ if (!run_command_safe (['mkdir ' , '-p ' , $ execbuilddir ])) {
360391 disable ('judgehost ' , 'hostname ' , $ myhost , "Could not create directory ' $ execbuilddir' " );
361392 }
362393
@@ -466,8 +497,7 @@ function fetch_executable_internal(
466497 putenv ('SCRIPTMEMLIMIT= ' . djconfig_get_value ('script_memory_limit ' ));
467498 putenv ('SCRIPTFILELIMIT= ' . djconfig_get_value ('script_filesize_limit ' ));
468499
469- system (LIBJUDGEDIR . '/build_executable.sh ' . dj_escapeshellarg ($ execdir ), $ retval );
470- if ($ retval !== 0 ) {
500+ if (!run_command_safe ([LIBJUDGEDIR . '/build_executable.sh ' , $ execdir ])) {
471501 return [null , "Failed to build executable in $ execdir. " , "$ execdir/build.log " ];
472502 }
473503 chmod ($ execrunpath , 0755 );
@@ -528,7 +558,11 @@ function fetch_executable_internal(
528558 exit (1 );
529559}
530560
531- $ myhost = trim (`hostname | cut -d . -f 1 `);
561+ $ hostname = gethostname ();
562+ if ($ hostname === false ) {
563+ error ("Could not determine hostname. " );
564+ }
565+ $ myhost = explode ('. ' , $ hostname )[0 ];
532566if (isset ($ options ['daemonid ' ])) {
533567 if (preg_match ('/^\d+$/ ' , $ options ['daemonid ' ])) {
534568 $ myhost = $ myhost . "- " . $ options ['daemonid ' ];
@@ -659,9 +693,8 @@ function fetch_executable_internal(
659693
660694// Check basic prerequisites for chroot at judgehost startup
661695logmsg (LOG_INFO , "🔏 Executing chroot script: ' " .CHROOT_SCRIPT ." check' " );
662- system (LIBJUDGEDIR .'/ ' .CHROOT_SCRIPT .' check ' , $ retval );
663- if ($ retval !==0 ) {
664- error ("chroot validation check exited with exitcode $ retval " );
696+ if (!run_command_safe ([LIBJUDGEDIR .'/ ' .CHROOT_SCRIPT , 'check ' ])) {
697+ error ("chroot validation check failed " );
665698}
666699
667700foreach ($ endpoints as $ id => $ endpoint ) {
@@ -753,8 +786,7 @@ function fetch_executable_internal(
753786 foreach ($ candidateDirs as $ d ) {
754787 $ cnt ++;
755788 logmsg (LOG_INFO , " - deleting $ d " );
756- system ('rm -rf ' . dj_escapeshellarg ($ d ), $ retval );
757- if ($ retval !== 0 ) {
789+ if (!run_command_safe (['rm ' , '-rf ' , $ d ])) {
758790 logmsg (LOG_WARNING , "Deleting ' $ d' was unsuccessful. " );
759791 }
760792 $ after = disk_free_space (JUDGEDIR );
@@ -878,10 +910,7 @@ function fetch_executable_internal(
878910 $ judgeTask ['judgetaskid ' ]
879911 );
880912
881- $ debug_cmd = implode (' ' , array_map ('dj_escapeshellarg ' ,
882- [$ runpath , $ workdir , $ tmpfile ]));
883- system ($ debug_cmd , $ retval );
884- if ($ retval !== 0 ) {
913+ if (!run_command_safe ([$ runpath , $ workdir , $ tmpfile ])) {
885914 disable ('run_script ' , 'run_script_id ' , $ judgeTask ['run_script_id ' ], "Running ' $ runpath' failed. " );
886915 }
887916
@@ -950,8 +979,7 @@ function fetch_executable_internal(
950979 }
951980
952981
953- system ('mkdir -p ' . dj_escapeshellarg ("$ workdir/compile " ), $ retval );
954- if ($ retval !== 0 ) {
982+ if (!run_command_safe (['mkdir ' , '-p ' , "$ workdir/compile " ])) {
955983 error ("Could not create ' $ workdir/compile' " );
956984 }
957985
@@ -964,8 +992,7 @@ function fetch_executable_internal(
964992 if ($ lastWorkdir !== $ workdir ) {
965993 // create chroot environment
966994 logmsg (LOG_INFO , " 🔒 Executing chroot script: ' " .CHROOT_SCRIPT ." start' " );
967- system (LIBJUDGEDIR .'/ ' .CHROOT_SCRIPT .' start ' , $ retval );
968- if ($ retval !==0 ) {
995+ if (!run_command_safe ([LIBJUDGEDIR .'/ ' .CHROOT_SCRIPT , 'start ' ], $ retval )) {
969996 logmsg (LOG_ERR , "chroot script exited with exitcode $ retval " );
970997 disable ('judgehost ' , 'hostname ' , $ myhost , "chroot script exited with exitcode $ retval on $ myhost " );
971998 continue ;
@@ -1027,8 +1054,7 @@ function registerJudgehost(string $myhost): void
10271054
10281055 // Create directory where to test submissions
10291056 $ workdirpath = JUDGEDIR . "/ $ myhost/endpoint- $ endpointID " ;
1030- system ('mkdir -p ' . dj_escapeshellarg ("$ workdirpath/testcase " ), $ retval );
1031- if ($ retval !== 0 ) {
1057+ if (!run_command_safe (['mkdir ' , '-p ' , "$ workdirpath/testcase " ])) {
10321058 error ("Could not create $ workdirpath " );
10331059 }
10341060 chmod ("$ workdirpath/testcase " , 0700 );
@@ -1106,8 +1132,7 @@ function cleanup_judging(string $workdir) : void
11061132
11071133 // destroy chroot environment
11081134 logmsg (LOG_INFO , " 🔓 Executing chroot script: ' " .CHROOT_SCRIPT ." stop' " );
1109- system (LIBJUDGEDIR .'/ ' .CHROOT_SCRIPT .' stop ' , $ retval );
1110- if ($ retval !==0 ) {
1135+ if (!run_command_safe ([LIBJUDGEDIR .'/ ' .CHROOT_SCRIPT , 'stop ' ], $ retval )) {
11111136 logmsg (LOG_ERR , "chroot script exited with exitcode $ retval " );
11121137 disable ('judgehost ' , 'hostname ' , $ myhost , "chroot script exited with exitcode $ retval on $ myhost " );
11131138 // Just continue here: even though we might continue a current
@@ -1117,9 +1142,8 @@ function cleanup_judging(string $workdir) : void
11171142 }
11181143
11191144 // Evict all contents of the workdir from the kernel fs cache
1120- system (LIBJUDGEDIR . '/evict ' . dj_escapeshellarg ($ workdir ), $ retval );
1121- if ($ retval !==0 ) {
1122- warning ("evict script exited with exitcode $ retval " );
1145+ if (!run_command_safe ([LIBJUDGEDIR . '/evict ' , $ workdir ])) {
1146+ warning ("evict script failed, continuing gracefully " );
11231147 }
11241148}
11251149
@@ -1128,7 +1152,7 @@ function compile(
11281152 string $ workdir ,
11291153 string $ workdirpath ,
11301154 array $ compile_config ,
1131- string $ cpuset_opt ,
1155+ ? string $ daemonid ,
11321156 int $ output_storage_limit
11331157): bool {
11341158 global $ myhost , $ EXITCODES ;
@@ -1148,26 +1172,24 @@ function compile(
11481172 $ args = 'hostname= ' . urlencode ($ myhost );
11491173 foreach (['compiler ' , 'runner ' ] as $ type ) {
11501174 if (isset ($ version_verification [$ type . '_version_command ' ])) {
1175+ if (file_exists ($ version_output_file )) {
1176+ unlink ($ version_output_file );
1177+ }
1178+
11511179 $ vcscript_content = $ version_verification [$ type . '_version_command ' ];
11521180 $ vcscript = tempnam (TMPDIR , 'version_check- ' );
11531181 file_put_contents ($ vcscript , $ vcscript_content );
11541182 chmod ($ vcscript , 0755 );
1155- $ version_cmd = LIBJUDGEDIR . "/version_check.sh " .
1156- implode (' ' , array_map ('dj_escapeshellarg ' , [
1157- $ vcscript ,
1158- $ workdir ,
1159- ]));
11601183
1161- if (file_exists ($ version_output_file )) {
1162- unlink ($ version_output_file );
1163- }
1164- system ($ version_cmd , $ retval );
1184+ run_command_safe ([LIBJUDGEDIR . "/version_check.sh " , $ vcscript , $ workdir ], $ retval );
1185+
11651186 $ versions [$ type ] = trim (file_get_contents ($ version_output_file ));
11661187 if ($ retval !== 0 ) {
11671188 $ versions [$ type ] =
11681189 "Getting $ type version failed with exit code $ retval \n"
11691190 . $ versions [$ type ];
11701191 }
1192+
11711193 unlink ($ vcscript );
11721194 }
11731195 if (isset ($ versions [$ type ])) {
@@ -1245,16 +1267,14 @@ function compile(
12451267 }
12461268
12471269 // Compile the program.
1248- $ compile_cmd = LIBJUDGEDIR . "/compile.sh $ cpuset_opt " .
1249- implode (' ' , array_map ('dj_escapeshellarg ' , array_merge ([
1250- $ execrunpath ,
1251- $ workdir ,
1252- ], $ files )));
1253- logmsg (LOG_DEBUG , "Compile command: " .$ compile_cmd );
1254- system ($ compile_cmd , $ retval );
1255- if ($ retval !==0 ) {
1256- warning ("compile script exited with exitcode $ retval " );
1270+ $ compile_command_parts = [LIBJUDGEDIR . '/compile.sh ' ];
1271+ if (isset ($ daemonid )) {
1272+ $ compile_command_parts [] = '-n ' ;
1273+ $ compile_command_parts [] = $ daemonid ;
12571274 }
1275+ array_push ($ compile_command_parts , $ execrunpath , $ workdir , ...$ files );
1276+ // Note that the $retval is handled further down after reading/writing metadata.
1277+ run_command_safe ($ compile_command_parts , $ retval , log_nonzero_exitcode: false );
12581278
12591279 $ compile_output = '' ;
12601280 if (is_readable ($ workdir . '/compile.out ' )) {
@@ -1355,7 +1375,7 @@ function judge(array $judgeTask): bool
13551375 }
13561376
13571377 $ workdir = judging_directory ($ workdirpath , $ judgeTask );
1358- $ compile_success = compile ($ judgeTask , $ workdir , $ workdirpath , $ compile_config , $ cpuset_opt , $ output_storage_limit );
1378+ $ compile_success = compile ($ judgeTask , $ workdir , $ workdirpath , $ compile_config , $ options [ ' daemonid ' ] ?? null , $ output_storage_limit );
13591379 if (!$ compile_success ) {
13601380 return false ;
13611381 }
@@ -1457,37 +1477,39 @@ function judge(array $judgeTask): bool
14571477 // after the first (note that $passCnt starts at 1).
14581478 if ($ passCnt > 1 ) {
14591479 $ prevPassdir = $ testcasedir . '/ ' . ($ passCnt - 1 ) . '/feedback ' ;
1460- system ( 'cp -R ' . dj_escapeshellarg ( $ prevPassdir) . ' ' . dj_escapeshellarg ( $ passdir . '/ ' ) );
1461- system ( 'rm ' . dj_escapeshellarg ( $ passdir . '/feedback/nextpass.in ' ) );
1480+ run_command_safe ([ 'cp ' , ' -R ' , $ prevPassdir, $ passdir . '/ ' ] );
1481+ run_command_safe ([ 'rm ' , $ passdir . '/feedback/nextpass.in ' ] );
14621482 }
14631483
14641484 // Copy program with all possible additional files to testcase
14651485 // dir. Use hardlinks to preserve space with big executables.
14661486 $ programdir = $ passdir . '/execdir ' ;
1467- system ('mkdir -p ' . dj_escapeshellarg ($ programdir ), $ retval );
1468- if ($ retval !==0 ) {
1487+ if (!run_command_safe (['mkdir ' , '-p ' , $ programdir ])) {
14691488 error ("Could not create directory ' $ programdir' " );
14701489 }
14711490
14721491 foreach (glob ("$ workdir/compile/* " ) as $ compile_file ) {
1473- system ('cp -PRl ' . dj_escapeshellarg ($ compile_file ) . ' ' . dj_escapeshellarg ($ programdir ), $ retval );
1474- if ($ retval !==0 ) {
1492+ if (!run_command_safe (['cp ' , '-PRl ' , $ compile_file , $ programdir ])) {
14751493 error ("Could not copy program to ' $ programdir' " );
14761494 }
14771495 }
14781496
14791497 $ timelimit_str = implode (': ' , $ timelimit ['cpu ' ]) . ', ' . implode (': ' , $ timelimit ['wall ' ]);
1480- $ test_run_cmd = LIBJUDGEDIR . "/testcase_run.sh $ cpuset_opt " .
1481- implode (' ' , array_map ('dj_escapeshellarg ' , [
1482- $ input ,
1483- $ output ,
1484- $ timelimit_str ,
1485- $ passdir ,
1486- $ run_runpath ,
1487- $ compare_runpath ,
1488- $ compare_config ['compare_args ' ]
1489- ]));
1490- system ($ test_run_cmd , $ retval );
1498+ $ run_command_parts = [LIBJUDGEDIR . '/testcase_run.sh ' ];
1499+ if (isset ($ options ['daemonid ' ])) {
1500+ $ run_command_parts [] = '-n ' ;
1501+ $ run_command_parts [] = $ options ['daemonid ' ];
1502+ }
1503+ array_push ($ run_command_parts ,
1504+ $ input ,
1505+ $ output ,
1506+ $ timelimit_str ,
1507+ $ passdir ,
1508+ $ run_runpath ,
1509+ $ compare_runpath ,
1510+ $ compare_config ['compare_args ' ]
1511+ );
1512+ run_command_safe ($ run_command_parts , $ retval , log_nonzero_exitcode: false );
14911513
14921514 // What does the exitcode mean?
14931515 if (!isset ($ EXITCODES [$ retval ])) {
0 commit comments