RepairTree.php 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
  5. * SPDX-License-Identifier: AGPL-3.0-or-later
  6. */
  7. namespace OCA\Files\Command;
  8. use OCP\IDBConnection;
  9. use Symfony\Component\Console\Command\Command;
  10. use Symfony\Component\Console\Input\InputInterface;
  11. use Symfony\Component\Console\Output\OutputInterface;
  12. class RepairTree extends Command {
  13. public const CHUNK_SIZE = 200;
  14. public function __construct(
  15. protected IDBConnection $connection,
  16. ) {
  17. parent::__construct();
  18. }
  19. protected function configure(): void {
  20. $this
  21. ->setName('files:repair-tree')
  22. ->setDescription('Try and repair malformed filesystem tree structures')
  23. ->addOption('dry-run');
  24. }
  25. public function execute(InputInterface $input, OutputInterface $output): int {
  26. $rows = $this->findBrokenTreeBits();
  27. $fix = !$input->getOption('dry-run');
  28. $output->writeln('Found ' . count($rows) . ' file entries with an invalid path');
  29. if ($fix) {
  30. $this->connection->beginTransaction();
  31. }
  32. $query = $this->connection->getQueryBuilder();
  33. $query->update('filecache')
  34. ->set('path', $query->createParameter('path'))
  35. ->set('path_hash', $query->func()->md5($query->createParameter('path')))
  36. ->set('storage', $query->createParameter('storage'))
  37. ->where($query->expr()->eq('fileid', $query->createParameter('fileid')));
  38. foreach ($rows as $row) {
  39. $output->writeln("Path of file {$row['fileid']} is {$row['path']} but should be {$row['parent_path']}/{$row['name']} based on its parent", OutputInterface::VERBOSITY_VERBOSE);
  40. if ($fix) {
  41. $fileId = $this->getFileId((int)$row['parent_storage'], $row['parent_path'] . '/' . $row['name']);
  42. if ($fileId > 0) {
  43. $output->writeln("Cache entry has already be recreated with id $fileId, deleting instead");
  44. $this->deleteById((int)$row['fileid']);
  45. } else {
  46. $query->setParameters([
  47. 'fileid' => $row['fileid'],
  48. 'path' => $row['parent_path'] . '/' . $row['name'],
  49. 'storage' => $row['parent_storage'],
  50. ]);
  51. $query->execute();
  52. }
  53. }
  54. }
  55. if ($fix) {
  56. $this->connection->commit();
  57. }
  58. return self::SUCCESS;
  59. }
  60. private function getFileId(int $storage, string $path) {
  61. $query = $this->connection->getQueryBuilder();
  62. $query->select('fileid')
  63. ->from('filecache')
  64. ->where($query->expr()->eq('storage', $query->createNamedParameter($storage)))
  65. ->andWhere($query->expr()->eq('path_hash', $query->createNamedParameter(md5($path))));
  66. return $query->execute()->fetch(\PDO::FETCH_COLUMN);
  67. }
  68. private function deleteById(int $fileId): void {
  69. $query = $this->connection->getQueryBuilder();
  70. $query->delete('filecache')
  71. ->where($query->expr()->eq('fileid', $query->createNamedParameter($fileId)));
  72. $query->execute();
  73. }
  74. private function findBrokenTreeBits(): array {
  75. $query = $this->connection->getQueryBuilder();
  76. $query->select('f.fileid', 'f.path', 'f.parent', 'f.name')
  77. ->selectAlias('p.path', 'parent_path')
  78. ->selectAlias('p.storage', 'parent_storage')
  79. ->from('filecache', 'f')
  80. ->innerJoin('f', 'filecache', 'p', $query->expr()->eq('f.parent', 'p.fileid'))
  81. ->where($query->expr()->orX(
  82. $query->expr()->andX(
  83. $query->expr()->neq('p.path_hash', $query->createNamedParameter(md5(''))),
  84. $query->expr()->neq('f.path', $query->func()->concat('p.path', $query->func()->concat($query->createNamedParameter('/'), 'f.name')))
  85. ),
  86. $query->expr()->andX(
  87. $query->expr()->eq('p.path_hash', $query->createNamedParameter(md5(''))),
  88. $query->expr()->neq('f.path', 'f.name')
  89. ),
  90. $query->expr()->neq('f.storage', 'p.storage')
  91. ));
  92. return $query->execute()->fetchAll();
  93. }
  94. }