fsnotifier self diagnostics
[idea/community.git] / native / fileWatcher / fileWatcher3.cpp
1 /*
2  * Copyright 2000-2009 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17 #include "stdafx.h"
18
19 struct WatchRootInfo {
20         char driveLetter;
21         HANDLE hThread;
22         HANDLE hStopEvent;
23         bool bInitialized;
24         bool bUsed;
25         bool bFailed;
26 };
27
28 struct WatchRoot {
29         char *path;
30         WatchRoot *next;
31 };
32
33 const int ROOT_COUNT = 26;
34
35 WatchRootInfo watchRootInfos[ROOT_COUNT];
36
37 WatchRoot *firstWatchRoot = NULL;
38
39 CRITICAL_SECTION csOutput;
40
41 void NormalizeSlashes(char *path, char slash)
42 {
43         for(char *p=path; *p; p++)
44                 if (*p == '\\' || *p == '/') 
45                         *p = slash;
46 }
47
48 // -- Watchable root checks ---------------------------------------------------
49
50 bool IsNetworkDrive(const char *name) 
51 {
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;
56
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
62     );
63
64     return result == NO_ERROR;
65 }
66
67 bool IsUnwatchableFS(const char *path)
68 {
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;
78
79     if (!strcmp(fsName, "NTFS") && maxComponentLength != 255 && !(fsFlags & FILE_SUPPORTS_REPARSE_POINTS))
80         {
81                 // SAMBA reports itself as NTFS
82                 return true;
83         }
84
85         return false;
86 }
87
88 bool IsWatchable(const char *path)
89 {
90         if (IsNetworkDrive(path))
91                 return false;
92         if (IsUnwatchableFS(path))
93                 return false;
94         return true;
95 }
96
97 // -- Substed drive checks ----------------------------------------------------
98
99 void PrintRemapForSubstDrive(char driveLetter) 
100 {
101     const int BUF_SIZE = 1024;
102     char targetPath[BUF_SIZE];
103
104         char rootPath[8];
105         sprintf_s(rootPath, 8, "%c:", driveLetter);
106
107     DWORD result = QueryDosDeviceA(rootPath, targetPath, BUF_SIZE);
108     if (result == 0) {
109         return;
110     }
111     else 
112         {
113         if (targetPath[0] == '\\' && targetPath[1] == '?' && targetPath[2] == '?' && targetPath[3] == '\\')
114                 {
115                         // example path: \??\C:\jetbrains\idea
116                         NormalizeSlashes(targetPath, '/');
117                         printf("%c:\n%s\n", driveLetter, targetPath+4);
118                 }
119     }
120 }
121
122 void PrintRemapForSubstDrives()
123 {
124         for(int i=0; i<ROOT_COUNT; i++)
125         {
126                 if (watchRootInfos [i].bUsed)
127                 {
128                         PrintRemapForSubstDrive(watchRootInfos [i].driveLetter);
129                 }
130         }
131 }
132
133 // -- Mount point enumeration -------------------------------------------------
134
135 const int BUFSIZE = 1024;
136
137 void PrintDirectoryReparsePoint(const char *path)
138 {
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, "\\");
145                 
146         char volumeName[_MAX_PATH];
147         int rc = GetVolumeNameForVolumeMountPointA(directory, volumeName, sizeof(volumeName));
148         if (rc)
149         {
150                 char volumePathNames[_MAX_PATH];
151                 DWORD returnLength;
152                 rc = GetVolumePathNamesForVolumeNameA(volumeName, volumePathNames, sizeof(volumePathNames), &returnLength);
153                 if (rc)
154                 {
155                         char *p = volumePathNames;
156                         while(*p)
157                         {
158                                 if (_stricmp(p, directory))   // if it's not the path we've already found
159                                 {
160                                         NormalizeSlashes(directory, '/');
161                                         NormalizeSlashes(p, '/');
162                                         puts(directory);
163                                         puts(p);
164                                 }
165                                 p += strlen(p)+1;
166                         }
167                 }
168         }
169         free(directory);
170 }
171
172 bool PrintMountPointsForVolume(HANDLE hVol, const char* volumePath, char *Buf)
173 {
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];
178
179     // Is this volume NTFS? 
180     GetVolumeInformationA(Buf, NULL, 0, NULL, NULL, &dwSysFlags, FileSysNameBuf, BUFSIZE);
181
182     // Detect support for reparse points, and therefore for volume 
183     // mount points, which are implemented using reparse points. 
184
185     if (! (dwSysFlags & FILE_SUPPORTS_REPARSE_POINTS)) {
186        return true;
187     } 
188
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
194     );
195
196     // Shall we error out?
197     if (hPt == INVALID_HANDLE_VALUE) {
198                 return GetLastError() != ERROR_ACCESS_DENIED;
199     } 
200
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));
208
209     FindVolumeMountPointClose(hPt);
210         return true;
211 }
212
213 bool PrintMountPoints(const char *path)
214 {
215         char volumeUniqueName[128];
216         BOOL res = GetVolumeNameForVolumeMountPointA(path, volumeUniqueName, 128);
217         if (!res) {
218         return false;
219     }
220    
221     char buf[BUFSIZE];            // buffer for unique volume identifiers
222     HANDLE hVol;                  // handle for the volume scan
223
224     // Open a scan for volumes.
225     hVol = FindFirstVolumeA(buf, BUFSIZE );
226
227     // Shall we error out?
228     if (hVol == INVALID_HANDLE_VALUE) {
229         return false;
230     }
231
232     bool success = true;
233         do {
234        if (!strcmp(buf, volumeUniqueName)) {
235                    success = PrintMountPointsForVolume(hVol, path, buf);
236                    if (!success) break;
237            }
238     } while (FindNextVolumeA(hVol, buf, BUFSIZE));
239
240     FindVolumeClose(hVol);
241         return success;
242 }
243
244 // -- Searching for mount points in watch roots (fallback) --------------------
245
246 void PrintDirectoryReparsePoints(const char *path)
247 {
248         char *const buf = _strdup(path);
249         while(strchr(buf, '/'))
250         {
251                 DWORD attributes = GetFileAttributesA(buf);
252                 if (attributes == INVALID_FILE_ATTRIBUTES)
253                         break;
254                 if (attributes & FILE_ATTRIBUTE_REPARSE_POINT)
255                 {
256                         PrintDirectoryReparsePoint(buf);
257                 }
258                 char *pSlash = strrchr(buf, '/');
259                 if (pSlash)
260                 {
261                         *pSlash = '\0';
262                 }
263         }
264         free(buf);
265 }
266
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()
271 {
272         WatchRoot *pWatchRoot = firstWatchRoot;
273         while(pWatchRoot)
274         {
275                 PrintDirectoryReparsePoints(pWatchRoot->path);
276                 pWatchRoot = pWatchRoot->next;
277         }
278 }
279
280 // -- Watcher thread ----------------------------------------------------------
281
282 void PrintChangeInfo(char *rootPath, FILE_NOTIFY_INFORMATION *info)
283 {
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)
289         {
290                 command = "CREATE";
291         }
292         else if (info->Action == FILE_ACTION_REMOVED || info->Action == FILE_ACTION_RENAMED_OLD_NAME)
293         {
294                 command = "DELETE";
295         }
296         else if (info->Action == FILE_ACTION_MODIFIED)
297         {
298                 command = "CHANGE";     
299         }
300         else
301         {
302                 return;  // unknown command
303         }
304
305         EnterCriticalSection(&csOutput);
306         puts(command);
307         printf("%s", rootPath);
308         puts(FileNameBuffer);
309         fflush(stdout);
310         LeaveCriticalSection(&csOutput);
311 }
312
313 DWORD WINAPI WatcherThread(void *param)
314 {
315         WatchRootInfo *info = (WatchRootInfo *) param;
316
317         OVERLAPPED overlapped;
318         memset(&overlapped, 0, sizeof(overlapped));
319         overlapped.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
320
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);
325
326         int buffer_size = 10240;
327         char *buffer = new char[buffer_size];
328
329         HANDLE handles [2];
330         handles [0] = info->hStopEvent;
331         handles [1] = overlapped.hEvent;
332         while(true)
333         {
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)
344                 {
345                         info->bFailed = true;
346                         break;
347                 }
348
349                 int rc = WaitForMultipleObjects(2, handles, FALSE, INFINITE);
350                 if (rc == WAIT_OBJECT_0)
351                 {
352                         break;
353                 }
354                 if (rc == WAIT_OBJECT_0+1)
355                 {
356                         FILE_NOTIFY_INFORMATION *info = (FILE_NOTIFY_INFORMATION *) buffer;
357                         while(true) 
358                         {
359                                 PrintChangeInfo(rootPath, info);
360                                 if (!info->NextEntryOffset)
361                                         break;
362                                 info = (FILE_NOTIFY_INFORMATION *) ((char *) info + info->NextEntryOffset);
363                         }
364                 }
365         }
366         CloseHandle(overlapped.hEvent);
367         CloseHandle(hRootDir);
368         delete[] buffer;
369         return 0;
370 }
371
372 // -- Roots update ------------------------------------------------------------
373
374 void MarkAllRootsUnused()
375 {
376         for(int i=0; i<ROOT_COUNT; i++)
377         {
378                 watchRootInfos [i].bUsed = false;
379         }
380 }
381
382 void StartRoot(WatchRootInfo *info)
383 {
384         info->hStopEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
385         info->hThread = CreateThread(NULL, 0, &WatcherThread, info, 0, NULL);
386         info->bInitialized = true;
387 }
388
389 void StopRoot(WatchRootInfo *info)
390 {
391         SetEvent(info->hStopEvent);
392         WaitForSingleObject(info->hThread, INFINITE);
393         CloseHandle(info->hThread);
394         CloseHandle(info->hStopEvent);
395         info->bInitialized = false;
396 }
397
398 void UpdateRoots()
399 {
400         char infoBuffer [256];
401         strcpy_s(infoBuffer, "UNWATCHEABLE\n");
402         for(int i=0; i<ROOT_COUNT; i++)
403         {
404                 if (watchRootInfos [i].bInitialized && (!watchRootInfos [i].bUsed || watchRootInfos [i].bFailed))
405                 {
406                         StopRoot(&watchRootInfos [i]);
407                         watchRootInfos [i].bFailed = false;
408                 }
409                 if (watchRootInfos [i].bUsed)
410                 {
411                         char rootPath[8];
412                         sprintf_s(rootPath, 8, "%c:\\", watchRootInfos [i].driveLetter);
413                         if (!IsWatchable(rootPath))
414                         {
415                                 strcat_s(infoBuffer, rootPath);
416                                 strcat_s(infoBuffer, "\n");
417                                 continue;
418                         }
419                         if (!watchRootInfos [i].bInitialized)
420                         {
421                                 StartRoot(&watchRootInfos [i]);
422                         }
423                 }
424         }
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++)
431         {
432                 if (watchRootInfos [i].bUsed)
433                 {
434                         char rootPath[8];
435                         sprintf_s(rootPath, 8, "%c:\\", watchRootInfos [i].driveLetter);
436                         if (!PrintMountPoints(rootPath))
437                                 printedMountPoints = false;
438                 }
439         }
440         if (!printedMountPoints)
441         {
442                 PrintWatchRootReparsePoints();
443         }
444         puts("#");
445         fflush(stdout);
446         LeaveCriticalSection(&csOutput);
447 }
448
449 void AddWatchRoot(const char *path)
450 {
451         WatchRoot *watchRoot = (WatchRoot *) malloc(sizeof(WatchRoot));
452         watchRoot->next = NULL;
453         watchRoot->path = _strdup(path);
454         watchRoot->next = firstWatchRoot;
455         firstWatchRoot = watchRoot;
456 }
457
458 void FreeWatchRootsList()
459 {
460         WatchRoot *pWatchRoot = firstWatchRoot;
461         WatchRoot *pNext;
462         while(pWatchRoot)
463         {
464                 pNext = pWatchRoot->next;
465                 free(pWatchRoot->path);
466                 free(pWatchRoot);
467                 pWatchRoot=pNext;
468         }
469         firstWatchRoot = NULL;
470 }
471
472 // -- Main - filewatcher protocol ---------------------------------------------
473
474 int _tmain(int argc, _TCHAR* argv[])
475 {
476         InitializeCriticalSection(&csOutput);
477
478         for(int i=0; i<26; i++)
479         {
480                 watchRootInfos [i].driveLetter = 'A' + i;
481                 watchRootInfos [i].bInitialized = false;
482                 watchRootInfos [i].bUsed = false;
483         }
484
485         char buffer[8192];
486         while(true)
487         {
488                 if (!gets_s(buffer, sizeof(buffer)-1))
489                         break;
490
491                 if (!strcmp(buffer, "ROOTS"))
492                 {
493                         MarkAllRootsUnused();
494                         FreeWatchRootsList();
495                         bool failed = false;
496                         while(true)
497                         {
498                                 if (!gets_s(buffer, sizeof(buffer)-1))
499                                 {
500                                         failed = true;
501                                         break;
502                                 }
503                                 if (buffer [0] == '#')
504                                         break;
505                                 int driveLetterPos = 0;
506                                 char *pDriveLetter = buffer;
507                                 if (*pDriveLetter == '|')
508                                         pDriveLetter++;
509
510                                 AddWatchRoot(pDriveLetter);
511
512                                 _strupr_s(buffer, sizeof(buffer)-1);
513                                 char driveLetter = *pDriveLetter;
514                                 if (driveLetter >= 'A' && driveLetter <= 'Z')
515                                 {
516                                         watchRootInfos [driveLetter-'A'].bUsed = true;
517                                 }
518                         }
519                         if (failed)
520                                 break;
521
522                         UpdateRoots();
523                 }
524                 if (!strcmp(buffer, "EXIT"))
525                         break;
526         }
527
528         MarkAllRootsUnused();
529         UpdateRoots();
530         
531         DeleteCriticalSection(&csOutput);
532 }