Permalink
Cannot retrieve contributors at this time
Fetching contributors…
| /* | |
| * Copyright 2000-2017 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.Patches; | |
| import com.intellij.openapi.diagnostic.Logger; | |
| import com.intellij.openapi.util.SystemInfo; | |
| import com.intellij.openapi.util.io.win32.FileInfo; | |
| import com.intellij.openapi.util.io.win32.IdeaWin32; | |
| import com.intellij.util.ArrayUtil; | |
| import com.intellij.util.SystemProperties; | |
| import com.intellij.util.containers.ContainerUtil; | |
| import com.sun.jna.Memory; | |
| import com.sun.jna.Native; | |
| import com.sun.jna.Platform; | |
| import com.sun.jna.Pointer; | |
| import org.jetbrains.annotations.NotNull; | |
| import org.jetbrains.annotations.Nullable; | |
| import org.jetbrains.annotations.TestOnly; | |
| import java.io.File; | |
| import java.io.IOException; | |
| import java.lang.reflect.Array; | |
| import java.lang.reflect.Field; | |
| import java.lang.reflect.InvocationTargetException; | |
| import java.lang.reflect.Method; | |
| import java.util.Collection; | |
| import java.util.Locale; | |
| import java.util.Map; | |
| import static com.intellij.util.BitUtil.isSet; | |
| /** | |
| * @version 11.1 | |
| */ | |
| public class FileSystemUtil { | |
| static final String FORCE_USE_NIO2_KEY = "idea.io.use.nio2"; | |
| static final String FORCE_USE_FALLBACK_KEY = "idea.io.use.fallback"; | |
| static final String COARSE_TIMESTAMP_KEY = "idea.io.coarse.ts"; | |
| private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.util.io.FileSystemUtil"); | |
| private abstract static class Mediator { | |
| @Nullable | |
| protected abstract FileAttributes getAttributes(@NotNull String path) throws Exception; | |
| @Nullable | |
| protected abstract String resolveSymLink(@NotNull String path) throws Exception; | |
| protected boolean clonePermissions(@NotNull String source, @NotNull String target, boolean onlyPermissionsToExecute) throws Exception { return false; } | |
| @NotNull | |
| private String getName() { return getClass().getSimpleName().replace("MediatorImpl", ""); } | |
| } | |
| @NotNull | |
| private static Mediator ourMediator = getMediator(); | |
| private static Mediator getMediator() { | |
| boolean forceNio2 = SystemProperties.getBooleanProperty(FORCE_USE_NIO2_KEY, false); | |
| boolean forceFallback = SystemProperties.getBooleanProperty(FORCE_USE_FALLBACK_KEY, false); | |
| Throwable error = null; | |
| if (!forceNio2 && !forceFallback) { | |
| if (SystemInfo.isWindows && IdeaWin32.isAvailable()) { | |
| try { | |
| return check(new IdeaWin32MediatorImpl()); | |
| } | |
| catch (Throwable t) { | |
| error = t; | |
| } | |
| } | |
| else if (SystemInfo.isLinux || SystemInfo.isMac || SystemInfo.isSolaris || SystemInfo.isFreeBSD) { | |
| try { | |
| return check(new JnaUnixMediatorImpl()); | |
| } | |
| catch (Throwable t) { | |
| error = t; | |
| } | |
| } | |
| } | |
| if (!forceFallback && SystemInfo.isJavaVersionAtLeast("1.7") && !"1.7.0-ea".equals(SystemInfo.JAVA_VERSION)) { | |
| try { | |
| return check(new Nio2MediatorImpl()); | |
| } | |
| catch (Throwable t) { | |
| error = t; | |
| } | |
| } | |
| if (!forceFallback) { | |
| LOG.warn("Failed to load filesystem access layer: " + SystemInfo.OS_NAME + ", " + SystemInfo.JAVA_VERSION + ", " + "nio2=" + forceNio2, error); | |
| } | |
| return new FallbackMediatorImpl(); | |
| } | |
| private static Mediator check(final Mediator mediator) throws Exception { | |
| final String quickTestPath = SystemInfo.isWindows ? "C:\\" : "/"; | |
| mediator.getAttributes(quickTestPath); | |
| return mediator; | |
| } | |
| private FileSystemUtil() { } | |
| @Nullable | |
| public static FileAttributes getAttributes(@NotNull String path) { | |
| try { | |
| return ourMediator.getAttributes(path); | |
| } | |
| catch (Exception e) { | |
| LOG.warn(e); | |
| } | |
| return null; | |
| } | |
| @Nullable | |
| public static FileAttributes getAttributes(@NotNull File file) { | |
| return getAttributes(file.getPath()); | |
| } | |
| public static long lastModified(@NotNull File file) { | |
| FileAttributes attributes = getAttributes(file); | |
| return attributes != null ? attributes.lastModified : 0; | |
| } | |
| /** | |
| * Checks if a last element in the path is a symlink. | |
| */ | |
| public static boolean isSymLink(@NotNull String path) { | |
| if (SystemInfo.areSymLinksSupported) { | |
| final FileAttributes attributes = getAttributes(path); | |
| return attributes != null && attributes.isSymLink(); | |
| } | |
| return false; | |
| } | |
| /** | |
| * Checks if a last element in the path is a symlink. | |
| */ | |
| public static boolean isSymLink(@NotNull File file) { | |
| return isSymLink(file.getAbsolutePath()); | |
| } | |
| @Nullable | |
| public static String resolveSymLink(@NotNull String path) { | |
| try { | |
| final String realPath = ourMediator.resolveSymLink(path); | |
| if (realPath != null && new File(realPath).exists()) { | |
| return realPath; | |
| } | |
| } | |
| catch (Exception e) { | |
| LOG.warn(e); | |
| } | |
| return null; | |
| } | |
| @Nullable | |
| public static String resolveSymLink(@NotNull File file) { | |
| return resolveSymLink(file.getAbsolutePath()); | |
| } | |
| /** | |
| * Gives the second file permissions of the first one if possible; returns true if succeed. | |
| * Will do nothing on Windows. | |
| */ | |
| public static boolean clonePermissions(@NotNull String source, @NotNull String target) { | |
| try { | |
| return ourMediator.clonePermissions(source, target, false); | |
| } | |
| catch (Exception e) { | |
| LOG.warn(e); | |
| return false; | |
| } | |
| } | |
| /** | |
| * Gives the second file permissions to execute of the first one if possible; returns true if succeed. | |
| * Will do nothing on Windows. | |
| */ | |
| public static boolean clonePermissionsToExecute(@NotNull String source, @NotNull String target) { | |
| try { | |
| return ourMediator.clonePermissions(source, target, true); | |
| } | |
| catch (Exception e) { | |
| LOG.warn(e); | |
| return false; | |
| } | |
| } | |
| private static class Nio2MediatorImpl extends Mediator { | |
| private final Method myGetPath; | |
| private final Object myLinkOptions; | |
| private final Object myNoFollowLinkOptions; | |
| private final Method myReadAttributes; | |
| private final Method mySetAttribute; | |
| private final Method myToRealPath; | |
| private final Method myToMillis; | |
| private final Class<?> mySchema; | |
| private final Method myIsSymbolicLink; | |
| private final Method myIsDirectory; | |
| private final Method myIsOther; | |
| private final Method mySize; | |
| private final Method myLastModifiedTime; | |
| private final Method myIsHidden; | |
| private final Method myIsReadOnly; | |
| private Nio2MediatorImpl() throws Exception { | |
| assert Patches.USE_REFLECTION_TO_ACCESS_JDK7; | |
| myGetPath = accessible(Class.forName("java.nio.file.Paths").getMethod("get", String.class, String[].class)); | |
| Class<?> pathClass = Class.forName("java.nio.file.Path"); | |
| Class<?> filesClass = Class.forName("java.nio.file.Files"); | |
| Class<?> linkOptClass = Class.forName("java.nio.file.LinkOption"); | |
| myLinkOptions = Array.newInstance(linkOptClass, 0); | |
| myNoFollowLinkOptions = Array.newInstance(linkOptClass, 1); | |
| Array.set(myNoFollowLinkOptions, 0, linkOptClass.getField("NOFOLLOW_LINKS").get(null)); | |
| Class<?> linkOptArrayClass = myLinkOptions.getClass(); | |
| myReadAttributes = accessible(filesClass.getMethod("readAttributes", pathClass, Class.class, linkOptArrayClass)); | |
| mySetAttribute = accessible(filesClass.getMethod("setAttribute", pathClass, String.class, Object.class, linkOptArrayClass)); | |
| myToRealPath = accessible(pathClass.getMethod("toRealPath", linkOptArrayClass)); | |
| myToMillis = accessible(Class.forName("java.nio.file.attribute.FileTime").getMethod("toMillis")); | |
| mySchema = Class.forName("java.nio.file.attribute." + (SystemInfo.isWindows ? "DosFileAttributes" : "PosixFileAttributes")); | |
| myIsSymbolicLink = accessible(mySchema.getMethod("isSymbolicLink")); | |
| myIsDirectory = accessible(mySchema.getMethod("isDirectory")); | |
| myIsOther = accessible(mySchema.getMethod("isOther")); | |
| mySize = accessible(mySchema.getMethod("size")); | |
| myLastModifiedTime = accessible(mySchema.getMethod("lastModifiedTime")); | |
| if (SystemInfo.isWindows) { | |
| myIsHidden = accessible(mySchema.getMethod("isHidden")); | |
| myIsReadOnly = accessible(mySchema.getMethod("isReadOnly")); | |
| } | |
| else { | |
| myIsHidden = myIsReadOnly = null; | |
| } | |
| } | |
| private static Method accessible(Method method) { | |
| method.setAccessible(true); | |
| return method; | |
| } | |
| @Override | |
| protected FileAttributes getAttributes(@NotNull String path) throws Exception { | |
| try { | |
| Object pathObj = myGetPath.invoke(null, path, ArrayUtil.EMPTY_STRING_ARRAY); | |
| Object attributes = myReadAttributes.invoke(null, pathObj, mySchema, myNoFollowLinkOptions); | |
| boolean isSymbolicLink = (Boolean)myIsSymbolicLink.invoke(attributes) || | |
| SystemInfo.isWindows && (Boolean)myIsOther.invoke(attributes) && (Boolean)myIsDirectory.invoke(attributes); | |
| if (isSymbolicLink) { | |
| try { | |
| attributes = myReadAttributes.invoke(null, pathObj, mySchema, myLinkOptions); | |
| } | |
| catch (InvocationTargetException e) { | |
| Throwable cause = e.getCause(); | |
| if (cause != null && "java.nio.file.NoSuchFileException".equals(cause.getClass().getName())) { | |
| return FileAttributes.BROKEN_SYMLINK; | |
| } | |
| } | |
| } | |
| boolean isDirectory = (Boolean)myIsDirectory.invoke(attributes); | |
| boolean isOther = (Boolean)myIsOther.invoke(attributes); | |
| long size = (Long)mySize.invoke(attributes); | |
| long lastModified = (Long)myToMillis.invoke(myLastModifiedTime.invoke(attributes)); | |
| if (SystemInfo.isWindows) { | |
| boolean isHidden = new File(path).getParent() == null ? false : (Boolean)myIsHidden.invoke(attributes); | |
| boolean isWritable = isDirectory || !(Boolean)myIsReadOnly.invoke(attributes); | |
| return new FileAttributes(isDirectory, isOther, isSymbolicLink, isHidden, size, lastModified, isWritable); | |
| } | |
| else { | |
| boolean isWritable = new File(path).canWrite(); | |
| return new FileAttributes(isDirectory, isOther, isSymbolicLink, false, size, lastModified, isWritable); | |
| } | |
| } | |
| catch (InvocationTargetException e) { | |
| Throwable cause = e.getCause(); | |
| if (cause instanceof IOException || cause != null && "java.nio.file.InvalidPathException".equals(cause.getClass().getName())) { | |
| LOG.debug(cause); | |
| return null; | |
| } | |
| throw e; | |
| } | |
| } | |
| @Override | |
| protected String resolveSymLink(@NotNull String path) throws Exception { | |
| Object pathObj = myGetPath.invoke(null, path, ArrayUtil.EMPTY_STRING_ARRAY); | |
| try { | |
| return myToRealPath.invoke(pathObj, myLinkOptions).toString(); | |
| } | |
| catch (InvocationTargetException e) { | |
| Throwable cause = e.getCause(); | |
| if (cause != null && "java.nio.file.NoSuchFileException".equals(cause.getClass().getName())) return null; | |
| throw e; | |
| } | |
| } | |
| @Override | |
| protected boolean clonePermissions(@NotNull String source, @NotNull String target, boolean onlyPermissionsToExecute) throws Exception { | |
| if (SystemInfo.isUnix) { | |
| Object sourcePath = myGetPath.invoke(null, source, ArrayUtil.EMPTY_STRING_ARRAY); | |
| Object targetPath = myGetPath.invoke(null, target, ArrayUtil.EMPTY_STRING_ARRAY); | |
| Collection sourcePermissions = getPermissions(sourcePath); | |
| Collection targetPermissions = getPermissions(targetPath); | |
| if (sourcePermissions != null && targetPermissions != null) { | |
| if (onlyPermissionsToExecute) { | |
| Collection<Object> permissionsToSet = ContainerUtil.newHashSet(); | |
| for (Object permission : targetPermissions) { | |
| if (!permission.toString().endsWith("_EXECUTE")) { | |
| permissionsToSet.add(permission); | |
| } | |
| } | |
| for (Object permission : sourcePermissions) { | |
| if (permission.toString().endsWith("_EXECUTE")) { | |
| permissionsToSet.add(permission); | |
| } | |
| } | |
| mySetAttribute.invoke(null, targetPath, "posix:permissions", permissionsToSet, myLinkOptions); | |
| } | |
| else { | |
| mySetAttribute.invoke(null, targetPath, "posix:permissions", sourcePermissions, myLinkOptions); | |
| } | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| private Collection getPermissions(Object sourcePath) throws IllegalAccessException, InvocationTargetException { | |
| Map attributes = (Map)myReadAttributes.invoke(null, sourcePath, "posix:permissions", myLinkOptions); | |
| if (attributes == null) return null; | |
| Object permissions = attributes.get("permissions"); | |
| return permissions instanceof Collection ? (Collection)permissions : null; | |
| } | |
| } | |
| private static class IdeaWin32MediatorImpl extends Mediator { | |
| private IdeaWin32 myInstance = IdeaWin32.getInstance(); | |
| @Override | |
| protected FileAttributes getAttributes(@NotNull final String path) throws Exception { | |
| final FileInfo fileInfo = myInstance.getInfo(path); | |
| return fileInfo != null ? fileInfo.toFileAttributes() : null; | |
| } | |
| @Override | |
| protected String resolveSymLink(@NotNull final String path) throws Exception { | |
| return myInstance.resolveSymLink(path); | |
| } | |
| } | |
| // thanks to SVNKit for the idea of platform-specific offsets | |
| private static class JnaUnixMediatorImpl extends Mediator { | |
| @SuppressWarnings({"OctalInteger", "SpellCheckingInspection"}) | |
| private static class LibC { | |
| static final int S_MASK = 0177777; | |
| static final int S_IFMT = 0170000; | |
| static final int S_IFLNK = 0120000; // symbolic link | |
| static final int S_IFREG = 0100000; // regular file | |
| static final int S_IFDIR = 0040000; // directory | |
| static final int PERM_MASK = 0777; | |
| static final int EXECUTE_MASK = 0111; | |
| static final int WRITE_MASK = 0222; | |
| static final int W_OK = 2; // write permission flag for access(2) | |
| static native int getuid(); | |
| static native int getgid(); | |
| static native int chmod(String path, int mode); | |
| static native int access(String path, int mode); | |
| } | |
| @SuppressWarnings("SpellCheckingInspection") | |
| private static class UnixLibC { | |
| static native int lstat(String path, Pointer stat); | |
| static native int stat(String path, Pointer stat); | |
| } | |
| @SuppressWarnings("SpellCheckingInspection") | |
| private static class LinuxLibC { | |
| static native int __lxstat64(int ver, String path, Pointer stat); | |
| static native int __xstat64(int ver, String path, Pointer stat); | |
| } | |
| private static final int[] LINUX_32 = {16, 44, 72, 24, 28}; | |
| private static final int[] LINUX_64 = {24, 48, 88, 28, 32}; | |
| private static final int[] LNX_PPC32 = {16, 48, 80, 24, 28}; | |
| private static final int[] LNX_PPC64 = LINUX_64; | |
| private static final int[] LNX_ARM32 = LNX_PPC32; | |
| private static final int[] BSD_32 = { 8, 48, 32, 12, 16}; | |
| private static final int[] BSD_64 = { 8, 72, 40, 12, 16}; | |
| private static final int[] SUN_OS_32 = {20, 48, 64, 28, 32}; | |
| private static final int[] SUN_OS_64 = {16, 40, 64, 24, 28}; | |
| private static final int STAT_VER = 1; | |
| private static final int OFF_MODE = 0; | |
| private static final int OFF_SIZE = 1; | |
| private static final int OFF_TIME = 2; | |
| private static final int OFF_UID = 3; | |
| private static final int OFF_GID = 4; | |
| private final int[] myOffsets; | |
| private final int myUid; | |
| private final int myGid; | |
| private final boolean myCoarseTs = SystemProperties.getBooleanProperty(COARSE_TIMESTAMP_KEY, false); | |
| private JnaUnixMediatorImpl() throws Exception { | |
| if ("linux-x86".equals(Platform.RESOURCE_PREFIX)) myOffsets = LINUX_32; | |
| else if ("linux-x86-64".equals(Platform.RESOURCE_PREFIX)) myOffsets = LINUX_64; | |
| else if ("linux-arm".equals(Platform.RESOURCE_PREFIX)) myOffsets = LNX_ARM32; | |
| else if ("linux-ppc".equals(Platform.RESOURCE_PREFIX)) myOffsets = LNX_PPC32; | |
| else if ("linux-ppc64le".equals(Platform.RESOURCE_PREFIX)) myOffsets = LNX_PPC64; | |
| else if ("freebsd-x86".equals(Platform.RESOURCE_PREFIX)) myOffsets = BSD_32; | |
| else if ("darwin".equals(Platform.RESOURCE_PREFIX) || | |
| "freebsd-x86-64".equals(Platform.RESOURCE_PREFIX)) myOffsets = BSD_64; | |
| else if ("sunos-x86".equals(Platform.RESOURCE_PREFIX)) myOffsets = SUN_OS_32; | |
| else if ("sunos-x86-64".equals(Platform.RESOURCE_PREFIX)) myOffsets = SUN_OS_64; | |
| else throw new IllegalStateException("Unsupported OS/arch: " + SystemInfo.OS_NAME + "/" + SystemInfo.OS_ARCH); | |
| Native.register(LibC.class, "c"); | |
| Native.register(SystemInfo.isLinux ? LinuxLibC.class : UnixLibC.class, "c"); | |
| myUid = LibC.getuid(); | |
| myGid = LibC.getgid(); | |
| } | |
| @Override | |
| protected FileAttributes getAttributes(@NotNull String path) throws Exception { | |
| Memory buffer = new Memory(256); | |
| int res = SystemInfo.isLinux ? LinuxLibC.__lxstat64(STAT_VER, path, buffer) : UnixLibC.lstat(path, buffer); | |
| if (res != 0) return null; | |
| int mode = getModeFlags(buffer) & LibC.S_MASK; | |
| boolean isSymlink = (mode & LibC.S_IFMT) == LibC.S_IFLNK; | |
| if (isSymlink) { | |
| if (!loadFileStatus(path, buffer)) { | |
| return FileAttributes.BROKEN_SYMLINK; | |
| } | |
| mode = getModeFlags(buffer) & LibC.S_MASK; | |
| } | |
| boolean isDirectory = (mode & LibC.S_IFMT) == LibC.S_IFDIR; | |
| boolean isSpecial = !isDirectory && (mode & LibC.S_IFMT) != LibC.S_IFREG; | |
| long size = buffer.getLong(myOffsets[OFF_SIZE]); | |
| long mTime1 = SystemInfo.is32Bit ? buffer.getInt(myOffsets[OFF_TIME]) : buffer.getLong(myOffsets[OFF_TIME]); | |
| long mTime2 = myCoarseTs ? 0 : SystemInfo.is32Bit ? buffer.getInt(myOffsets[OFF_TIME] + 4) : buffer.getLong(myOffsets[OFF_TIME] + 8); | |
| long mTime = mTime1 * 1000 + mTime2 / 1000000; | |
| boolean writable = ownFile(buffer) ? (mode & LibC.WRITE_MASK) != 0 : LibC.access(path, LibC.W_OK) == 0; | |
| return new FileAttributes(isDirectory, isSpecial, isSymlink, false, size, mTime, writable); | |
| } | |
| private static boolean loadFileStatus(String path, Memory buffer) { | |
| return (SystemInfo.isLinux ? LinuxLibC.__xstat64(STAT_VER, path, buffer) : UnixLibC.stat(path, buffer)) == 0; | |
| } | |
| @Override | |
| protected String resolveSymLink(@NotNull final String path) throws Exception { | |
| try { | |
| return new File(path).getCanonicalPath(); | |
| } | |
| catch (IOException e) { | |
| String message = e.getMessage(); | |
| if (message != null && message.toLowerCase(Locale.US).contains("too many levels of symbolic links")) { | |
| LOG.debug(e); | |
| return null; | |
| } | |
| throw new IOException("Cannot resolve '" + path + "'", e); | |
| } | |
| } | |
| @Override | |
| protected boolean clonePermissions(@NotNull String source, @NotNull String target, boolean onlyPermissionsToExecute) throws Exception { | |
| Memory buffer = new Memory(256); | |
| if (!loadFileStatus(source, buffer)) return false; | |
| int permissions; | |
| int sourcePermissions = getModeFlags(buffer) & LibC.PERM_MASK; | |
| if (onlyPermissionsToExecute) { | |
| if (!loadFileStatus(target, buffer)) return false; | |
| int targetPermissions = getModeFlags(buffer) & LibC.PERM_MASK; | |
| permissions = targetPermissions & ~LibC.EXECUTE_MASK | sourcePermissions & LibC.EXECUTE_MASK; | |
| } | |
| else { | |
| permissions = sourcePermissions; | |
| } | |
| return LibC.chmod(target, permissions) == 0; | |
| } | |
| private int getModeFlags(Memory buffer) { | |
| return SystemInfo.isLinux ? buffer.getInt(myOffsets[OFF_MODE]) : buffer.getShort(myOffsets[OFF_MODE]); | |
| } | |
| private boolean ownFile(Memory buffer) { | |
| return buffer.getInt(myOffsets[OFF_UID]) == myUid && buffer.getInt(myOffsets[OFF_GID]) == myGid; | |
| } | |
| } | |
| private static class FallbackMediatorImpl extends Mediator { | |
| // from java.io.FileSystem | |
| private static final int BA_REGULAR = 0x02; | |
| private static final int BA_DIRECTORY = 0x04; | |
| private static final int BA_HIDDEN = 0x08; | |
| private final Object myFileSystem; | |
| private final Method myGetBooleanAttributes; | |
| private FallbackMediatorImpl() { | |
| Object fileSystem; | |
| Method getBooleanAttributes; | |
| try { | |
| Field fs = File.class.getDeclaredField("fs"); | |
| fs.setAccessible(true); | |
| fileSystem = fs.get(null); | |
| getBooleanAttributes = fileSystem.getClass().getMethod("getBooleanAttributes", File.class); | |
| getBooleanAttributes.setAccessible(true); | |
| } | |
| catch (Throwable t) { | |
| fileSystem = null; | |
| getBooleanAttributes = null; | |
| } | |
| myFileSystem = fileSystem; | |
| myGetBooleanAttributes = getBooleanAttributes; | |
| } | |
| @Override | |
| protected FileAttributes getAttributes(@NotNull final String path) throws Exception { | |
| final File file = new File(path); | |
| if (myFileSystem != null) { | |
| final int flags = (Integer)myGetBooleanAttributes.invoke(myFileSystem, file); | |
| if (flags != 0) { | |
| boolean isDirectory = isSet(flags, BA_DIRECTORY); | |
| boolean isSpecial = !isSet(flags, BA_REGULAR) && !isSet(flags, BA_DIRECTORY); | |
| boolean isHidden = isSet(flags, BA_HIDDEN) && !isWindowsRoot(path); | |
| boolean isWritable = SystemInfo.isWindows && isDirectory || file.canWrite(); | |
| return new FileAttributes(isDirectory, isSpecial, false, isHidden, file.length(), file.lastModified(), isWritable); | |
| } | |
| } | |
| else if (file.exists()) { | |
| boolean isDirectory = file.isDirectory(); | |
| boolean isSpecial = !isDirectory && !file.isFile(); | |
| boolean isHidden = file.isHidden() && !isWindowsRoot(path); | |
| boolean isWritable = SystemInfo.isWindows && isDirectory || file.canWrite(); | |
| return new FileAttributes(isDirectory, isSpecial, false, isHidden, file.length(), file.lastModified(), isWritable); | |
| } | |
| return null; | |
| } | |
| private static boolean isWindowsRoot(String p) { | |
| return SystemInfo.isWindows && p.length() >= 2 && p.length() <= 3 && Character.isLetter(p.charAt(0)) && p.charAt(1) == ':'; | |
| } | |
| @Override | |
| protected String resolveSymLink(@NotNull final String path) throws Exception { | |
| return new File(path).getCanonicalPath(); | |
| } | |
| @Override | |
| protected boolean clonePermissions(@NotNull String source, @NotNull String target, boolean onlyPermissionsToExecute) throws Exception { | |
| if (SystemInfo.isUnix) { | |
| File srcFile = new File(source); | |
| File dstFile = new File(target); | |
| if (!onlyPermissionsToExecute) { | |
| if (!dstFile.setWritable(srcFile.canWrite(), true)) return false; | |
| } | |
| return dstFile.setExecutable(srcFile.canExecute(), true); | |
| } | |
| return false; | |
| } | |
| } | |
| @TestOnly | |
| static void resetMediator() { | |
| ourMediator = getMediator(); | |
| } | |
| @TestOnly | |
| static String getMediatorName() { | |
| return ourMediator.getName(); | |
| } | |
| } |