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
);