Skip to content

Commit 89a1e82

Browse files
feat(storage): improve robustness + logging in Local::rmdir()
Signed-off-by: Josh <josh.t.richards@gmail.com>
1 parent c50c5a9 commit 89a1e82

File tree

1 file changed

+81
-23
lines changed

1 file changed

+81
-23
lines changed

lib/private/Files/Storage/Local.php

Lines changed: 81 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -87,41 +87,99 @@ public function mkdir(string $path): bool {
8787
return $result;
8888
}
8989

90+
/**
91+
* Recursively deletes a directory and its contents.
92+
*
93+
* Continues deleting other items if individual deletions fail,
94+
* logging an error for each failure. Returns true only if all deletions succeed
95+
*
96+
* Certain entries (e.g., with unresolved real paths or unknown file types) are skipped
97+
* and logged, but are not considered failures.
98+
*
99+
* TODO: Consider immediately failing (logging and returning false) upon any individual failure.
100+
*
101+
* @param string $path Path to the directory to be deleted
102+
* @return bool True on full success, false if any deletions failed
103+
*/
90104
public function rmdir(string $path): bool {
91105
if (!$this->isDeletable($path)) {
92106
return false;
93107
}
108+
109+
$sourcePath = $this->getSourcePath($path);
110+
$allSucceeded = true;
111+
112+
$logger = Server::get(LoggerInterface::class);
113+
94114
try {
95-
$it = new \RecursiveIteratorIterator(
96-
new \RecursiveDirectoryIterator($this->getSourcePath($path)),
115+
$iterator = new \RecursiveIteratorIterator(
116+
new \RecursiveDirectoryIterator(
117+
$sourcePath,
118+
\FilesystemIterator::SKIP_DOTS
119+
),
97120
\RecursiveIteratorIterator::CHILD_FIRST
98121
);
99-
/**
100-
* RecursiveDirectoryIterator on an NFS path isn't iterable with foreach
101-
* This bug is fixed in PHP 5.5.9 or before
102-
* See #8376
103-
*/
104-
$it->rewind();
105-
while ($it->valid()) {
106-
/**
107-
* @var \SplFileInfo $file
108-
*/
109-
$file = $it->current();
110-
clearstatcache(true, $file->getRealPath());
111-
if (in_array($file->getBasename(), ['.', '..'])) {
112-
$it->next();
122+
123+
foreach ($iterator as $file) {
124+
$realPath = $file->getRealPath();
125+
$pathName = $file->getPathname();
126+
127+
// Skip if real path could not be resolved (file may have been deleted or is inaccessible)
128+
if ($realPath === false) {
129+
$logger->warning(
130+
"Skipped entry with unresolved real path: {$pathName}",
131+
[ 'app' => 'files', 'storage' => static::class, 'path' => $pathName ]
132+
);
133+
// considered a soft error - skipped
113134
continue;
114-
} elseif ($file->isFile() || $file->isLink()) {
115-
unlink($file->getPathname());
135+
}
136+
137+
clearstatcache(true, $realPath);
138+
139+
$success = true;
140+
$operation = '';
141+
if ($file->isFile() || $file->isLink()) {
142+
$success = unlink($pathName);
143+
$operation = 'delete file or link';
116144
} elseif ($file->isDir()) {
117-
rmdir($file->getPathname());
145+
$success = rmdir($pathName);
146+
$operation = 'delete directory';
147+
} else {
148+
// Unknown type found (unexpected, but not impossible; considered a soft error - skipped)
149+
$logger->debug(
150+
"Encountered unknown file type: {$pathName}",
151+
[ 'app' => 'files', 'storage' => static::class, 'path' => $pathName ]
152+
);
153+
continue;
154+
}
155+
156+
if (!$success) {
157+
$logger->error(
158+
"Failed to {$operation}: {$pathName}",
159+
[ 'app' => 'files', 'storage' => static::class, 'path' => $pathName ]
160+
);
161+
$allSucceeded = false;
118162
}
119-
$it->next();
120163
}
121-
unset($it); // Release iterator and thereby its potential directory lock (e.g. in case of VirtualBox shared folders)
122-
clearstatcache(true, $this->getSourcePath($path));
123-
return rmdir($this->getSourcePath($path));
164+
// Release iterator and thereby its potential directory lock (e.g. in case of VirtualBox shared folders)
165+
unset($iterator);
166+
167+
clearstatcache(true, $sourcePath);
168+
$success = rmdir($sourcePath);
169+
if (!$success) {
170+
$logger->error(
171+
"Failed to delete root directory: {$sourcePath}",
172+
[ 'app' => 'files', 'storage' => static::class, 'path' => $sourcePath ]
173+
);
174+
$allSucceeded = false;
175+
}
176+
177+
return $allSucceeded;
124178
} catch (\UnexpectedValueException $e) {
179+
$logger->error(
180+
"Recursive deletion failed: {$e->getMessage()}",
181+
[ 'app' => 'files', 'storage' => static::class, 'path' => $path, 'exception' => $e ]
182+
);
125183
return false;
126184
}
127185
}

0 commit comments

Comments
 (0)