diff options
Diffstat (limited to 'lib/private/files/view.php')
-rw-r--r-- | lib/private/files/view.php | 216 |
1 files changed, 153 insertions, 63 deletions
diff --git a/lib/private/files/view.php b/lib/private/files/view.php index a206eab54e4..73daf8a141f 100644 --- a/lib/private/files/view.php +++ b/lib/private/files/view.php @@ -535,25 +535,38 @@ class View { ) { $path = $this->getRelativePath($absolutePath); + $this->lockFile($path, ILockingProvider::LOCK_SHARED); + $exists = $this->file_exists($path); $run = true; if ($this->shouldEmitHooks($path)) { $this->emit_file_hooks_pre($exists, $path, $run); } if (!$run) { + $this->unlockFile($path, ILockingProvider::LOCK_SHARED); return false; } - $target = $this->fopen($path, 'w'); + + $this->changeLock($path, ILockingProvider::LOCK_EXCLUSIVE); + + /** @var \OC\Files\Storage\Storage $storage */ + list($storage, $internalPath) = $this->resolvePath($path); + $target = $storage->fopen($internalPath, 'w'); if ($target) { - list ($count, $result) = \OC_Helper::streamCopy($data, $target); + list (, $result) = \OC_Helper::streamCopy($data, $target); fclose($target); fclose($data); + $this->updater->update($path); + + $this->unlockFile($path, ILockingProvider::LOCK_EXCLUSIVE); + if ($this->shouldEmitHooks($path) && $result !== false) { $this->emit_file_hooks_post($exists, $path); } return $result; } else { + $this->unlockFile($path, ILockingProvider::LOCK_EXCLUSIVE); return false; } } else { @@ -667,6 +680,19 @@ class View { $result = $storage2->moveFromStorage($storage1, $internalPath1, $internalPath2); } + if ((Cache\Scanner::isPartialFile($path1) && !Cache\Scanner::isPartialFile($path2)) && $result !== false) { + // if it was a rename from a part file to a regular file it was a write and not a rename operation + $this->updater->update($path2); + } else if ($result) { + if ($internalPath1 !== '') { // dont do a cache update for moved mounts + $this->updater->rename($path1, $path2); + } else { // only do etag propagation + $this->getUpdater()->getPropagator()->addChange($path1); + $this->getUpdater()->getPropagator()->addChange($path2); + $this->getUpdater()->getPropagator()->propagateChanges(); + } + } + $this->unlockFile($path1, ILockingProvider::LOCK_EXCLUSIVE); $this->unlockFile($path2, ILockingProvider::LOCK_EXCLUSIVE); @@ -676,19 +702,10 @@ class View { } if ((Cache\Scanner::isPartialFile($path1) && !Cache\Scanner::isPartialFile($path2)) && $result !== false) { - // if it was a rename from a part file to a regular file it was a write and not a rename operation - $this->updater->update($path2); if ($this->shouldEmitHooks()) { $this->emit_file_hooks_post($exists, $path2); } } elseif ($result) { - if ($internalPath1 !== '') { // dont do a cache update for moved mounts - $this->updater->rename($path1, $path2); - } else { // only do etag propagation - $this->getUpdater()->getPropagator()->addChange($path1); - $this->getUpdater()->getPropagator()->addChange($path2); - $this->getUpdater()->getPropagator()->propagateChanges(); - } if ($this->shouldEmitHooks($path1) and $this->shouldEmitHooks($path2)) { \OC_Hook::emit( Filesystem::CLASSNAME, @@ -771,6 +788,7 @@ class View { } else { $result = $storage2->copyFromStorage($storage1, $internalPath1, $internalPath2); } + $this->updater->update($path2); $this->unlockFile($path2, ILockingProvider::LOCK_EXCLUSIVE); @@ -970,7 +988,7 @@ class View { return false; } - if(in_array('write', $hooks) || in_array('delete', $hooks) || in_array('read', $hooks)) { + if (in_array('write', $hooks) || in_array('delete', $hooks) || in_array('read', $hooks)) { // always a shared lock during pre-hooks so the hook can read the file $this->lockFile($path, ILockingProvider::LOCK_SHARED); } @@ -1006,7 +1024,11 @@ class View { $this->updater->update($path, $extraParam); } - if ($operation === 'fopen' and $result) { + if ((in_array('write', $hooks) || in_array('delete', $hooks)) && ($operation !== 'fopen' || $result === false)) { + $this->changeLock($path, ILockingProvider::LOCK_SHARED); + } + + if ($operation === 'fopen' and is_resource($result)) { $result = CallbackWrapper::wrap($result, null, null, function () use ($hooks, $path) { if (in_array('write', $hooks)) { $this->unlockFile($path, ILockingProvider::LOCK_EXCLUSIVE); @@ -1014,12 +1036,8 @@ class View { $this->unlockFile($path, ILockingProvider::LOCK_SHARED); } }); - } else { - if (in_array('write', $hooks) || in_array('delete', $hooks)) { - $this->unlockFile($path, ILockingProvider::LOCK_EXCLUSIVE); - } else if (in_array('read', $hooks)) { - $this->unlockFile($path, ILockingProvider::LOCK_SHARED); - } + } else if (in_array('write', $hooks) || in_array('delete', $hooks) || in_array('read', $hooks)) { + $this->unlockFile($path, ILockingProvider::LOCK_SHARED); } @@ -1063,7 +1081,8 @@ class View { if ($this->fakeRoot === $defaultRoot) { return true; } - return (strlen($this->fakeRoot) > strlen($defaultRoot)) && (substr($this->fakeRoot, 0, strlen($defaultRoot) + 1) === $defaultRoot . '/'); + $fullPath = $this->getAbsolutePath($path); + return (strlen($fullPath) > strlen($defaultRoot)) && (substr($fullPath, 0, strlen($defaultRoot) + 1) === $defaultRoot . '/'); } /** @@ -1073,10 +1092,11 @@ class View { * @return bool */ private function runHooks($hooks, $path, $post = false) { + $relativePath = $path; $path = $this->getHookPath($path); $prefix = ($post) ? 'post_' : ''; $run = true; - if ($this->shouldEmitHooks($path)) { + if ($this->shouldEmitHooks($relativePath)) { foreach ($hooks as $hook) { if ($hook != 'read') { \OC_Hook::emit( @@ -1130,6 +1150,7 @@ class View { if (Cache\Scanner::isPartialFile($path)) { return $this->getPartFileInfo($path); } + $relativePath = $path; $path = Filesystem::normalizePath($this->fakeRoot . '/' . $path); $mount = Filesystem::getMountManager()->find($path); @@ -1139,19 +1160,27 @@ class View { if ($storage) { $cache = $storage->getCache($internalPath); - $data = $cache->get($internalPath); - $watcher = $storage->getWatcher($internalPath); + try { + $this->lockFile($relativePath, ILockingProvider::LOCK_SHARED); + $data = $cache->get($internalPath); + $watcher = $storage->getWatcher($internalPath); - // if the file is not in the cache or needs to be updated, trigger the scanner and reload the data - if (!$data) { - if (!$storage->file_exists($internalPath)) { - return false; + // if the file is not in the cache or needs to be updated, trigger the scanner and reload the data + if (!$data) { + if (!$storage->file_exists($internalPath)) { + $this->unlockFile($relativePath, ILockingProvider::LOCK_SHARED); + return false; + } + $scanner = $storage->getScanner($internalPath); + $scanner->scan($internalPath, Cache\Scanner::SCAN_SHALLOW); + $data = $cache->get($internalPath); + } else if (!Cache\Scanner::isPartialFile($internalPath) && $watcher->checkUpdate($internalPath, $data)) { + $this->updater->propagate($path); + $data = $cache->get($internalPath); } - $scanner = $storage->getScanner($internalPath); - $scanner->scan($internalPath, Cache\Scanner::SCAN_SHALLOW); - $data = $cache->get($internalPath); - } else if (!Cache\Scanner::isPartialFile($internalPath) && $watcher->checkUpdate($internalPath, $data)) { - $this->updater->propagate($path); + $this->unlockFile($relativePath, ILockingProvider::LOCK_SHARED); + } catch (LockedException $e) { + // dont try to update the cache when the file is locked $data = $cache->get($internalPath); } @@ -1213,26 +1242,38 @@ class View { $cache = $storage->getCache($internalPath); $user = \OC_User::getUser(); - $data = $cache->get($internalPath); - $watcher = $storage->getWatcher($internalPath); - if (!$data or $data['size'] === -1) { - if (!$storage->file_exists($internalPath)) { - return array(); - } - $scanner = $storage->getScanner($internalPath); - $scanner->scan($internalPath, Cache\Scanner::SCAN_SHALLOW); - $data = $cache->get($internalPath); - } else if ($watcher->checkUpdate($internalPath, $data)) { - $this->updater->propagate($path); - $data = $cache->get($internalPath); - } - - $folderId = $data['fileid']; /** * @var \OC\Files\FileInfo[] $files */ $files = array(); - $contents = $cache->getFolderContentsById($folderId); //TODO: mimetype_filter + + try { + $this->lockFile($directory, ILockingProvider::LOCK_SHARED); + + $data = $cache->get($internalPath); + $watcher = $storage->getWatcher($internalPath); + if (!$data or $data['size'] === -1) { + if (!$storage->file_exists($internalPath)) { + $this->unlockFile($directory, ILockingProvider::LOCK_SHARED); + return array(); + } + $scanner = $storage->getScanner($internalPath); + $scanner->scan($internalPath, Cache\Scanner::SCAN_SHALLOW); + $data = $cache->get($internalPath); + } else if ($watcher->checkUpdate($internalPath, $data)) { + $this->updater->propagate($path); + $data = $cache->get($internalPath); + } + + $folderId = $data['fileid']; + $contents = $cache->getFolderContentsById($folderId); //TODO: mimetype_filter + + $this->unlockFile($directory, ILockingProvider::LOCK_SHARED); + } catch (LockedException $e) { + // dont try to update the cache when the file is locked + $contents = $cache->getFolderContents($internalPath); + } + foreach ($contents as $content) { if ($content['permissions'] === 0) { $content['permissions'] = $storage->getPermissions($content['path']); @@ -1659,6 +1700,8 @@ class View { } /** + * Lock the given path + * * @param string $path the path of the file to lock, relative to the view * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE * @return bool False if the path is excluded from locking, true otherwise @@ -1666,53 +1709,77 @@ class View { */ private function lockPath($path, $type) { $absolutePath = $this->getAbsolutePath($path); + $absolutePath = Filesystem::normalizePath($absolutePath); if (!$this->shouldLockFile($absolutePath)) { return false; } $mount = $this->getMount($path); if ($mount) { - $mount->getStorage()->acquireLock( - $mount->getInternalPath($absolutePath), - $type, - $this->lockingProvider - ); + try { + $mount->getStorage()->acquireLock( + $mount->getInternalPath($absolutePath), + $type, + $this->lockingProvider + ); + } catch (\OCP\Lock\LockedException $e) { + // rethrow with the a human-readable path + throw new \OCP\Lock\LockedException( + $this->getPathRelativeToFiles($absolutePath), + $e + ); + } } return true; } /** + * Change the lock type + * * @param string $path the path of the file to lock, relative to the view * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE * @return bool False if the path is excluded from locking, true otherwise * @throws \OCP\Lock\LockedException if the path is already locked */ - private function changeLock($path, $type) { + public function changeLock($path, $type) { + $path = Filesystem::normalizePath($path); $absolutePath = $this->getAbsolutePath($path); + $absolutePath = Filesystem::normalizePath($absolutePath); if (!$this->shouldLockFile($absolutePath)) { return false; } $mount = $this->getMount($path); if ($mount) { - $mount->getStorage()->changeLock( - $mount->getInternalPath($absolutePath), - $type, - $this->lockingProvider - ); + try { + $mount->getStorage()->changeLock( + $mount->getInternalPath($absolutePath), + $type, + $this->lockingProvider + ); + } catch (\OCP\Lock\LockedException $e) { + // rethrow with the a human-readable path + throw new \OCP\Lock\LockedException( + $this->getPathRelativeToFiles($absolutePath), + $e + ); + } } return true; } /** + * Unlock the given path + * * @param string $path the path of the file to unlock, relative to the view * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE * @return bool False if the path is excluded from locking, true otherwise */ private function unlockPath($path, $type) { $absolutePath = $this->getAbsolutePath($path); + $absolutePath = Filesystem::normalizePath($absolutePath); if (!$this->shouldLockFile($absolutePath)) { return false; } @@ -1737,9 +1804,8 @@ class View { * @return bool False if the path is excluded from locking, true otherwise */ public function lockFile($path, $type) { - $path = '/' . trim($path, '/'); - $absolutePath = $this->getAbsolutePath($path); + $absolutePath = Filesystem::normalizePath($absolutePath); if (!$this->shouldLockFile($absolutePath)) { return false; } @@ -1762,9 +1828,8 @@ class View { * @return bool False if the path is excluded from locking, true otherwise */ public function unlockFile($path, $type) { - $path = rtrim($path, '/'); - $absolutePath = $this->getAbsolutePath($path); + $absolutePath = Filesystem::normalizePath($absolutePath); if (!$this->shouldLockFile($absolutePath)) { return false; } @@ -1796,4 +1861,29 @@ class View { return true; } + + /** + * Shortens the given absolute path to be relative to + * "$user/files". + * + * @param string $absolutePath absolute path which is under "files" + * + * @return string path relative to "files" with trimmed slashes or null + * if the path was NOT relative to files + * + * @throws \InvalidArgumentException if the given path was not under "files" + * @since 8.1.0 + */ + public function getPathRelativeToFiles($absolutePath) { + $path = Filesystem::normalizePath($absolutePath); + $parts = explode('/', trim($path, '/'), 3); + // "$user", "files", "path/to/dir" + if (!isset($parts[1]) || $parts[1] !== 'files') { + throw new \InvalidArgumentException('$absolutePath must be relative to "files"'); + } + if (isset($parts[2])) { + return $parts[2]; + } + return ''; + } } |