aboutsummaryrefslogtreecommitdiffstats
path: root/lib/private/Files/Utils
diff options
context:
space:
mode:
Diffstat (limited to 'lib/private/Files/Utils')
-rw-r--r--lib/private/Files/Utils/PathHelper.php26
-rw-r--r--lib/private/Files/Utils/Scanner.php146
2 files changed, 87 insertions, 85 deletions
diff --git a/lib/private/Files/Utils/PathHelper.php b/lib/private/Files/Utils/PathHelper.php
index 07985e884ce..db1294bcc10 100644
--- a/lib/private/Files/Utils/PathHelper.php
+++ b/lib/private/Files/Utils/PathHelper.php
@@ -2,25 +2,9 @@
declare(strict_types=1);
/**
- * @copyright Copyright (c) 2022 Robin Appelman <robin@icewind.nl>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
-
namespace OC\Files\Utils;
class PathHelper {
@@ -37,7 +21,7 @@ class PathHelper {
}
if ($path === $root) {
return '/';
- } elseif (strpos($path, $root . '/') !== 0) {
+ } elseif (!str_starts_with($path, $root . '/')) {
return null;
} else {
$path = substr($path, strlen($root));
@@ -53,6 +37,8 @@ class PathHelper {
if ($path === '' or $path === '/') {
return '/';
}
+ // No null bytes
+ $path = str_replace(chr(0), '', $path);
//no windows style slashes
$path = str_replace('\\', '/', $path);
//add leading slash
@@ -60,7 +46,7 @@ class PathHelper {
$path = '/' . $path;
}
//remove duplicate slashes
- while (strpos($path, '//') !== false) {
+ while (str_contains($path, '//')) {
$path = str_replace('//', '/', $path);
}
//remove trailing slash
diff --git a/lib/private/Files/Utils/Scanner.php b/lib/private/Files/Utils/Scanner.php
index dc220bc710d..576cb66b3cf 100644
--- a/lib/private/Files/Utils/Scanner.php
+++ b/lib/private/Files/Utils/Scanner.php
@@ -1,37 +1,16 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Blaok <i@blaok.me>
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Jörn Friedrich Dreyer <jfd@butonic.de>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Olivier Paroz <github@oparoz.com>
- * @author Robin Appelman <robin@icewind.nl>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- * @author Thomas Müller <thomas.mueller@tmit.eu>
- * @author Vincent Petry <vincent@nextcloud.com>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Files\Utils;
use OC\Files\Cache\Cache;
use OC\Files\Filesystem;
use OC\Files\Storage\FailedStorage;
+use OC\Files\Storage\Home;
use OC\ForbiddenException;
use OC\Hooks\PublicEmitter;
use OC\Lock\DBLockingProvider;
@@ -39,15 +18,18 @@ use OCA\Files_Sharing\SharedStorage;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\Files\Events\BeforeFileScannedEvent;
use OCP\Files\Events\BeforeFolderScannedEvent;
-use OCP\Files\Events\NodeAddedToCache;
use OCP\Files\Events\FileCacheUpdated;
-use OCP\Files\Events\NodeRemovedFromCache;
use OCP\Files\Events\FileScannedEvent;
use OCP\Files\Events\FolderScannedEvent;
+use OCP\Files\Events\NodeAddedToCache;
+use OCP\Files\Events\NodeRemovedFromCache;
+use OCP\Files\Mount\IMountPoint;
use OCP\Files\NotFoundException;
use OCP\Files\Storage\IStorage;
use OCP\Files\StorageNotAvailableException;
use OCP\IDBConnection;
+use OCP\Lock\ILockingProvider;
+use OCP\Lock\LockedException;
use Psr\Log\LoggerInterface;
/**
@@ -98,14 +80,14 @@ class Scanner extends PublicEmitter {
$this->dispatcher = $dispatcher;
$this->logger = $logger;
// when DB locking is used, no DB transactions will be used
- $this->useTransaction = !(\OC::$server->getLockingProvider() instanceof DBLockingProvider);
+ $this->useTransaction = !(\OC::$server->get(ILockingProvider::class) instanceof DBLockingProvider);
}
/**
* get all storages for $dir
*
* @param string $dir
- * @return \OC\Files\Mount\MountPoint[]
+ * @return array<string, IMountPoint>
*/
protected function getMounts($dir) {
//TODO: move to the node based fileapi once that's done
@@ -116,8 +98,9 @@ class Scanner extends PublicEmitter {
$mounts = $mountManager->findIn($dir);
$mounts[] = $mountManager->find($dir);
$mounts = array_reverse($mounts); //start with the mount of $dir
+ $mountPoints = array_map(fn ($mount) => $mount->getMountPoint(), $mounts);
- return $mounts;
+ return array_combine($mountPoints, $mounts);
}
/**
@@ -126,6 +109,7 @@ class Scanner extends PublicEmitter {
* @param \OC\Files\Mount\MountPoint $mount
*/
protected function attachListener($mount) {
+ /** @var \OC\Files\Cache\Scanner $scanner */
$scanner = $mount->getStorage()->getScanner();
$scanner->listen('\OC\Files\Cache\Scanner', 'scanFile', function ($path) use ($mount) {
$this->emit('\OC\Files\Utils\Scanner', 'scanFile', [$mount->getMountPoint() . $path]);
@@ -154,33 +138,38 @@ class Scanner extends PublicEmitter {
public function backgroundScan($dir) {
$mounts = $this->getMounts($dir);
foreach ($mounts as $mount) {
- $storage = $mount->getStorage();
- if (is_null($storage)) {
- continue;
- }
+ try {
+ $storage = $mount->getStorage();
+ if (is_null($storage)) {
+ continue;
+ }
- // don't bother scanning failed storages (shortcut for same result)
- if ($storage->instanceOfStorage(FailedStorage::class)) {
- continue;
- }
+ // don't bother scanning failed storages (shortcut for same result)
+ if ($storage->instanceOfStorage(FailedStorage::class)) {
+ continue;
+ }
- $scanner = $storage->getScanner();
- $this->attachListener($mount);
+ /** @var \OC\Files\Cache\Scanner $scanner */
+ $scanner = $storage->getScanner();
+ $this->attachListener($mount);
- $scanner->listen('\OC\Files\Cache\Scanner', 'removeFromCache', function ($path) use ($storage) {
- $this->triggerPropagator($storage, $path);
- });
- $scanner->listen('\OC\Files\Cache\Scanner', 'updateCache', function ($path) use ($storage) {
- $this->triggerPropagator($storage, $path);
- });
- $scanner->listen('\OC\Files\Cache\Scanner', 'addToCache', function ($path) use ($storage) {
- $this->triggerPropagator($storage, $path);
- });
+ $scanner->listen('\OC\Files\Cache\Scanner', 'removeFromCache', function ($path) use ($storage) {
+ $this->triggerPropagator($storage, $path);
+ });
+ $scanner->listen('\OC\Files\Cache\Scanner', 'updateCache', function ($path) use ($storage) {
+ $this->triggerPropagator($storage, $path);
+ });
+ $scanner->listen('\OC\Files\Cache\Scanner', 'addToCache', function ($path) use ($storage) {
+ $this->triggerPropagator($storage, $path);
+ });
- $propagator = $storage->getPropagator();
- $propagator->beginBatch();
- $scanner->backgroundScan();
- $propagator->commitBatch();
+ $propagator = $storage->getPropagator();
+ $propagator->beginBatch();
+ $scanner->backgroundScan();
+ $propagator->commitBatch();
+ } catch (\Exception $e) {
+ $this->logger->error("Error while trying to scan mount as {$mount->getMountPoint()}:" . $e->getMessage(), ['exception' => $e, 'app' => 'files']);
+ }
}
}
@@ -191,7 +180,7 @@ class Scanner extends PublicEmitter {
* @throws ForbiddenException
* @throws NotFoundException
*/
- public function scan($dir = '', $recursive = \OC\Files\Cache\Scanner::SCAN_RECURSIVE, callable $mountFilter = null) {
+ public function scan($dir = '', $recursive = \OC\Files\Cache\Scanner::SCAN_RECURSIVE, ?callable $mountFilter = null) {
if (!Filesystem::isValidPath($dir)) {
throw new \InvalidArgumentException('Invalid path to scan');
}
@@ -211,13 +200,27 @@ class Scanner extends PublicEmitter {
}
// if the home storage isn't writable then the scanner is run as the wrong user
- if ($storage->instanceOfStorage('\OC\Files\Storage\Home') and
- (!$storage->isCreatable('') or !$storage->isCreatable('files'))
- ) {
- if ($storage->is_dir('files')) {
- throw new ForbiddenException();
- } else {// if the root exists in neither the cache nor the storage the user isn't setup yet
- break;
+ if ($storage->instanceOfStorage(Home::class)) {
+ /** @var Home $storage */
+ foreach (['', 'files'] as $path) {
+ if (!$storage->isCreatable($path)) {
+ $fullPath = $storage->getSourcePath($path);
+ if (isset($mounts[$mount->getMountPoint() . $path . '/'])) {
+ // /<user>/files is overwritten by a mountpoint, so this check is irrelevant
+ break;
+ } elseif (!$storage->is_dir($path) && $storage->getCache()->inCache($path)) {
+ throw new NotFoundException("User folder $fullPath exists in cache but not on disk");
+ } elseif ($storage->is_dir($path)) {
+ $ownerUid = fileowner($fullPath);
+ $owner = posix_getpwuid($ownerUid);
+ $owner = $owner['name'] ?? $ownerUid;
+ $permissions = decoct(fileperms($fullPath));
+ throw new ForbiddenException("User folder $fullPath is not writable, folders is owned by $owner and has mode $permissions");
+ } else {
+ // if the root exists in neither the cache nor the storage the user isn't setup yet
+ break 2;
+ }
+ }
}
}
@@ -226,6 +229,7 @@ class Scanner extends PublicEmitter {
continue;
}
$relativePath = $mount->getInternalPath($dir);
+ /** @var \OC\Files\Cache\Scanner $scanner */
$scanner = $storage->getScanner();
$scanner->setUseTransactions(false);
$this->attachListener($mount);
@@ -238,9 +242,13 @@ class Scanner extends PublicEmitter {
$this->postProcessEntry($storage, $path);
$this->dispatcher->dispatchTyped(new FileCacheUpdated($storage, $path));
});
- $scanner->listen('\OC\Files\Cache\Scanner', 'addToCache', function ($path) use ($storage) {
+ $scanner->listen('\OC\Files\Cache\Scanner', 'addToCache', function ($path, $storageId, $data, $fileId) use ($storage) {
$this->postProcessEntry($storage, $path);
- $this->dispatcher->dispatchTyped(new NodeAddedToCache($storage, $path));
+ if ($fileId) {
+ $this->dispatcher->dispatchTyped(new FileCacheUpdated($storage, $path));
+ } else {
+ $this->dispatcher->dispatchTyped(new NodeAddedToCache($storage, $path));
+ }
});
if (!$storage->file_exists($relativePath)) {
@@ -253,7 +261,15 @@ class Scanner extends PublicEmitter {
try {
$propagator = $storage->getPropagator();
$propagator->beginBatch();
- $scanner->scan($relativePath, $recursive, \OC\Files\Cache\Scanner::REUSE_ETAG | \OC\Files\Cache\Scanner::REUSE_SIZE);
+ try {
+ $scanner->scan($relativePath, $recursive, \OC\Files\Cache\Scanner::REUSE_ETAG | \OC\Files\Cache\Scanner::REUSE_SIZE);
+ } catch (LockedException $e) {
+ if (is_string($e->getReadablePath()) && str_starts_with($e->getReadablePath(), 'scanner::')) {
+ throw new LockedException("scanner::$dir", $e, $e->getExistingLock());
+ } else {
+ throw $e;
+ }
+ }
$cache = $storage->getCache();
if ($cache instanceof Cache) {
// only re-calculate for the root folder we scanned, anything below that is taken care of by the scanner