blob: 905eed7ad17504d44c79015b16c20e962613e19f [file] [log] [blame]
Jean-Baptiste Querub56ea2a2013-01-08 11:11:20 -08001/*
2 * Copyright 2000-2011 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
19struct WatchRootInfo {
20 char driveLetter;
21 HANDLE hThread;
22 HANDLE hStopEvent;
23 bool bInitialized;
24 bool bUsed;
25 bool bFailed;
26};
27
28struct WatchRoot {
29 char *path;
30 WatchRoot *next;
31};
32
33const int ROOT_COUNT = 26;
34
35WatchRootInfo watchRootInfos[ROOT_COUNT];
36
37WatchRoot *firstWatchRoot = NULL;
38
39CRITICAL_SECTION csOutput;
40
41void 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
50bool 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
67bool 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;
Tor Norbye76753662013-10-15 19:49:27 -070076 if (strcmp(fsName, "NTFS") && strcmp(fsName, "FAT") && strcmp(fsName, "FAT32") && stricmp(fsName, "exFAT"))
Jean-Baptiste Querub56ea2a2013-01-08 11:11:20 -080077 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
88bool 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
99void 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
122void 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
135const int BUFSIZE = 1024;
136
137void 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
172bool 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
213bool 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
246void 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.
270void 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
282void 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
313void PrintEverythingChangedUnderRoot(char *rootPath)
314{
315 EnterCriticalSection(&csOutput);
316 puts("RECDIRTY");
317 puts(rootPath);
318 fflush(stdout);
319 LeaveCriticalSection(&csOutput);
320}
321
322DWORD WINAPI WatcherThread(void *param)
323{
324 WatchRootInfo *info = (WatchRootInfo *) param;
325
326 OVERLAPPED overlapped;
327 memset(&overlapped, 0, sizeof(overlapped));
328 overlapped.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
329
330 char rootPath[8];
331 sprintf_s(rootPath, 8, "%c:\\", info->driveLetter);
332 HANDLE hRootDir = CreateFileA(rootPath, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
333 NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL);
334
335 int buffer_size = 10240;
336 char *buffer = new char[buffer_size];
337
338 HANDLE handles [2];
339 handles [0] = info->hStopEvent;
340 handles [1] = overlapped.hEvent;
341 while(true)
342 {
343 int rcDir = ReadDirectoryChangesW(hRootDir, buffer, buffer_size, TRUE,
344 FILE_NOTIFY_CHANGE_FILE_NAME |
345 FILE_NOTIFY_CHANGE_DIR_NAME |
346 FILE_NOTIFY_CHANGE_ATTRIBUTES |
347 FILE_NOTIFY_CHANGE_SIZE |
348 FILE_NOTIFY_CHANGE_LAST_WRITE,
349 NULL,
350 &overlapped,
351 NULL);
352 if (rcDir == 0)
353 {
354 info->bFailed = true;
355 break;
356 }
357
358 int rc = WaitForMultipleObjects(2, handles, FALSE, INFINITE);
359 if (rc == WAIT_OBJECT_0)
360 {
361 break;
362 }
363 if (rc == WAIT_OBJECT_0+1)
364 {
365 DWORD dwBytesReturned;
366 if(!GetOverlappedResult(hRootDir, &overlapped, &dwBytesReturned, FALSE))
367 {
368 info->bFailed = true;
369 break;
370 }
371
372 if (dwBytesReturned == 0)
373 {
374 // don't send dirty too much, everything is changed anyway
375 if (WaitForSingleObject(info->hStopEvent, 500) == WAIT_OBJECT_0)
376 break;
377
378 // Got a buffer overflow => current changes lost => send RECDIRTY on root
379 PrintEverythingChangedUnderRoot(rootPath);
380 } else {
381 FILE_NOTIFY_INFORMATION *info = (FILE_NOTIFY_INFORMATION *) buffer;
382 while(true)
383 {
384 PrintChangeInfo(rootPath, info);
385 if (!info->NextEntryOffset)
386 break;
387 info = (FILE_NOTIFY_INFORMATION *) ((char *) info + info->NextEntryOffset);
388 }
389 }
390 }
391 }
392 CloseHandle(overlapped.hEvent);
393 CloseHandle(hRootDir);
394 delete[] buffer;
395 return 0;
396}
397
398// -- Roots update ------------------------------------------------------------
399
400void MarkAllRootsUnused()
401{
402 for(int i=0; i<ROOT_COUNT; i++)
403 {
404 watchRootInfos [i].bUsed = false;
405 }
406}
407
408void StartRoot(WatchRootInfo *info)
409{
410 info->hStopEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
411 info->hThread = CreateThread(NULL, 0, &WatcherThread, info, 0, NULL);
412 info->bInitialized = true;
413}
414
415void StopRoot(WatchRootInfo *info)
416{
417 SetEvent(info->hStopEvent);
418 WaitForSingleObject(info->hThread, INFINITE);
419 CloseHandle(info->hThread);
420 CloseHandle(info->hStopEvent);
421 info->bInitialized = false;
422}
423
424void UpdateRoots()
425{
426 char infoBuffer [256];
427 strcpy_s(infoBuffer, "UNWATCHEABLE\n");
428 for(int i=0; i<ROOT_COUNT; i++)
429 {
430 if (watchRootInfos [i].bInitialized && (!watchRootInfos [i].bUsed || watchRootInfos [i].bFailed))
431 {
432 StopRoot(&watchRootInfos [i]);
433 watchRootInfos [i].bFailed = false;
434 }
435 if (watchRootInfos [i].bUsed)
436 {
437 char rootPath[8];
438 sprintf_s(rootPath, 8, "%c:\\", watchRootInfos [i].driveLetter);
439 if (!IsWatchable(rootPath))
440 {
441 strcat_s(infoBuffer, rootPath);
442 strcat_s(infoBuffer, "\n");
443 continue;
444 }
445 if (!watchRootInfos [i].bInitialized)
446 {
447 StartRoot(&watchRootInfos [i]);
448 }
449 }
450 }
451 EnterCriticalSection(&csOutput);
452 fprintf(stdout, "%s", infoBuffer);
453 puts("#\nREMAP");
454 PrintRemapForSubstDrives();
455 bool printedMountPoints = true;
456 for(int i=0; i<ROOT_COUNT; i++)
457 {
458 if (watchRootInfos [i].bUsed)
459 {
460 char rootPath[8];
461 sprintf_s(rootPath, 8, "%c:\\", watchRootInfos [i].driveLetter);
462 if (!PrintMountPoints(rootPath))
463 printedMountPoints = false;
464 }
465 }
466 if (!printedMountPoints)
467 {
468 PrintWatchRootReparsePoints();
469 }
470 puts("#");
471 fflush(stdout);
472 LeaveCriticalSection(&csOutput);
473}
474
475void AddWatchRoot(const char *path)
476{
477 WatchRoot *watchRoot = (WatchRoot *) malloc(sizeof(WatchRoot));
478 watchRoot->next = NULL;
479 watchRoot->path = _strdup(path);
480 watchRoot->next = firstWatchRoot;
481 firstWatchRoot = watchRoot;
482}
483
484void FreeWatchRootsList()
485{
486 WatchRoot *pWatchRoot = firstWatchRoot;
487 WatchRoot *pNext;
488 while(pWatchRoot)
489 {
490 pNext = pWatchRoot->next;
491 free(pWatchRoot->path);
492 free(pWatchRoot);
493 pWatchRoot=pNext;
494 }
495 firstWatchRoot = NULL;
496}
497
498// -- Main - filewatcher protocol ---------------------------------------------
499
500int _tmain(int argc, _TCHAR* argv[])
501{
502 InitializeCriticalSection(&csOutput);
503
504 for(int i=0; i<26; i++)
505 {
506 watchRootInfos [i].driveLetter = 'A' + i;
507 watchRootInfos [i].bInitialized = false;
508 watchRootInfos [i].bUsed = false;
509 }
510
511 char buffer[8192];
512 while(true)
513 {
514 if (!gets_s(buffer, sizeof(buffer)-1))
515 break;
516
517 if (!strcmp(buffer, "ROOTS"))
518 {
519 MarkAllRootsUnused();
520 FreeWatchRootsList();
521 bool failed = false;
522 while(true)
523 {
524 if (!gets_s(buffer, sizeof(buffer)-1))
525 {
526 failed = true;
527 break;
528 }
529 if (buffer [0] == '#')
530 break;
531 int driveLetterPos = 0;
532 char *pDriveLetter = buffer;
533 if (*pDriveLetter == '|')
534 pDriveLetter++;
535
536 AddWatchRoot(pDriveLetter);
537
538 _strupr_s(buffer, sizeof(buffer)-1);
539 char driveLetter = *pDriveLetter;
540 if (driveLetter >= 'A' && driveLetter <= 'Z')
541 {
542 watchRootInfos [driveLetter-'A'].bUsed = true;
543 }
544 }
545 if (failed)
546 break;
547
548 UpdateRoots();
549 }
550 if (!strcmp(buffer, "EXIT"))
551 break;
552 }
553
554 MarkAllRootsUnused();
555 UpdateRoots();
556
557 DeleteCriticalSection(&csOutput);
558}