Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 36 additions & 2 deletions judge/judgedaemon.main.php
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@

logmsg(LOG_DEBUG, "Executing command: $command");
system($command, $retval_local);
if ($retval !== DONT_CARE) $retval = $retval_local;

Check failure on line 319 in judge/judgedaemon.main.php

View workflow job for this annotation

GitHub Actions / phpcs

Inline control structures are not allowed

if ($retval_local !== 0) {
if ($log_nonzero_exitcode) {
Expand Down Expand Up @@ -463,10 +463,10 @@
}
switch ($execlang) {
case 'c':
$buildscript .= "gcc -Wall -O2 -std=gnu11 $source -o run -lm\n";
$buildscript .= "gcc -Wall -O2 -static -std=gnu11 $source -o run -lm\n";
break;
case 'cpp':
$buildscript .= "g++ -Wall -O2 -std=gnu++20 $source -o run\n";
$buildscript .= "g++ -Wall -O2 -static -std=gnu++20 $source -o run\n";
break;
case 'java':
$buildscript .= "javac -cp . -d . $source\n";
Expand Down Expand Up @@ -891,6 +891,40 @@
}
logmsg(LOG_INFO, " 🔥 Pre-heating judgehost completed.");
continue;
} elseif ($type == 'generic_task') {
foreach ($row as $judgeTask) {
if (!(isset($judgeTask['run_script_id']) && isset($judgeTask['run_config']))) {
// TODO: Should we actually exit here, we do for malformed above but the mistake is not on our side.
error("Received judgehost_check task without run_script_id/run_config.");
}

$run_config = dj_json_decode($judgeTask['run_config']);
$tmpfile = tempnam(TMPDIR, 'generic_task_');
[$runpath, $error] = fetch_executable(
$workdirpath,
'generic_task',
$judgeTask['run_script_id'],
$run_config['hash'],
$judgeTask['judgetaskid']
);

if (!run_command_safe([$runpath, $tmpfile])) {
disable('run_script', 'run_script_id', $judgeTask['run_script_id'], "Running '$runpath' failed.");
}

request(
sprintf('judgehosts/add-generic-task/%s/%s', urlencode($myhost), urlencode((string)$judgeTask['judgetaskid'])),
'POST',
['generic_task' => rest_encode_file($tmpfile, false)],
false
);

unlink($tmpfile);
logmsg(LOG_INFO, " ⇡ Uploading task output.");
}

logmsg(LOG_INFO, " 🔥 Running generic judgehost tasks completed.");
continue;
}

// Create workdir for judging.
Expand Down
2 changes: 2 additions & 0 deletions sql/files/defaultdata/chroot_upgrade/build
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#!/bin/sh
# nothing to compile
13 changes: 13 additions & 0 deletions sql/files/defaultdata/chroot_upgrade/run
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/bin/bash
# This is an example to upgrade the chroot packages
# The basic calling convention of these scripts is:
# ./script <working_dir> <output_file>

output_file="$1"

exec &>> "${output_file}"

bin/dj_run_chroot "apt-get update"
bin/dj_run_chroot "apt-get full-upgrade -y"

exit 0
2 changes: 1 addition & 1 deletion sql/files/defaultdata/compare/build
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
#!/bin/sh
g++ -std=c++11 -pedantic -g -O1 -Wall -fstack-protector -D_FORTIFY_SOURCE=2 -fPIE -Wformat -Wformat-security -fPIE -Wl,-z,relro -Wl,-z,now compare.cc -o run
g++ -std=c++11 -pedantic -g -O1 -static -Wall -fstack-protector -D_FORTIFY_SOURCE=2 -fPIE -Wformat -Wformat-security -fPIE -Wl,-z,relro -Wl,-z,now compare.cc -o run
2 changes: 2 additions & 0 deletions sql/files/defaultdata/judgehost_info/build
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#!/bin/sh
# nothing to compile
33 changes: 33 additions & 0 deletions sql/files/defaultdata/judgehost_info/run
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/bin/sh
# This is the default script to retrieve a the configuration of both
# software & hardware of the judgehost. The basic
# calling convention of these scripts is:
# ./script <working_dir> <output_file>

output_file="$1"

# Source - https://stackoverflow.com/a
# Posted by fjarlq, modified by community. See post 'Timeline' for change history
# Retrieved 2025-11-15, License - CC BY-SA 4.0

# We can't use &>> as it's not POSIX,
# this does introduce a racecondition
exec 1>> "${output_file}"
exec 2>> "${output_file}"

# Generic linux/distro information
uname -a
cat /etc/os-release

# Information about the php version
php -v

# Generic hardware information
lsusb
lspci
lscpu
cat /proc/cpuinfo
free -h
cat /proc/meminfo

exit 0
42 changes: 42 additions & 0 deletions webapp/migrations/Version20251117185929.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

declare(strict_types=1);

namespace DoctrineMigrations;

use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;

/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20251117185929 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}

public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE TABLE generic_task (taskid INT UNSIGNED AUTO_INCREMENT NOT NULL COMMENT \'Task ID\', judgetaskid INT UNSIGNED DEFAULT NULL COMMENT \'JudgeTask ID\', runtime DOUBLE PRECISION DEFAULT NULL COMMENT \'Running time for this task\', endtime NUMERIC(32, 9) UNSIGNED DEFAULT NULL COMMENT \'Time task ended\', start_time NUMERIC(32, 9) UNSIGNED DEFAULT NULL COMMENT \'Time task started\', INDEX IDX_680437B63CBA64F2 (judgetaskid), PRIMARY KEY(taskid)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB COMMENT = \'Result of a generic task\' ');
$this->addSql('CREATE TABLE generic_task_output (taskid INT UNSIGNED NOT NULL COMMENT \'Task ID\', output_task LONGBLOB DEFAULT NULL COMMENT \'Output of running the program(DC2Type:blobtext)\', output_error LONGBLOB DEFAULT NULL COMMENT \'Standard error output of the program(DC2Type:blobtext)\', INDEX taskid (taskid), PRIMARY KEY(taskid)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB COMMENT = \'Stores output of generic task\' ');
$this->addSql('ALTER TABLE generic_task ADD CONSTRAINT FK_680437B63CBA64F2 FOREIGN KEY (judgetaskid) REFERENCES judgetask (judgetaskid) ON DELETE CASCADE');
$this->addSql('ALTER TABLE generic_task_output ADD CONSTRAINT FK_6425C7BE46CBEE95 FOREIGN KEY (taskid) REFERENCES generic_task (taskid) ON DELETE CASCADE');
}

public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE generic_task DROP FOREIGN KEY FK_680437B63CBA64F2');
$this->addSql('ALTER TABLE generic_task_output DROP FOREIGN KEY FK_6425C7BE46CBEE95');
$this->addSql('DROP TABLE generic_task');
$this->addSql('DROP TABLE generic_task_output');
}

public function isTransactional(): bool
{
return false;
}
}
66 changes: 63 additions & 3 deletions webapp/src/Controller/API/JudgehostController.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
use App\Entity\DebugPackage;
use App\Entity\Executable;
use App\Entity\ExecutableFile;
use App\Entity\GenericTask;
use App\Entity\GenericTaskOutput;
use App\Entity\InternalError;
use App\Entity\Judgehost;
use App\Entity\JudgeTask;
Expand Down Expand Up @@ -553,6 +555,63 @@ public function addDebugInfo(
$this->em->flush();
}

/**
* Add generic task output.
*/
#[IsGranted('ROLE_JUDGEHOST')]
#[Rest\Post('/add-generic-task/{hostname}/{judgeTaskId<\d+>}')]
#[OA\Response(response: 200, description: 'When the task output has been added')]
public function addGenericTaskOutput(
Request $request,
#[OA\PathParameter(description: 'The hostname of the judgehost that wants to add the task output')]
string $hostname,
#[OA\PathParameter(description: 'The ID of the judgetask to add', schema: new OA\Schema(type: 'integer'))]
int $judgeTaskId
): void {
$judgeTask = $this->em->getRepository(JudgeTask::class)->find($judgeTaskId);
if ($judgeTask === null) {
throw new BadRequestHttpException(
'Inconsistent data, no judgetask known with judgetaskid = ' . $judgeTaskId . '.');
}

$required = ['generic_task'];
foreach ($required as $argument) {
if (!$request->request->has($argument)) {
throw new BadRequestHttpException(
sprintf("Argument '%s' is mandatory", $argument));
}
}

$judgehost = $this->em->getRepository(Judgehost::class)->findOneBy(['hostname' => $hostname]);
if (!$judgehost) {
throw new BadRequestHttpException("Who are you and why are you sending us any data?");
}

$genericTask = $judgeTask->getGenericTasks();
if (count($genericTask) === 0) {
$genericTask = new GenericTask();
$genericTask
->setJudgeTask($judgeTask)
->setStarttime($judgeTask->getStarttime())
->setEndtime(Utils::now());
$genericTaskOutput = new GenericTaskOutput();
$genericTaskOutput->setGenericTask($genericTask);
$genericTask->setOutput($genericTaskOutput);
$this->em->persist($genericTask);
$this->em->persist($genericTaskOutput);
} elseif (count($genericTask) !== 1) {
throw new BadRequestHttpException("There should be only one generic task for this judgetask.");
} else {
$genericTask = $genericTask->first();
}

$genericTask->setEndtime(Utils::now());
$outputTask = base64_decode($request->request->get('generic_task'));
$genericTaskOutput = $genericTask->getOutput();
$genericTaskOutput->setOutputTask($outputTask);
$this->em->flush();
}

/**
* Add one JudgingRun. When relevant, finalize the judging.
* @throws DBALException
Expand Down Expand Up @@ -1238,7 +1297,7 @@ public function getFilesAction(
return match ($type) {
'source' => $this->getSourceFiles($id),
'testcase' => $this->getTestcaseFiles($id),
'compare', 'compile', 'debug', 'run' => $this->getExecutableFiles($id),
'compare', 'compile', 'debug', 'run', 'generic_task' => $this->getExecutableFiles($id),
default => throw new BadRequestHttpException('Unknown type requested.'),
};
}
Expand Down Expand Up @@ -1581,9 +1640,10 @@ public function getJudgeTasksAction(Request $request): array
->andWhere('jt.judgehost = :judgehost')
->andWhere('jt.starttime IS NULL')
->andWhere('jt.valid = 1')
->andWhere('jt.type = :type')
->andWhere('jt.type in (:type)')
->setParameter('judgehost', $judgehost)
->setParameter('type', JudgeTaskType::DEBUG_INFO)
->setParameter('type', [JudgeTaskType::DEBUG_INFO, JudgeTaskType::GENERIC_TASK],
ArrayParameterType::STRING)
->addOrderBy('jt.priority')
->addOrderBy('jt.judgetaskid')
->setMaxResults(1)
Expand Down
6 changes: 6 additions & 0 deletions webapp/src/Controller/Jury/ExecutableController.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ public function indexAction(Request $request): Response
->addOrderBy('e.type', 'ASC')
->addOrderBy('e.execid', 'ASC')
->getQuery()->getResult();
// PhpStorm doesn't pick this up without,
// based on a dump this is `"c" => App\Entity\Executable`.
/** @var Executable[] $executables */
$executables = array_column($executables, 'executable', 'execid');
$table_fields = [
'icon' => ['title' => 'type', 'sort' => false],
Expand Down Expand Up @@ -139,6 +142,9 @@ public function indexAction(Request $request): Response
case 'debug':
$execdata['icon']['icon'] = 'bug';
break;
case 'generic_task':
$execdata['icon']['icon'] = 'check-double';
break;
case 'run':
$execdata['icon']['icon'] = 'person-running';
break;
Expand Down
80 changes: 80 additions & 0 deletions webapp/src/Controller/Jury/JudgehostController.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use App\Controller\BaseController;
use App\Doctrine\DBAL\Types\JudgeTaskType;
use App\Entity\Executable;
use App\Entity\Judgehost;
use App\Entity\JudgeTask;
use App\Entity\Judging;
Expand Down Expand Up @@ -210,8 +211,19 @@ public function indexAction(Request $request): Response
return strnatcasecmp($a['data']['hostname']['value'], $b['data']['hostname']['value']);
});

/** @var Executable[] $executables */
$executables = $this->em->createQueryBuilder()
->select('e as executable, e.execid as execid')
->from(Executable::class, 'e')
->addOrderBy('e.type', 'ASC')
->addOrderBy('e.execid', 'ASC')
->andWhere('e.type = :type')
->setParameter('type', JudgeTaskType::GENERIC_TASK)
->getQuery()->getResult();

$data = [
'judgehosts' => $judgehosts_table,
'executables' => $executables,
'table_fields' => $table_fields,
'all_checked_in_recently' => $all_checked_in_recently,
'refresh' => [
Expand Down Expand Up @@ -278,7 +290,18 @@ public function viewAction(Request $request, int $judgehostid): Response
->getResult();
}

/** @var Executable[] $executables */
$executables = $this->em->createQueryBuilder()
->select('e as executable, e.execid as execid')
->from(Executable::class, 'e')
->addOrderBy('e.type', 'ASC')
->addOrderBy('e.execid', 'ASC')
->andWhere('e.type = :type')
->setParameter('type', JudgeTaskType::GENERIC_TASK)
->getQuery()->getResult();

$data = [
'executables' => $executables,
'judgehost' => $judgehost,
'status' => $status,
'statusIcon' => $statusIcon,
Expand All @@ -296,6 +319,63 @@ public function viewAction(Request $request, int $judgehostid): Response
}
}

private function helperGenericTask(string $execid, ?JudgeHost $judgehost = null): void {
$executable = $this->em->getRepository(Executable::class)->findOneBy(['execid' => $execid]);
if (!$executable) {
throw new NotFoundHttpException(sprintf('Executable with ID %s not found', $execid));
}

$executable = $executable->getImmutableExecutable();

$judgehosts = [];
if ($judgehost) {
$judgehosts[] = $judgehost;
} else {
/** @var Judgehost[] $judgehosts */
$judgehosts = $this->em->createQueryBuilder()
->from(Judgehost::class, 'j')
->select('j')
->andWhere('j.hidden = 0')
->getQuery()->getResult();
}
foreach ($judgehosts as $judgehost) {
$judgeTask = new JudgeTask();
$judgeTask
->setType(JudgeTaskType::GENERIC_TASK)
->setJudgehost($judgehost)
->setPriority(JudgeTask::PRIORITY_HIGH)
->setRunScriptId($executable->getImmutableExecId())
->setRunConfig(Utils::jsonEncode(['hash' => $executable->getHash()]));
$this->em->persist($judgeTask);
}
$this->em->flush();
}

#[IsGranted('ROLE_ADMIN')]
#[Route(path: '/{judgehostid}/request-generic-task/{execid}', name: 'jury_request_judgehost_generic')]
public function requestGenericTaskJudgehost(Request $request, int $judgehostid, string $execid): RedirectResponse
{
$judgehost = $this->em->getRepository(Judgehost::class)->find($judgehostid);
if (!$judgehost) {
throw new NotFoundHttpException(sprintf('Judgehost with ID %d not found', $judgehostid));
}

$this->helperGenericTask($execid, $judgehost);

return $this->redirectToRoute('jury_judgehost', [
'judgehostid' => $judgehostid
]);
}

// TODO: Does the ordering matter in the file.
#[IsGranted('ROLE_ADMIN')]
#[Route(path: '/request-generic-task/{execid}', name: 'jury_request_generic')]
public function requestGenericTask(Request $request, string $execid): RedirectResponse
{
$this->helperGenericTask($execid);
return $this->redirectToRoute('jury_judgehosts');
}

/**
* @throws DBALException
* @throws NoResultException
Expand Down
10 changes: 6 additions & 4 deletions webapp/src/DataFixtures/DefaultData/ExecutableFixture.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,12 @@ public function load(ObjectManager $manager): void
{
$data = [
// ID, description, type
['compare', 'default compare script', 'compare'],
['full_debug', 'default full debug script', 'debug'],
['java_javac', 'java_javac', 'compile'],
['run', 'default run script', 'run'],
['compare', 'default compare script', 'compare'],
['full_debug', 'default full debug script', 'debug'],
['java_javac', 'java_javac', 'compile'],
['judgehost_info', 'generic information about the judgehost', 'generic_task'],
['chroot_upgrade', 'upgrade chroot packages', 'generic_task'],
['run', 'default run script', 'run'],
];

foreach ($data as $item) {
Expand Down
Loading
Loading