Full restore goes on where it stopped; SMB recovers from a failed read
CI / unit (8.2) (push) Has been cancelled
CI / unit (8.3) (push) Has been cancelled
CI / unit (8.4) (push) Has been cancelled
CI / psalm (push) Has been cancelled
CI / frontend (push) Has been cancelled
CI / integration (mariadb, 32) (push) Has been cancelled
CI / integration (mariadb, 33) (push) Has been cancelled
CI / integration (mariadb, 34) (push) Has been cancelled
CI / integration (mariadb, 35) (push) Has been cancelled
CI / integration (pgsql, 32) (push) Has been cancelled
CI / integration (pgsql, 33) (push) Has been cancelled
CI / integration (pgsql, 34) (push) Has been cancelled
CI / integration (pgsql, 35) (push) Has been cancelled
CI / integration (sqlite, 32) (push) Has been cancelled
CI / integration (sqlite, 33) (push) Has been cancelled
CI / integration (sqlite, 34) (push) Has been cancelled
CI / integration (sqlite, 35) (push) Has been cancelled
CI / integration-targets (push) Has been cancelled
CI / integration-user (32) (push) Has been cancelled
CI / integration-user (33) (push) Has been cancelled
CI / integration-user (34) (push) Has been cancelled
CI / integration-user (35) (push) Has been cancelled
CI / integration-full-restore (push) Has been cancelled

The DR test of 1.0.2 over SMB stopped after 392 GiB of 983: the connection to the Storage Box
dropped, and the smbclient extension then returns a string of length -1 for a failed read (it
stores smbc_read()'s -1 in an unsigned length). The read loop counted that as data and went
backwards forever at 100% CPU. Reads over SMB are now checked; a failed one is read again the
usual way over a new connection.

backup:restore:full that runs again (--force) leaves the data files already in place with the
snapshot's size and modification time, as a backup takes them as unchanged, and reads ahead only
what is still missing: after 392 GiB it goes on instead of starting over.
This commit is contained in:
2026-11-05 08:18:50 +01:00
parent dce93aa75e
commit 029b3b9cc7
5 changed files with 142 additions and 29 deletions
+28
View File
@@ -101,6 +101,34 @@ class RepositoryTest extends TestCase {
$this->assertFileExists($stripped . '/note-1.txt');
}
public function testARestoreThatRunsAgainKeepsWhatIsInPlaceAndRestoresTheRest(): void {
$location = $this->tempDir();
$backend = new LocalBackend($location);
$repo = Repository::init($backend, 'pw');
$r = $repo->backupDirectory($this->src);
$target = $this->tempDir();
$repo->restore($r['snapshot'], $target);
// as a stopped restore leaves it: one file not there yet, one with other content
unlink($target . '/docs/d1/note-1.txt');
file_put_contents($target . '/docs/d0/note-0.txt', 'something else');
$this->assertSame(34, $repo->restore($r['snapshot'], $target, keepUnchanged: true));
$this->assertSame(self::hashTree($this->src), self::hashTree($target));
// all in place: nothing is read from the location, which could not give it anyway now
self::removeTree($location . '/packs');
$this->assertSame(34, Repository::open($backend, 'pw')->restore($r['snapshot'], $target, keepUnchanged: true));
$this->expectException(\Throwable::class);
Repository::open($backend, 'pw')->restore($r['snapshot'], $target);
}
private static function removeTree(string $dir): void {
foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($dir, \FilesystemIterator::SKIP_DOTS), \RecursiveIteratorIterator::CHILD_FIRST) as $f) {
$f->isDir() ? rmdir($f->getPathname()) : unlink($f->getPathname());
}
rmdir($dir);
}
public function testRestoreRemovesTemporariesOfAStoppedRestore(): void {
$repo = Repository::init($this->backend, 'pw');
$r = $repo->backupDirectory($this->src);