|
49 | 49 | * @author Martin Desruisseaux
|
50 | 50 | */
|
51 | 51 | final class Cleaner implements FileVisitor<Path> {
|
52 |
| - |
| 52 | + /** |
| 53 | + * Whether the host operating system is from the Windows family. |
| 54 | + */ |
53 | 55 | private static final boolean ON_WINDOWS = (File.separatorChar == '\\');
|
54 | 56 |
|
55 | 57 | private static final SessionData.Key<Path> LAST_DIRECTORY_TO_DELETE =
|
@@ -193,7 +195,7 @@ public void delete(Path basedir) throws IOException {
|
193 | 195 | var options = EnumSet.noneOf(FileVisitOption.class);
|
194 | 196 | if (followSymlinks) {
|
195 | 197 | options.add(FileVisitOption.FOLLOW_LINKS);
|
196 |
| - basedir = getCanonicalPath(basedir); |
| 198 | + basedir = getCanonicalPath(basedir, null); |
197 | 199 | }
|
198 | 200 | if (selector == null && !followSymlinks && fastDir != null && session != null) {
|
199 | 201 | // If anything wrong happens, we'll just use the usual deletion mechanism
|
@@ -275,12 +277,21 @@ public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) th
|
275 | 277 | */
|
276 | 278 | @Override
|
277 | 279 | public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
278 |
| - if ((selector == null || selector.matches(file)) && tryDelete(file)) { |
279 |
| - if (listDeletedFiles) { |
280 |
| - logDelete(file, attrs); |
281 |
| - } |
| 280 | + if (ON_WINDOWS && followSymlinks && attrs.isDirectory() && attrs.isOther()) { |
| 281 | + /* |
| 282 | + * MCLEAN-93: NTFS junctions have isDirectory() and isOther() attributes set. |
| 283 | + * Handle as a directory walked by a new `FileVisitor`. |
| 284 | + */ |
| 285 | + file = getCanonicalPath(file, null); |
| 286 | + Files.walkFileTree(file, EnumSet.of(FileVisitOption.FOLLOW_LINKS), Integer.MAX_VALUE, this); |
282 | 287 | } else {
|
283 |
| - nonEmptyDirectoryLevels.set(currentDepth); // Remember that the directory will not be empty. |
| 288 | + if ((selector == null || selector.matches(file)) && tryDelete(file)) { |
| 289 | + if (listDeletedFiles) { |
| 290 | + logDelete(file, attrs); |
| 291 | + } |
| 292 | + } else { |
| 293 | + nonEmptyDirectoryLevels.set(currentDepth); // Remember that the directory will not be empty. |
| 294 | + } |
284 | 295 | }
|
285 | 296 | return FileVisitResult.CONTINUE;
|
286 | 297 | }
|
@@ -333,11 +344,30 @@ public FileVisitResult postVisitDirectory(Path dir, IOException failure) throws
|
333 | 344 | return FileVisitResult.CONTINUE;
|
334 | 345 | }
|
335 | 346 |
|
336 |
| - private static Path getCanonicalPath(Path path) { |
| 347 | + /** |
| 348 | + * Returns the real path of the given file. If the real path cannot be obtained, |
| 349 | + * this method tries to get the real path of the parent and to append the rest of |
| 350 | + * the filename. |
| 351 | + * |
| 352 | + * @param path the path to get as a canonical path |
| 353 | + * @param mainError should be {@code null} (reserved to recursive calls of this method) |
| 354 | + * @return the real path of the given path |
| 355 | + * @throws IOException if the canonical path cannot be obtained |
| 356 | + */ |
| 357 | + private static Path getCanonicalPath(final Path path, IOException mainError) throws IOException { |
337 | 358 | try {
|
338 | 359 | return path.toRealPath();
|
339 | 360 | } catch (IOException e) {
|
340 |
| - return getCanonicalPath(path.getParent()).resolve(path.getFileName()); |
| 361 | + if (mainError == null) { |
| 362 | + mainError = e; |
| 363 | + } else { |
| 364 | + mainError.addSuppressed(e); |
| 365 | + } |
| 366 | + final Path parent = path.getParent(); |
| 367 | + if (parent != null) { |
| 368 | + return getCanonicalPath(parent, mainError).resolve(path.getFileName()); |
| 369 | + } |
| 370 | + throw e; |
341 | 371 | }
|
342 | 372 | }
|
343 | 373 |
|
|
0 commit comments