aboutsummaryrefslogtreecommitdiffstats
path: root/core/command/upgrade.php
blob: 0f1b828ba25c4ad7fae0809c425ed416e7413d24 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
<?php
/**
 * @author Andreas Fischer <bantu@owncloud.com>
 * @author Joas Schilling <nickvergessen@owncloud.com>
 * @author Morris Jobke <hey@morrisjobke.de>
 * @author Owen Winkler <a_github@midnightcircus.com>
 * @author Steffen Lindner <mail@steffen-lindner.de>
 * @author Thomas Müller <thomas.mueller@tmit.eu>
 * @author Vincent Petry <pvince81@owncloud.com>
 *
 * @copyright Copyright (c) 2015, ownCloud, Inc.
 * @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/>
 *
 */

namespace OC\Core\Command;

use OC\Console\TimestampFormatter;
use OC\Updater;
use OCP\IConfig;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputOption;

class Upgrade extends Command {

	const ERROR_SUCCESS = 0;
	const ERROR_NOT_INSTALLED = 1;
	const ERROR_MAINTENANCE_MODE = 2;
	const ERROR_UP_TO_DATE = 3;
	const ERROR_INVALID_ARGUMENTS = 4;
	const ERROR_FAILURE = 5;

	/**
	 * @var IConfig
	 */
	private $config;

	/**
	 * @param IConfig $config
	 */
	public function __construct(IConfig $config) {
		parent::__construct();
		$this->config = $config;
	}

	protected function configure() {
		$this
			->setName('upgrade')
			->setDescription('run upgrade routines after installation of a new release. The release has to be installed before.')
			->addOption(
				'--skip-migration-test',
				null,
				InputOption::VALUE_NONE,
				'skips the database schema migration simulation and update directly'
			)
			->addOption(
				'--dry-run',
				null,
				InputOption::VALUE_NONE,
				'only runs the database schema migration simulation, do not actually update'
			)
			->addOption(
				'--no-app-disable',
				null,
				InputOption::VALUE_NONE,
				'skips the disable of third party apps'
			);
	}

	/**
	 * Execute the upgrade command
	 *
	 * @param InputInterface $input input interface
	 * @param OutputInterface $output output interface
	 */
	protected function execute(InputInterface $input, OutputInterface $output) {

		$simulateStepEnabled = true;
		$updateStepEnabled = true;
		$skip3rdPartyAppsDisable = false;

		if ($this->config->getSystemValue('update.skip-migration-test', false)) {
			$output->writeln(
				'<info>"skip-migration-test" is activated via config.php</info>'
			);
			$simulateStepEnabled = false;
		}
		if ($input->getOption('skip-migration-test')) {
			$simulateStepEnabled = false;
		}
	   	if ($input->getOption('dry-run')) {
			$updateStepEnabled = false;
		}
		if ($input->getOption('no-app-disable')) {
			$skip3rdPartyAppsDisable = true;
		}

		if (!$simulateStepEnabled && !$updateStepEnabled) {
			$output->writeln(
				'<error>Only one of "--skip-migration-test" or "--dry-run" ' .
				'can be specified at a time.</error>'
			);
			return self::ERROR_INVALID_ARGUMENTS;
		}

		if(\OC::checkUpgrade(false)) {
			if (OutputInterface::VERBOSITY_NORMAL < $output->getVerbosity()) {
				// Prepend each line with a little timestamp
				$timestampFormatter = new TimestampFormatter($this->config, $output->getFormatter());
				$output->setFormatter($timestampFormatter);
			}

			$self = $this;
			$updater = new Updater(\OC::$server->getHTTPHelper(),
				$this->config);

			$updater->setSimulateStepEnabled($simulateStepEnabled);
			$updater->setUpdateStepEnabled($updateStepEnabled);
			$updater->setSkip3rdPartyAppsDisable($skip3rdPartyAppsDisable);

			$updater->listen('\OC\Updater', 'maintenanceEnabled', function () use($output) {
				$output->writeln('<info>Turned on maintenance mode</info>');
			});
			$updater->listen('\OC\Updater', 'maintenanceDisabled', function () use($output) {
				$output->writeln('<info>Turned off maintenance mode</info>');
			});
			$updater->listen('\OC\Updater', 'maintenanceActive', function () use($output) {
				$output->writeln('<info>Maintenance mode is kept active</info>');
			});
			$updater->listen('\OC\Updater', 'updateEnd',
				function ($success) use($output, $updateStepEnabled, $self) {
					$mode = $updateStepEnabled ? 'Update' : 'Update simulation';
					if ($success) {
						$message = "<info>$mode successful</info>";
					} else {
						$message = "<error>$mode failed</error>";
					}
					$output->writeln($message);
				});
			$updater->listen('\OC\Updater', 'dbUpgrade', function () use($output) {
				$output->writeln('<info>Updated database</info>');
			});
			$updater->listen('\OC\Updater', 'dbSimulateUpgrade', function () use($output) {
				$output->writeln('<info>Checked database schema update</info>');
			});
			$updater->listen('\OC\Updater', 'incompatibleAppDisabled', function ($app) use($output) {
				$output->writeln('<info>Disabled incompatible app: ' . $app . '</info>');
			});
			$updater->listen('\OC\Updater', 'thirdPartyAppDisabled', function ($app) use ($output) {
				$output->writeln('<info>Disabled 3rd-party app: ' . $app . '</info>');
			});
			$updater->listen('\OC\Updater', 'upgradeAppStoreApp', function ($app) use($output) {
				$output->writeln('<info>Update 3rd-party app: ' . $app . '</info>');
			});
			$updater->listen('\OC\Updater', 'repairWarning', function ($app) use($output) {
				$output->writeln('<error>Repair warning: ' . $app . '</error>');
			});
			$updater->listen('\OC\Updater', 'repairError', function ($app) use($output) {
				$output->writeln('<error>Repair error: ' . $app . '</error>');
			});
			$updater->listen('\OC\Updater', 'appUpgradeCheck', function () use ($output) {
				$output->writeln('<info>Checked database schema update for apps</info>');
			});
			$updater->listen('\OC\Updater', 'appUpgradeStarted', function ($app, $version) use ($output) {
				$output->writeln("<info>Updating <$app> ...</info>");
			});
			$updater->listen('\OC\Updater', 'appUpgrade', function ($app, $version) use ($output) {
				$output->writeln("<info>Updated <$app> to $version</info>");
			});
			$updater->listen('\OC\Updater', 'failure', function ($message) use($output, $self) {
				$output->writeln("<error>$message</error>");
			});
			$updater->listen('\OC\Updater', 'setDebugLogLevel', function ($logLevel, $logLevelName) use($output) {
				$output->writeln("<info>Set log level to debug - current level: '$logLevelName'</info>");
			});
			$updater->listen('\OC\Updater', 'resetLogLevel', function ($logLevel, $logLevelName) use($output) {
				$output->writeln("<info>Reset log level to '$logLevelName'</info>");
			});

			if(OutputInterface::VERBOSITY_NORMAL < $output->getVerbosity()) {
				$updater->listen('\OC\Updater', 'repairInfo', function ($message) use($output) {
					$output->writeln('<info>Repair info: ' . $message . '</info>');
				});
				$updater->listen('\OC\Updater', 'repairStep', function ($message) use($output) {
					$output->writeln('<info>Repair step: ' . $message . '</info>');
				});
			}

			$success = $updater->upgrade();

			$this->postUpgradeCheck($input, $output);

			if(!$success) {
				return self::ERROR_FAILURE;
			}

			return self::ERROR_SUCCESS;
		} else if($this->config->getSystemValue('maintenance', false)) {
			//Possible scenario: ownCloud core is updated but an app failed
			$output->writeln('<warning>ownCloud is in maintenance mode</warning>');
			$output->write('<comment>Maybe an upgrade is already in process. Please check the '
				. 'logfile (data/owncloud.log). If you want to re-run the '
				. 'upgrade procedure, remove the "maintenance mode" from '
				. 'config.php and call this script again.</comment>'
				, true);
			return self::ERROR_MAINTENANCE_MODE;
		} else {
			$output->writeln('<info>ownCloud is already latest version</info>');
			return self::ERROR_UP_TO_DATE;
		}
	}

	/**
	 * Perform a post upgrade check (specific to the command line tool)
	 *
	 * @param InputInterface $input input interface
	 * @param OutputInterface $output output interface
	 */
	protected function postUpgradeCheck(InputInterface $input, OutputInterface $output) {
		$trustedDomains = $this->config->getSystemValue('trusted_domains', array());
		if (empty($trustedDomains)) {
			$output->write(
				'<warning>The setting "trusted_domains" could not be ' .
				'set automatically by the upgrade script, ' .
				'please set it manually</warning>'
			);
		}
	}
}
/span>::PERMISSION_CREATE; } if ($this->isReadable($path)) { $permissions |= \OCP\Constants::PERMISSION_READ; } if ($this->isUpdatable($path)) { $permissions |= \OCP\Constants::PERMISSION_UPDATE; } if ($this->isDeletable($path)) { $permissions |= \OCP\Constants::PERMISSION_DELETE; } if ($this->isSharable($path)) { $permissions |= \OCP\Constants::PERMISSION_SHARE; } return $permissions; } public function filemtime($path) { $stat = $this->stat($path); if (isset($stat['mtime']) && $stat['mtime'] > 0) { return $stat['mtime']; } else { return 0; } } public function file_get_contents($path) { $handle = $this->fopen($path, 'r'); if (!$handle) { return false; } $data = stream_get_contents($handle); fclose($handle); return $data; } public function file_put_contents($path, $data) { $handle = $this->fopen($path, 'w'); if (!$handle) { return false; } $this->removeCachedFile($path); $count = fwrite($handle, $data); fclose($handle); return $count; } public function rename($source, $target) { $this->remove($target); $this->removeCachedFile($source); return $this->copy($source, $target) and $this->remove($source); } public function copy($source, $target) { if ($this->is_dir($source)) { $this->remove($target); $dir = $this->opendir($source); $this->mkdir($target); while (($file = readdir($dir)) !== false) { if (!Filesystem::isIgnoredDir($file)) { if (!$this->copy($source . '/' . $file, $target . '/' . $file)) { closedir($dir); return false; } } } closedir($dir); return true; } else { $sourceStream = $this->fopen($source, 'r'); $targetStream = $this->fopen($target, 'w'); [, $result] = \OC_Helper::streamCopy($sourceStream, $targetStream); if (!$result) { \OCP\Server::get(LoggerInterface::class)->warning("Failed to write data while copying $source to $target"); } $this->removeCachedFile($target); return $result; } } public function getMimeType($path) { if ($this->is_dir($path)) { return 'httpd/unix-directory'; } elseif ($this->file_exists($path)) { return \OC::$server->getMimeTypeDetector()->detectPath($path); } else { return false; } } public function hash($type, $path, $raw = false) { $fh = $this->fopen($path, 'rb'); $ctx = hash_init($type); hash_update_stream($ctx, $fh); fclose($fh); return hash_final($ctx, $raw); } public function search($query) { return $this->searchInDir($query); } public function getLocalFile($path) { return $this->getCachedFile($path); } /** * @param string $path * @param string $target */ private function addLocalFolder($path, $target) { $dh = $this->opendir($path); if (is_resource($dh)) { while (($file = readdir($dh)) !== false) { if (!\OC\Files\Filesystem::isIgnoredDir($file)) { if ($this->is_dir($path . '/' . $file)) { mkdir($target . '/' . $file); $this->addLocalFolder($path . '/' . $file, $target . '/' . $file); } else { $tmp = $this->toTmpFile($path . '/' . $file); rename($tmp, $target . '/' . $file); } } } } } /** * @param string $query * @param string $dir * @return array */ protected function searchInDir($query, $dir = '') { $files = []; $dh = $this->opendir($dir); if (is_resource($dh)) { while (($item = readdir($dh)) !== false) { if (\OC\Files\Filesystem::isIgnoredDir($item)) { continue; } if (strstr(strtolower($item), strtolower($query)) !== false) { $files[] = $dir . '/' . $item; } if ($this->is_dir($dir . '/' . $item)) { $files = array_merge($files, $this->searchInDir($query, $dir . '/' . $item)); } } } closedir($dh); return $files; } /** * Check if a file or folder has been updated since $time * * The method is only used to check if the cache needs to be updated. Storage backends that don't support checking * the mtime should always return false here. As a result storage implementations that always return false expect * exclusive access to the backend and will not pick up files that have been added in a way that circumvents * Nextcloud filesystem. * * @param string $path * @param int $time * @return bool */ public function hasUpdated($path, $time) { return $this->filemtime($path) > $time; } protected function getCacheDependencies(): CacheDependencies { static $dependencies = null; if (!$dependencies) { $dependencies = Server::get(CacheDependencies::class); } return $dependencies; } public function getCache($path = '', $storage = null) { if (!$storage) { $storage = $this; } if (!isset($storage->cache)) { $storage->cache = new Cache($storage, $this->getCacheDependencies()); } return $storage->cache; } public function getScanner($path = '', $storage = null) { if (!$storage) { $storage = $this; } if (!isset($storage->scanner)) { $storage->scanner = new Scanner($storage); } return $storage->scanner; } public function getWatcher($path = '', $storage = null) { if (!$storage) { $storage = $this; } if (!isset($this->watcher)) { $this->watcher = new Watcher($storage); $globalPolicy = \OC::$server->getConfig()->getSystemValue('filesystem_check_changes', Watcher::CHECK_NEVER); $this->watcher->setPolicy((int)$this->getMountOption('filesystem_check_changes', $globalPolicy)); } return $this->watcher; } /** * get a propagator instance for the cache * * @param \OC\Files\Storage\Storage (optional) the storage to pass to the watcher * @return \OC\Files\Cache\Propagator */ public function getPropagator($storage = null) { if (!$storage) { $storage = $this; } if (!isset($storage->propagator)) { $config = \OC::$server->getSystemConfig(); $storage->propagator = new Propagator($storage, \OC::$server->getDatabaseConnection(), ['appdata_' . $config->getValue('instanceid')]); } return $storage->propagator; } public function getUpdater($storage = null) { if (!$storage) { $storage = $this; } if (!isset($storage->updater)) { $storage->updater = new Updater($storage); } return $storage->updater; } public function getStorageCache($storage = null) { return $this->getCache($storage)->getStorageCache(); } /** * get the owner of a path * * @param string $path The path to get the owner * @return string|false uid or false */ public function getOwner($path) { if ($this->owner === null) { $this->owner = \OC_User::getUser(); } return $this->owner; } /** * get the ETag for a file or folder * * @param string $path * @return string|false */ public function getETag($path) { return uniqid(); } /** * clean a path, i.e. remove all redundant '.' and '..' * making sure that it can't point to higher than '/' * * @param string $path The path to clean * @return string cleaned path */ public function cleanPath($path) { if (strlen($path) == 0 or $path[0] != '/') { $path = '/' . $path; } $output = []; foreach (explode('/', $path) as $chunk) { if ($chunk == '..') { array_pop($output); } elseif ($chunk == '.') { } else { $output[] = $chunk; } } return implode('/', $output); } /** * Test a storage for availability * * @return bool */ public function test() { try { if ($this->stat('')) { return true; } \OC::$server->get(LoggerInterface::class)->info('External storage not available: stat() failed'); return false; } catch (\Exception $e) { \OC::$server->get(LoggerInterface::class)->warning( 'External storage not available: ' . $e->getMessage(), ['exception' => $e] ); return false; } } /** * get the free space in the storage * * @param string $path * @return int|float|false */ public function free_space($path) { return \OCP\Files\FileInfo::SPACE_UNKNOWN; } /** * {@inheritdoc} */ public function isLocal() { // the common implementation returns a temporary file by // default, which is not local return false; } /** * Check if the storage is an instance of $class or is a wrapper for a storage that is an instance of $class * * @param string $class * @return bool */ public function instanceOfStorage($class) { if (ltrim($class, '\\') === 'OC\Files\Storage\Shared') { // FIXME Temporary fix to keep existing checks working $class = '\OCA\Files_Sharing\SharedStorage'; } return is_a($this, $class); } /** * A custom storage implementation can return an url for direct download of a give file. * * For now the returned array can hold the parameter url - in future more attributes might follow. * * @param string $path * @return array|false */ public function getDirectDownload($path) { return []; } /** * @inheritdoc * @throws InvalidPathException */ public function verifyPath($path, $fileName) { $this->getFilenameValidator() ->validateFilename($fileName); // verify also the path is valid if ($path && $path !== '/' && $path !== '.') { try { $this->verifyPath(dirname($path), basename($path)); } catch (InvalidPathException $e) { // Ignore invalid file type exceptions on directories if ($e->getCode() !== FilenameValidator::INVALID_FILE_TYPE) { $l = \OCP\Util::getL10N('lib'); throw new InvalidPathException($l->t('Invalid parent path'), previous: $e); } } } } /** * Get the filename validator * (cached for performance) */ protected function getFilenameValidator(): IFilenameValidator { if ($this->filenameValidator === null) { $this->filenameValidator = \OCP\Server::get(IFilenameValidator::class); } return $this->filenameValidator; } /** * @param array $options */ public function setMountOptions(array $options) { $this->mountOptions = $options; } /** * @param string $name * @param mixed $default * @return mixed */ public function getMountOption($name, $default = null) { return $this->mountOptions[$name] ?? $default; } /** * @param IStorage $sourceStorage * @param string $sourceInternalPath * @param string $targetInternalPath * @param bool $preserveMtime * @return bool */ public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = false) { if ($sourceStorage === $this) { return $this->copy($sourceInternalPath, $targetInternalPath); } if ($sourceStorage->is_dir($sourceInternalPath)) { $dh = $sourceStorage->opendir($sourceInternalPath); $result = $this->mkdir($targetInternalPath); if (is_resource($dh)) { $result = true; while ($result and ($file = readdir($dh)) !== false) { if (!Filesystem::isIgnoredDir($file)) { $result &= $this->copyFromStorage($sourceStorage, $sourceInternalPath . '/' . $file, $targetInternalPath . '/' . $file); } } } } else { $source = $sourceStorage->fopen($sourceInternalPath, 'r'); $result = false; if ($source) { try { $this->writeStream($targetInternalPath, $source); $result = true; } catch (\Exception $e) { \OC::$server->get(LoggerInterface::class)->warning('Failed to copy stream to storage', ['exception' => $e]); } } if ($result && $preserveMtime) { $mtime = $sourceStorage->filemtime($sourceInternalPath); $this->touch($targetInternalPath, is_int($mtime) ? $mtime : null); } if (!$result) { // delete partially written target file $this->unlink($targetInternalPath); // delete cache entry that was created by fopen $this->getCache()->remove($targetInternalPath); } } return (bool)$result; } /** * Check if a storage is the same as the current one, including wrapped storages * * @param IStorage $storage * @return bool */ private function isSameStorage(IStorage $storage): bool { while ($storage->instanceOfStorage(Wrapper::class)) { /** * @var Wrapper $storage */ $storage = $storage->getWrapperStorage(); } return $storage === $this; } /** * @param IStorage $sourceStorage * @param string $sourceInternalPath * @param string $targetInternalPath * @return bool */ public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) { if ($this->isSameStorage($sourceStorage)) { // resolve any jailed paths while ($sourceStorage->instanceOfStorage(Jail::class)) { /** * @var Jail $sourceStorage */ $sourceInternalPath = $sourceStorage->getUnjailedPath($sourceInternalPath); $sourceStorage = $sourceStorage->getUnjailedStorage(); } return $this->rename($sourceInternalPath, $targetInternalPath); } if (!$sourceStorage->isDeletable($sourceInternalPath)) { return false; } $result = $this->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath, true); if ($result) { if ($sourceStorage->is_dir($sourceInternalPath)) { $result = $sourceStorage->rmdir($sourceInternalPath); } else { $result = $sourceStorage->unlink($sourceInternalPath); } } return $result; } /** * @inheritdoc */ public function getMetaData($path) { if (Filesystem::isFileBlacklisted($path)) { throw new ForbiddenException('Invalid path: ' . $path, false); } $permissions = $this->getPermissions($path); if (!$permissions & \OCP\Constants::PERMISSION_READ) { //can't read, nothing we can do return null; } $data = []; $data['mimetype'] = $this->getMimeType($path); $data['mtime'] = $this->filemtime($path); if ($data['mtime'] === false) { $data['mtime'] = time(); } if ($data['mimetype'] == 'httpd/unix-directory') { $data['size'] = -1; //unknown } else { $data['size'] = $this->filesize($path); } $data['etag'] = $this->getETag($path); $data['storage_mtime'] = $data['mtime']; $data['permissions'] = $permissions; $data['name'] = basename($path); return $data; } /** * @param string $path * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE * @param \OCP\Lock\ILockingProvider $provider * @throws \OCP\Lock\LockedException */ public function acquireLock($path, $type, ILockingProvider $provider) { $logger = $this->getLockLogger(); if ($logger) { $typeString = ($type === ILockingProvider::LOCK_SHARED) ? 'shared' : 'exclusive'; $logger->info( sprintf( 'acquire %s lock on "%s" on storage "%s"', $typeString, $path, $this->getId() ), [ 'app' => 'locking', ] ); } try { $provider->acquireLock('files/' . md5($this->getId() . '::' . trim($path, '/')), $type, $this->getId() . '::' . $path); } catch (LockedException $e) { $e = new LockedException($e->getPath(), $e, $e->getExistingLock(), $path); if ($logger) { $logger->info($e->getMessage(), ['exception' => $e]); } throw $e; } } /** * @param string $path * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE * @param \OCP\Lock\ILockingProvider $provider * @throws \OCP\Lock\LockedException */ public function releaseLock($path, $type, ILockingProvider $provider) { $logger = $this->getLockLogger(); if ($logger) { $typeString = ($type === ILockingProvider::LOCK_SHARED) ? 'shared' : 'exclusive'; $logger->info( sprintf( 'release %s lock on "%s" on storage "%s"', $typeString, $path, $this->getId() ), [ 'app' => 'locking', ] ); } try { $provider->releaseLock('files/' . md5($this->getId() . '::' . trim($path, '/')), $type); } catch (LockedException $e) { $e = new LockedException($e->getPath(), $e, $e->getExistingLock(), $path); if ($logger) { $logger->info($e->getMessage(), ['exception' => $e]); } throw $e; } } /** * @param string $path * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE * @param \OCP\Lock\ILockingProvider $provider * @throws \OCP\Lock\LockedException */ public function changeLock($path, $type, ILockingProvider $provider) { $logger = $this->getLockLogger(); if ($logger) { $typeString = ($type === ILockingProvider::LOCK_SHARED) ? 'shared' : 'exclusive'; $logger->info( sprintf( 'change lock on "%s" to %s on storage "%s"', $path, $typeString, $this->getId() ), [ 'app' => 'locking', ] ); } try { $provider->changeLock('files/' . md5($this->getId() . '::' . trim($path, '/')), $type); } catch (LockedException $e) { $e = new LockedException($e->getPath(), $e, $e->getExistingLock(), $path); if ($logger) { $logger->info($e->getMessage(), ['exception' => $e]); } throw $e; } } private function getLockLogger(): ?LoggerInterface { if (is_null($this->shouldLogLocks)) { $this->shouldLogLocks = \OC::$server->getConfig()->getSystemValueBool('filelocking.debug', false); $this->logger = $this->shouldLogLocks ? \OC::$server->get(LoggerInterface::class) : null; } return $this->logger; } /** * @return array [ available, last_checked ] */ public function getAvailability() { return $this->getStorageCache()->getAvailability(); } /** * @param bool $isAvailable */ public function setAvailability($isAvailable) { $this->getStorageCache()->setAvailability($isAvailable); } /** * Allow setting the storage owner * * This can be used for storages that do not have a dedicated owner, where we want to * pass the user that we setup the mountpoint for along to the storage layer * * @param string|null $user * @return void */ public function setOwner(?string $user): void { $this->owner = $user; } /** * @return bool */ public function needsPartFile() { return true; } /** * fallback implementation * * @param string $path * @param resource $stream * @param int $size * @return int */ public function writeStream(string $path, $stream, ?int $size = null): int { $target = $this->fopen($path, 'w'); if (!$target) { throw new GenericFileException("Failed to open $path for writing"); } try { [$count, $result] = \OC_Helper::streamCopy($stream, $target); if (!$result) { throw new GenericFileException('Failed to copy stream'); } } finally { fclose($target); fclose($stream); } return $count; } public function getDirectoryContent($directory): \Traversable { $dh = $this->opendir($directory); if ($dh === false) { throw new StorageNotAvailableException('Directory listing failed'); } if (is_resource($dh)) { $basePath = rtrim($directory, '/'); while (($file = readdir($dh)) !== false) { if (!Filesystem::isIgnoredDir($file)) { $childPath = $basePath . '/' . trim($file, '/'); $metadata = $this->getMetaData($childPath); if ($metadata !== null) { yield $metadata; } } } } } }