feat(control): connect physical feedback to bounded V4 command and fallback paths
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/** File-only installation helpers. No module classes loaded from the staging tree. */
|
||||
function v4FeedbackWrite(string $path, string $content, int $mode=0644): void
|
||||
{
|
||||
if (is_link($path)) throw new RuntimeException('Symlink wird nicht ersetzt.');
|
||||
$temp=tempnam(dirname($path),'.v4-feedback-');
|
||||
if ($temp===false) throw new RuntimeException('Temporaere Datei nicht verfuegbar.');
|
||||
try {
|
||||
if (file_put_contents($temp,$content,LOCK_EX)!==strlen($content) || !chmod($temp,$mode) || !rename($temp,$path)) throw new RuntimeException('Atomarer Dateitausch fehlgeschlagen.');
|
||||
} finally { if(is_file($temp))unlink($temp); }
|
||||
}
|
||||
|
||||
function v4FeedbackFiles(string $stage,string $target): array
|
||||
{
|
||||
$allowed=['libs/NetzfahrplanV4Rueckmeldung.php','libs/BatterieNetzfahrplanV4RueckmeldungTrait.php','libs/NetzfahrplanV4Regeltest.php',
|
||||
'libs/BatterieNetzfahrplanV4TestTrait.php','libs/ManagerNetzfahrplanV4TestTrait.php','libs/ManagerNetzfahrplanV4EmpfangTrait.php','libs/BatterieRegler.php','Batterie/module.php','Manager/module.php'];
|
||||
$m=json_decode((string)file_get_contents($stage.'/MANIFEST.json'),true,32,JSON_THROW_ON_ERROR);
|
||||
if (($m['scope']??null)!=='corrected_feedback_trial_disabled' || count($m['files']??[])!==count($allowed) || array_diff(array_keys($m['files']),$allowed)) throw new RuntimeException('Unerwarteter Dateiumfang.');
|
||||
if (realpath($target)!==$target || is_link($target)) throw new RuntimeException('Modulpfad unerwartet.');
|
||||
foreach($m['dependencies'] as $name=>$hash) {
|
||||
if (!preg_match('~^libs/[A-Za-z0-9]+\.php$~D',$name) || is_link($target.'/'.$name) || hash_file('sha256',$target.'/'.$name)!==$hash) throw new RuntimeException('Abhaengigkeit wurde parallel geaendert: '.$name);
|
||||
}
|
||||
$before=[];$source=[];$already=true;
|
||||
// Explicit dependency order. Both top-level modules are replaced only after all dependencies.
|
||||
foreach($allowed as$name) {
|
||||
$path=$target.'/'.$name;$candidate=$stage.'/source/'.$name;
|
||||
if (is_link($path)||is_link($candidate)||realpath(dirname($path))!==$target.'/'.dirname($name)) throw new RuntimeException('Unerwarteter Dateipfad.');
|
||||
$source[$name]=(string)file_get_contents($candidate);
|
||||
if (hash('sha256',$source[$name])!==$m['files'][$name]['after']) throw new RuntimeException('Paketpruefsumme geaendert: '.$name);
|
||||
token_get_all($source[$name],TOKEN_PARSE);
|
||||
$before[$name]=is_file($path)?file_get_contents($path):null;
|
||||
$hash=$before[$name]===null?null:hash('sha256',$before[$name]);
|
||||
if (!in_array($hash,[$m['files'][$name]['before'],$m['files'][$name]['after']],true)) throw new RuntimeException('Paralleler Modulstand; nichts ueberschrieben: '.$name);
|
||||
if($hash!==$m['files'][$name]['after'])$already=false;
|
||||
}
|
||||
if($already)return ['status'=>'already_installed','backup'=>null];
|
||||
$backup=$stage.'/backups/'.gmdate('Ymd\THis\Z').'-'.bin2hex(random_bytes(4));
|
||||
if(!mkdir($backup,0700,true))throw new RuntimeException('Sicherung fehlgeschlagen.');
|
||||
foreach($allowed as$name) {
|
||||
if($before[$name]!==null) {
|
||||
$dest=$backup.'/'.$name;if(!is_dir(dirname($dest)))mkdir(dirname($dest),0700,true);
|
||||
v4FeedbackWrite($dest,$before[$name],0600);
|
||||
}
|
||||
}
|
||||
v4FeedbackWrite($backup.'/MANIFEST.json',json_encode($m,JSON_THROW_ON_ERROR|JSON_PRETTY_PRINT),0600);
|
||||
$written=[];
|
||||
try {
|
||||
foreach($allowed as$name) {
|
||||
if((is_file($target.'/'.$name)?file_get_contents($target.'/'.$name):null)!==$before[$name])throw new RuntimeException('Parallele Aenderung waehrend Installation.');
|
||||
if($before[$name]===$source[$name])continue;
|
||||
v4FeedbackWrite($target.'/'.$name,$source[$name]);$written[]=$name;
|
||||
}
|
||||
} catch(Throwable $e) {
|
||||
foreach(array_reverse($written)as$name) {
|
||||
$p=$target.'/'.$name;
|
||||
if(!is_link($p)&&is_file($p)&&hash_file('sha256',$p)===$m['files'][$name]['after']) {
|
||||
if($before[$name]===null)unlink($p);else v4FeedbackWrite($p,$before[$name]);
|
||||
}
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
return ['status'=>'installed','backup'=>$backup,'filesChanged'=>$written];
|
||||
}
|
||||
Reference in New Issue
Block a user