197 lines
5.4 KiB
PHP
197 lines
5.4 KiB
PHP
<?php
|
|
/*
|
|
* Shared handling for the Racket installation zip and its binary parts.
|
|
*/
|
|
|
|
define('RACKET_ZIP_FILE', __DIR__ . '/config/racket.zip');
|
|
define('RACKET_ZIP_DATA_DIR', __DIR__ . '/data');
|
|
define('RACKET_ZIP_PART_PREFIX', 'racket-part-');
|
|
define('RACKET_ZIP_MANIFEST_FILE', RACKET_ZIP_DATA_DIR . '/racket-parts.json');
|
|
|
|
function racket_zip_binary_chunk_bytes_for_base64_kib($kib)
|
|
{
|
|
return intdiv(((int)$kib) * 1024, 4) * 3;
|
|
}
|
|
|
|
function racket_zip_part_name($nr)
|
|
{
|
|
return RACKET_ZIP_PART_PREFIX . sprintf('%06d', $nr);
|
|
}
|
|
|
|
function racket_zip_part_file($partNumber)
|
|
{
|
|
return RACKET_ZIP_DATA_DIR . '/' . RACKET_ZIP_PART_PREFIX . $partNumber;
|
|
}
|
|
|
|
function racket_zip_ensure_data_dir()
|
|
{
|
|
if (!is_dir(RACKET_ZIP_DATA_DIR)) {
|
|
if (!mkdir(RACKET_ZIP_DATA_DIR, 0755, true)) {
|
|
throw new RuntimeException('Kan data directory niet maken: ' . RACKET_ZIP_DATA_DIR);
|
|
}
|
|
}
|
|
|
|
if (!is_writable(RACKET_ZIP_DATA_DIR)) {
|
|
throw new RuntimeException('Data directory is niet schrijfbaar: ' . RACKET_ZIP_DATA_DIR);
|
|
}
|
|
}
|
|
|
|
function racket_zip_remove_old_parts()
|
|
{
|
|
foreach (glob(RACKET_ZIP_DATA_DIR . '/' . RACKET_ZIP_PART_PREFIX . '*') ?: array() as $file) {
|
|
if (is_file($file)) {
|
|
@unlink($file);
|
|
}
|
|
}
|
|
|
|
if (is_file(RACKET_ZIP_MANIFEST_FILE)) {
|
|
@unlink(RACKET_ZIP_MANIFEST_FILE);
|
|
}
|
|
}
|
|
|
|
function split_racket_zip_parts($maxBase64Kib)
|
|
{
|
|
$maxBase64Bytes = (int)$maxBase64Kib * 1024;
|
|
$binaryChunkBytes = racket_zip_binary_chunk_bytes_for_base64_kib($maxBase64Kib);
|
|
|
|
if ($binaryChunkBytes < 3) {
|
|
throw new RuntimeException('Ongeldige racket.zip base64 chunk-instelling.');
|
|
}
|
|
|
|
if (!is_file(RACKET_ZIP_FILE) || !is_readable(RACKET_ZIP_FILE)) {
|
|
throw new RuntimeException('racket.zip ontbreekt of is niet leesbaar: ' . RACKET_ZIP_FILE);
|
|
}
|
|
|
|
racket_zip_ensure_data_dir();
|
|
racket_zip_remove_old_parts();
|
|
|
|
$in = fopen(RACKET_ZIP_FILE, 'rb');
|
|
|
|
if ($in === false) {
|
|
throw new RuntimeException('Kan racket.zip niet openen.');
|
|
}
|
|
|
|
$parts = array();
|
|
$nr = 1;
|
|
$totalBinaryBytes = 0;
|
|
|
|
while (!feof($in)) {
|
|
$buf = fread($in, $binaryChunkBytes);
|
|
|
|
if ($buf === false) {
|
|
fclose($in);
|
|
throw new RuntimeException('Fout bij lezen van racket.zip.');
|
|
}
|
|
|
|
if ($buf === '') {
|
|
break;
|
|
}
|
|
|
|
$name = racket_zip_part_name($nr);
|
|
$file = RACKET_ZIP_DATA_DIR . '/' . $name;
|
|
|
|
if (file_put_contents($file, $buf, LOCK_EX) === false) {
|
|
fclose($in);
|
|
throw new RuntimeException('Kan part niet schrijven: ' . $file);
|
|
}
|
|
|
|
$n = sprintf('%06d', $nr);
|
|
$binaryBytes = strlen($buf);
|
|
|
|
$parts[] = array(
|
|
'number' => $n,
|
|
'name' => $name,
|
|
'file' => $file,
|
|
'size' => $binaryBytes,
|
|
);
|
|
|
|
$totalBinaryBytes += $binaryBytes;
|
|
$nr++;
|
|
}
|
|
|
|
fclose($in);
|
|
|
|
if (count($parts) === 0) {
|
|
throw new RuntimeException('racket.zip lijkt leeg te zijn.');
|
|
}
|
|
|
|
$manifest = array(
|
|
'source_file' => RACKET_ZIP_FILE,
|
|
'source_bytes' => filesize(RACKET_ZIP_FILE),
|
|
'source_mtime' => filemtime(RACKET_ZIP_FILE),
|
|
'source_sha256' => hash_file('sha256', RACKET_ZIP_FILE),
|
|
'max_base64_kib' => (int)$maxBase64Kib,
|
|
'max_base64_bytes' => $maxBase64Bytes,
|
|
'binary_chunk_bytes' => $binaryChunkBytes,
|
|
'part_count' => count($parts),
|
|
'total_binary_bytes' => $totalBinaryBytes,
|
|
'parts' => $parts,
|
|
'created_at' => gmdate('c'),
|
|
);
|
|
|
|
if (file_put_contents(
|
|
RACKET_ZIP_MANIFEST_FILE,
|
|
json_encode($manifest, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n",
|
|
LOCK_EX
|
|
) === false) {
|
|
throw new RuntimeException('Kan racket.zip manifest niet schrijven: ' . RACKET_ZIP_MANIFEST_FILE);
|
|
}
|
|
|
|
return $manifest;
|
|
}
|
|
|
|
function load_racket_zip_parts_manifest()
|
|
{
|
|
if (!is_file(RACKET_ZIP_MANIFEST_FILE) || !is_readable(RACKET_ZIP_MANIFEST_FILE)) {
|
|
throw new RuntimeException('racket.zip parts-manifest ontbreekt: ' . RACKET_ZIP_MANIFEST_FILE);
|
|
}
|
|
|
|
$raw = file_get_contents(RACKET_ZIP_MANIFEST_FILE);
|
|
|
|
if ($raw === false) {
|
|
throw new RuntimeException('Kan racket.zip parts-manifest niet lezen: ' . RACKET_ZIP_MANIFEST_FILE);
|
|
}
|
|
|
|
$manifest = json_decode($raw, true);
|
|
|
|
if (!is_array($manifest)) {
|
|
throw new RuntimeException('racket.zip parts-manifest is geen geldige JSON: ' . RACKET_ZIP_MANIFEST_FILE);
|
|
}
|
|
|
|
return $manifest;
|
|
}
|
|
|
|
function racket_zip_parts_current($manifest, $maxBase64Kib)
|
|
{
|
|
if (!is_array($manifest)) {
|
|
return false;
|
|
}
|
|
|
|
if ((int)($manifest['max_base64_kib'] ?? 0) !== (int)$maxBase64Kib) {
|
|
return false;
|
|
}
|
|
|
|
if (!is_file(RACKET_ZIP_FILE)) {
|
|
return false;
|
|
}
|
|
|
|
if ((int)($manifest['source_bytes'] ?? -1) !== (int)filesize(RACKET_ZIP_FILE)) {
|
|
return false;
|
|
}
|
|
|
|
if ((int)($manifest['source_mtime'] ?? -1) !== (int)filemtime(RACKET_ZIP_FILE)) {
|
|
return false;
|
|
}
|
|
|
|
foreach (($manifest['parts'] ?? array()) as $part) {
|
|
$number = (string)($part['number'] ?? '');
|
|
$file = racket_zip_part_file($number);
|
|
|
|
if (!preg_match('/^[0-9]{6}$/', $number) || !is_file($file) || !is_readable($file)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return count($manifest['parts'] ?? array()) > 0;
|
|
}
|