mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Symlink support for Windows
This commit is contained in:
@@ -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() {
|
||||
|
||||
+9
-9
@@ -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
|
||||
|
||||
+7
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+6
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
+21
-10
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
}
|
||||
Reference in New Issue
Block a user