Skip to content
Merged
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
31 changes: 31 additions & 0 deletions webapp/migrations/Version20240917113927.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

declare(strict_types=1);

namespace DoctrineMigrations;

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

final class Version20240917113927 extends AbstractMigration
{
public function getDescription(): string
{
return 'Adding executable bit to problem attachments.';
}

public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE problem_attachment_content ADD is_executable TINYINT(1) DEFAULT 0 NOT NULL COMMENT \'Whether this file gets an executable bit.\'');
}

public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE problem_attachment_content DROP is_executable');
}

public function isTransactional(): bool
{
return false;
}
}
16 changes: 16 additions & 0 deletions webapp/src/Entity/ProblemAttachmentContent.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;
use JMS\Serializer\Annotation as Serializer;

#[ORM\Entity]
#[ORM\Table(options: [
Expand All @@ -25,6 +26,10 @@ class ProblemAttachmentContent
#[ORM\Column(type: 'blobtext', options: ['comment' => 'Attachment content'])]
private string $content;

#[ORM\Column(options: ['comment' => 'Whether this file gets an executable bit.', 'default' => 0])]
#[Serializer\Exclude]
private bool $isExecutable = false;

public function getAttachment(): ProblemAttachment
{
return $this->attachment;
Expand All @@ -48,4 +53,15 @@ public function setContent(string $content): self

return $this;
}

public function setIsExecutable(bool $isExecutable): ProblemAttachmentContent
{
$this->isExecutable = $isExecutable;
return $this;
}

public function isExecutable(): bool
{
return $this->isExecutable;
}
}
8 changes: 8 additions & 0 deletions webapp/src/Service/DOMJudgeService.php
Original file line number Diff line number Diff line change
Expand Up @@ -892,6 +892,14 @@ public function getSamplesZipForContest(Contest $contest): StreamedResponse
foreach ($problem->getProblem()->getAttachments() as $attachment) {
$filename = sprintf('%s/attachments/%s', $problem->getShortname(), $attachment->getName());
$zip->addFromString($filename, $attachment->getContent()->getContent());
if ($attachment->getContent()->isExecutable()) {
// 100755 = regular file, executable
$zip->setExternalAttributesName(
$filename,
ZipArchive::OPSYS_UNIX,
octdec('100755') << 16
);
}
}
}

Expand Down
16 changes: 15 additions & 1 deletion webapp/src/Service/ImportProblemService.php
Original file line number Diff line number Diff line change
Expand Up @@ -539,6 +539,14 @@ public function importZippedProblem(
continue;
}

// In doubt make files executable, but try to read it from the zip file.
$executableBit = true;
if ($zip->getExternalAttributesIndex($j, $opsys, $attr)
&& $opsys==ZipArchive::OPSYS_UNIX
&& (($attr >> 16) & 0100) === 0) {
$executableBit = false;
}

$name = basename($filename);

$fileParts = explode('.', $name);
Expand All @@ -558,6 +566,10 @@ public function importZippedProblem(
$messages['info'][] = sprintf("Updated attachment '%s'", $name);
$numAttachments++;
}
if ($executableBit !== $attachmentContent->isExecutable()) {
$attachmentContent->setIsExecutable($executableBit);
$messages['info'][] = sprintf("Updated executable bit of attachment '%s'", $name);
}
} else {
$attachment = new ProblemAttachment();
$attachmentContent = new ProblemAttachmentContent();
Expand All @@ -567,7 +579,9 @@ public function importZippedProblem(
->setType($type)
->setContent($attachmentContent);

$attachmentContent->setContent($content);
$attachmentContent
->setContent($content)
->setIsExecutable($executableBit);

$this->em->persist($attachment);

Expand Down
Loading