diff --git a/src/main/java/org/apache/maven/plugins/clean/Cleaner.java b/src/main/java/org/apache/maven/plugins/clean/Cleaner.java index 70bfe2e..9f5c2fb 100644 --- a/src/main/java/org/apache/maven/plugins/clean/Cleaner.java +++ b/src/main/java/org/apache/maven/plugins/clean/Cleaner.java @@ -24,8 +24,10 @@ import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.nio.file.Files; +import java.nio.file.LinkOption; import java.nio.file.Path; import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayDeque; import java.util.Deque; @@ -215,9 +217,8 @@ private Result delete( if (isDirectory) { if (selector == null || selector.couldHoldSelected(pathname)) { - final boolean isSymlink = Files.isSymbolicLink(file.toPath()); - File canonical = followSymlinks ? file : file.getCanonicalFile(); - if (followSymlinks || !isSymlink) { + if (followSymlinks || !isSymbolicLink(file.toPath())) { + File canonical = followSymlinks ? file : file.getCanonicalFile(); String[] filenames = canonical.list(); if (filenames != null) { String prefix = pathname.length() > 0 ? pathname + File.separatorChar : ""; @@ -254,6 +255,13 @@ private Result delete( return result; } + private boolean isSymbolicLink(Path path) throws IOException { + BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS); + return attrs.isSymbolicLink() + // MCLEAN-93: NTFS junctions have isDirectory() and isOther() attributes set + || (attrs.isDirectory() && attrs.isOther()); + } + /** * Deletes the specified file, directory. If the path denotes a symlink, only the link is removed, its target is * left untouched. diff --git a/src/test/java/org/apache/maven/plugins/clean/CleanMojoTest.java b/src/test/java/org/apache/maven/plugins/clean/CleanMojoTest.java index 48fa782..f82e615 100644 --- a/src/test/java/org/apache/maven/plugins/clean/CleanMojoTest.java +++ b/src/test/java/org/apache/maven/plugins/clean/CleanMojoTest.java @@ -18,15 +18,22 @@ */ package org.apache.maven.plugins.clean; +import java.io.ByteArrayOutputStream; import java.io.File; +import java.io.IOException; import java.io.RandomAccessFile; import java.nio.channels.FileChannel; import java.nio.channels.FileLock; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Collections; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.testing.AbstractMojoTestCase; import static org.apache.commons.io.FileUtils.copyDirectory; +import static org.codehaus.plexus.util.IOUtil.copy; /** * Test the clean mojo. @@ -205,7 +212,7 @@ public void testMissingDirectory() throws Exception { */ public void testCleanLockedFile() throws Exception { if (!System.getProperty("os.name").toLowerCase().contains("windows")) { - assertTrue("Ignored this test on none Windows based systems", true); + assertTrue("Ignored this test on non Windows based systems", true); return; } @@ -239,7 +246,7 @@ public void testCleanLockedFile() throws Exception { */ public void testCleanLockedFileWithNoError() throws Exception { if (!System.getProperty("os.name").toLowerCase().contains("windows")) { - assertTrue("Ignored this test on none Windows based systems", true); + assertTrue("Ignore this test on non Windows based systems", true); return; } @@ -264,6 +271,90 @@ public void testCleanLockedFileWithNoError() throws Exception { } } + /** + * Test the followLink option with windows junctions + * @throws Exception + */ + public void testFollowLinksWithWindowsJunction() throws Exception { + if (!System.getProperty("os.name").toLowerCase().contains("windows")) { + assertTrue("Ignore this test on non Windows based systems", true); + return; + } + + testSymlink((link, target) -> { + Process process = new ProcessBuilder() + .directory(link.getParent().toFile()) + .command("cmd", "/c", "mklink", "/j", link.getFileName().toString(), target.toString()) + .start(); + process.waitFor(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + copy(process.getInputStream(), baos); + copy(process.getErrorStream(), baos); + if (!Files.exists(link)) { + throw new IOException("Unable to create junction: " + baos); + } + }); + } + + /** + * Test the followLink option with sym link + * @throws Exception + */ + public void testFollowLinksWithSymLinkOnPosix() throws Exception { + if (System.getProperty("os.name").toLowerCase().contains("windows")) { + assertTrue("Ignore this test on Windows based systems", true); + return; + } + + testSymlink((link, target) -> { + try { + Files.createSymbolicLink(link, target); + } catch (IOException e) { + throw new IOException("Unable to create symbolic link", e); + } + }); + } + + @FunctionalInterface + interface LinkCreator { + void createLink(Path link, Path target) throws Exception; + } + + private void testSymlink(LinkCreator linkCreator) throws Exception { + Cleaner cleaner = new Cleaner(null, null, false, null, null); + Path testDir = Paths.get("target/test-classes/unit/test-dir").toAbsolutePath(); + Path dirWithLnk = testDir.resolve("dir"); + Path orgDir = testDir.resolve("org-dir"); + Path jctDir = dirWithLnk.resolve("jct-dir"); + Path file = orgDir.resolve("file.txt"); + + // create directories, links and file + Files.createDirectories(dirWithLnk); + Files.createDirectories(orgDir); + Files.write(file, Collections.singleton("Hello world")); + linkCreator.createLink(jctDir, orgDir); + // delete + cleaner.delete(dirWithLnk.toFile(), null, false, true, false); + // verify + assertTrue(Files.exists(file)); + assertFalse(Files.exists(jctDir)); + assertTrue(Files.exists(orgDir)); + assertFalse(Files.exists(dirWithLnk)); + + // create directories, links and file + Files.createDirectories(dirWithLnk); + Files.createDirectories(orgDir); + Files.write(file, Collections.singleton("Hello world")); + linkCreator.createLink(jctDir, orgDir); + // delete + cleaner.delete(dirWithLnk.toFile(), null, true, true, false); + // verify + assertFalse(Files.exists(file)); + assertFalse(Files.exists(jctDir)); + assertTrue(Files.exists(orgDir)); + assertFalse(Files.exists(dirWithLnk)); + } + /** * @param dir a dir or a file * @return true if a file/dir exists, false otherwise