Skip to content

Commit

Permalink
[MCLEAN-93] Support junctions on NTFS (#10)
Browse files Browse the repository at this point in the history
  • Loading branch information
gnodet authored Jun 14, 2023
1 parent c185ad1 commit 7350464
Show file tree
Hide file tree
Showing 2 changed files with 104 additions and 5 deletions.
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

0 comments on commit 7350464

Please sign in to comment.