diff --git a/bin/win/IdeaWin32.dll b/bin/win/IdeaWin32.dll index 995f0ebc66c2..62fc0d58ab9b 100644 Binary files a/bin/win/IdeaWin32.dll and b/bin/win/IdeaWin32.dll differ diff --git a/bin/win/IdeaWin64.dll b/bin/win/IdeaWin64.dll index 9b8f2b13a79e..4f67183261ba 100644 Binary files a/bin/win/IdeaWin64.dll and b/bin/win/IdeaWin64.dll differ diff --git a/native/IdeaWin32/IdeaWin32.cpp b/native/IdeaWin32/IdeaWin32.cpp index a408023ce251..f6648cc04978 100644 --- a/native/IdeaWin32/IdeaWin32.cpp +++ b/native/IdeaWin32/IdeaWin32.cpp @@ -142,15 +142,24 @@ JNIEXPORT void JNICALL Java_com_intellij_openapi_util_io_win32_IdeaWin32_initIDs JNIEXPORT jobject JNICALL Java_com_intellij_openapi_util_io_win32_IdeaWin32_getInfo0(JNIEnv *env, jobject method, jstring path) { - WIN32_FIND_DATA data; - HANDLE h = FindFileInner(env, path, &data); + bool isDrive = false; + if (env->GetStringLength(path) == 2) { + const jchar* str = env->GetStringChars(path, NULL); + isDrive = (str[1] == ':'); + env->ReleaseStringChars(path, str); + } - if (h == INVALID_HANDLE_VALUE) { - if (GetLastError() != ERROR_ACCESS_DENIED) { + WIN32_FIND_DATA data; + HANDLE h = INVALID_HANDLE_VALUE; + + if (!isDrive) { + h = FindFileInner(env, path, &data); + if (h == INVALID_HANDLE_VALUE && GetLastError() != ERROR_ACCESS_DENIED) { return NULL; } + } - // there is a chance that directory listing is denied but direct file access will succeed + if (h == INVALID_HANDLE_VALUE) { WIN32_FILE_ATTRIBUTE_DATA attrData; const jchar* str = env->GetStringChars(path, 0); BOOL res = GetFileAttributesEx((LPCWSTR)str, GetFileExInfoStandard, &attrData); diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/vfs/local/FileWatcherTest.java b/platform/platform-tests/testSrc/com/intellij/openapi/vfs/local/FileWatcherTest.java index 91cbc04fb418..dbf34795b350 100644 --- a/platform/platform-tests/testSrc/com/intellij/openapi/vfs/local/FileWatcherTest.java +++ b/platform/platform-tests/testSrc/com/intellij/openapi/vfs/local/FileWatcherTest.java @@ -15,13 +15,13 @@ */ package com.intellij.openapi.vfs.local; -import com.intellij.execution.configurations.GeneralCommandLine; import com.intellij.openapi.application.AccessToken; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.SystemInfo; import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.io.IoTestUtil; import com.intellij.openapi.vfs.*; import com.intellij.openapi.vfs.impl.local.FileWatcher; import com.intellij.openapi.vfs.newvfs.BulkFileListener; @@ -417,35 +417,16 @@ public class FileWatcherTest extends PlatformLangTestCase { return; } - final Set roots = ContainerUtil.map2Set(File.listRoots(), new Function() { - @Override - public Character fun(File root) { - return root.getPath().toLowerCase(Locale.US).charAt(0); - } - }); - char subst = 0; - for (char c = 'e'; c <= 'z'; c++) { - if (!roots.contains(c)) { - subst = c; - break; - } - } - assertFalse("Occupied: " + roots.toString(), subst == 0); - final File targetDir = FileUtil.createTempDirectory("top.", null); final File subDir = FileUtil.createTempDirectory(targetDir, "sub.", null); final File file = FileUtil.createTempFile(subDir, "test.", ".txt", true, false); - final int rv = new GeneralCommandLine("subst", subst + ":", targetDir.getAbsolutePath()).createProcess().waitFor(); - assertEquals(0, rv); - - final String substRoot = (subst + ":").toUpperCase(Locale.US); - VirtualDirectoryImpl.allowRootAccess(substRoot); - - final VirtualFile vfsRoot = myFileSystem.findFileByPath(substRoot); - assertNotNull(substRoot, vfsRoot); + final File rootFile = IoTestUtil.createSubst(targetDir.getAbsolutePath()); + VirtualDirectoryImpl.allowRootAccess(rootFile.getPath()); + final VirtualFile vfsRoot = myFileSystem.findFileByIoFile(rootFile); try { - final File substDir = new File(substRoot, subDir.getName()); + assertNotNull(rootFile.getPath(), vfsRoot); + final File substDir = new File(rootFile, subDir.getName()); final File substFile = new File(substDir, file.getName()); refresh(targetDir); refresh(substDir); @@ -476,10 +457,12 @@ public class FileWatcherTest extends PlatformLangTestCase { } finally { delete(targetDir); - new GeneralCommandLine("subst", subst + ":", "/d").createProcess().waitFor(); - ((NewVirtualFile)vfsRoot).markDirty(); - myFileSystem.refresh(false); - VirtualDirectoryImpl.disallowRootAccess(substRoot); + IoTestUtil.deleteSubst(rootFile.getPath()); + if (vfsRoot != null) { + ((NewVirtualFile)vfsRoot).markDirty(); + myFileSystem.refresh(false); + } + VirtualDirectoryImpl.disallowRootAccess(rootFile.getPath()); } } diff --git a/platform/util/src/com/intellij/openapi/util/io/win32/IdeaWin32.java b/platform/util/src/com/intellij/openapi/util/io/win32/IdeaWin32.java index 575ae4f9f787..89a7b51fa01f 100644 --- a/platform/util/src/com/intellij/openapi/util/io/win32/IdeaWin32.java +++ b/platform/util/src/com/intellij/openapi/util/io/win32/IdeaWin32.java @@ -97,22 +97,33 @@ public class IdeaWin32 { @Nullable public FileInfo getInfo(@NotNull final String path) { - return getInfo0(unc(path)); + return getInfo0(path(path)); } @Nullable public String resolveSymLink(@NotNull final String path) { - final String result = resolveSymLink0(unc(path)); + final String result = resolveSymLink0(path(path)); return result != null && result.startsWith(PATH_PREFIX) ? result.substring(PREFIX_SIZE) : result; } @Nullable public FileInfo[] listChildren(@NotNull final String path) { - return listChildren0(unc(path) + PATH_SUFFIX); + return listChildren0(path(path) + PATH_SUFFIX); } - private static String unc(final String path) { - return path.length() < MAX_PATH ? path : PATH_PREFIX + path; + private static String path(final String path) { + final int length = path.length(); + if (length > 0 && path.charAt(length - 1) == '\\' || length >= MAX_PATH) { + final StringBuilder sb = new StringBuilder(path); + while (sb.length() > 0 && sb.charAt(sb.length() - 1) == '\\') { + sb.deleteCharAt(sb.length() - 1); + } + if (sb.length() >= MAX_PATH) { + sb.insert(0, PATH_PREFIX); + } + return sb.toString(); + } + return path; } private native FileInfo getInfo0(String path); diff --git a/platform/util/testSrc/com/intellij/openapi/util/io/FileAttributesReadingTest.java b/platform/util/testSrc/com/intellij/openapi/util/io/FileAttributesReadingTest.java index 41bd996d02b1..53d1964d6563 100644 --- a/platform/util/testSrc/com/intellij/openapi/util/io/FileAttributesReadingTest.java +++ b/platform/util/testSrc/com/intellij/openapi/util/io/FileAttributesReadingTest.java @@ -26,6 +26,7 @@ import org.junit.BeforeClass; import org.junit.Test; import java.io.File; +import java.util.Arrays; import static org.junit.Assert.*; import static org.junit.Assume.assumeTrue; @@ -67,12 +68,7 @@ public class FileAttributesReadingTest { final File file = FileUtil.createTempFile(myTempDirectory, "test.", ".txt", true); FileUtil.writeToFile(file, myTestData); - final FileAttributes attributes = getAttributes(file); - assertEquals(FileAttributes.Type.FILE, attributes.type); - assertEquals(0, attributes.flags); - assertEquals(myTestData.length, attributes.length); - assertTimestampEquals(file.lastModified(), attributes.lastModified); - assertTrue(attributes.isWritable()); + assertFileAttributes(file); } @Test @@ -83,8 +79,34 @@ public class FileAttributesReadingTest { assertEquals(FileAttributes.Type.DIRECTORY, attributes.type); assertEquals(0, attributes.flags); assertEquals(file.length(), attributes.length); - assertTimestampEquals(file.lastModified(), attributes.lastModified); + assertTimestampsEqual(file.lastModified(), attributes.lastModified); assertTrue(attributes.isWritable()); + if (SystemInfo.isWindows) { + assertDirectoriesEqual(file); + } + } + + @Test + public void root() throws Exception { + final File file = new File(SystemInfo.isWindows ? "C:\\" : "/"); + + final FileAttributes attributes = getAttributes(file); + assertEquals(FileAttributes.Type.DIRECTORY, attributes.type); + if (SystemInfo.isWindows) { + assertDirectoriesEqual(file); + } + } + + @Test + public void badNames() throws Exception { + final File file = FileUtil.createTempFile(myTempDirectory, "test.", ".txt", true); + FileUtil.writeToFile(file, myTestData); + + assertFileAttributes(new File(file.getPath() + StringUtil.repeat(File.separator, 3))); + assertFileAttributes(new File(file.getPath().replace(File.separator, StringUtil.repeat(File.separator, 3)))); + assertFileAttributes(new File(file.getPath().replace(File.separator, File.separator + "." + File.separator))); + assertFileAttributes(new File(myTempDirectory, + File.separator + ".." + File.separator + myTempDirectory.getName() + File.separator + file.getName())); } @Test @@ -113,7 +135,7 @@ public class FileAttributesReadingTest { assertEquals(FileAttributes.Type.FILE, attributes.type); assertEquals(FileAttributes.SYM_LINK, attributes.flags); assertEquals(myTestData.length, attributes.length); - assertTimestampEquals(file.lastModified(), attributes.lastModified); + assertTimestampsEqual(file.lastModified(), attributes.lastModified); assertFalse(attributes.isWritable()); final String target = FileSystemUtil.resolveSymLink(link); @@ -135,7 +157,7 @@ public class FileAttributesReadingTest { assertEquals(FileAttributes.Type.FILE, attributes.type); assertEquals(FileAttributes.SYM_LINK, attributes.flags); assertEquals(myTestData.length, attributes.length); - assertTimestampEquals(file.lastModified(), attributes.lastModified); + assertTimestampsEqual(file.lastModified(), attributes.lastModified); assertFalse(attributes.isWritable()); final String target = FileSystemUtil.resolveSymLink(link2); @@ -155,7 +177,7 @@ public class FileAttributesReadingTest { assertEquals(FileAttributes.Type.DIRECTORY, attributes.type); assertEquals(FileAttributes.SYM_LINK, attributes.flags); assertEquals(file.length(), attributes.length); - assertTimestampEquals(file.lastModified(), attributes.lastModified); + assertTimestampsEqual(file.lastModified(), attributes.lastModified); if (SystemInfo.isUnix) assertFalse(attributes.isWritable()); final String target = FileSystemUtil.resolveSymLink(link); @@ -209,7 +231,7 @@ public class FileAttributesReadingTest { assertEquals(FileAttributes.Type.FILE, attributes.type); assertEquals(FileAttributes.HIDDEN, attributes.flags); assertEquals(file.length(), attributes.length); - assertTimestampEquals(file.lastModified(), attributes.lastModified); + assertTimestampsEqual(file.lastModified(), attributes.lastModified); } @Test @@ -231,19 +253,25 @@ public class FileAttributesReadingTest { assertTrue(file.exists()); FileUtil.writeToFile(file, myTestData); - final FileAttributes attributes = getAttributes(file); - assertEquals(FileAttributes.Type.FILE, attributes.type); - assertEquals(0, attributes.flags); - assertEquals(myTestData.length, attributes.length); - assertTimestampEquals(file.lastModified(), attributes.lastModified); - assertTrue(attributes.isWritable()); - + assertFileAttributes(file); if (SystemInfo.isWindows) { - final String[] list1 = dir.list(); - assertNotNull(list1); - final FileInfo[] list2 = IdeaWin32.getInstance().listChildren(dir.getPath()); - assertNotNull(list2); - assertEquals(list1.length + 2, list2.length); + assertDirectoriesEqual(dir); + } + } + + @Test + public void subst() throws Exception { + assumeTrue(SystemInfo.isWindows); + + FileUtil.createTempFile(myTempDirectory, "test.", ".txt", true); // just to populate a directory + final File substRoot = IoTestUtil.createSubst(myTempDirectory.getPath()); + try { + final FileAttributes attributes = getAttributes(substRoot); + assertEquals(FileAttributes.Type.DIRECTORY, attributes.type); + assertDirectoriesEqual(substRoot); + } + finally { + IoTestUtil.deleteSubst(substRoot.getPath()); } } @@ -255,10 +283,29 @@ public class FileAttributesReadingTest { return attributes; } - private static void assertTimestampEquals(final long expected, final long actual) { + private static void assertFileAttributes(@NotNull final File file) { + final FileAttributes attributes = getAttributes(file); + assertEquals(FileAttributes.Type.FILE, attributes.type); + assertEquals(0, attributes.flags); + assertEquals(file.length(), attributes.length); + assertTimestampsEqual(file.lastModified(), attributes.lastModified); + assertTrue(attributes.isWritable()); + } + + private static void assertTimestampsEqual(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); } + + private static void assertDirectoriesEqual(@NotNull final File dir) { + final String[] list1 = dir.list(); + assertNotNull(list1); + final FileInfo[] list2 = IdeaWin32.getInstance().listChildren(dir.getPath()); + assertNotNull(list2); + if (list1.length + 2 != list2.length) { + assertEquals(Arrays.toString(list1), Arrays.toString(list2)); + } + } } diff --git a/platform/util/testSrc/com/intellij/openapi/util/io/IoTestUtil.java b/platform/util/testSrc/com/intellij/openapi/util/io/IoTestUtil.java index 2a69a5489ff8..f88ad642e0a1 100644 --- a/platform/util/testSrc/com/intellij/openapi/util/io/IoTestUtil.java +++ b/platform/util/testSrc/com/intellij/openapi/util/io/IoTestUtil.java @@ -17,6 +17,8 @@ package com.intellij.openapi.util.io; import com.intellij.openapi.util.SystemInfo; import com.intellij.openapi.util.text.StringUtil; +import com.intellij.util.Function; +import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import java.io.BufferedReader; @@ -24,9 +26,10 @@ import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.net.URL; +import java.util.Locale; +import java.util.Set; import static org.junit.Assert.*; -import static org.junit.Assert.assertEquals; public class IoTestUtil { private IoTestUtil() { } @@ -92,6 +95,47 @@ public class IoTestUtil { return junctionFile; } + public static File createSubst(@NotNull final String target) throws InterruptedException, IOException { + assertTrue(SystemInfo.isWindows); + + final File targetFile = new File(target); + assertTrue(targetFile.getPath(), targetFile.isDirectory()); + + final String substRoot = getFirstFreeDriveLetter() + ":"; + + final ProcessBuilder command = new ProcessBuilder("subst", substRoot, target); + final int res = runCommand(command); + assertEquals(command.command().toString(), 0, res); + + final File rootFile = new File(substRoot); + assertTrue("target=" + target + ", subst=" + rootFile, rootFile.isDirectory()); + return rootFile; + } + + public static void deleteSubst(@NotNull final String substRoot) throws InterruptedException, IOException { + runCommand(new ProcessBuilder("subst", substRoot, "/d")); + } + + private static char getFirstFreeDriveLetter() { + final Set roots = ContainerUtil.map2Set(File.listRoots(), new Function() { + @Override + public Character fun(File root) { + return root.getPath().toUpperCase(Locale.US).charAt(0); + } + }); + + char drive = 0; + for (char c = 'E'; c <= 'Z'; c++) { + if (!roots.contains(c)) { + drive = c; + break; + } + } + + assertFalse("Occupied: " + roots.toString(), drive == 0); + return drive; + } + private static File getFullLinkPath(final String link) { final boolean isAbsolute = SystemInfo.isUnix && StringUtil.startsWithChar(link, '/') || SystemInfo.isWindows && link.matches("^[c-zC-Z]:[/\\\\].*$");