Merge branch 'master' of git@git.labs.intellij.net:idea/community
[fedora-idea.git] / native / fileWatcher / fileWatcher3.cpp
blob77b8f36f8e40b604bdd1bcdb4960c4ef4a05d227
1 /*
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.
17 #include "stdafx.h"
19 struct WatchRootInfo {
20 char driveLetter;
21 HANDLE hThread;
22 HANDLE hStopEvent;
23 bool bInitialized;
24 bool bUsed;
25 bool bFailed;
28 struct WatchRoot {
29 char *path;
30 WatchRoot *next;
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 == '/')
45 *p = slash;
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];
71 DWORD fsFlags;
72 DWORD maxComponentLength;
73 SetErrorMode(SEM_FAILCRITICALERRORS);
74 if (!GetVolumeInformationA(path, volumeName, MAX_PATH-1, NULL, &maxComponentLength, &fsFlags, fsName, MAX_PATH-1))
75 return false;
76 if (strcmp(fsName, "NTFS") && strcmp(fsName, "FAT") && strcmp(fsName, "FAT32"))
77 return true;
79 if (!strcmp(fsName, "NTFS") && maxComponentLength != 255 && !(fsFlags & FILE_SUPPORTS_REPARSE_POINTS))
81 // SAMBA reports itself as NTFS
82 return true;
85 return false;
88 bool IsWatchable(const char *path)
90 if (IsNetworkDrive(path))
91 return false;
92 if (IsUnwatchableFS(path))
93 return false;
94 return true;
97 // -- Substed drive checks ----------------------------------------------------
99 void PrintRemapForSubstDrive(char driveLetter)
101 const int BUF_SIZE = 1024;
102 char targetPath[BUF_SIZE];
104 char rootPath[8];
105 sprintf_s(rootPath, 8, "%c:", driveLetter);
107 DWORD result = QueryDosDeviceA(rootPath, targetPath, BUF_SIZE);
108 if (result == 0) {
109 return;
111 else
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));
148 if (rc)
150 char volumePathNames[_MAX_PATH];
151 DWORD returnLength;
152 rc = GetVolumePathNamesForVolumeNameA(volumeName, volumePathNames, sizeof(volumePathNames), &returnLength);
153 if (rc)
155 char *p = volumePathNames;
156 while(*p)
158 if (_stricmp(p, directory)) // if it's not the path we've already found
160 NormalizeSlashes(directory, '/');
161 NormalizeSlashes(p, '/');
162 puts(directory);
163 puts(p);
165 p += strlen(p)+1;
169 free(directory);
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)) {
186 return true;
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];
203 do {
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);
210 return true;
213 bool PrintMountPoints(const char *path)
215 char volumeUniqueName[128];
216 BOOL res = GetVolumeNameForVolumeMountPointA(path, volumeUniqueName, 128);
217 if (!res) {
218 return false;
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) {
229 return false;
232 bool success = true;
233 do {
234 if (!strcmp(buf, volumeUniqueName)) {
235 success = PrintMountPointsForVolume(hVol, path, buf);
236 if (!success) break;
238 } while (FindNextVolumeA(hVol, buf, BUFSIZE));
240 FindVolumeClose(hVol);
241 return success;
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)
253 break;
254 if (attributes & FILE_ATTRIBUTE_REPARSE_POINT)
256 PrintDirectoryReparsePoint(buf);
258 char *pSlash = strrchr(buf, '/');
259 if (pSlash)
261 *pSlash = '\0';
264 free(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;
273 while(pWatchRoot)
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';
287 char *command;
288 if (info->Action == FILE_ACTION_ADDED || info->Action == FILE_ACTION_RENAMED_OLD_NAME)
290 command = "CREATE";
292 else if (info->Action == FILE_ACTION_REMOVED || info->Action == FILE_ACTION_RENAMED_OLD_NAME)
294 command = "DELETE";
296 else if (info->Action == FILE_ACTION_MODIFIED)
298 command = "CHANGE";
300 else
302 return; // unknown command
305 EnterCriticalSection(&csOutput);
306 puts(command);
307 printf("%s", rootPath);
308 puts(FileNameBuffer);
309 fflush(stdout);
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);
321 char rootPath[8];
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];
329 HANDLE handles [2];
330 handles [0] = info->hStopEvent;
331 handles [1] = overlapped.hEvent;
332 while(true)
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,
340 NULL,
341 &overlapped,
342 NULL);
343 if (rcDir == 0)
345 info->bFailed = true;
346 break;
349 int rc = WaitForMultipleObjects(2, handles, FALSE, INFINITE);
350 if (rc == WAIT_OBJECT_0)
352 break;
354 if (rc == WAIT_OBJECT_0+1)
356 FILE_NOTIFY_INFORMATION *info = (FILE_NOTIFY_INFORMATION *) buffer;
357 while(true)
359 PrintChangeInfo(rootPath, info);
360 if (!info->NextEntryOffset)
361 break;
362 info = (FILE_NOTIFY_INFORMATION *) ((char *) info + info->NextEntryOffset);
366 CloseHandle(overlapped.hEvent);
367 CloseHandle(hRootDir);
368 delete[] buffer;
369 return 0;
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;
398 void UpdateRoots()
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)
411 char rootPath[8];
412 sprintf_s(rootPath, 8, "%c:\\", watchRootInfos [i].driveLetter);
413 if (!IsWatchable(rootPath))
415 strcat_s(infoBuffer, rootPath);
416 strcat_s(infoBuffer, "\n");
417 continue;
419 if (!watchRootInfos [i].bInitialized)
421 StartRoot(&watchRootInfos [i]);
425 EnterCriticalSection(&csOutput);
426 fprintf(stdout, "%s", infoBuffer);
427 puts("#\nREMAP");
428 PrintRemapForSubstDrives();
429 bool printedMountPoints = true;
430 for(int i=0; i<ROOT_COUNT; i++)
432 if (watchRootInfos [i].bUsed)
434 char rootPath[8];
435 sprintf_s(rootPath, 8, "%c:\\", watchRootInfos [i].driveLetter);
436 if (!PrintMountPoints(rootPath))
437 printedMountPoints = false;
440 if (!printedMountPoints)
442 PrintWatchRootReparsePoints();
444 puts("#");
445 fflush(stdout);
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;
461 WatchRoot *pNext;
462 while(pWatchRoot)
464 pNext = pWatchRoot->next;
465 free(pWatchRoot->path);
466 free(pWatchRoot);
467 pWatchRoot=pNext;
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;
485 char buffer[8192];
486 while(true)
488 if (!gets_s(buffer, sizeof(buffer)-1))
489 break;
491 if (!strcmp(buffer, "ROOTS"))
493 MarkAllRootsUnused();
494 FreeWatchRootsList();
495 bool failed = false;
496 while(true)
498 if (!gets_s(buffer, sizeof(buffer)-1))
500 failed = true;
501 break;
503 if (buffer [0] == '#')
504 break;
505 int driveLetterPos = 0;
506 char *pDriveLetter = buffer;
507 if (*pDriveLetter == '|')
508 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;
519 if (failed)
520 break;
522 UpdateRoots();
524 if (!strcmp(buffer, "EXIT"))
525 break;
528 MarkAllRootsUnused();
529 UpdateRoots();
531 DeleteCriticalSection(&csOutput);