From e35905fa257a1c007d3aa884ac6f6e0e412fab2c Mon Sep 17 00:00:00 2001 From: Tobias Werth Date: Tue, 17 Sep 2024 16:57:57 +0200 Subject: [PATCH] Preserve information whether attachments are executable. This is relevant for testing tools which are often uploaded as attachments for interactive problems. When we download the samples (via the web interface or API), we should mark these files as executable iff they were executable when uploading. --- webapp/migrations/Version20240917113927.php | 31 +++++++++++++++++++ .../src/Entity/ProblemAttachmentContent.php | 16 ++++++++++ webapp/src/Service/DOMJudgeService.php | 8 +++++ webapp/src/Service/ImportProblemService.php | 16 +++++++++- 4 files changed, 70 insertions(+), 1 deletion(-) create mode 100755 webapp/migrations/Version20240917113927.php diff --git a/webapp/migrations/Version20240917113927.php b/webapp/migrations/Version20240917113927.php new file mode 100755 index 0000000000..9eea7036b9 --- /dev/null +++ b/webapp/migrations/Version20240917113927.php @@ -0,0 +1,31 @@ +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; + } +} diff --git a/webapp/src/Entity/ProblemAttachmentContent.php b/webapp/src/Entity/ProblemAttachmentContent.php index d961a526e6..7dd60b2c83 100644 --- a/webapp/src/Entity/ProblemAttachmentContent.php +++ b/webapp/src/Entity/ProblemAttachmentContent.php @@ -3,6 +3,7 @@ namespace App\Entity; use Doctrine\ORM\Mapping as ORM; +use JMS\Serializer\Annotation as Serializer; #[ORM\Entity] #[ORM\Table(options: [ @@ -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; @@ -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; + } } diff --git a/webapp/src/Service/DOMJudgeService.php b/webapp/src/Service/DOMJudgeService.php index 423f7b3f96..744bfe4201 100644 --- a/webapp/src/Service/DOMJudgeService.php +++ b/webapp/src/Service/DOMJudgeService.php @@ -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 + ); + } } } diff --git a/webapp/src/Service/ImportProblemService.php b/webapp/src/Service/ImportProblemService.php index 8b58db4f46..64d5059137 100644 --- a/webapp/src/Service/ImportProblemService.php +++ b/webapp/src/Service/ImportProblemService.php @@ -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); @@ -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(); @@ -567,7 +579,9 @@ public function importZippedProblem( ->setType($type) ->setContent($attachmentContent); - $attachmentContent->setContent($content); + $attachmentContent + ->setContent($content) + ->setIsExecutable($executableBit); $this->em->persist($attachment);