From 3232d41d1f8de07f734b3c63b402186885d0a8d9 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Mon, 27 Jul 2026 22:25:29 +0200 Subject: [PATCH 1/2] chore: add more info to the logging around failing to move part file Signed-off-by: Robin Appelman --- apps/dav/lib/Connector/Sabre/File.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/apps/dav/lib/Connector/Sabre/File.php b/apps/dav/lib/Connector/Sabre/File.php index e35f466e63120..ae68770d9a90c 100644 --- a/apps/dav/lib/Connector/Sabre/File.php +++ b/apps/dav/lib/Connector/Sabre/File.php @@ -322,7 +322,14 @@ public function put($data) { $renameOkay = $storage->moveFromStorage($partStorage, $internalPartPath, $internalPath); $fileExists = $storage->file_exists($internalPath); if ($renameOkay === false || $fileExists === false) { - Server::get(LoggerInterface::class)->error('renaming part file to final file failed $renameOkay: ' . ($renameOkay ? 'true' : 'false') . ', $fileExists: ' . ($fileExists ? 'true' : 'false') . ')', ['app' => 'webdav']); + Server::get(LoggerInterface::class) + ->error('renaming part file to final file failed $renameOkay: ' . ($renameOkay ? 'true' : 'false') . ', $fileExists: ' . ($fileExists ? 'true' : 'false') . ')', [ + 'app' => 'webdav', + 'source_storage' => $partStorage->getId(), + 'target_storage' => $storage->getId(), + 'source_internal_path' => $internalPartPath, + 'target_internal_path' => $internalPath, + ]); throw new Exception($this->l10n->t('Could not rename part file to final file')); } } catch (ForbiddenException $ex) { From 61b39fbbf10c7b1d2fc64638874a5031c6b14966 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Mon, 27 Jul 2026 22:33:15 +0200 Subject: [PATCH 2/2] fix: add more logging around failed local renames Signed-off-by: Robin Appelman --- lib/private/Files/Storage/Local.php | 37 ++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/lib/private/Files/Storage/Local.php b/lib/private/Files/Storage/Local.php index e38485f755482..35942816a8f69 100644 --- a/lib/private/Files/Storage/Local.php +++ b/lib/private/Files/Storage/Local.php @@ -355,18 +355,20 @@ public function rename(string $source, string $target): bool { $srcParent = dirname($source); $dstParent = dirname($target); + $logger = Server::get(LoggerInterface::class); + if (!$this->isUpdatable($srcParent)) { - Server::get(LoggerInterface::class)->error('unable to rename, source directory is not writable : ' . $srcParent, ['app' => 'core']); + $logger->error('unable to rename, source directory is not writable : ' . $srcParent, ['app' => 'core']); return false; } if (!$this->isUpdatable($dstParent)) { - Server::get(LoggerInterface::class)->error('unable to rename, destination directory is not writable : ' . $dstParent, ['app' => 'core']); + $logger->error('unable to rename, destination directory is not writable : ' . $dstParent, ['app' => 'core']); return false; } if (!$this->file_exists($source)) { - Server::get(LoggerInterface::class)->error('unable to rename, file does not exists : ' . $source, ['app' => 'core']); + $logger->error('unable to rename, file does not exists : ' . $source, ['app' => 'core']); return false; } @@ -378,20 +380,43 @@ public function rename(string $source, string $target): bool { } } + $absoluteSource = $this->getSourcePath($source); + $absoluteTarget = $this->getSourcePath($target); + if ($this->is_dir($source)) { - $this->checkTreeForForbiddenItems($this->getSourcePath($source)); + $this->checkTreeForForbiddenItems($absoluteSource); } - if (@rename($this->getSourcePath($source), $this->getSourcePath($target))) { + if (@rename($absoluteSource, $absoluteTarget)) { if ($this->caseInsensitive) { if (mb_strtolower($target) === mb_strtolower($source) && !$this->file_exists($target)) { return false; } } return true; + } else { + $logger->error('failed to rename ' . $absoluteSource . ' to ' . $absoluteTarget . ', trying copy+delete fallback instead', [ + 'app' => 'core', + 'last_error' => error_get_last(), + ]); } - return $this->copy($source, $target) && $this->unlink($source); + if (!$this->copy($source, $target)) { + $logger->error('failed to copy ' . $absoluteSource . ' to ' . $absoluteTarget . ' as part of rename fallback', [ + 'app' => 'core', + 'last_error' => error_get_last(), + ]); + return false; + } + + if (!$this->unlink($source)) { + $logger->error('failed to delete ' . $absoluteSource . ' as part of rename fallback', [ + 'app' => 'core', + 'last_error' => error_get_last(), + ]); + return false; + } + return true; } #[\Override]