Snapshot of commit d5ec1d5018ed24f1b4f32b1d09df6dbd7e2fc425
from branch master of git://git.jetbrains.org/idea/community.git
diff --git a/native/fileWatcher/fileWatcher3.cpp b/native/fileWatcher/fileWatcher3.cpp
new file mode 100644
index 0000000..9a0b3d8
--- /dev/null
+++ b/native/fileWatcher/fileWatcher3.cpp
@@ -0,0 +1,558 @@
+/*
+ * 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.
+ */
+
+#include "stdafx.h"
+
+struct WatchRootInfo {
+ char driveLetter;
+ HANDLE hThread;
+ HANDLE hStopEvent;
+ bool bInitialized;
+ bool bUsed;
+ bool bFailed;
+};
+
+struct WatchRoot {
+ char *path;
+ WatchRoot *next;
+};
+
+const int ROOT_COUNT = 26;
+
+WatchRootInfo watchRootInfos[ROOT_COUNT];
+
+WatchRoot *firstWatchRoot = NULL;
+
+CRITICAL_SECTION csOutput;
+
+void NormalizeSlashes(char *path, char slash)
+{
+ for(char *p=path; *p; p++)
+ if (*p == '\\' || *p == '/')
+ *p = slash;
+}
+
+// -- Watchable root checks ---------------------------------------------------
+
+bool IsNetworkDrive(const char *name)
+{
+ const int BUF_SIZE = 1024;
+ char buffer[BUF_SIZE];
+ UNIVERSAL_NAME_INFO* uni = (UNIVERSAL_NAME_INFO*) buffer;
+ DWORD size = BUF_SIZE;
+
+ DWORD result = WNetGetUniversalNameA(
+ name, // path for network resource
+ UNIVERSAL_NAME_INFO_LEVEL, // level of information
+ buffer, // name buffer
+ &size // size of buffer
+ );
+
+ return result == NO_ERROR;
+}
+
+bool IsUnwatchableFS(const char *path)
+{
+ char volumeName[MAX_PATH];
+ char fsName[MAX_PATH];
+ DWORD fsFlags;
+ DWORD maxComponentLength;
+ SetErrorMode(SEM_FAILCRITICALERRORS);
+ if (!GetVolumeInformationA(path, volumeName, MAX_PATH-1, NULL, &maxComponentLength, &fsFlags, fsName, MAX_PATH-1))
+ return false;
+ if (strcmp(fsName, "NTFS") && strcmp(fsName, "FAT") && strcmp(fsName, "FAT32"))
+ return true;
+
+ if (!strcmp(fsName, "NTFS") && maxComponentLength != 255 && !(fsFlags & FILE_SUPPORTS_REPARSE_POINTS))
+ {
+ // SAMBA reports itself as NTFS
+ return true;
+ }
+
+ return false;
+}
+
+bool IsWatchable(const char *path)
+{
+ if (IsNetworkDrive(path))
+ return false;
+ if (IsUnwatchableFS(path))
+ return false;
+ return true;
+}
+
+// -- Substed drive checks ----------------------------------------------------
+
+void PrintRemapForSubstDrive(char driveLetter)
+{
+ const int BUF_SIZE = 1024;
+ char targetPath[BUF_SIZE];
+
+ char rootPath[8];
+ sprintf_s(rootPath, 8, "%c:", driveLetter);
+
+ DWORD result = QueryDosDeviceA(rootPath, targetPath, BUF_SIZE);
+ if (result == 0) {
+ return;
+ }
+ else
+ {
+ if (targetPath[0] == '\\' && targetPath[1] == '?' && targetPath[2] == '?' && targetPath[3] == '\\')
+ {
+ // example path: \??\C:\jetbrains\idea
+ NormalizeSlashes(targetPath, '/');
+ printf("%c:\n%s\n", driveLetter, targetPath+4);
+ }
+ }
+}
+
+void PrintRemapForSubstDrives()
+{
+ for(int i=0; i<ROOT_COUNT; i++)
+ {
+ if (watchRootInfos [i].bUsed)
+ {
+ PrintRemapForSubstDrive(watchRootInfos [i].driveLetter);
+ }
+ }
+}
+
+// -- Mount point enumeration -------------------------------------------------
+
+const int BUFSIZE = 1024;
+
+void PrintDirectoryReparsePoint(const char *path)
+{
+ int size = strlen(path)+2;
+ char *directory = (char *) malloc(size);
+ strcpy_s(directory, size, path);
+ NormalizeSlashes(directory, '\\');
+ if (directory [strlen(directory)-1] != '\\')
+ strcat_s(directory, size, "\\");
+
+ char volumeName[_MAX_PATH];
+ int rc = GetVolumeNameForVolumeMountPointA(directory, volumeName, sizeof(volumeName));
+ if (rc)
+ {
+ char volumePathNames[_MAX_PATH];
+ DWORD returnLength;
+ rc = GetVolumePathNamesForVolumeNameA(volumeName, volumePathNames, sizeof(volumePathNames), &returnLength);
+ if (rc)
+ {
+ char *p = volumePathNames;
+ while(*p)
+ {
+ if (_stricmp(p, directory)) // if it's not the path we've already found
+ {
+ NormalizeSlashes(directory, '/');
+ NormalizeSlashes(p, '/');
+ puts(directory);
+ puts(p);
+ }
+ p += strlen(p)+1;
+ }
+ }
+ }
+ free(directory);
+}
+
+bool PrintMountPointsForVolume(HANDLE hVol, const char* volumePath, char *Buf)
+{
+ HANDLE hPt; // handle for mount point scan
+ char Path[BUFSIZE]; // string buffer for mount points
+ DWORD dwSysFlags; // flags that describe the file system
+ char FileSysNameBuf[BUFSIZE];
+
+ // Is this volume NTFS?
+ GetVolumeInformationA(Buf, NULL, 0, NULL, NULL, &dwSysFlags, FileSysNameBuf, BUFSIZE);
+
+ // Detect support for reparse points, and therefore for volume
+ // mount points, which are implemented using reparse points.
+
+ if (! (dwSysFlags & FILE_SUPPORTS_REPARSE_POINTS)) {
+ return true;
+ }
+
+ // Start processing mount points on this volume.
+ hPt = FindFirstVolumeMountPointA(
+ Buf, // root path of volume to be scanned
+ Path, // pointer to output string
+ BUFSIZE // size of output buffer
+ );
+
+ // Shall we error out?
+ if (hPt == INVALID_HANDLE_VALUE) {
+ return GetLastError() != ERROR_ACCESS_DENIED;
+ }
+
+ // Process the volume mount point.
+ char *buf = new char[MAX_PATH];
+ do {
+ strcpy_s(buf, MAX_PATH, volumePath);
+ strcat_s(buf, MAX_PATH, Path);
+ PrintDirectoryReparsePoint(buf);
+ } while (FindNextVolumeMountPointA(hPt, Path, BUFSIZE));
+
+ FindVolumeMountPointClose(hPt);
+ return true;
+}
+
+bool PrintMountPoints(const char *path)
+{
+ char volumeUniqueName[128];
+ BOOL res = GetVolumeNameForVolumeMountPointA(path, volumeUniqueName, 128);
+ if (!res) {
+ return false;
+ }
+
+ char buf[BUFSIZE]; // buffer for unique volume identifiers
+ HANDLE hVol; // handle for the volume scan
+
+ // Open a scan for volumes.
+ hVol = FindFirstVolumeA(buf, BUFSIZE );
+
+ // Shall we error out?
+ if (hVol == INVALID_HANDLE_VALUE) {
+ return false;
+ }
+
+ bool success = true;
+ do {
+ if (!strcmp(buf, volumeUniqueName)) {
+ success = PrintMountPointsForVolume(hVol, path, buf);
+ if (!success) break;
+ }
+ } while (FindNextVolumeA(hVol, buf, BUFSIZE));
+
+ FindVolumeClose(hVol);
+ return success;
+}
+
+// -- Searching for mount points in watch roots (fallback) --------------------
+
+void PrintDirectoryReparsePoints(const char *path)
+{
+ char *const buf = _strdup(path);
+ while(strchr(buf, '/'))
+ {
+ DWORD attributes = GetFileAttributesA(buf);
+ if (attributes == INVALID_FILE_ATTRIBUTES)
+ break;
+ if (attributes & FILE_ATTRIBUTE_REPARSE_POINT)
+ {
+ PrintDirectoryReparsePoint(buf);
+ }
+ char *pSlash = strrchr(buf, '/');
+ if (pSlash)
+ {
+ *pSlash = '\0';
+ }
+ }
+ free(buf);
+}
+
+// This is called if we got an ERROR_ACCESS_DENIED when trying to enumerate all mount points for volume.
+// In this case, we walk the directory tree up from each watch root, and look at each parent directory
+// to check whether it's a reparse point.
+void PrintWatchRootReparsePoints()
+{
+ WatchRoot *pWatchRoot = firstWatchRoot;
+ while(pWatchRoot)
+ {
+ PrintDirectoryReparsePoints(pWatchRoot->path);
+ pWatchRoot = pWatchRoot->next;
+ }
+}
+
+// -- Watcher thread ----------------------------------------------------------
+
+void PrintChangeInfo(char *rootPath, FILE_NOTIFY_INFORMATION *info)
+{
+ char FileNameBuffer[_MAX_PATH];
+ int converted = WideCharToMultiByte(CP_ACP, 0, info->FileName, info->FileNameLength/sizeof(WCHAR), FileNameBuffer, _MAX_PATH-1, NULL, NULL);
+ FileNameBuffer[converted] = '\0';
+ char *command;
+ if (info->Action == FILE_ACTION_ADDED || info->Action == FILE_ACTION_RENAMED_OLD_NAME)
+ {
+ command = "CREATE";
+ }
+ else if (info->Action == FILE_ACTION_REMOVED || info->Action == FILE_ACTION_RENAMED_OLD_NAME)
+ {
+ command = "DELETE";
+ }
+ else if (info->Action == FILE_ACTION_MODIFIED)
+ {
+ command = "CHANGE";
+ }
+ else
+ {
+ return; // unknown command
+ }
+
+ EnterCriticalSection(&csOutput);
+ puts(command);
+ printf("%s", rootPath);
+ puts(FileNameBuffer);
+ fflush(stdout);
+ LeaveCriticalSection(&csOutput);
+}
+
+void PrintEverythingChangedUnderRoot(char *rootPath)
+{
+ EnterCriticalSection(&csOutput);
+ puts("RECDIRTY");
+ puts(rootPath);
+ fflush(stdout);
+ LeaveCriticalSection(&csOutput);
+}
+
+DWORD WINAPI WatcherThread(void *param)
+{
+ WatchRootInfo *info = (WatchRootInfo *) param;
+
+ OVERLAPPED overlapped;
+ memset(&overlapped, 0, sizeof(overlapped));
+ overlapped.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
+
+ char rootPath[8];
+ sprintf_s(rootPath, 8, "%c:\\", info->driveLetter);
+ HANDLE hRootDir = CreateFileA(rootPath, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+ NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL);
+
+ int buffer_size = 10240;
+ char *buffer = new char[buffer_size];
+
+ HANDLE handles [2];
+ handles [0] = info->hStopEvent;
+ handles [1] = overlapped.hEvent;
+ while(true)
+ {
+ int rcDir = ReadDirectoryChangesW(hRootDir, buffer, buffer_size, TRUE,
+ FILE_NOTIFY_CHANGE_FILE_NAME |
+ FILE_NOTIFY_CHANGE_DIR_NAME |
+ FILE_NOTIFY_CHANGE_ATTRIBUTES |
+ FILE_NOTIFY_CHANGE_SIZE |
+ FILE_NOTIFY_CHANGE_LAST_WRITE,
+ NULL,
+ &overlapped,
+ NULL);
+ if (rcDir == 0)
+ {
+ info->bFailed = true;
+ break;
+ }
+
+ int rc = WaitForMultipleObjects(2, handles, FALSE, INFINITE);
+ if (rc == WAIT_OBJECT_0)
+ {
+ break;
+ }
+ if (rc == WAIT_OBJECT_0+1)
+ {
+ DWORD dwBytesReturned;
+ if(!GetOverlappedResult(hRootDir, &overlapped, &dwBytesReturned, FALSE))
+ {
+ info->bFailed = true;
+ break;
+ }
+
+ if (dwBytesReturned == 0)
+ {
+ // don't send dirty too much, everything is changed anyway
+ if (WaitForSingleObject(info->hStopEvent, 500) == WAIT_OBJECT_0)
+ break;
+
+ // Got a buffer overflow => current changes lost => send RECDIRTY on root
+ PrintEverythingChangedUnderRoot(rootPath);
+ } else {
+ FILE_NOTIFY_INFORMATION *info = (FILE_NOTIFY_INFORMATION *) buffer;
+ while(true)
+ {
+ PrintChangeInfo(rootPath, info);
+ if (!info->NextEntryOffset)
+ break;
+ info = (FILE_NOTIFY_INFORMATION *) ((char *) info + info->NextEntryOffset);
+ }
+ }
+ }
+ }
+ CloseHandle(overlapped.hEvent);
+ CloseHandle(hRootDir);
+ delete[] buffer;
+ return 0;
+}
+
+// -- Roots update ------------------------------------------------------------
+
+void MarkAllRootsUnused()
+{
+ for(int i=0; i<ROOT_COUNT; i++)
+ {
+ watchRootInfos [i].bUsed = false;
+ }
+}
+
+void StartRoot(WatchRootInfo *info)
+{
+ info->hStopEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
+ info->hThread = CreateThread(NULL, 0, &WatcherThread, info, 0, NULL);
+ info->bInitialized = true;
+}
+
+void StopRoot(WatchRootInfo *info)
+{
+ SetEvent(info->hStopEvent);
+ WaitForSingleObject(info->hThread, INFINITE);
+ CloseHandle(info->hThread);
+ CloseHandle(info->hStopEvent);
+ info->bInitialized = false;
+}
+
+void UpdateRoots()
+{
+ char infoBuffer [256];
+ strcpy_s(infoBuffer, "UNWATCHEABLE\n");
+ for(int i=0; i<ROOT_COUNT; i++)
+ {
+ if (watchRootInfos [i].bInitialized && (!watchRootInfos [i].bUsed || watchRootInfos [i].bFailed))
+ {
+ StopRoot(&watchRootInfos [i]);
+ watchRootInfos [i].bFailed = false;
+ }
+ if (watchRootInfos [i].bUsed)
+ {
+ char rootPath[8];
+ sprintf_s(rootPath, 8, "%c:\\", watchRootInfos [i].driveLetter);
+ if (!IsWatchable(rootPath))
+ {
+ strcat_s(infoBuffer, rootPath);
+ strcat_s(infoBuffer, "\n");
+ continue;
+ }
+ if (!watchRootInfos [i].bInitialized)
+ {
+ StartRoot(&watchRootInfos [i]);
+ }
+ }
+ }
+ EnterCriticalSection(&csOutput);
+ fprintf(stdout, "%s", infoBuffer);
+ puts("#\nREMAP");
+ PrintRemapForSubstDrives();
+ bool printedMountPoints = true;
+ for(int i=0; i<ROOT_COUNT; i++)
+ {
+ if (watchRootInfos [i].bUsed)
+ {
+ char rootPath[8];
+ sprintf_s(rootPath, 8, "%c:\\", watchRootInfos [i].driveLetter);
+ if (!PrintMountPoints(rootPath))
+ printedMountPoints = false;
+ }
+ }
+ if (!printedMountPoints)
+ {
+ PrintWatchRootReparsePoints();
+ }
+ puts("#");
+ fflush(stdout);
+ LeaveCriticalSection(&csOutput);
+}
+
+void AddWatchRoot(const char *path)
+{
+ WatchRoot *watchRoot = (WatchRoot *) malloc(sizeof(WatchRoot));
+ watchRoot->next = NULL;
+ watchRoot->path = _strdup(path);
+ watchRoot->next = firstWatchRoot;
+ firstWatchRoot = watchRoot;
+}
+
+void FreeWatchRootsList()
+{
+ WatchRoot *pWatchRoot = firstWatchRoot;
+ WatchRoot *pNext;
+ while(pWatchRoot)
+ {
+ pNext = pWatchRoot->next;
+ free(pWatchRoot->path);
+ free(pWatchRoot);
+ pWatchRoot=pNext;
+ }
+ firstWatchRoot = NULL;
+}
+
+// -- Main - filewatcher protocol ---------------------------------------------
+
+int _tmain(int argc, _TCHAR* argv[])
+{
+ InitializeCriticalSection(&csOutput);
+
+ for(int i=0; i<26; i++)
+ {
+ watchRootInfos [i].driveLetter = 'A' + i;
+ watchRootInfos [i].bInitialized = false;
+ watchRootInfos [i].bUsed = false;
+ }
+
+ char buffer[8192];
+ while(true)
+ {
+ if (!gets_s(buffer, sizeof(buffer)-1))
+ break;
+
+ if (!strcmp(buffer, "ROOTS"))
+ {
+ MarkAllRootsUnused();
+ FreeWatchRootsList();
+ bool failed = false;
+ while(true)
+ {
+ if (!gets_s(buffer, sizeof(buffer)-1))
+ {
+ failed = true;
+ break;
+ }
+ if (buffer [0] == '#')
+ break;
+ int driveLetterPos = 0;
+ char *pDriveLetter = buffer;
+ if (*pDriveLetter == '|')
+ pDriveLetter++;
+
+ AddWatchRoot(pDriveLetter);
+
+ _strupr_s(buffer, sizeof(buffer)-1);
+ char driveLetter = *pDriveLetter;
+ if (driveLetter >= 'A' && driveLetter <= 'Z')
+ {
+ watchRootInfos [driveLetter-'A'].bUsed = true;
+ }
+ }
+ if (failed)
+ break;
+
+ UpdateRoots();
+ }
+ if (!strcmp(buffer, "EXIT"))
+ break;
+ }
+
+ MarkAllRootsUnused();
+ UpdateRoots();
+
+ DeleteCriticalSection(&csOutput);
+}