]> BookStack Code Mirror - system-cli/commitdiff
Improved symlink support for backup/restore
authorDan Brown <redacted>
Mon, 22 May 2023 08:03:59 +0000 (09:03 +0100)
committerDan Brown <redacted>
Mon, 22 May 2023 08:03:59 +0000 (09:03 +0100)
Updates filesystem iteration to specifically handle symlinks when
iterating through files, and uses the file's real path when fetching
their contents.
Fixes #10
Adds test to cover backup case.

src/Commands/BackupCommand.php
src/Services/AppLocator.php
src/Services/Directories.php
tests/Commands/BackupCommandTest.php
tests/Commands/RestoreCommandTest.php

index 0c4f014022d9d8e7e776a34077b2439eca263ccc..47da179ba60c04c0c858c8ee76524978c94928a7 100644 (file)
@@ -6,6 +6,7 @@ use Cli\Services\AppLocator;
 use Cli\Services\EnvironmentLoader;
 use Cli\Services\MySqlRunner;
 use Cli\Services\Paths;
+use FilesystemIterator;
 use RecursiveDirectoryIterator;
 use SplFileInfo;
 use Symfony\Component\Console\Command\Command;
@@ -158,12 +159,15 @@ final class BackupCommand extends Command
      */
     protected function addFolderToZipRecursive(ZipArchive $zip, string $dirPath, string $targetZipPath): void
     {
-        $dirIter = new RecursiveDirectoryIterator($dirPath);
+        $dirIter = new RecursiveDirectoryIterator(
+            $dirPath,
+            FilesystemIterator::KEY_AS_PATHNAME | FilesystemIterator::CURRENT_AS_FILEINFO | FilesystemIterator::FOLLOW_SYMLINKS
+        );
         $fileIter = new \RecursiveIteratorIterator($dirIter);
         /** @var SplFileInfo $file */
         foreach ($fileIter as $file) {
             if (!$file->isDir()) {
-                $zip->addFile($file->getPathname(), $targetZipPath . '/' . $fileIter->getSubPathname());
+                $zip->addFile($file->getRealPath(), $targetZipPath . '/' . $fileIter->getSubPathname());
             }
         }
     }
index 155b8bb61d7a6aadc1886af34208af9b849de155..c97290606fa81c13d3926182c2f414997e202006 100644 (file)
@@ -13,8 +13,6 @@ class AppLocator
             static::getCliDirectory(),
         ];
 
-        var_dump($directoriesToSearch);
-
         foreach ($directoriesToSearch as $directory) {
             if ($directory && static::isProbablyAppDirectory($directory)) {
                 return $directory;
index 641823550e1139bace5a382ba2654a28df6ff4bc..8886c602d6f8d2b61bc70fb70ee4a44c65a700e6 100644 (file)
@@ -1,6 +1,7 @@
-<?php
+<?php declare(strict_types=1);
 
 namespace Cli\Services;
+use FilesystemIterator;
 use RecursiveDirectoryIterator;
 use RecursiveIteratorIterator;
 
@@ -19,7 +20,7 @@ class Directories
 
         /** @var RecursiveDirectoryIterator $files */
         $files = new RecursiveIteratorIterator(
-            new RecursiveDirectoryIterator($src, RecursiveDirectoryIterator::SKIP_DOTS),
+            new RecursiveDirectoryIterator($src, FilesystemIterator::SKIP_DOTS | FilesystemIterator::FOLLOW_SYMLINKS),
             RecursiveIteratorIterator::SELF_FIRST
         );
 
@@ -43,7 +44,7 @@ class Directories
     public static function delete(string $dir): void
     {
         $files = new RecursiveIteratorIterator(
-            new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS),
+            new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS | FilesystemIterator::FOLLOW_SYMLINKS),
             RecursiveIteratorIterator::CHILD_FIRST
         );
 
index 11a0e6cc6b646563623f6247cf2b9424ab7f18e7..db5f116cf5f723c97fb0b05a4bafd26f880c813a 100644 (file)
@@ -119,4 +119,42 @@ class BackupCommandTest extends TestCase
         unlink($zipFile);
     }
 
+    public function test_command_resolves_nested_symlinks()
+    {
+        $symDirs = ['storage/uploads/files', 'storage/uploads/images'];
+        exec('cp -r /var/www/bookstack /var/www/bookstack-symlink-backup');
+        mkdir('/symlinks');
+        foreach ($symDirs as $dir) {
+            $targetFile = str_replace('/', '-', $dir);
+            $code = 0;
+            $output = null;
+            exec("mkdir -p /var/www/bookstack-symlink-backup/{$dir}", $output, $code);
+            exec("mv /var/www/bookstack-symlink-backup/{$dir} /symlinks/{$targetFile}", $output, $code);
+            exec("ln -s /symlinks/{$targetFile} /var/www/bookstack-symlink-backup/{$dir}", $output, $code);
+            file_put_contents("/symlinks/{$targetFile}/test.txt", "Hello from $dir");
+            if ($code !== 0) {
+                $this->fail("Error when setting up symlinks");
+            }
+        }
+
+        chdir('/var/www/bookstack-symlink-backup');
+        $this->assertCount(0, glob('storage/backups/bookstack-backup-*.zip'));
+        $result = $this->runCommand('backup');
+        $result->assertSuccessfulExit();
+        $this->assertCount(1, glob('storage/backups/bookstack-backup-*.zip'));
+        $zipFile = glob('storage/backups/bookstack-backup-*.zip')[0];
+
+        $zip = new \ZipArchive();
+        $zip->open($zipFile);
+        foreach ($symDirs as $dir) {
+            $fileData = $zip->getFromName("{$dir}/test.txt");
+            $this->assertNotFalse($fileData);
+            $this->assertStringContainsString("Hello from {$dir}", $fileData);
+        }
+        $zip->close();
+
+        exec('rm -rf /symlinks');
+        exec('rm -rf /var/www/bookstack-symlink-backup');
+    }
+
 }
\ No newline at end of file
index 288b0568d0773fcd9d7ca5712807e1b040faaf54..93b27bfd6caa285194158a0dfc540a30843a9fdb 100644 (file)
@@ -162,6 +162,7 @@ class RestoreCommandTest extends TestCase
         $this->assertStringEqualsFile('/var/www/bookstack-symlink-restore/themes/test.txt', 'hello-themes');
 
         exec('rm -rf /var/www/bookstack-symlink-restore');
+        exec('rm -rf /symlinks');
     }
 
     protected function buildZip(callable $builder): string