From ff9bfb967243227cfd41b9b877ea3891a5943106 Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Fri, 20 Jul 2012 20:28:46 +0200 Subject: [PATCH] One-shot fIle attributes reading --- .../openapi/vfs/impl/jar/JarHandlerBase.java | 2 +- .../openapi/vfs/ex/temp/TempFileSystem.java | 2 +- .../vfs/impl/win32/Win32LocalFileSystem.java | 6 +- .../vfs/local/SymlinkHandlingTest.java | 31 +--- .../openapi/util/io/FileAttributes.java | 116 ++++++++++---- .../openapi/util/io/FileSystemUtil.java | 139 ++++++++++------- .../util/src/com/intellij/util/BitUtil.java | 8 +- .../io/FileAttributesNio2ReadingTest.java | 44 ++++++ .../util/io/FileAttributesReadingTest.java | 147 ++++++++++++++++++ .../intellij/openapi/util/io/IoTestUtil.java | 53 +++++++ 10 files changed, 428 insertions(+), 120 deletions(-) create mode 100644 platform/util/testSrc/com/intellij/openapi/util/io/FileAttributesNio2ReadingTest.java create mode 100644 platform/util/testSrc/com/intellij/openapi/util/io/FileAttributesReadingTest.java create mode 100644 platform/util/testSrc/com/intellij/openapi/util/io/IoTestUtil.java diff --git a/platform/core-impl/src/com/intellij/openapi/vfs/impl/jar/JarHandlerBase.java b/platform/core-impl/src/com/intellij/openapi/vfs/impl/jar/JarHandlerBase.java index e063890effdd..c75fa6105263 100644 --- a/platform/core-impl/src/com/intellij/openapi/vfs/impl/jar/JarHandlerBase.java +++ b/platform/core-impl/src/com/intellij/openapi/vfs/impl/jar/JarHandlerBase.java @@ -308,7 +308,7 @@ public class JarHandlerBase { public FileAttributes getAttributes(@NotNull final VirtualFile file) { synchronized (lock) { final JarFile.JarEntry entry = convertToEntry(file); - return entry != null ? new FileAttributes(entry.isDirectory(), false, false, entry.getSize(), entry.getTime(), false) : null; + return entry != null ? new FileAttributes(entry.isDirectory(), false, false, false, entry.getSize(), entry.getTime(), false) : null; } } } diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/ex/temp/TempFileSystem.java b/platform/platform-impl/src/com/intellij/openapi/vfs/ex/temp/TempFileSystem.java index 1b9a3bae4eb9..7e81c94562c0 100644 --- a/platform/platform-impl/src/com/intellij/openapi/vfs/ex/temp/TempFileSystem.java +++ b/platform/platform-impl/src/com/intellij/openapi/vfs/ex/temp/TempFileSystem.java @@ -382,6 +382,6 @@ public class TempFileSystem extends NewVirtualFileSystem { final FSItem item = convert(file); if (item == null) return null; final long length = item instanceof FSFile ? ((FSFile)item).myContent.length : 0; - return new FileAttributes(item.isDirectory(), false, false, length, item.myTimestamp, item.myWritable); + return new FileAttributes(item.isDirectory(), false, false, false, length, item.myTimestamp, item.myWritable); } } diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/impl/win32/Win32LocalFileSystem.java b/platform/platform-impl/src/com/intellij/openapi/vfs/impl/win32/Win32LocalFileSystem.java index 7bfd2404feda..0cec37f27b18 100644 --- a/platform/platform-impl/src/com/intellij/openapi/vfs/impl/win32/Win32LocalFileSystem.java +++ b/platform/platform-impl/src/com/intellij/openapi/vfs/impl/win32/Win32LocalFileSystem.java @@ -20,7 +20,6 @@ import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.SystemInfo; import com.intellij.openapi.util.io.FileAttributes; import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.impl.local.LocalFileSystemBase; import com.intellij.util.ArrayUtil; @@ -222,9 +221,10 @@ public class Win32LocalFileSystem extends LocalFileSystemBase { if (fileInfo == null) return null; final boolean isDirectory = isSet(fileInfo.attributes, Win32Kernel.FILE_ATTRIBUTE_DIRECTORY); - final boolean isSymlink = isSet(fileInfo.attributes, Win32Kernel.FILE_ATTRIBUTE_REPARSE_POINT); final boolean isSpecial = isSet(fileInfo.attributes, Win32Kernel.FILE_ATTRIBUTE_DEVICE); + final boolean isSymlink = isSet(fileInfo.attributes, Win32Kernel.FILE_ATTRIBUTE_REPARSE_POINT); + final boolean isHidden = isSet(fileInfo.attributes, Win32Kernel.FILE_ATTRIBUTE_HIDDEN); final boolean isWritable = !isSet(fileInfo.attributes, Win32Kernel.FILE_ATTRIBUTE_READONLY); - return new FileAttributes(isDirectory, isSymlink, isSpecial, fileInfo.length, fileInfo.timestamp, isWritable); + return new FileAttributes(isDirectory, isSpecial, isSymlink, isHidden, fileInfo.length, fileInfo.timestamp, isWritable); } } diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/vfs/local/SymlinkHandlingTest.java b/platform/platform-tests/testSrc/com/intellij/openapi/vfs/local/SymlinkHandlingTest.java index 2a8cb8d5d453..1d5ba117808a 100644 --- a/platform/platform-tests/testSrc/com/intellij/openapi/vfs/local/SymlinkHandlingTest.java +++ b/platform/platform-tests/testSrc/com/intellij/openapi/vfs/local/SymlinkHandlingTest.java @@ -15,13 +15,10 @@ */ package com.intellij.openapi.vfs.local; -import com.intellij.execution.ExecutionException; -import com.intellij.execution.configurations.GeneralCommandLine; import com.intellij.openapi.application.AccessToken; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.util.SystemInfo; import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VfsUtilCore; import com.intellij.openapi.vfs.VirtualFile; @@ -30,6 +27,8 @@ import org.jetbrains.annotations.Nullable; import java.io.File; +import static com.intellij.openapi.util.io.IoTestUtil.createTempLink; + public class SymlinkHandlingTest extends LightPlatformLangTestCase { private LocalFileSystem myFileSystem; @@ -265,32 +264,6 @@ public class SymlinkHandlingTest extends LightPlatformLangTestCase { assertEquals(vSubChild, vSubChildRel); } - // todo[r.sh] use NIO2 API after migration to JDK 7 - public static File createTempLink(final String target, final String link) throws InterruptedException, ExecutionException { - final boolean isAbsolute = SystemInfo.isUnix && StringUtil.startsWithChar(link, '/') || - SystemInfo.isWindows && link.matches("^[c-zC-Z]:[/\\\\].*$"); - final File linkFile = isAbsolute ? new File(link) : new File(FileUtil.getTempDirectory(), link); - assertTrue(link, !linkFile.exists() || linkFile.delete()); - final File parentDir = linkFile.getParentFile(); - assertTrue("link=" + link + ", parent=" + parentDir, parentDir != null && (parentDir.isDirectory() || parentDir.mkdirs())); - - final GeneralCommandLine commandLine; - if (SystemInfo.isWindows) { - commandLine = new File(target).isDirectory() - ? new GeneralCommandLine("cmd", "/C", "mklink", "/D", linkFile.getAbsolutePath(), target) - : new GeneralCommandLine("cmd", "/C", "mklink", linkFile.getAbsolutePath(), target); - } - else { - commandLine = new GeneralCommandLine("ln", "-s", target, linkFile.getAbsolutePath()); - } - final int res = commandLine.createProcess().waitFor(); - assertEquals(commandLine.getCommandLineString(), 0, res); - - final File targetFile = new File(target); - assertEquals("target=" + target + ", link=" + linkFile, targetFile.exists(), linkFile.exists()); - return linkFile; - } - @Nullable private VirtualFile refreshAndFind(final File ioFile) { refresh(); diff --git a/platform/util/src/com/intellij/openapi/util/io/FileAttributes.java b/platform/util/src/com/intellij/openapi/util/io/FileAttributes.java index 24b6a1001fb2..9ed82712a354 100644 --- a/platform/util/src/com/intellij/openapi/util/io/FileAttributes.java +++ b/platform/util/src/com/intellij/openapi/util/io/FileAttributes.java @@ -18,12 +18,23 @@ package com.intellij.openapi.util.io; import com.intellij.util.StringBuilderSpinAllocator; import org.intellij.lang.annotations.MagicConstant; +import static com.intellij.util.BitUtil.isSet; +import static com.intellij.util.BitUtil.notSet; + /** * @version 11.1 * @see FileSystemUtil#getAttributes(String) */ @SuppressWarnings("OctalInteger") public final class FileAttributes { + public static final int DIRECTORY = 0x0001; + public static final int SPECIAL = 0x0002; + public static final int SYM_LINK = 0x0010; + public static final int HIDDEN = 0x0100; + + @MagicConstant(flags = {DIRECTORY, SPECIAL, SYM_LINK, HIDDEN}) + public @interface FileType { } + public static final int OWNER_READ = 0400; public static final int OWNER_WRITE = 0200; public static final int OWNER_EXECUTE = 0100; @@ -34,52 +45,99 @@ public final class FileAttributes { public static final int OTHERS_WRITE = 0002; public static final int OTHERS_EXECUTE = 0001; - @MagicConstant(flags = { - OWNER_READ, OWNER_WRITE, OWNER_EXECUTE, GROUP_READ, GROUP_WRITE, GROUP_EXECUTE, OTHERS_READ, OTHERS_WRITE, OTHERS_EXECUTE - }) + @MagicConstant(flags = {OWNER_READ, OWNER_WRITE, OWNER_EXECUTE, GROUP_READ, GROUP_WRITE, GROUP_EXECUTE, OTHERS_READ, OTHERS_WRITE, OTHERS_EXECUTE}) public @interface Permissions { } - public final boolean isFile; - public final boolean isDirectory; - public final boolean isSymlink; - public final boolean isSpecial; - /** In bytes, 0 for symlinks and special files. */ + /** + * {@linkplain #DIRECTORY} and {@linkplain #SPECIAL} bits are mutually exclusive, none of them set means a regular file.
+ * {@linkplain #SYM_LINK} bit may be set along with above ones (which then denote a type of a link target).
+ * {@linkplain #HIDDEN} bit may be only set on Windows. + */ + @FileType + public final int type; + + /** + * In bytes, 0 for special files.
+ * For symlinks - length of a link target. + */ public final long length; - /** In milliseconds (note that actual resolution may be less accurate). */ + /** + * In milliseconds (actual resolution depends on a file system and may be less accurate).
+ * For symlinks - timestamp of a link target. + */ public final long lastModified; - /** UNIX permission bits (for Windows only OWNER_WRITE matters and OWNER_READ/EXECUTE are always set), or -1 if not supported. */ + /** + * UNIX permission bits (for Windows only OWNER_WRITE matters and OWNER_READ/EXECUTE are always set), or -1 if not supported.
+ * For symlinks - permissions of a link target. + */ @Permissions public final int permissions; - // todo: hidden flag (?) public FileAttributes(final boolean isDirectory, - final boolean isSymlink, final boolean isSpecial, + final boolean isSymlink, + final boolean isHidden, final long length, final long lastModified, - final boolean writable) { - this(isDirectory, isSymlink, isSpecial, length, lastModified, OWNER_READ | OWNER_EXECUTE | (writable ? OWNER_WRITE : 0)); + final boolean isWritable) { + this(type(isDirectory, isSpecial, isSymlink, isHidden), length, lastModified, OWNER_READ | OWNER_EXECUTE | (isWritable ? OWNER_WRITE : 0)); } public FileAttributes(final boolean isDirectory, - final boolean isSymlink, final boolean isSpecial, + final boolean isSymlink, final long length, final long lastModified, @Permissions final int permissions) { - this.isFile = !isDirectory && !isSymlink && !isSpecial; - this.isDirectory = isDirectory; - this.isSymlink = isSymlink; - this.isSpecial = isSpecial; - this.length = isSymlink || isSpecial ? 0 : length; + this(type(isDirectory, isSpecial, isSymlink, false), length, lastModified, permissions); + } + + private FileAttributes(@FileType final int type, + final long length, + final long lastModified, + @Permissions final int permissions) { + if (isSet(type, DIRECTORY) && isSet(type, SPECIAL)) { + throw new IllegalArgumentException("DIRECTORY and SPECIAL bits cannot be set simultaneously"); + } + + this.type = type; + this.length = length; this.lastModified = lastModified; this.permissions = permissions; } + @FileType + private static int type(final boolean isDirectory, final boolean isSpecial, final boolean isSymlink, final boolean isHidden) { + @FileType int type = 0; + if (isDirectory) type |= DIRECTORY; + if (isSpecial) type |= SPECIAL; + if (isSymlink) type |= SYM_LINK; + if (isHidden) type |= HIDDEN; + return type; + } + + /** Is {@code true} for files and symlinks to files (see {@linkplain #isRegularFile()}). */ + public boolean isFile() { + return notSet(type, DIRECTORY | SPECIAL); + } + + /** Is {@code true} for pure regular files only (see {@linkplain #isFile()}). */ + public boolean isRegularFile() { + return notSet(type, DIRECTORY | SPECIAL | SYM_LINK); + } + + public boolean isSymLink() { + return isSet(type, SYM_LINK); + } + + public boolean isWritable() { + return permissions == -1 || isSet(permissions, OWNER_WRITE) || isSet(permissions, GROUP_WRITE) || isSet(permissions, OTHERS_WRITE); + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -87,10 +145,7 @@ public final class FileAttributes { final FileAttributes that = (FileAttributes)o; - if (isDirectory != that.isDirectory) return false; - if (isFile != that.isFile) return false; - if (isSpecial != that.isSpecial) return false; - if (isSymlink != that.isSymlink) return false; + if (type != that.type) return false; if (lastModified != that.lastModified) return false; if (permissions != that.permissions) return false; if (length != that.length) return false; @@ -100,10 +155,7 @@ public final class FileAttributes { @Override public int hashCode() { - int result = (isFile ? 1 : 0); - result = 31 * result + (isDirectory ? 1 : 0); - result = 31 * result + (isSymlink ? 1 : 0); - result = 31 * result + (isSpecial ? 1 : 0); + int result = type; result = 31 * result + (int)(length ^ (length >>> 32)); result = 31 * result + (int)(lastModified ^ (lastModified >>> 32)); result = 31 * result + permissions; @@ -115,10 +167,10 @@ public final class FileAttributes { final StringBuilder sb = StringBuilderSpinAllocator.alloc(); try { sb.append("[type:"); - if (isFile) sb.append('f'); - if (isDirectory) sb.append('d'); - if (isSymlink) sb.append('l'); - if (isSpecial) sb.append('!'); + if (isSet(type, DIRECTORY)) sb.append('d'); + else if (isSet(type, SPECIAL)) sb.append('!'); + else sb.append('f'); + if (isSet(type, SYM_LINK)) sb.append('l'); sb.append(" length:").append(length); diff --git a/platform/util/src/com/intellij/openapi/util/io/FileSystemUtil.java b/platform/util/src/com/intellij/openapi/util/io/FileSystemUtil.java index 929902480269..b22a09e22d07 100644 --- a/platform/util/src/com/intellij/openapi/util/io/FileSystemUtil.java +++ b/platform/util/src/com/intellij/openapi/util/io/FileSystemUtil.java @@ -18,12 +18,14 @@ 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.intellij.util.SystemProperties; 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 org.jetbrains.annotations.TestOnly; import java.io.File; import java.lang.reflect.Array; @@ -31,47 +33,55 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.*; +import static com.intellij.util.BitUtil.isSet; +import static com.intellij.util.BitUtil.notSet; + /** * @version 11.1 */ public class FileSystemUtil { + public static final String FORCE_USE_NIO2_KEY = "idea.io.use.nio2"; + private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.util.io.FileSystemUtil"); @NotNull - private static final Mediator ourMediator; - static { - Mediator mediator = null; + private static Mediator ourMediator = getMediator(); + + private static Mediator getMediator() { + final boolean forceUseNio2 = SystemProperties.getBooleanProperty(FORCE_USE_NIO2_KEY, false); + final String quickTestPath = SystemInfo.isWindows ? "C:\\" : "/"; // todo: move IdeaWin32 interface to this package, add mediator - if (SystemInfo.isLinux || SystemInfo.isMac || SystemInfo.isSolaris || SystemInfo.isFreeBSD) { + if (!forceUseNio2 && (SystemInfo.isLinux || SystemInfo.isMac || SystemInfo.isSolaris || SystemInfo.isFreeBSD)) { try { - mediator = new JnaUnixMediatorImpl(); - mediator.getAttributes("/"); // quick test + final Mediator mediator = new JnaUnixMediatorImpl(); + mediator.getAttributes(quickTestPath); + return mediator; } catch (Throwable t) { - LOG.error(t); - mediator = null; + LOG.warn(t); } } - if (mediator == null && SystemInfo.isJavaVersionAtLeast("1.7") && !"1.7.0-ea".equals(SystemInfo.JAVA_VERSION)) { + if (SystemInfo.isJavaVersionAtLeast("1.7") && !"1.7.0-ea".equals(SystemInfo.JAVA_VERSION)) { try { - mediator = new Jdk7MediatorImpl(); - mediator.getAttributes("/"); // quick test + final Mediator mediator = new Nio2MediatorImpl(); + mediator.getAttributes(quickTestPath); + return mediator; } catch (Throwable t) { - LOG.error(t); - mediator = null; + LOG.warn(t); } } - if (mediator == null) { - // todo: after introducing IdeaWin32 mediator, fail tests at this point, or issue a warning in production - mediator = new StandardMediatorImpl(); - } + // todo: after introducing IdeaWin32 mediator, fail tests at this point, or issue a warning in production + return new StandardMediatorImpl(); + } - ourMediator = mediator; + @TestOnly + static void resetMediator() { + ourMediator = getMediator(); } private FileSystemUtil() { } @@ -87,14 +97,21 @@ public class FileSystemUtil { return null; } + @SuppressWarnings("UnusedDeclaration") + @Nullable + public static FileAttributes getAttributes(@NotNull final File file) { + return getAttributes(file.getAbsolutePath()); + } + public static boolean isSymLink(@NotNull final String path) { if (SystemInfo.areSymLinksSupported) { final FileAttributes attributes = getAttributes(path); - return attributes != null && attributes.isSymlink; + return attributes != null && attributes.isSymLink(); } return false; } + @SuppressWarnings("UnusedDeclaration") public static boolean isSymLink(@NotNull final File file) { return isSymLink(file.getAbsolutePath()); } @@ -113,6 +130,7 @@ public class FileSystemUtil { return null; } + @SuppressWarnings("UnusedDeclaration") @Nullable public static String resolveSymLink(@NotNull final File file) { return resolveSymLink(file.getAbsolutePath()); @@ -123,6 +141,7 @@ public class FileSystemUtil { return attributes != null ? attributes.permissions : -1; } + @SuppressWarnings("UnusedDeclaration") public static int getPermissions(@NotNull final File file) { return getPermissions(file.getAbsolutePath()); } @@ -154,7 +173,7 @@ public class FileSystemUtil { // todo[r.sh] remove reflection after migration to JDK 7 @SuppressWarnings("OctalInteger") - private static class Jdk7MediatorImpl implements Mediator { + private static class Nio2MediatorImpl implements Mediator { private final Object myDefaultFileSystem; private final Method myGetPath; private final Method myIsSymbolicLink; @@ -163,8 +182,9 @@ public class FileSystemUtil { private final Method myReadAttributes; private final Method mySetAttribute; private final Method myToMillis; + private final String mySchema; - private Jdk7MediatorImpl() throws Exception { + private Nio2MediatorImpl() 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); @@ -189,29 +209,33 @@ public class FileSystemUtil { final Class fileTimeClass = Class.forName("java.nio.file.attribute.FileTime"); myToMillis = fileTimeClass.getMethod("toMillis"); myToMillis.setAccessible(true); + + mySchema = SystemInfo.isWindows ? "dos:*" : "posix:*"; } @Override public FileAttributes getAttributes(@NotNull final String path) throws Exception { try { final Object pathObj = myGetPath.invoke(myDefaultFileSystem, path, ArrayUtil.EMPTY_STRING_ARRAY); + + Map attributes = (Map)myReadAttributes.invoke(null, pathObj, mySchema, myNoFollowLinkOptions); + final Boolean isSymbolicLink = (Boolean)attributes.get("isSymbolicLink"); + if (isSymbolicLink) { + attributes = (Map)myReadAttributes.invoke(null, pathObj, mySchema, myLinkOptions); + } + + final boolean isDirectory = (Boolean)attributes.get("isDirectory"); + final boolean isOther = (Boolean)attributes.get("isOther"); + final long size = (Long)attributes.get("size"); + final long lastModified = (Long)myToMillis.invoke(attributes.get("lastModifiedTime")); if (SystemInfo.isWindows) { - final Map attributes = (Map)myReadAttributes.invoke(null, pathObj, "dos:*", myNoFollowLinkOptions); - return new FileAttributes((Boolean)attributes.get("isDirectory"), - (Boolean)attributes.get("isSymbolicLink"), - (Boolean)attributes.get("isOther"), - (Long)attributes.get("size"), - (Long)myToMillis.invoke(attributes.get("lastModifiedTime")), - !(Boolean)attributes.get("readonly")); + final boolean isHidden = (Boolean)attributes.get("hidden"); + final boolean isWritable = !(Boolean)attributes.get("readonly"); + return new FileAttributes(isDirectory, isOther, isSymbolicLink, isHidden, size, lastModified, isWritable); } else { - final Map attributes = (Map)myReadAttributes.invoke(null, pathObj, "posix:*", myNoFollowLinkOptions); - return new FileAttributes((Boolean)attributes.get("isDirectory"), - (Boolean)attributes.get("isSymbolicLink"), - (Boolean)attributes.get("isOther"), - (Long)attributes.get("size"), - (Long)myToMillis.invoke(attributes.get("lastModifiedTime")), - decodePermissions(attributes.get("permissions"))); + final int permissions = decodePermissions(attributes.get("permissions")); + return new FileAttributes(isDirectory, isOther, isSymbolicLink, size, lastModified, permissions); } } catch (InvocationTargetException e) { @@ -302,7 +326,9 @@ public class FileSystemUtil { int PERM_MASK = 0777; int lstat(String path, Pointer stat); + int stat(String path, Pointer stat); int __lxstat64(int ver, String path, Pointer stat); + int __xstat64(int ver, String path, Pointer stat); int chmod(String path, int mode); } @@ -333,20 +359,26 @@ public class FileSystemUtil { @Override public synchronized FileAttributes getAttributes(@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(myModeOffset) : mySharedMem.getShort(myModeOffset)) & LibC.S_MASK; - final boolean isDirectory = (mode & LibC.S_IFDIR) == LibC.S_IFDIR; - final boolean isSymlink = (mode & LibC.S_IFLNK) == LibC.S_IFLNK; - final boolean isSpecial = !isDirectory && !isSymlink && (mode & LibC.S_IFREG) == 0; - final long size = mySharedMem.getLong(mySizeOffset); - final long mTime1 = SystemInfo.is32Bit ? mySharedMem.getInt(myTimeOffset) : mySharedMem.getLong(myTimeOffset); - final long mTime2 = SystemInfo.is32Bit ? mySharedMem.getInt(myTimeOffset + 4) : mySharedMem.getLong(myTimeOffset + 8); - final long mTime = mTime1 * 1000 + mTime2 / 1000000; - @FileAttributes.Permissions final int permissions = mode & LibC.PERM_MASK; - return new FileAttributes(isDirectory, isSymlink, isSpecial, size, mTime, permissions); + int res = SystemInfo.isLinux ? myLibC.__lxstat64(0, path, mySharedMem) : myLibC.lstat(path, mySharedMem); + if (res != 0) return null; + + int mode = (SystemInfo.isLinux ? mySharedMem.getInt(myModeOffset) : mySharedMem.getShort(myModeOffset)) & LibC.S_MASK; + final boolean isSymlink = (mode & LibC.S_IFLNK) == LibC.S_IFLNK; + if (isSymlink) { + mySharedMem.clear(); + res = SystemInfo.isLinux ? myLibC.__xstat64(0, path, mySharedMem) : myLibC.stat(path, mySharedMem); + if (res != 0) return null; + mode = (SystemInfo.isLinux ? mySharedMem.getInt(myModeOffset) : mySharedMem.getShort(myModeOffset)) & LibC.S_MASK; } - return null; + + final boolean isDirectory = (mode & LibC.S_IFDIR) == LibC.S_IFDIR; + final boolean isSpecial = !isDirectory && (mode & LibC.S_IFREG) == 0; + final long size = mySharedMem.getLong(mySizeOffset); + final long mTime1 = SystemInfo.is32Bit ? mySharedMem.getInt(myTimeOffset) : mySharedMem.getLong(myTimeOffset); + final long mTime2 = SystemInfo.is32Bit ? mySharedMem.getInt(myTimeOffset + 4) : mySharedMem.getLong(myTimeOffset + 8); + final long mTime = mTime1 * 1000 + mTime2 / 1000000; + @FileAttributes.Permissions final int permissions = mode & LibC.PERM_MASK; + return new FileAttributes(isDirectory, isSpecial, isSymlink, size, mTime, permissions); } @Override @@ -364,6 +396,7 @@ public class FileSystemUtil { // 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; @@ -393,16 +426,18 @@ public class FileSystemUtil { if (myFileSystem != null) { final int flags = (Integer)myGetBooleanAttributes.invoke(myFileSystem, file); if (flags != 0) { - final boolean isDirectory = (flags & BA_DIRECTORY) != 0; - final boolean isSpecial = (flags & (BA_REGULAR | BA_DIRECTORY)) == 0; - return new FileAttributes(isDirectory, false, isSpecial, file.length(), file.lastModified(), file.canWrite()); + final boolean isDirectory = isSet(flags, BA_DIRECTORY); + final boolean isSpecial = notSet(flags, BA_REGULAR | BA_DIRECTORY); + final boolean isHidden = isSet(flags, BA_HIDDEN); + return new FileAttributes(isDirectory, isSpecial, false, isHidden, file.length(), file.lastModified(), file.canWrite()); } } else { if (file.exists()) { final boolean isDirectory = file.isDirectory(); final boolean isSpecial = !isDirectory && !file.isFile(); - return new FileAttributes(isDirectory, false, isSpecial, file.length(), file.lastModified(), file.canWrite()); + final boolean isHidden = file.isHidden(); + return new FileAttributes(isDirectory, isSpecial, false, isHidden, file.length(), file.lastModified(), file.canWrite()); } } diff --git a/platform/util/src/com/intellij/util/BitUtil.java b/platform/util/src/com/intellij/util/BitUtil.java index 36de2fb8dfbf..485d1ad21c06 100644 --- a/platform/util/src/com/intellij/util/BitUtil.java +++ b/platform/util/src/com/intellij/util/BitUtil.java @@ -19,7 +19,11 @@ package com.intellij.util; * @since 27.03.2012 */ public class BitUtil { - public static boolean isSet(final int mask, final int flag) { - return (mask & flag) == flag; + public static boolean isSet(final int value, final int flag) { + return (value & flag) == flag; + } + + public static boolean notSet(final int value, final int mask) { + return (value & mask) == 0; } } diff --git a/platform/util/testSrc/com/intellij/openapi/util/io/FileAttributesNio2ReadingTest.java b/platform/util/testSrc/com/intellij/openapi/util/io/FileAttributesNio2ReadingTest.java new file mode 100644 index 000000000000..ffb2674334b0 --- /dev/null +++ b/platform/util/testSrc/com/intellij/openapi/util/io/FileAttributesNio2ReadingTest.java @@ -0,0 +1,44 @@ +/* + * Copyright 2000-2012 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.util.SystemInfo; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; + +import static org.junit.Assume.assumeTrue; + +public class FileAttributesNio2ReadingTest extends FileAttributesReadingTest { + @BeforeClass + public static void setUpClass() throws Exception { + System.setProperty(FileSystemUtil.FORCE_USE_NIO2_KEY, "true"); + FileSystemUtil.resetMediator(); + } + + @AfterClass + public static void tearDownClass() throws Exception { + System.setProperty(FileSystemUtil.FORCE_USE_NIO2_KEY, ""); + FileSystemUtil.resetMediator(); + } + + @Override + @Before + public void setUp() throws Exception { + assumeTrue(SystemInfo.isJavaVersionAtLeast("1.7")); + super.setUp(); + } +} diff --git a/platform/util/testSrc/com/intellij/openapi/util/io/FileAttributesReadingTest.java b/platform/util/testSrc/com/intellij/openapi/util/io/FileAttributesReadingTest.java new file mode 100644 index 000000000000..414ed48fcd15 --- /dev/null +++ b/platform/util/testSrc/com/intellij/openapi/util/io/FileAttributesReadingTest.java @@ -0,0 +1,147 @@ +/* + * Copyright 2000-2012 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.util.SystemInfo; +import org.jetbrains.annotations.NotNull; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.io.File; + +import static org.junit.Assert.*; +import static org.junit.Assume.assumeTrue; + +public class FileAttributesReadingTest { + private final byte[] myTestData = new byte[]{'t', 'e', 's', 't'}; + private File myTempDirectory; + + @Before + public void setUp() throws Exception { + myTempDirectory = FileUtil.createTempDirectory(getClass().getName(), ".tmp"); + } + + @After + public void tearDown() throws Exception { + if (myTempDirectory != null) { + FileUtil.delete(myTempDirectory); + } + } + + @Test + public void regularFile() throws Exception { + final File file = FileUtil.createTempFile(myTempDirectory, "test.", ".txt", true); + FileUtil.writeToFile(file, myTestData); + + final FileAttributes attributes = getAttributes(file); + assertEquals(0, attributes.type); + assertEquals(myTestData.length, attributes.length); + assertTimestampEquals(file.lastModified(), attributes.lastModified); + assertTrue(attributes.isWritable()); + } + + @Test + public void directory() throws Exception { + final File file = FileUtil.createTempDirectory(myTempDirectory, "test.", ".tmp"); + + final FileAttributes attributes = getAttributes(file); + assertEquals(FileAttributes.DIRECTORY, attributes.type); + assertEquals(file.length(), attributes.length); + assertTimestampEquals(file.lastModified(), attributes.lastModified); + assertTrue(attributes.isWritable()); + } + + @Test + public void special() throws Exception { + assumeTrue(SystemInfo.isUnix); + final File file = new File("/dev/null"); + + final FileAttributes attributes = getAttributes(file); + assertEquals(FileAttributes.SPECIAL, attributes.type); + assertEquals(0, attributes.length); + assertTrue(attributes.isWritable()); + } + + @Test + public void linkToFile() throws Exception { + final File file = FileUtil.createTempFile(myTempDirectory, "test.", ".txt", true); + FileUtil.writeToFile(file, myTestData); + assertTrue(file.setWritable(false, false)); + assertTrue(file.setLastModified(file.lastModified() - 5000)); + final File link = IoTestUtil.createTempLink(file.getPath(), new File(myTempDirectory, "link").getPath()); + + final FileAttributes attributes = getAttributes(link); + assertEquals(FileAttributes.SYM_LINK, attributes.type); + assertEquals(myTestData.length, attributes.length); + assertTimestampEquals(file.lastModified(), attributes.lastModified); + assertFalse(attributes.isWritable()); + + final String target = FileSystemUtil.resolveSymLink(link); + assertEquals(file.getPath(), target); + } + + @Test + public void doubleLink() throws Exception { + final File file = FileUtil.createTempFile(myTempDirectory, "test.", ".txt", true); + FileUtil.writeToFile(file, myTestData); + assertTrue(file.setWritable(false, false)); + assertTrue(file.setLastModified(file.lastModified() - 5000)); + final File link1 = IoTestUtil.createTempLink(file.getPath(), new File(myTempDirectory, "link1").getPath()); + final File link2 = IoTestUtil.createTempLink(link1.getPath(), new File(myTempDirectory, "link2").getPath()); + + final FileAttributes attributes = getAttributes(link2); + assertEquals(FileAttributes.SYM_LINK, attributes.type); + assertEquals(myTestData.length, attributes.length); + assertTimestampEquals(file.lastModified(), attributes.lastModified); + assertFalse(attributes.isWritable()); + + final String target = FileSystemUtil.resolveSymLink(link2); + assertEquals(file.getPath(), target); + } + + @Test + public void linkToDirectory() throws Exception { + final File file = FileUtil.createTempDirectory(myTempDirectory, "test.", ".tmp"); + assertTrue(file.setWritable(false, false)); + assertTrue(file.setLastModified(file.lastModified() - 5000)); + final File link = IoTestUtil.createTempLink(file.getPath(), new File(myTempDirectory, "link").getPath()); + + final FileAttributes attributes = getAttributes(link); + assertEquals(FileAttributes.DIRECTORY | FileAttributes.SYM_LINK, attributes.type); + assertEquals(file.length(), attributes.length); + assertTimestampEquals(file.lastModified(), attributes.lastModified); + assertFalse(attributes.isWritable()); + + final String target = FileSystemUtil.resolveSymLink(link); + assertEquals(file.getPath(), target); + } + + @NotNull + private static FileAttributes getAttributes(@NotNull final File file) { + final FileAttributes attributes = FileSystemUtil.getAttributes(file.getPath()); + assertNotNull(attributes); + System.out.println(attributes); + return attributes; + } + + private static void assertTimestampEquals(final long expected, final long actual) { + final long roundedExpected = (expected / 1000) * 1000; + final long roundedActual = (actual / 1000) * 1000; + assertEquals("expected: " + expected + ", actual: " + actual, + roundedExpected, roundedActual); + } +} diff --git a/platform/util/testSrc/com/intellij/openapi/util/io/IoTestUtil.java b/platform/util/testSrc/com/intellij/openapi/util/io/IoTestUtil.java new file mode 100644 index 000000000000..da880c0560f3 --- /dev/null +++ b/platform/util/testSrc/com/intellij/openapi/util/io/IoTestUtil.java @@ -0,0 +1,53 @@ +/* + * Copyright 2000-2012 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.util.SystemInfo; +import com.intellij.openapi.util.text.StringUtil; + +import java.io.File; +import java.io.IOException; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class IoTestUtil { + // todo[r.sh] use NIO2 API after migration to JDK 7 + public static File createTempLink(final String target, final String link) throws InterruptedException, IOException { + final boolean isAbsolute = SystemInfo.isUnix && StringUtil.startsWithChar(link, '/') || + SystemInfo.isWindows && link.matches("^[c-zC-Z]:[/\\\\].*$"); + final File linkFile = isAbsolute ? new File(link) : new File(FileUtil.getTempDirectory(), link); + assertTrue(link, !linkFile.exists() || linkFile.delete()); + final File parentDir = linkFile.getParentFile(); + assertTrue("link=" + link + ", parent=" + parentDir, parentDir != null && (parentDir.isDirectory() || parentDir.mkdirs())); + + final ProcessBuilder commandLine; + if (SystemInfo.isWindows) { + commandLine = new File(target).isDirectory() + ? new ProcessBuilder("cmd", "/C", "mklink", "/D", linkFile.getAbsolutePath(), target) + : new ProcessBuilder("cmd", "/C", "mklink", linkFile.getAbsolutePath(), target); + } + else { + commandLine = new ProcessBuilder("ln", "-s", target, linkFile.getAbsolutePath()); + } + final int res = commandLine.start().waitFor(); + assertEquals(commandLine.command().toString(), 0, res); + + final File targetFile = new File(target); + assertEquals("target=" + target + ", link=" + linkFile, targetFile.exists(), linkFile.exists()); + return linkFile; + } +}