[MCLEAN-93] Support junctions on NTFS (#10)
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 @@
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 @@
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 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 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;
}
@@ -265,6 +272,90 @@
}
/**
+ * 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
*/