Quelltext des aktuellen Standes
<?php
declare(strict_types=1);
use OpenNIT\Core\AtomicFile;
/**
* Deckt das atomare Schreiben von Konfigurationsdateien ab.
*
* Entscheidend sind die Betriebs-Garantien: bestehende Dateien werden
* vollständig ersetzt (Rechte bleiben erhalten), es bleiben keine Tempdateien
* zurück, und in Umgebungen ohne beschreibbares Verzeichnis greift der
* Rückfall auf direktes Überschreiben, statt das Speichern zu brechen.
*/
return static function (): void {
$dir = sys_get_temp_dir() . '/opennit-atomic-' . bin2hex(random_bytes(6));
mkdir($dir, 0755, true);
test('Neue Datei wird angelegt und ist sofort vollständig lesbar', function () use ($dir) {
$file = $dir . '/neu.php';
assertTrue(AtomicFile::write($file, "<?php return ['a' => 1];\n"));
assertSame(['a' => 1], include $file);
});
test('Bestehende Datei wird ersetzt und behält ihre Rechte', function () use ($dir) {
$file = $dir . '/bestehend.php';
file_put_contents($file, "<?php return ['alt' => true];\n");
chmod($file, 0640);
assertTrue(AtomicFile::write($file, "<?php return ['neu' => true];\n"));
assertSame(['neu' => true], include $file);
// 0640 bleibt erhalten (Owner-rw ist garantiert)
assertSame(0640, fileperms($file) & 0777);
});
test('Es bleiben keine Tempdateien im Verzeichnis zurück', function () use ($dir) {
$file = $dir . '/sauber.php';
AtomicFile::write($file, "<?php return [];\n");
AtomicFile::write($file, "<?php return [1];\n");
$reste = array_diff(scandir($dir), ['.', '..', 'neu.php', 'bestehend.php', 'sauber.php']);
assertSame([], array_values($reste));
});
test('Ohne beschreibbares Verzeichnis greift der Rückfall auf die Datei', function () use ($dir) {
if (function_exists('posix_geteuid') && posix_geteuid() === 0) {
// Als root sind Verzeichnisrechte wirkungslos — Prüfung nicht möglich.
assertTrue(true);
return;
}
$sub = $dir . '/nur-datei';
mkdir($sub, 0755);
$file = $sub . '/runtime.php';
file_put_contents($file, "<?php return ['alt' => 1];\n");
chmod($file, 0666);
chmod($sub, 0555); // Verzeichnis schreibgeschützt, Datei beschreibbar
$ok = AtomicFile::write($file, "<?php return ['neu' => 2];\n");
chmod($sub, 0755);
assertTrue($ok);
assertSame(['neu' => 2], include $file);
});
test('Nicht schreibbare Datei in nicht schreibbarem Verzeichnis meldet Fehlschlag', function () use ($dir) {
if (function_exists('posix_geteuid') && posix_geteuid() === 0) {
assertTrue(true);
return;
}
$sub = $dir . '/gesperrt';
mkdir($sub, 0755);
$file = $sub . '/runtime.php';
file_put_contents($file, "<?php return ['alt' => 1];\n");
chmod($file, 0444);
chmod($sub, 0555);
$ok = AtomicFile::write($file, "<?php return ['neu' => 2];\n");
chmod($sub, 0755);
chmod($file, 0644);
assertFalse($ok);
// Die alte Datei ist unangetastet
assertSame(['alt' => 1], include $file);
});
};