Phase 0: fixes from review (torn files, pending blobs, exact sequences, UTF-8, storage checks)
- detect files changing during reading or between resumable steps and re-read them (size/mtime/ctime/inode), spike F overwrites mid-run - drop pending blob ids when a pack or index upload fails - dump and restore PostgreSQL sequence state exactly, bound parameters, validated catalog names; spike C checks all sequences exactly - keep invalid UTF-8 byte-exact (tagged base64) - refuse large tables without a primary key - StorageBackend: check write/flush/close results, validate base path - user_migration import/export: root paths, intermediate folders, partial writes, read errors - spikes: never touch an existing account, own fixtures, 512M note
This commit is contained in:
@@ -11,6 +11,7 @@ use OCA\NgBackup\Repository\Repository;
|
||||
use OCP\Files\File;
|
||||
use OCP\Files\Folder;
|
||||
use OCP\UserMigration\IExportDestination;
|
||||
use OCP\UserMigration\UserMigrationException;
|
||||
|
||||
/**
|
||||
* Export destination for user_migration that writes straight into the encrypted repository:
|
||||
@@ -42,7 +43,7 @@ final class RepositoryExportDestination implements IExportDestination {
|
||||
while (!feof($stream)) {
|
||||
$chunk = fread($stream, 1048576);
|
||||
if ($chunk === false) {
|
||||
break;
|
||||
throw new UserMigrationException('Read error while exporting ' . $path);
|
||||
}
|
||||
$writer->write($chunk);
|
||||
}
|
||||
|
||||
@@ -35,7 +35,13 @@ final class RepositoryImportSource implements IImportSource {
|
||||
// Phase 0: buffer in php://temp (memory up to 8 MiB, then a temp file for this one file).
|
||||
$stream = fopen('php://temp/maxmemory:' . (8 * 1048576), 'w+b');
|
||||
foreach ($this->repo->readBlobs($entry['b']) as $data) {
|
||||
fwrite($stream, $data);
|
||||
for ($done = 0, $len = strlen($data); $done < $len; $done += $n) {
|
||||
$n = fwrite($stream, $done === 0 ? $data : substr($data, $done));
|
||||
if ($n === false || $n === 0) {
|
||||
fclose($stream);
|
||||
throw new UserMigrationException('Cannot buffer ' . $path);
|
||||
}
|
||||
}
|
||||
}
|
||||
rewind($stream);
|
||||
return $stream;
|
||||
@@ -48,11 +54,13 @@ final class RepositoryImportSource implements IImportSource {
|
||||
foreach (array_keys($this->manifest['entries']) as $p) {
|
||||
if ($prefix === '' || str_starts_with($p, $prefix)) {
|
||||
$rest = substr($p, strlen($prefix));
|
||||
if ($rest !== '' && !str_contains($rest, '/')) {
|
||||
$names[] = $rest;
|
||||
if ($rest !== '') {
|
||||
// First path segment: also lists folders that only exist as a parent of a file.
|
||||
$names[explode('/', $rest, 2)[0]] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
$names = array_keys($names);
|
||||
sort($names);
|
||||
return $names;
|
||||
}
|
||||
@@ -72,7 +80,8 @@ final class RepositoryImportSource implements IImportSource {
|
||||
|
||||
public function copyToFolder(Folder $destination, string $sourcePath): void {
|
||||
foreach ($this->getFolderListing($sourcePath) as $name) {
|
||||
$path = trim($sourcePath, '/') . '/' . $name;
|
||||
$base = trim($sourcePath, '/');
|
||||
$path = $base === '' ? $name : $base . '/' . $name;
|
||||
$entry = $this->manifest['entries'][$path] ?? ['t' => 'd'];
|
||||
if ($entry['t'] === 'd') {
|
||||
$sub = $destination->nodeExists($name) ? $destination->get($name) : $destination->newFolder($name);
|
||||
|
||||
Reference in New Issue
Block a user