mirror of
https://github.com/Vateron-Media/XC_VM.git
synced 2026-11-01 03:03:23 +01:00
root_signals remembered the modules it had last written for as an MD5 of config/modules.php in config/crontab.modules. Three things were wrong with that: - root wrote the marker with file_put_contents() in a directory xc_vm can write: a link planted there had root overwrite the file it pointed to; - a `startup` or `status` holding an older module list could write it over a newer crontab while the marker still matched, and nothing put it right; - the hash was MD5. installRootCrontab() already compares the crontab with the list it wants and writes only on a difference, so root_signals now simply calls it every minute: an older list written by another process is corrected a minute later, and there is no marker left to link or to hash. The every-minute call is quiet when nothing changes, and writes nothing when the crontab cannot be read: it runs from that crontab, so an unreadable one is not an empty one, and writing over it would drop the administrator's own lines. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
44 lines
2.3 KiB
PHP
44 lines
2.3 KiB
PHP
<?php
|
|
|
|
use PHPUnit\Framework\TestCase;
|
|
use XcVm\Cli\Commands\StartupCommand;
|
|
use XcVm\Cli\CronJobs\RootSignalsCronJob;
|
|
|
|
/**
|
|
* root_signals brings root's crontab up to date with the modules every
|
|
* minute, so a module installed or enabled from the panel gets its cron lines
|
|
* (the anti-abuse shield's, which starts its honeypot) without a restart, and
|
|
* a `startup` or `status` that wrote an older list is put right a minute
|
|
* later. It keeps nothing of its last write: the crontab is what is compared.
|
|
*/
|
|
final class ModuleCronRefreshTest extends TestCase {
|
|
public function testEveryRunChecksTheCrontab(): void {
|
|
$rRuns = 0;
|
|
$rWrite = static function () use (&$rRuns): bool {
|
|
$rRuns++;
|
|
return true;
|
|
};
|
|
$this->assertTrue(RootSignalsCronJob::refreshModuleCrons($rWrite));
|
|
$this->assertTrue(RootSignalsCronJob::refreshModuleCrons($rWrite), 'the next minute too: nothing says "already done"');
|
|
$this->assertSame(2, $rRuns);
|
|
$this->assertFalse(RootSignalsCronJob::refreshModuleCrons(static fn(): bool => false), 'a crontab that could not be written');
|
|
}
|
|
|
|
/** Root writes no file where xc_vm could plant a link, and hashes nothing. */
|
|
public function testNothingIsKeptOfTheLastWrite(): void {
|
|
$rMethod = new ReflectionMethod(RootSignalsCronJob::class, 'refreshModuleCrons');
|
|
$rSource = implode('', array_slice(file((string) $rMethod->getFileName()), $rMethod->getStartLine() - 1, $rMethod->getEndLine() - $rMethod->getStartLine() + 1));
|
|
$this->assertStringNotContainsString('file_put_contents', $rSource);
|
|
$this->assertStringNotContainsString('md5', $rSource);
|
|
}
|
|
|
|
/** The every-minute check says nothing when the list is there: cron would mail it each minute. */
|
|
public function testTheCheckIsQuietWhenNothingChanges(): void {
|
|
$rParameters = (new ReflectionMethod(StartupCommand::class, 'installRootCrontab'))->getParameters();
|
|
$this->assertSame('rQuiet', $rParameters[0]->getName());
|
|
$this->assertFalse($rParameters[0]->getDefaultValue(), 'startup and status still say it');
|
|
$this->assertStringContainsString('StartupCommand::installRootCrontab(true)', (string) file_get_contents(MAIN_HOME . 'Cli/CronJobs/RootSignalsCronJob.php'));
|
|
$this->assertStringContainsString('if ($rQuiet && $rListed !== 0)', (string) file_get_contents(MAIN_HOME . 'Cli/Commands/StartupCommand.php'), 'an unreadable crontab is not rewritten each minute');
|
|
}
|
|
}
|