$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]; }