Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions src/main/java/org/apache/maven/plugins/clean/Cleaner.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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 : "";
Expand Down Expand Up @@ -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.
Expand Down
95 changes: 93 additions & 2 deletions src/test/java/org/apache/maven/plugins/clean/CleanMojoTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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;
}

Expand All @@ -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
Expand Down