Fix trash on SFTP and the disaster recovery test after the review fixes
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 / 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
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 / 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
forget() and untrash() uploaded a remote read stream directly, which the new size check could not judge on SFTP, so cleanup failed there. They now copy through a local buffer, and the size check only trusts local streams (php://temp, php://memory, plain files). The disaster recovery test calls backup:restore:full with --no-interaction: docker exec without a terminal still counts as interactive, so the new confirmation question read an empty answer and cancelled.
This commit is contained in:
@@ -385,7 +385,7 @@ final class Repository {
|
||||
// either the old state or the new one plus unreferenced leftovers, never a catalog entry
|
||||
// pointing at a missing object. The snapshot object is bound to its id (not its path),
|
||||
// so it can be copied as raw bytes.
|
||||
$this->backend->put('trash/snapshots/' . $snapshotId, $this->backend->get('snapshots/' . $snapshotId));
|
||||
$this->copyRaw('snapshots/' . $snapshotId, 'trash/snapshots/' . $snapshotId);
|
||||
$this->putObject('trash/info/' . $snapshotId, json_encode(['forgottenAt' => time(), 'by' => $by, 'db' => $meta['db'] ?? null,
|
||||
'dbInTrash' => $dbInTrash, 'time' => $meta['time'] ?? null, 'label' => $meta['label'] ?? ''], JSON_THROW_ON_ERROR));
|
||||
unset($this->catalog->snapshots[$snapshotId]);
|
||||
@@ -405,7 +405,7 @@ final class Repository {
|
||||
$this->putObject($info['db'], $this->getObject($info['dbInTrash']));
|
||||
}
|
||||
// Same order as forget(): copy, switch the catalog, then delete the trash copies.
|
||||
$this->backend->put('snapshots/' . $snapshotId, $this->backend->get('trash/snapshots/' . $snapshotId));
|
||||
$this->copyRaw('trash/snapshots/' . $snapshotId, 'snapshots/' . $snapshotId);
|
||||
$this->catalog->snapshots[$snapshotId] = true;
|
||||
unset($this->catalog->trash[$snapshotId]);
|
||||
$this->catalog->write();
|
||||
@@ -416,6 +416,25 @@ final class Repository {
|
||||
$this->backend->delete('trash/info/' . $snapshotId);
|
||||
}
|
||||
|
||||
/** Copy an object as raw bytes, buffered locally (remote read streams cannot be uploaded directly everywhere). */
|
||||
private function copyRaw(string $from, string $to): void {
|
||||
$in = $this->backend->get($from);
|
||||
$buffer = fopen('php://temp/maxmemory:' . (4 * 1048576), 'w+b');
|
||||
if ($buffer === false) {
|
||||
fclose($in);
|
||||
throw new RepositoryException("Cannot buffer $from");
|
||||
}
|
||||
try {
|
||||
if (stream_copy_to_stream($in, $buffer) === false) {
|
||||
throw new RepositoryException("Cannot read $from");
|
||||
}
|
||||
} finally {
|
||||
fclose($in);
|
||||
}
|
||||
rewind($buffer);
|
||||
$this->backend->put($to, $buffer);
|
||||
}
|
||||
|
||||
/** Record a user_migration export's manifest in the catalog (rollback-protected like a snapshot). */
|
||||
public function recordUserExport(string $manifestPath): void {
|
||||
$this->catalog->userExports[$manifestPath] = true;
|
||||
|
||||
Reference in New Issue
Block a user