| /* |
| * Copyright 2000-2014 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 <process.h> |
| #include <stdio.h> |
| #include <tchar.h> |
| #include <windows.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") && _stricmp(fsName, "exFAT") && _stricmp(fsName, "reFS")) |
| 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); |
| } |