2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
19 struct WatchRootInfo {
33 const int ROOT_COUNT = 26;
35 WatchRootInfo watchRootInfos[ROOT_COUNT];
37 WatchRoot *firstWatchRoot = NULL;
39 CRITICAL_SECTION csOutput;
41 void NormalizeSlashes(char *path, char slash)
43 for(char *p=path; *p; p++)
44 if (*p == '\\' || *p == '/')
48 // -- Watchable root checks ---------------------------------------------------
50 bool IsNetworkDrive(const char *name)
52 const int BUF_SIZE = 1024;
53 char buffer[BUF_SIZE];
54 UNIVERSAL_NAME_INFO* uni = (UNIVERSAL_NAME_INFO*) buffer;
55 DWORD size = BUF_SIZE;
57 DWORD result = WNetGetUniversalNameA(
58 name, // path for network resource
59 UNIVERSAL_NAME_INFO_LEVEL, // level of information
60 buffer, // name buffer
61 &size // size of buffer
64 return result == NO_ERROR;
67 bool IsUnwatchableFS(const char *path)
69 char volumeName[MAX_PATH];
70 char fsName[MAX_PATH];
72 DWORD maxComponentLength;
73 SetErrorMode(SEM_FAILCRITICALERRORS);
74 if (!GetVolumeInformationA(path, volumeName, MAX_PATH-1, NULL, &maxComponentLength, &fsFlags, fsName, MAX_PATH-1))
76 if (strcmp(fsName, "NTFS") && strcmp(fsName, "FAT") && strcmp(fsName, "FAT32"))
79 if (!strcmp(fsName, "NTFS") && maxComponentLength != 255 && !(fsFlags & FILE_SUPPORTS_REPARSE_POINTS))
81 // SAMBA reports itself as NTFS
88 bool IsWatchable(const char *path)
90 if (IsNetworkDrive(path))
92 if (IsUnwatchableFS(path))
97 // -- Substed drive checks ----------------------------------------------------
99 void PrintRemapForSubstDrive(char driveLetter)
101 const int BUF_SIZE = 1024;
102 char targetPath[BUF_SIZE];
105 sprintf_s(rootPath, 8, "%c:", driveLetter);
107 DWORD result = QueryDosDeviceA(rootPath, targetPath, BUF_SIZE);
113 if (targetPath[0] == '\\' && targetPath[1] == '?' && targetPath[2] == '?' && targetPath[3] == '\\')
115 // example path: \??\C:\jetbrains\idea
116 NormalizeSlashes(targetPath, '/');
117 printf("%c:\n%s\n", driveLetter, targetPath+4);
122 void PrintRemapForSubstDrives()
124 for(int i=0; i<ROOT_COUNT; i++)
126 if (watchRootInfos [i].bUsed)
128 PrintRemapForSubstDrive(watchRootInfos [i].driveLetter);
133 // -- Mount point enumeration -------------------------------------------------
135 const int BUFSIZE = 1024;
137 void PrintDirectoryReparsePoint(const char *path)
139 int size = strlen(path)+2;
140 char *directory = (char *) malloc(size);
141 strcpy_s(directory, size, path);
142 NormalizeSlashes(directory, '\\');
143 if (directory [strlen(directory)-1] != '\\')
144 strcat_s(directory, size, "\\");
146 char volumeName[_MAX_PATH];
147 int rc = GetVolumeNameForVolumeMountPointA(directory, volumeName, sizeof(volumeName));
150 char volumePathNames[_MAX_PATH];
152 rc = GetVolumePathNamesForVolumeNameA(volumeName, volumePathNames, sizeof(volumePathNames), &returnLength);
155 char *p = volumePathNames;
158 if (_stricmp(p, directory)) // if it's not the path we've already found
160 NormalizeSlashes(directory, '/');
161 NormalizeSlashes(p, '/');
172 bool PrintMountPointsForVolume(HANDLE hVol, const char* volumePath, char *Buf)
174 HANDLE hPt; // handle for mount point scan
175 char Path[BUFSIZE]; // string buffer for mount points
176 DWORD dwSysFlags; // flags that describe the file system
177 char FileSysNameBuf[BUFSIZE];
179 // Is this volume NTFS?
180 GetVolumeInformationA(Buf, NULL, 0, NULL, NULL, &dwSysFlags, FileSysNameBuf, BUFSIZE);
182 // Detect support for reparse points, and therefore for volume
183 // mount points, which are implemented using reparse points.
185 if (! (dwSysFlags & FILE_SUPPORTS_REPARSE_POINTS)) {
189 // Start processing mount points on this volume.
190 hPt = FindFirstVolumeMountPointA(
191 Buf, // root path of volume to be scanned
192 Path, // pointer to output string
193 BUFSIZE // size of output buffer
196 // Shall we error out?
197 if (hPt == INVALID_HANDLE_VALUE) {
198 return GetLastError() != ERROR_ACCESS_DENIED;
201 // Process the volume mount point.
202 char *buf = new char[MAX_PATH];
204 strcpy_s(buf, MAX_PATH, volumePath);
205 strcat_s(buf, MAX_PATH, Path);
206 PrintDirectoryReparsePoint(buf);
207 } while (FindNextVolumeMountPointA(hPt, Path, BUFSIZE));
209 FindVolumeMountPointClose(hPt);
213 bool PrintMountPoints(const char *path)
215 char volumeUniqueName[128];
216 BOOL res = GetVolumeNameForVolumeMountPointA(path, volumeUniqueName, 128);
221 char buf[BUFSIZE]; // buffer for unique volume identifiers
222 HANDLE hVol; // handle for the volume scan
224 // Open a scan for volumes.
225 hVol = FindFirstVolumeA(buf, BUFSIZE );
227 // Shall we error out?
228 if (hVol == INVALID_HANDLE_VALUE) {
234 if (!strcmp(buf, volumeUniqueName)) {
235 success = PrintMountPointsForVolume(hVol, path, buf);
238 } while (FindNextVolumeA(hVol, buf, BUFSIZE));
240 FindVolumeClose(hVol);
244 // -- Searching for mount points in watch roots (fallback) --------------------
246 void PrintDirectoryReparsePoints(const char *path)
248 char *const buf = _strdup(path);
249 while(strchr(buf, '/'))
251 DWORD attributes = GetFileAttributesA(buf);
252 if (attributes == INVALID_FILE_ATTRIBUTES)
254 if (attributes & FILE_ATTRIBUTE_REPARSE_POINT)
256 PrintDirectoryReparsePoint(buf);
258 char *pSlash = strrchr(buf, '/');
267 // This is called if we got an ERROR_ACCESS_DENIED when trying to enumerate all mount points for volume.
268 // In this case, we walk the directory tree up from each watch root, and look at each parent directory
269 // to check whether it's a reparse point.
270 void PrintWatchRootReparsePoints()
272 WatchRoot *pWatchRoot = firstWatchRoot;
275 PrintDirectoryReparsePoints(pWatchRoot->path);
276 pWatchRoot = pWatchRoot->next;
280 // -- Watcher thread ----------------------------------------------------------
282 void PrintChangeInfo(char *rootPath, FILE_NOTIFY_INFORMATION *info)
284 char FileNameBuffer[_MAX_PATH];
285 int converted = WideCharToMultiByte(CP_ACP, 0, info->FileName, info->FileNameLength/sizeof(WCHAR), FileNameBuffer, _MAX_PATH-1, NULL, NULL);
286 FileNameBuffer[converted] = '\0';
288 if (info->Action == FILE_ACTION_ADDED || info->Action == FILE_ACTION_RENAMED_OLD_NAME)
292 else if (info->Action == FILE_ACTION_REMOVED || info->Action == FILE_ACTION_RENAMED_OLD_NAME)
296 else if (info->Action == FILE_ACTION_MODIFIED)
302 return; // unknown command
305 EnterCriticalSection(&csOutput);
307 printf("%s", rootPath);
308 puts(FileNameBuffer);
310 LeaveCriticalSection(&csOutput);
313 DWORD WINAPI WatcherThread(void *param)
315 WatchRootInfo *info = (WatchRootInfo *) param;
317 OVERLAPPED overlapped;
318 memset(&overlapped, 0, sizeof(overlapped));
319 overlapped.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
322 sprintf_s(rootPath, 8, "%c:\\", info->driveLetter);
323 HANDLE hRootDir = CreateFileA(rootPath, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
324 NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL);
326 int buffer_size = 10240;
327 char *buffer = new char[buffer_size];
330 handles [0] = info->hStopEvent;
331 handles [1] = overlapped.hEvent;
334 int rcDir = ReadDirectoryChangesW(hRootDir, buffer, buffer_size, TRUE,
335 FILE_NOTIFY_CHANGE_FILE_NAME |
336 FILE_NOTIFY_CHANGE_DIR_NAME |
337 FILE_NOTIFY_CHANGE_ATTRIBUTES |
338 FILE_NOTIFY_CHANGE_SIZE |
339 FILE_NOTIFY_CHANGE_LAST_WRITE,
345 info->bFailed = true;
349 int rc = WaitForMultipleObjects(2, handles, FALSE, INFINITE);
350 if (rc == WAIT_OBJECT_0)
354 if (rc == WAIT_OBJECT_0+1)
356 FILE_NOTIFY_INFORMATION *info = (FILE_NOTIFY_INFORMATION *) buffer;
359 PrintChangeInfo(rootPath, info);
360 if (!info->NextEntryOffset)
362 info = (FILE_NOTIFY_INFORMATION *) ((char *) info + info->NextEntryOffset);
366 CloseHandle(overlapped.hEvent);
367 CloseHandle(hRootDir);
372 // -- Roots update ------------------------------------------------------------
374 void MarkAllRootsUnused()
376 for(int i=0; i<ROOT_COUNT; i++)
378 watchRootInfos [i].bUsed = false;
382 void StartRoot(WatchRootInfo *info)
384 info->hStopEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
385 info->hThread = CreateThread(NULL, 0, &WatcherThread, info, 0, NULL);
386 info->bInitialized = true;
389 void StopRoot(WatchRootInfo *info)
391 SetEvent(info->hStopEvent);
392 WaitForSingleObject(info->hThread, INFINITE);
393 CloseHandle(info->hThread);
394 CloseHandle(info->hStopEvent);
395 info->bInitialized = false;
400 char infoBuffer [256];
401 strcpy_s(infoBuffer, "UNWATCHEABLE\n");
402 for(int i=0; i<ROOT_COUNT; i++)
404 if (watchRootInfos [i].bInitialized && (!watchRootInfos [i].bUsed || watchRootInfos [i].bFailed))
406 StopRoot(&watchRootInfos [i]);
407 watchRootInfos [i].bFailed = false;
409 if (watchRootInfos [i].bUsed)
412 sprintf_s(rootPath, 8, "%c:\\", watchRootInfos [i].driveLetter);
413 if (!IsWatchable(rootPath))
415 strcat_s(infoBuffer, rootPath);
416 strcat_s(infoBuffer, "\n");
419 if (!watchRootInfos [i].bInitialized)
421 StartRoot(&watchRootInfos [i]);
425 EnterCriticalSection(&csOutput);
426 fprintf(stdout, "%s", infoBuffer);
428 PrintRemapForSubstDrives();
429 bool printedMountPoints = true;
430 for(int i=0; i<ROOT_COUNT; i++)
432 if (watchRootInfos [i].bUsed)
435 sprintf_s(rootPath, 8, "%c:\\", watchRootInfos [i].driveLetter);
436 if (!PrintMountPoints(rootPath))
437 printedMountPoints = false;
440 if (!printedMountPoints)
442 PrintWatchRootReparsePoints();
446 LeaveCriticalSection(&csOutput);
449 void AddWatchRoot(const char *path)
451 WatchRoot *watchRoot = (WatchRoot *) malloc(sizeof(WatchRoot));
452 watchRoot->next = NULL;
453 watchRoot->path = _strdup(path);
454 watchRoot->next = firstWatchRoot;
455 firstWatchRoot = watchRoot;
458 void FreeWatchRootsList()
460 WatchRoot *pWatchRoot = firstWatchRoot;
464 pNext = pWatchRoot->next;
465 free(pWatchRoot->path);
469 firstWatchRoot = NULL;
472 // -- Main - filewatcher protocol ---------------------------------------------
474 int _tmain(int argc, _TCHAR* argv[])
476 InitializeCriticalSection(&csOutput);
478 for(int i=0; i<26; i++)
480 watchRootInfos [i].driveLetter = 'A' + i;
481 watchRootInfos [i].bInitialized = false;
482 watchRootInfos [i].bUsed = false;
488 if (!gets_s(buffer, sizeof(buffer)-1))
491 if (!strcmp(buffer, "ROOTS"))
493 MarkAllRootsUnused();
494 FreeWatchRootsList();
498 if (!gets_s(buffer, sizeof(buffer)-1))
503 if (buffer [0] == '#')
505 int driveLetterPos = 0;
506 char *pDriveLetter = buffer;
507 if (*pDriveLetter == '|')
510 AddWatchRoot(pDriveLetter);
512 _strupr_s(buffer, sizeof(buffer)-1);
513 char driveLetter = *pDriveLetter;
514 if (driveLetter >= 'A' && driveLetter <= 'Z')
516 watchRootInfos [driveLetter-'A'].bUsed = true;
524 if (!strcmp(buffer, "EXIT"))
528 MarkAllRootsUnused();
531 DeleteCriticalSection(&csOutput);