Skip to content

Commit 7350464

Browse files
authored
[MCLEAN-93] Support junctions on NTFS (#10)
1 parent c185ad1 commit 7350464

File tree

2 files changed

+104
-5
lines changed

2 files changed

+104
-5
lines changed

src/main/java/org/apache/maven/plugins/clean/Cleaner.java

+11-3
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,10 @@
2424
import java.lang.reflect.Method;
2525
import java.lang.reflect.Proxy;
2626
import java.nio.file.Files;
27+
import java.nio.file.LinkOption;
2728
import java.nio.file.Path;
2829
import java.nio.file.StandardCopyOption;
30+
import java.nio.file.attribute.BasicFileAttributes;
2931
import java.util.ArrayDeque;
3032
import java.util.Deque;
3133

@@ -215,9 +217,8 @@ private Result delete(
215217

216218
if (isDirectory) {
217219
if (selector == null || selector.couldHoldSelected(pathname)) {
218-
final boolean isSymlink = Files.isSymbolicLink(file.toPath());
219-
File canonical = followSymlinks ? file : file.getCanonicalFile();
220-
if (followSymlinks || !isSymlink) {
220+
if (followSymlinks || !isSymbolicLink(file.toPath())) {
221+
File canonical = followSymlinks ? file : file.getCanonicalFile();
221222
String[] filenames = canonical.list();
222223
if (filenames != null) {
223224
String prefix = pathname.length() > 0 ? pathname + File.separatorChar : "";
@@ -254,6 +255,13 @@ private Result delete(
254255
return result;
255256
}
256257

258+
private boolean isSymbolicLink(Path path) throws IOException {
259+
BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS);
260+
return attrs.isSymbolicLink()
261+
// MCLEAN-93: NTFS junctions have isDirectory() and isOther() attributes set
262+
|| (attrs.isDirectory() && attrs.isOther());
263+
}
264+
257265
/**
258266
* Deletes the specified file, directory. If the path denotes a symlink, only the link is removed, its target is
259267
* left untouched.

src/test/java/org/apache/maven/plugins/clean/CleanMojoTest.java

+93-2
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,22 @@
1818
*/
1919
package org.apache.maven.plugins.clean;
2020

21+
import java.io.ByteArrayOutputStream;
2122
import java.io.File;
23+
import java.io.IOException;
2224
import java.io.RandomAccessFile;
2325
import java.nio.channels.FileChannel;
2426
import java.nio.channels.FileLock;
27+
import java.nio.file.Files;
28+
import java.nio.file.Path;
29+
import java.nio.file.Paths;
30+
import java.util.Collections;
2531

2632
import org.apache.maven.plugin.MojoExecutionException;
2733
import org.apache.maven.plugin.testing.AbstractMojoTestCase;
2834

2935
import static org.apache.commons.io.FileUtils.copyDirectory;
36+
import static org.codehaus.plexus.util.IOUtil.copy;
3037

3138
/**
3239
* Test the clean mojo.
@@ -205,7 +212,7 @@ public void testMissingDirectory() throws Exception {
205212
*/
206213
public void testCleanLockedFile() throws Exception {
207214
if (!System.getProperty("os.name").toLowerCase().contains("windows")) {
208-
assertTrue("Ignored this test on none Windows based systems", true);
215+
assertTrue("Ignored this test on non Windows based systems", true);
209216
return;
210217
}
211218

@@ -239,7 +246,7 @@ public void testCleanLockedFile() throws Exception {
239246
*/
240247
public void testCleanLockedFileWithNoError() throws Exception {
241248
if (!System.getProperty("os.name").toLowerCase().contains("windows")) {
242-
assertTrue("Ignored this test on none Windows based systems", true);
249+
assertTrue("Ignore this test on non Windows based systems", true);
243250
return;
244251
}
245252

@@ -264,6 +271,90 @@ public void testCleanLockedFileWithNoError() throws Exception {
264271
}
265272
}
266273

274+
/**
275+
* Test the followLink option with windows junctions
276+
* @throws Exception
277+
*/
278+
public void testFollowLinksWithWindowsJunction() throws Exception {
279+
if (!System.getProperty("os.name").toLowerCase().contains("windows")) {
280+
assertTrue("Ignore this test on non Windows based systems", true);
281+
return;
282+
}
283+
284+
testSymlink((link, target) -> {
285+
Process process = new ProcessBuilder()
286+
.directory(link.getParent().toFile())
287+
.command("cmd", "/c", "mklink", "/j", link.getFileName().toString(), target.toString())
288+
.start();
289+
process.waitFor();
290+
ByteArrayOutputStream baos = new ByteArrayOutputStream();
291+
copy(process.getInputStream(), baos);
292+
copy(process.getErrorStream(), baos);
293+
if (!Files.exists(link)) {
294+
throw new IOException("Unable to create junction: " + baos);
295+
}
296+
});
297+
}
298+
299+
/**
300+
* Test the followLink option with sym link
301+
* @throws Exception
302+
*/
303+
public void testFollowLinksWithSymLinkOnPosix() throws Exception {
304+
if (System.getProperty("os.name").toLowerCase().contains("windows")) {
305+
assertTrue("Ignore this test on Windows based systems", true);
306+
return;
307+
}
308+
309+
testSymlink((link, target) -> {
310+
try {
311+
Files.createSymbolicLink(link, target);
312+
} catch (IOException e) {
313+
throw new IOException("Unable to create symbolic link", e);
314+
}
315+
});
316+
}
317+
318+
@FunctionalInterface
319+
interface LinkCreator {
320+
void createLink(Path link, Path target) throws Exception;
321+
}
322+
323+
private void testSymlink(LinkCreator linkCreator) throws Exception {
324+
Cleaner cleaner = new Cleaner(null, null, false, null, null);
325+
Path testDir = Paths.get("target/test-classes/unit/test-dir").toAbsolutePath();
326+
Path dirWithLnk = testDir.resolve("dir");
327+
Path orgDir = testDir.resolve("org-dir");
328+
Path jctDir = dirWithLnk.resolve("jct-dir");
329+
Path file = orgDir.resolve("file.txt");
330+
331+
// create directories, links and file
332+
Files.createDirectories(dirWithLnk);
333+
Files.createDirectories(orgDir);
334+
Files.write(file, Collections.singleton("Hello world"));
335+
linkCreator.createLink(jctDir, orgDir);
336+
// delete
337+
cleaner.delete(dirWithLnk.toFile(), null, false, true, false);
338+
// verify
339+
assertTrue(Files.exists(file));
340+
assertFalse(Files.exists(jctDir));
341+
assertTrue(Files.exists(orgDir));
342+
assertFalse(Files.exists(dirWithLnk));
343+
344+
// create directories, links and file
345+
Files.createDirectories(dirWithLnk);
346+
Files.createDirectories(orgDir);
347+
Files.write(file, Collections.singleton("Hello world"));
348+
linkCreator.createLink(jctDir, orgDir);
349+
// delete
350+
cleaner.delete(dirWithLnk.toFile(), null, true, true, false);
351+
// verify
352+
assertFalse(Files.exists(file));
353+
assertFalse(Files.exists(jctDir));
354+
assertTrue(Files.exists(orgDir));
355+
assertFalse(Files.exists(dirWithLnk));
356+
}
357+
267358
/**
268359
* @param dir a dir or a file
269360
* @return true if a file/dir exists, false otherwise

0 commit comments

Comments
 (0)