@@ -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