16

I've been searching around trying to find a way to determine if a file is a junction or not, and have not found any satisfactory answers.

First thing I tried was:

Files.isSymbolicLink(aPath)

It detects only symbolic links not the files referred to as junctions in Windows.

Also tried the solution proposed here (using JNA library): Stackoverflow question (3249117) , but it never returned true on any of the files I know to be junctions.

The only way I've found to determine which files are junctions is the following command run in windows command prompt:

DIR /S /A:L

On my computer it returns 66 folders, wheras Files.isSymbolicLink(aPath) returned only 2. So I suppose I could find a way to utilize this, but I don't think it would be very effiecient when traversing a filetree.

Is there any way to do this using the standard java library, or alternativly JNA?

Community
  • 1
  • 1
Martin
  • 1,130
  • 10
  • 14

6 Answers6

9

There can be a way to do it without JNA, if you have the right java, such as Oracle jdk 8. It's dodgy, it can cease to work, but....

You can get BasicFileAttributes interface related to the link:

BasicFileAttributes attr = Files.readAttributes(path, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS);

It can happen that this interface implementation is a class sun.nio.fs.WindowsFileAttributes. And this class has a method isReparsePoint, which returns true for both junction points and symbolic links. So you can try to use reflection and call the method:

    boolean isReparsePoint = false;
    if (DosFileAttributes.class.isInstance(attr))
        try {
            Method m = attr.getClass().getDeclaredMethod("isReparsePoint");
            m.setAccessible(true);
            isReparsePoint = (boolean) m.invoke(attr);
        } catch (Exception e) {
            // just gave it a try
        }

Now you only can discover whether it really is symbolic link: Files.isSymbolicLink(path)

If its not, but it is reparse point, then that's junction.

jan.supol
  • 2,636
  • 1
  • 26
  • 31
  • 1
    You can save the need for the `Files.isSylmbolicLink(path)` step by reflectively checking the `parseTag` field of the `WindowsFileAttributes` A Junction will have a value of -1610612733 (and a symlink -1610612724), Just replace what's in the `try` block with this: `Field field = attr.getClass().getDeclaredField("reparseTag"); field.setAccessible(true); int parseTag = (int) field.get(attr); boolean isJunction = parseTag == -1610612733;` – Javaru May 27 '15 at 22:40
  • 1
    Note, that starting with Java 16 it's not that easy any more. – Thomas S. Apr 08 '22 at 11:36
8

If you can write native code in JNA, you can directly call the Win32 API GetFileAttributes() function and check for the FILE_ATTRIBUTE_REPARSE_POINT flag (junctions are implemented as reparse points).

Update: To differentiate between different types of reparse points, you have to retreive the ReparseTag of the actual reparse point. For a junction point, it will be set to IO_REPARSE_TAG_MOUNT_POINT (0xA0000003).

There are two ways to retreive the ReparseTag:

  1. Use DeviceIoControl() with the FSCTL_GET_REPARSE_POINT control code to obtain an REPARSE_DATA_BUFFER struct, which as a ReparseTag field. You can see an example of an IsDirectoryJunction() implementation using this technique in the following article:

    NTFS Hard Links, Directory Junctions, and Windows Shortcuts

  2. Use FindFirstFile() to obtain a WIN32_FIND_DATA struct. If the path has the FILE_ATTRIBUTE_REPARSE_POINT attribute, the dwReserved0 field will contain the ReparseTag.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • Thank you, never really used JNA before except for copy&paste code, but if it's the only way I suppose I better learn. – Martin Dec 05 '12 at 22:25
  • What you suggested seems similar to the suggestion in the link I posted, it would be greatly appriciated if you could provide me with an example of how to check for the reparse-flag. – Martin Dec 05 '12 at 22:37
  • `FILE_ATTRIBUTE_REPARSE_POINT` is defined as `0x400`, so that example code you linked to is checking for `FILE_ATTRIBUTE_REPARSE_POINT` specifically. – Remy Lebeau Dec 05 '12 at 22:42
  • Thank you for your clarification, seems there was some discrepancy between the results I got from commandline and JNA, but I can't find them now, so if there's no way to do this with the standard java library, I'll accept this as the answer. Thanks for your patience. – Martin Dec 05 '12 at 22:51
  • 1
    This won't detect just junctions. It detects all reparse points. Junctions are only one example of a reparse point. – David Heffernan Dec 06 '12 at 09:15
  • 1
    In Windows, junction points are implemented as reparse points. To differentiate between different types of reparse points, you have to use `DeviceIoControl(FSCTL_GET_REPARSE_POINT)`. The returned `REPARSE_DATA_BUFFER` struct has a `ReparseTag` field (it is set to `IO_REPARSE_TAG_MOUNT_POINT` for a junction point). – Remy Lebeau Dec 07 '12 at 00:44
  • Or via the `dwReserved0` field that `FindFirstFile()` returns for reparse points. – Remy Lebeau Dec 07 '12 at 00:56
7

With J2SE 1.7 use Java NIO

/**
* returns true if the Path is a Windows Junction
*/
private static boolean isJunction(Path p) {
    boolean isJunction = false;
    try {
        isJunction = (p.compareTo(p.toRealPath()) != 0);
    } catch (IOException e) {
        e.printStackTrace(); // TODO: handleMeProperly
    }
    return isJunction;
}
Peter Kirschner
  • 927
  • 1
  • 9
  • 17
  • 1
    it seems that it does not work. toRealPath() is the same as the path itself for a junction. – Osman-pasha Aug 04 '15 at 18:02
  • That is what is expected on a junction. – Peter Kirschner Aug 05 '15 at 03:50
  • So this code does not work, does it? For a junction p.compareTo(p.toRealPath()) is 0 as you agreed and as I saw in test. – Osman-pasha Oct 13 '15 at 19:54
  • Yes, really. Thank you for explaining! – Osman-pasha Oct 15 '15 at 07:11
  • 2
    @Peter Why is that expected? Junctions ARE NOT hard links despite popular opinion. NTFS has Hard links, Junctions and Symlinks. The difference between a symlink and junction has to do with traversing UNC targets which Junctions don't do well. – Andrew T Finnell Sep 12 '16 at 22:02
  • 1
    I updated the gist mentioned above to include also a hardlink example (which are only possible on files). So these can be distinguished via Files.isRegularFile(). But the above method is working fine for Junctions. Can you give me an example what you mean by traversing UNC targets. – Peter Kirschner Sep 15 '16 at 13:11
7

While on Windows a junction's attributes have isSymbolicLink() == false, they have isOther() == true. So you could do something like:

boolean isWindows = System.getProperty("os.name").toLowerCase().contains("windows")
BasicFileAttributes attrs = Files.readAttributes(aPath, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS);
boolean isJunction = isWindows && attrs.isDirectory() && attrs.isOther();
user11153
  • 8,536
  • 5
  • 47
  • 50
sschuberth
  • 28,386
  • 6
  • 101
  • 146
  • 2
    Possible source of solution: [JDK-8031083 NTFS junction points are not detected by Files.readAttributes()](https://bugs.openjdk.java.net/browse/JDK-8031083) – user11153 Feb 26 '21 at 16:03
0

Black-Box Solution:

aPath.toRealPath() resolves junctions and symbolic links, so the result will deviate from aPath.

In addition BasicFileAttributes.isSymbolicLink() delivers false for junctions for non-documented reason:

E.g. Path.toRealPath(LinkOption.NOFOLLOW_LINKS) well treats a junction as link an does not resolve it!!

So by non-identity of toRealPath() and BasicFileAttributes.isSymbolicLink() you may identify a junction.

Sam Ginrich
  • 661
  • 6
  • 7
0

You can discover the link type with PowerShell with the command

(Get-Item -Path fileName -Force).LinkType

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;

class WindowsFileLinkUtils {

    public enum WindowsLinkType {
        JUNCTION("Junction"),
        HARD_LINK("HardLink"),
        SYMBOLIC_LINK("SymbolicLink");

        private final String key;

        WindowsLinkType(String key) {
            this.key = key;
        }

        public String getKey() {
            return key;
        }
    }

    private static final String CREATE_JUNCTION_COMMAND = "(Get-Item -Path %s -Force).LinkType";

    public static Optional<WindowsLinkType> getLinkType(Path path) throws IOException, InterruptedException {
        ProcessBuilder processBuilder = createIsJunctionProcessBuilder(path);
        Process process = processBuilder.start();
        process.waitFor();

        try (BufferedReader inStreamReader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
            String output = inStreamReader.readLine();
            return Arrays.stream(WindowsLinkType.values()).filter(windowsLinkType -> windowsLinkType.getKey().equals(output)).findFirst();
        }
    }

    private static ProcessBuilder createIsJunctionProcessBuilder(Path target) {
        ProcessBuilder processBuilder = new ProcessBuilder();
        List<String> arguments = processBuilder.command();
        arguments.add("powershell.exe");

        arguments.add(String.format(CREATE_JUNCTION_COMMAND, target.toString()));
        return processBuilder;
    }

    private WindowsFileLinkUtils() {
    }
}
basix86
  • 1
  • 1