Close remaining review gaps

- restore runs from the web are created under the location's exclusive
  lock, like backup runs
- raw restore writes to an unpredictable temporary name opened with
  exclusive create (never follows a planted symlink) and validates the
  path prefix; the restore service rejects non-canonical paths instead
  of trimming them (occ normalises user input before calling it)
- restore jobs report progress every minute, so a long restore is not
  taken for a dead one
- README: Object Lock turns deletions into delete markers
This commit is contained in:
2026-10-02 11:25:22 +02:00
parent ac82b38e37
commit 131bb6178d
8 changed files with 57 additions and 8 deletions
+17
View File
@@ -41,6 +41,23 @@ class PruneService {
}
}
/**
* Run $fn under the location's exclusive lock (to create a run atomically with respect to a
* cleanup, which takes the same lock and then refuses while a run exists).
*/
public function underStartLock(Target $target, callable $fn): mixed {
try {
$this->locking->acquireLock(self::lockKey($target), ILockingProvider::LOCK_EXCLUSIVE);
} catch (LockedException) {
throw new \RuntimeException('A cleanup of ' . $target->getName() . ' is in progress; try again later');
}
try {
return $fn();
} finally {
$this->locking->releaseLock(self::lockKey($target), ILockingProvider::LOCK_EXCLUSIVE);
}
}
/**
* Exclusive lock on the location, then make sure no run of any kind (backup, restore, export)
* is in progress: their data may not be referenced by a snapshot yet, or is being read.