Symlink support for Windows

This commit is contained in:
Roman Shevchenko
2011-08-22 17:36:36 +02:00
parent c7effe47af
commit 329fe66a65
11 changed files with 421 additions and 179 deletions
@@ -200,10 +200,29 @@ public abstract class VirtualFile extends UserDataHolderBase implements Modifica
*/
public abstract boolean isDirectory();
/**
* Checks whether this file is a symbolic link.
*
* @since 11.0
* @return <code>true</code> if this file is a symbolic link, <code>false</code> otherwise
*/
public boolean isSymLink() {
return false;
}
/**
* Attempts to resolve a symbolic link represented by this file and returns link target.
*
* @since 11.0
* @return <code>this</code> if the file isn't a symbolic link;
* instance of <code>VirtualFile</code> if the link was successfully resolved;
* <code>null</code> otherwise
*/
@Nullable
public VirtualFile getRealFile() {
return this;
}
/**
* Checks whether this <code>VirtualFile</code> is valid. File can be invalidated either by deleting it or one of its
* parents with {@link #delete} method or by an external change.
@@ -40,10 +40,31 @@ public abstract class LocalFileSystem extends NewVirtualFileSystem {
return LocalFileSystemHolder.ourInstance;
}
/**
* Checks whether given file is a symbolic link.
*
* @param file a file to check.
* @return <code>true</code> if the file is a symbolic link, <code>false</code> otherwise
* @since 11.0
*/
public boolean isSymLink(@NotNull final VirtualFile file) {
return false;
}
/**
* Attempts to resolve a symbolic link represented by given file and returns link target.
*
* @since 11.0
* @param file a file to resolve.
* @return <code>this</code> if the file isn't a symbolic link;
* instance of <code>VirtualFile</code> if the link was successfully resolved;
* <code>null</code> otherwise
*/
@Nullable
public VirtualFile getRealFile(@NotNull final VirtualFile file) {
return file;
}
@Nullable
public abstract VirtualFile findFileByIoFile(File file);
@@ -26,6 +26,7 @@ import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.io.SymLinkUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.newvfs.ManagingFS;
import com.intellij.openapi.vfs.newvfs.NewVirtualFile;
@@ -214,13 +215,8 @@ public class FileWatcher {
}
private static String getCanonicalPath(final String path) {
try {
return new File(path).getCanonicalPath();
}
catch (IOException e) {
LOG.warn(e.getMessage() + ": " + path);
return path;
}
final String realPath = SymLinkUtil.resolveSymLink(path);
return realPath != null ? realPath : path;
}
private boolean isAlive() {
@@ -20,6 +20,7 @@ import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.io.SymLinkUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.*;
import com.intellij.openapi.vfs.ex.VirtualFileManagerEx;
@@ -170,22 +171,21 @@ public abstract class LocalFileSystemBase extends LocalFileSystem {
}
}
File ioFile = convertToIOFile(file);
if (file.isSymLink() && isRecursiveSymLink(ioFile)) {
if (isInvalidSymLink(file)) {
return ArrayUtil.EMPTY_STRING_ARRAY;
}
final File ioFile = convertToIOFile(file);
final String[] names = ioFile.list();
return names != null ? names : ArrayUtil.EMPTY_STRING_ARRAY;
}
protected static boolean isRecursiveSymLink(File ioFile) {
try {
if (FileUtil.isAncestor(ioFile.getCanonicalFile(), ioFile, true)) return true;
}
catch (IOException ignore) {
}
return false;
protected static boolean isInvalidSymLink(@NotNull final VirtualFile file) {
if (!file.isSymLink()) return false;
final VirtualFile realFile = file.getRealFile();
return realFile == null ||
realFile == file ||
FileUtil.isAncestor(convertToIOFile(realFile), convertToIOFile(file), true);
}
@NotNull
@@ -22,6 +22,7 @@ import com.intellij.openapi.components.ApplicationComponent;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.io.SymLinkUtil;
import com.intellij.openapi.vfs.JarFileSystem;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
@@ -471,6 +472,12 @@ public final class LocalFileSystemImpl extends LocalFileSystemBase implements Ap
return SymLinkUtil.isSymLink(file.getPath());
}
@Override
public VirtualFile getRealFile(@NotNull final VirtualFile file) {
final String realPath = SymLinkUtil.resolveSymLink(file.getPath());
return realPath != null ? findFileByPath(realPath) : null;
}
public boolean isWritable(@NotNull final VirtualFile file) {
if (myNativeFileSystem == null) return super.isWritable(file);
else return myNativeFileSystem.isWritable(file);
@@ -1,141 +0,0 @@
/*
* Copyright 2000-2011 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.openapi.vfs.impl.local;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.util.ArrayUtil;
import com.sun.jna.Library;
import com.sun.jna.Memory;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.lang.reflect.Method;
// todo[r.sh] use NIO2 API after migration to JDK 7
public class SymLinkUtil {
private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.vfs.impl.local.SymLinkUtil");
@Nullable
private static final Mediator ourMediator;
static {
Mediator mediator = null;
if (SystemInfo.isJavaVersionAtLeast("1.7")) {
try {
mediator = new Jdk7MediatorImpl();
mediator.isSymLink("/"); // quick test
}
catch (Exception e) {
LOG.error(e);
mediator = null;
}
}
if (mediator == null && (SystemInfo.isLinux || SystemInfo.isMac || SystemInfo.isSolaris)) {
try {
mediator = new JnaMediatorImpl();
mediator.isSymLink("/"); // quick test
}
catch (Exception e) {
LOG.error(e);
mediator = null;
}
}
ourMediator = mediator;
}
private SymLinkUtil() { }
public static boolean isSymLink(@NotNull final File file) {
return isSymLink(file.getAbsolutePath());
}
public static boolean isSymLink(@NotNull final String path) {
try {
return ourMediator != null && ourMediator.isSymLink(path);
}
catch (Exception e) {
LOG.error(e);
return false;
}
}
private interface Mediator {
boolean isSymLink(@NotNull final String path) throws Exception;
}
private static class Jdk7MediatorImpl implements Mediator {
private final Method myGetDefault;
private final Method myGetPath;
private final Method myIsSymbolicLink;
private Jdk7MediatorImpl() throws Exception {
myGetDefault = Class.forName("java.nio.file.FileSystems").getMethod("getDefault");
myGetPath = Class.forName("java.nio.file.FileSystem").getMethod("getPath", String.class, String[].class);
myIsSymbolicLink = Class.forName("java.nio.file.Files").getMethod("isSymbolicLink", Class.forName("java.nio.file.Path"));
}
@Override
public boolean isSymLink(@NotNull final String path) throws Exception {
final Object fileSystem = myGetDefault.invoke(null);
final Object pathObj = myGetPath.invoke(fileSystem, path, ArrayUtil.EMPTY_STRING_ARRAY);
return (Boolean)myIsSymbolicLink.invoke(null, pathObj);
}
}
// thanks to SVNKit for the idea
@SuppressWarnings("OctalInteger")
private static class JnaMediatorImpl implements Mediator {
private interface LibC extends Library {
int S_MASK = 0177777;
int S_IFLNK = 0120000;
int lstat(String path, Pointer stat);
int __lxstat64(int ver, String path, Pointer stat);
}
private final LibC myLibC;
private final Memory mySharedMem;
private final int myOffset;
private JnaMediatorImpl() throws Exception {
myLibC = (LibC)Native.loadLibrary("c", LibC.class);
mySharedMem = new Memory(512);
myOffset = SystemInfo.isLinux ? (SystemInfo.is32Bit ? 16 : 24) :
SystemInfo.isMac ? 8 :
SystemInfo.isSolaris ? (SystemInfo.is32Bit ? 20 : 16) :
-1;
if (myOffset < 0) throw new IllegalStateException("Unsupported OS: " + SystemInfo.OS_NAME);
}
@Override
public synchronized boolean isSymLink(@NotNull final String path) throws Exception {
mySharedMem.clear();
final int res = SystemInfo.isLinux ? myLibC.__lxstat64(0, path, mySharedMem) : myLibC.lstat(path, mySharedMem);
if (res == 0) {
final int mode = (SystemInfo.isLinux ? mySharedMem.getInt(myOffset) : mySharedMem.getShort(myOffset)) & LibC.S_MASK;
return (mode & LibC.S_IFLNK) == LibC.S_IFLNK;
}
else {
//LOG.warn("lstat(" + path + "): " + res);
return false;
}
}
}
}
@@ -425,4 +425,10 @@ public abstract class VirtualFileSystemEntry extends NewVirtualFile {
final NewVirtualFileSystem fs = getFileSystem();
return fs instanceof LocalFileSystem && ((LocalFileSystem)fs).isSymLink(this);
}
@Override
public VirtualFile getRealFile() {
final NewVirtualFileSystem fs = getFileSystem();
return fs instanceof LocalFileSystem ? ((LocalFileSystem)fs).getRealFile(this) : super.getRealFile();
}
}
@@ -67,12 +67,17 @@ public class SymLinkHandlingTest extends LightPlatformTestCase {
final VirtualFile linkVDir = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(linkDir);
assertTrue("link=" + linkDir + ", vLink=" + linkVDir, linkVDir != null && linkVDir.isDirectory() && linkVDir.isSymLink());
assertTrue(targetDir.getAbsolutePath(), targetDir.setWritable(true, false) && targetDir.canWrite());
linkVDir.refresh(false, true);
assertTrue(linkVDir.getPath(), linkVDir.isWritable());
assertTrue(targetDir.getAbsolutePath(), targetDir.setWritable(false, false) && !targetDir.canWrite());
linkVDir.refresh(false, true);
assertFalse(linkVDir.getPath(), linkVDir.isWritable());
if (!SystemInfo.isWindows) {
assertTrue(targetDir.getAbsolutePath(), targetDir.setWritable(true, false) && targetDir.canWrite());
linkVDir.refresh(false, true);
assertTrue(linkVDir.getPath(), linkVDir.isWritable());
assertTrue(targetDir.getAbsolutePath(), targetDir.setWritable(false, false) && !targetDir.canWrite());
linkVDir.refresh(false, true);
assertFalse(linkVDir.getPath(), linkVDir.isWritable());
}
else {
assertEquals(linkVDir.getPath(), targetDir.canWrite(), linkVDir.isWritable());
}
}
public void testLinkDeleteIsSafe() throws Exception {
@@ -122,14 +127,20 @@ public class SymLinkHandlingTest extends LightPlatformTestCase {
final File parentDir = linkFile.getParentFile();
assertTrue("link=" + link + ", parent=" + parentDir, parentDir != null && (parentDir.isDirectory() || parentDir.mkdirs()));
final ProcessBuilder builder = new ProcessBuilder("ln", "-s", target, linkFile.getAbsolutePath());
final ProcessBuilder builder;
if (SystemInfo.isWindows) {
builder = new File(target).isDirectory()
? new ProcessBuilder("cmd", "/C", "mklink", "/D", linkFile.getAbsolutePath(), target)
: new ProcessBuilder("cmd", "/C", "mklink", linkFile.getAbsolutePath(), target);
}
else {
builder = new ProcessBuilder("ln", "-s", target, linkFile.getAbsolutePath());
}
final Process process = builder.start();
final int res = process.waitFor();
assertTrue(builder.command() + ": " + res, res == 0);
final File targetFile = new File(target);
assertTrue("target=" + target + ", link=" + linkFile,
linkFile.exists() == targetFile.exists() &&
linkFile.getCanonicalPath().equals(targetFile.getAbsolutePath()) == targetFile.exists());
assertEquals("target=" + target + ", link=" + linkFile, targetFile.exists(), linkFile.exists());
return linkFile;
}
}
@@ -50,12 +50,12 @@ public class SystemInfo {
public static final boolean isMacSystemMenu = isMac && "true".equals(System.getProperty("apple.laf.useScreenMenuBar"));
public static final boolean isFileSystemCaseSensitive = !isWindows && !isOS2 && !isMac;
public static final boolean areSymLinksSupported = isUnix;
public static final boolean areSymLinksSupported = isUnix ||
isWindows && OS_VERSION.compareTo("6.0") >= 0 && isJavaVersionAtLeast("1.7");
public static final boolean is32Bit = ARCH_DATA_MODEL == null || ARCH_DATA_MODEL.equals("32");
public static final boolean is64Bit = !is32Bit;
public static final boolean isAMD64 = "amd64".equals(OS_ARCH);
public static final boolean isMacIntel64 = isMac && "x86_64".equals(OS_ARCH);
public static final String nativeFileManagerName = isMac ? "Finder" : isGnome ? "Nautilus" : isKDE ? "Konqueror" : "Explorer";
@@ -42,8 +42,10 @@ import java.util.regex.Pattern;
@SuppressWarnings({"UtilityClassWithoutPrivateConstructor"})
public class FileUtil {
public static final int MEGABYTE = 1024 * 1024;
public static final String ASYNC_DELETE_EXTENSION = ".__del__";
private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.util.io.FileUtil");
private static final ThreadLocal<byte[]> BUFFER = new ThreadLocal<byte[]>() {
protected byte[] initialValue() {
return new byte[1024 * 20];
@@ -51,9 +53,9 @@ public class FileUtil {
};
// do not use channels to copy files larger than 5 Mb because of possible MapFailed error
private static final long CHANNELS_COPYING_LIMIT = 5L * 1024L * 1024L;
private static final long CHANNELS_COPYING_LIMIT = 5L * MEGABYTE;
private static String ourCanonicalTempPathCache = null;
public static final String ASYNC_DELETE_EXTENSION = ".__del__";
private static final int MAX_FILE_DELETE_ATTEMPTS = 10;
@Nullable
public static String getRelativePath(File base, File file) {
@@ -541,21 +543,22 @@ public class FileUtil {
}
public static boolean delete(@NotNull File file) {
File[] files = file.listFiles();
if (files != null) {
for (File file1 : files) {
if (!delete(file1)) return false;
if (!SymLinkUtil.isSymLink(file)) {
File[] files = file.listFiles();
if (files != null) {
for (File child : files) {
if (!delete(child)) return false;
}
}
}
for (int i = 0; i < 10; i++) {
for (int i = 0; i < MAX_FILE_DELETE_ATTEMPTS; i++) {
if (file.delete() || !file.exists()) return true;
try {
//noinspection BusyWait
Thread.sleep(10);
}
catch (InterruptedException ignored) {
}
catch (InterruptedException ignored) { }
}
return false;
}
@@ -0,0 +1,320 @@
/*
* Copyright 2000-2011 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.openapi.util.io;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.util.ArrayUtil;
import com.sun.jna.Library;
import com.sun.jna.Memory;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
// todo[r.sh] use NIO2 API after migration to JDK 7
public class SymLinkUtil {
private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.util.io.SymLinkUtil");
@Nullable
private static final Mediator ourMediator;
static {
Mediator mediator = null;
if (SystemInfo.areSymLinksSupported) {
if (SystemInfo.isJavaVersionAtLeast("1.7")) {
try {
mediator = new Jdk7MediatorImpl();
mediator.isSymLink("/"); // quick test
}
catch (Throwable t) {
LOG.error(t);
mediator = null;
}
}
if (mediator == null) {
if (SystemInfo.isLinux || SystemInfo.isMac || SystemInfo.isSolaris) {
try {
mediator = new JnaUnixMediatorImpl();
mediator.isSymLink("/"); // quick test
}
catch (Throwable t) {
LOG.error(t);
mediator = null;
}
}
/*else if (SystemInfo.isWindows) {
try {
mediator = new JnaWindowsMediatorImpl();
mediator.isSymLink("/"); // quick test
}
catch (Throwable t) {
LOG.error(t);
mediator = null;
}
}*/
}
}
ourMediator = mediator;
}
private SymLinkUtil() { }
public static boolean isSymLink(@NotNull final File file) {
return isSymLink(file.getAbsolutePath());
}
public static boolean isSymLink(@NotNull final String path) {
try {
return ourMediator != null && ourMediator.isSymLink(path);
}
catch (Exception e) {
LOG.warn(e);
return false;
}
}
@Nullable
public static String resolveSymLink(@NotNull final File file) {
return resolveSymLink(file.getAbsolutePath());
}
@Nullable
public static String resolveSymLink(@NotNull final String path) {
if (ourMediator != null) {
try {
final String realPath = ourMediator.resolveSymLink(path);
if (realPath != null && new File(realPath).exists()) {
return realPath;
}
}
catch (Exception e) {
LOG.warn(e);
}
}
return null;
}
private interface Mediator {
boolean isSymLink(@NotNull final String path) throws Exception;
@Nullable
String resolveSymLink(@NotNull final String path) throws Exception;
}
private static class Jdk7MediatorImpl implements Mediator {
private final Object myDefaultFileSystem;
private final Method myGetPath;
private final Method myIsSymbolicLink;
private final Object myLinkOptions;
private Jdk7MediatorImpl() throws Exception {
myDefaultFileSystem = Class.forName("java.nio.file.FileSystems").getMethod("getDefault").invoke(null);
myGetPath = Class.forName("java.nio.file.FileSystem").getMethod("getPath", String.class, String[].class);
myGetPath.setAccessible(true);
myIsSymbolicLink = Class.forName("java.nio.file.Files").getMethod("isSymbolicLink", Class.forName("java.nio.file.Path"));
myIsSymbolicLink.setAccessible(true);
myLinkOptions = Array.newInstance(Class.forName("java.nio.file.LinkOption"), 0);
}
@Override
public boolean isSymLink(@NotNull final String path) throws Exception {
final Object pathObj = myGetPath.invoke(myDefaultFileSystem, path, ArrayUtil.EMPTY_STRING_ARRAY);
return (Boolean)myIsSymbolicLink.invoke(null, pathObj);
}
@Override
public String resolveSymLink(@NotNull final String path) throws Exception {
final Object pathObj = myGetPath.invoke(myDefaultFileSystem, path, ArrayUtil.EMPTY_STRING_ARRAY);
final Method toRealPath = pathObj.getClass().getMethod("toRealPath", myLinkOptions.getClass());
toRealPath.setAccessible(true);
return toRealPath.invoke(pathObj, myLinkOptions).toString();
}
}
// thanks to SVNKit for the idea
@SuppressWarnings("OctalInteger")
private static class JnaUnixMediatorImpl implements Mediator {
private interface LibC extends Library {
int S_MASK = 0177777;
int S_IFLNK = 0120000;
int lstat(String path, Pointer stat);
int __lxstat64(int ver, String path, Pointer stat);
}
private final LibC myLibC;
private final Memory mySharedMem;
private final int myOffset;
private JnaUnixMediatorImpl() throws Exception {
myLibC = (LibC)Native.loadLibrary("c", LibC.class);
mySharedMem = new Memory(512);
myOffset = SystemInfo.isLinux ? (SystemInfo.is32Bit ? 16 : 24) :
SystemInfo.isMac ? 8 :
SystemInfo.isSolaris ? (SystemInfo.is32Bit ? 20 : 16) :
-1;
if (myOffset < 0) throw new IllegalStateException("Unsupported OS: " + SystemInfo.OS_NAME);
}
@Override
public synchronized boolean isSymLink(@NotNull final String path) throws Exception {
mySharedMem.clear();
final int res = SystemInfo.isLinux ? myLibC.__lxstat64(0, path, mySharedMem) : myLibC.lstat(path, mySharedMem);
if (res == 0) {
final int mode = (SystemInfo.isLinux ? mySharedMem.getInt(myOffset) : mySharedMem.getShort(myOffset)) & LibC.S_MASK;
return (mode & LibC.S_IFLNK) == LibC.S_IFLNK;
}
else {
LOG.debug("lstat(" + path + "): " + res);
return false;
}
}
@Override
public String resolveSymLink(@NotNull final String path) throws Exception {
return new File(path).getCanonicalPath();
}
}
/*private static class JnaWindowsMediatorImpl implements Mediator {
private interface Kernel32 extends StdCallLibrary {
int IO_REPARSE_TAG_SYMLINK = 0xA000000C;
int FILE_ACCESS_FLAGS = 0x0080;
int FILE_SHARE_FLAGS = 0x00000001 | 0x00000002 | 0x00000004;
int OPEN_EXISTING = 3;
int FILE_OPEN_FLAGS = 0x02000000 | 0x00200000;
int FSCTL_GET_REPARSE_POINT = 0x000900A8;
int SYMLINK_FLAG_RELATIVE = 0x00000001;
@SuppressWarnings({"UnusedDeclaration", "MultipleVariablesInDeclaration"})
class Win32FindData extends Structure implements Structure.ByReference {
public int dwFileAttributes;
public int ftCreationTimeL, ftCreationTimeH;
public int ftLastAccessTimeL, ftLastAccessTimeH;
public int ftLastWriteTimeL, ftLastWriteTimeH;
public int lFileSizeH, lFileSizeL;
public int dwReserved0;
public int dwReserved1;
public char[] cFileName = new char[260];
public char[] cAlternateFileName = new char[14];
}
int MAX_SUPPORTED_TARGET_LENGTH = 4 * 1024;
@SuppressWarnings({"UnusedDeclaration", "MultipleVariablesInDeclaration"})
class ReparseDataBuffer extends Structure implements Structure.ByReference {
public NativeLong ReparseTag;
public short ReparseDataLength;
public short Reserved;
public short SubstituteNameOffset, SubstituteNameLength;
public short PrintNameOffset, PrintNameLength;
public NativeLong Flags;
public char[] PathBuffer = new char[MAX_SUPPORTED_TARGET_LENGTH];
}
Pointer INVALID_HANDLE = Pointer.createConstant(-1);
Pointer FindFirstFile(String lpFileName, Win32FindData lpFindFileData);
boolean FindClose(Pointer hFindFile);
Pointer CreateFile(String lpFileName,
int dwDesiredAccess,
int dwShareMode,
@Nullable Pointer lpSecurityAttributes,
int dwCreationDisposition,
int dwFlagsAndAttributes,
@Nullable Pointer hTemplateFile);
boolean CloseHandle(Pointer hObject);
boolean DeviceIoControl(Pointer hDevice,
int dwIoControlCode,
@Nullable Structure.ByReference lpInBuffer,
int nInBufferSize,
@Nullable Structure.ByReference lpOutBuffer,
int nOutBufferSize,
IntByReference lpBytesReturned,
@Nullable Pointer lpOverlapped);
}
private final Kernel32 myKernel32;
private final Kernel32.Win32FindData myFindData;
private final Kernel32.ReparseDataBuffer myReparseData;
private JnaWindowsMediatorImpl() throws Exception {
myKernel32 = (Kernel32) Native.loadLibrary("kernel32", Kernel32.class, W32APIOptions.UNICODE_OPTIONS);
myFindData = new Kernel32.Win32FindData();
myReparseData = new Kernel32.ReparseDataBuffer();
}
@SuppressWarnings("NonPrivateFieldAccessedInSynchronizedContext")
@Override
public synchronized boolean isSymLink(@NotNull final String path) throws Exception {
synchronized (myFindData) {
myFindData.dwReserved0 = 0;
final Pointer handle = myKernel32.FindFirstFile(path, myFindData);
if (Kernel32.INVALID_HANDLE.equals(handle)) {
LOG.debug("FindFirstFile(" + path + "): " + handle);
return false;
}
myKernel32.FindClose(handle);
return (myFindData.dwReserved0 & Kernel32.IO_REPARSE_TAG_SYMLINK) == Kernel32.IO_REPARSE_TAG_SYMLINK;
}
}
@SuppressWarnings("NonPrivateFieldAccessedInSynchronizedContext")
@Override
public String resolveSymLink(@NotNull final String path) throws Exception {
final Pointer handle = myKernel32.CreateFile(path, Kernel32.FILE_ACCESS_FLAGS, Kernel32.FILE_SHARE_FLAGS, null,
Kernel32.OPEN_EXISTING, Kernel32.FILE_OPEN_FLAGS, null);
if (Kernel32.INVALID_HANDLE.equals(handle)) {
LOG.debug("CreateFile(" + path + "): " + handle);
return null;
}
synchronized (myReparseData) {
try {
myReparseData.ReparseTag.setValue(0);
myReparseData.SubstituteNameOffset = myReparseData.SubstituteNameLength = 0;
myReparseData.Flags.setValue(0);
final boolean result = myKernel32.DeviceIoControl(handle, Kernel32.FSCTL_GET_REPARSE_POINT, null, 0,
myReparseData, myReparseData.size(), new IntByReference(), null);
if (!result || myReparseData.ReparseTag.intValue() != Kernel32.IO_REPARSE_TAG_SYMLINK) {
LOG.debug("DeviceIoControl(" + path + "): " + result + "," + myReparseData.ReparseTag);
return null;
}
String target = new String(myReparseData.PathBuffer, myReparseData.SubstituteNameOffset / 2, myReparseData.SubstituteNameLength / 2);
if ((myReparseData.Flags.intValue() & Kernel32.SYMLINK_FLAG_RELATIVE) == Kernel32.SYMLINK_FLAG_RELATIVE) {
return new File(new File(path).getParent(), target).getCanonicalPath();
}
else {
if (target.startsWith("\\??\\") || target.startsWith("\\\\?\\")) {
target = target.substring(4);
}
return new File(target).getCanonicalPath();
}
}
finally {
myKernel32.CloseHandle(handle);
}
}
}
}*/
}