mirror of
https://github.com/nextcloud/server.git
synced 2026-06-09 00:32:29 -04:00
Merge pull request #16912 from owncloud/webdav-smalltransferlockfix
Webdav PUT small file lock must be shared during hooks
This commit is contained in:
commit
b6165b6865
4 changed files with 132 additions and 3 deletions
|
|
@ -114,7 +114,7 @@ class File extends Node implements IFile {
|
|||
}
|
||||
|
||||
try {
|
||||
$this->fileView->lockFile($this->path, ILockingProvider::LOCK_EXCLUSIVE);
|
||||
$this->fileView->lockFile($this->path, ILockingProvider::LOCK_SHARED);
|
||||
} catch (LockedException $e) {
|
||||
throw new FileLocked($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
|
|
@ -192,6 +192,12 @@ class File extends Node implements IFile {
|
|||
));
|
||||
}
|
||||
|
||||
try {
|
||||
$this->fileView->changeLock($this->path, ILockingProvider::LOCK_EXCLUSIVE);
|
||||
} catch (LockedException $e) {
|
||||
throw new FileLocked($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
|
||||
if ($needsPartFile) {
|
||||
// rename to correct path
|
||||
try {
|
||||
|
|
@ -213,6 +219,12 @@ class File extends Node implements IFile {
|
|||
// since we skipped the view we need to scan and emit the hooks ourselves
|
||||
$partStorage->getScanner()->scanFile($internalPath);
|
||||
|
||||
try {
|
||||
$this->fileView->changeLock($this->path, ILockingProvider::LOCK_SHARED);
|
||||
} catch (LockedException $e) {
|
||||
throw new FileLocked($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
|
||||
if ($view) {
|
||||
$this->fileView->getUpdater()->propagate($hookPath);
|
||||
if (!$exists) {
|
||||
|
|
@ -241,7 +253,7 @@ class File extends Node implements IFile {
|
|||
throw new ServiceUnavailable("Failed to check file size: " . $e->getMessage());
|
||||
}
|
||||
|
||||
$this->fileView->unlockFile($this->path, ILockingProvider::LOCK_EXCLUSIVE);
|
||||
$this->fileView->unlockFile($this->path, ILockingProvider::LOCK_SHARED);
|
||||
|
||||
return '"' . $this->info->getEtag() . '"';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1683,12 +1683,14 @@ class View {
|
|||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
$absolutePath = $this->getAbsolutePath($path);
|
||||
if (!$this->shouldLockFile($absolutePath)) {
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -404,4 +404,84 @@ class File extends \Test\TestCase {
|
|||
$params[Filesystem::signal_param_path]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether locks are set before and after the operation
|
||||
*/
|
||||
public function testPutLocking() {
|
||||
$view = new \OC\Files\View('/' . $this->user . '/files/');
|
||||
|
||||
$path = 'test-locking.txt';
|
||||
$info = new \OC\Files\FileInfo(
|
||||
'/' . $this->user . '/files/' . $path,
|
||||
null,
|
||||
null,
|
||||
['permissions' => \OCP\Constants::PERMISSION_ALL],
|
||||
null
|
||||
);
|
||||
|
||||
$file = new \OC\Connector\Sabre\File($view, $info);
|
||||
|
||||
$this->assertFalse(
|
||||
$this->isFileLocked($view, $path, \OCP\Lock\ILockingProvider::LOCK_SHARED),
|
||||
'File unlocked before put'
|
||||
);
|
||||
$this->assertFalse(
|
||||
$this->isFileLocked($view, $path, \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE),
|
||||
'File unlocked before put'
|
||||
);
|
||||
|
||||
$wasLockedPre = false;
|
||||
$wasLockedPost = false;
|
||||
$eventHandler = $this->getMockBuilder('\stdclass')
|
||||
->setMethods(['writeCallback', 'postWriteCallback'])
|
||||
->getMock();
|
||||
|
||||
// both pre and post hooks might need access to the file,
|
||||
// so only shared lock is acceptable
|
||||
$eventHandler->expects($this->once())
|
||||
->method('writeCallback')
|
||||
->will($this->returnCallback(
|
||||
function() use ($view, $path, &$wasLockedPre){
|
||||
$wasLockedPre = $this->isFileLocked($view, $path, \OCP\Lock\ILockingProvider::LOCK_SHARED);
|
||||
$wasLockedPre = $wasLockedPre && !$this->isFileLocked($view, $path, \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE);
|
||||
}
|
||||
));
|
||||
$eventHandler->expects($this->once())
|
||||
->method('postWriteCallback')
|
||||
->will($this->returnCallback(
|
||||
function() use ($view, $path, &$wasLockedPost){
|
||||
$wasLockedPost = $this->isFileLocked($view, $path, \OCP\Lock\ILockingProvider::LOCK_SHARED);
|
||||
$wasLockedPost = $wasLockedPost && !$this->isFileLocked($view, $path, \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE);
|
||||
}
|
||||
));
|
||||
|
||||
\OCP\Util::connectHook(
|
||||
Filesystem::CLASSNAME,
|
||||
Filesystem::signal_write,
|
||||
$eventHandler,
|
||||
'writeCallback'
|
||||
);
|
||||
\OCP\Util::connectHook(
|
||||
Filesystem::CLASSNAME,
|
||||
Filesystem::signal_post_write,
|
||||
$eventHandler,
|
||||
'postWriteCallback'
|
||||
);
|
||||
|
||||
$this->assertNotEmpty($file->put($this->getStream('test data')));
|
||||
|
||||
$this->assertTrue($wasLockedPre, 'File was locked during pre-hooks');
|
||||
$this->assertTrue($wasLockedPost, 'File was locked during post-hooks');
|
||||
|
||||
$this->assertFalse(
|
||||
$this->isFileLocked($view, $path, \OCP\Lock\ILockingProvider::LOCK_SHARED),
|
||||
'File unlocked after put'
|
||||
);
|
||||
$this->assertFalse(
|
||||
$this->isFileLocked($view, $path, \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE),
|
||||
'File unlocked after put'
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -242,4 +242,39 @@ abstract class TestCase extends \PHPUnit_Framework_TestCase {
|
|||
\OC_Util::setupFS($user);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given path is locked with a given type
|
||||
*
|
||||
* @param \OC\Files\View $view view
|
||||
* @param string $path path to check
|
||||
* @param int $type lock type
|
||||
*
|
||||
* @return boolean true if the file is locked with the
|
||||
* given type, false otherwise
|
||||
*/
|
||||
protected function isFileLocked($view, $path, $type) {
|
||||
// Note: this seems convoluted but is necessary because
|
||||
// the format of the lock key depends on the storage implementation
|
||||
// (in our case mostly md5)
|
||||
|
||||
if ($type === \OCP\Lock\ILockingProvider::LOCK_SHARED) {
|
||||
// to check if the file has a shared lock, try acquiring an exclusive lock
|
||||
$checkType = \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE;
|
||||
} else {
|
||||
// a shared lock cannot be set if exclusive lock is in place
|
||||
$checkType = \OCP\Lock\ILockingProvider::LOCK_SHARED;
|
||||
}
|
||||
try {
|
||||
$view->lockFile($path, $checkType);
|
||||
// no exception, which means the lock of $type is not set
|
||||
// clean up
|
||||
$view->unlockFile($path, $checkType);
|
||||
return false;
|
||||
} catch (\OCP\Lock\LockedException $e) {
|
||||
// we could not acquire the counter-lock, which means
|
||||
// the lock of $type was in place
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue