97c32fdbfafb9a121b1b15d489dab62a3fb8a18c
[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 const int ROOT_COUNT = 26;
29
30 WatchRootInfo watchRootInfos[ROOT_COUNT];
31
32 CRITICAL_SECTION csOutput;
33
34 bool IsNetworkDrive(const char *name) 
35 {
36     const int BUF_SIZE = 1024;
37     char buffer[BUF_SIZE];
38     UNIVERSAL_NAME_INFO* uni = (UNIVERSAL_NAME_INFO*) buffer;
39     DWORD size = BUF_SIZE;
40
41     DWORD result = WNetGetUniversalNameA(
42         name,  // path for network resource
43         UNIVERSAL_NAME_INFO_LEVEL, // level of information
44         buffer, // name buffer
45         &size // size of buffer
46     );
47
48     return result == NO_ERROR;
49 }
50
51 bool IsSubstedDrive(const char* name) 
52 {
53     char deviceName[3] = {name[0], name[1], 0};
54     const int BUF_SIZE = 1024;
55     char targetPath[BUF_SIZE];
56
57     DWORD result = QueryDosDeviceA(deviceName, targetPath, BUF_SIZE);
58     if (result == 0) {
59         return false;
60     }
61     else {
62         bool result = (targetPath[0] == '\\' && targetPath[1] == '?' && targetPath[2] == '?');
63         return result;
64     }
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 (IsSubstedDrive(path))
93                 return false;
94         if (IsUnwatchableFS(path))
95                 return false;
96         return true;
97 }
98
99 const int BUFSIZE = 1024;
100
101 void PrintMountPointsForVolume(HANDLE hVol, const char* volumePath, char *Buf)
102 {
103     HANDLE hPt;                  // handle for mount point scan
104     char Path[BUFSIZE];          // string buffer for mount points
105     DWORD dwSysFlags;            // flags that describe the file system
106     char FileSysNameBuf[BUFSIZE];
107
108     // Is this volume NTFS? 
109     GetVolumeInformationA(Buf, NULL, 0, NULL, NULL, &dwSysFlags, FileSysNameBuf, BUFSIZE);
110
111     // Detect support for reparse points, and therefore for volume 
112     // mount points, which are implemented using reparse points. 
113
114     if (! (dwSysFlags & FILE_SUPPORTS_REPARSE_POINTS)) {
115        return;
116     } 
117
118     // Start processing mount points on this volume. 
119     hPt = FindFirstVolumeMountPointA(
120         Buf, // root path of volume to be scanned
121         Path, // pointer to output string
122         BUFSIZE // size of output buffer
123     );
124
125     // Shall we error out?
126     if (hPt == INVALID_HANDLE_VALUE) {
127         return;
128     } 
129
130     // Process the volume mount point.
131     do {
132                 printf("%s%s\n", volumePath, Path);
133     } while (FindNextVolumeMountPointA(hPt, Path, BUFSIZE));
134
135     FindVolumeMountPointClose(hPt);
136 }
137
138 void PrintMountPoints(const char *path)
139 {
140         char volumeUniqueName[128];
141         BOOL res = GetVolumeNameForVolumeMountPointA(path, volumeUniqueName, 128);
142         if (!res) {
143         return;
144     }
145    
146     char buf[BUFSIZE];            // buffer for unique volume identifiers
147     HANDLE hVol;                  // handle for the volume scan
148
149     // Open a scan for volumes.
150     hVol = FindFirstVolumeA(buf, BUFSIZE );
151
152     // Shall we error out?
153     if (hVol == INVALID_HANDLE_VALUE) {
154         return;
155     }
156
157     do {
158        if (!strcmp(buf, volumeUniqueName)) {
159                    PrintMountPointsForVolume(hVol, path, buf);
160            }
161     } while (FindNextVolumeA(hVol, buf, BUFSIZE));
162
163     FindVolumeClose(hVol);
164 }
165
166 void PrintChangeInfo(char *rootPath, FILE_NOTIFY_INFORMATION *info)
167 {
168         char FileNameBuffer[_MAX_PATH];
169         int converted = WideCharToMultiByte(CP_ACP, 0, info->FileName, info->FileNameLength/sizeof(WCHAR), FileNameBuffer, _MAX_PATH-1, NULL, NULL);
170         FileNameBuffer[converted] = '\0';
171         char *command;
172         if (info->Action == FILE_ACTION_ADDED || info->Action == FILE_ACTION_RENAMED_OLD_NAME)
173         {
174                 command = "CREATE";
175         }
176         else if (info->Action == FILE_ACTION_REMOVED || info->Action == FILE_ACTION_RENAMED_OLD_NAME)
177         {
178                 command = "DELETE";
179         }
180         else if (info->Action == FILE_ACTION_MODIFIED)
181         {
182                 command = "CHANGE";     
183         }
184         else
185         {
186                 return;  // unknown command
187         }
188
189         EnterCriticalSection(&csOutput);
190         puts(command);
191         printf("%s", rootPath);
192         puts(FileNameBuffer);
193         fflush(stdout);
194         LeaveCriticalSection(&csOutput);
195 }
196
197 DWORD WINAPI WatcherThread(void *param)
198 {
199         WatchRootInfo *info = (WatchRootInfo *) param;
200
201         OVERLAPPED overlapped;
202         memset(&overlapped, 0, sizeof(overlapped));
203         overlapped.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
204
205         char rootPath[8];
206         sprintf_s(rootPath, 8, "%c:\\", info->driveLetter);
207         HANDLE hRootDir = CreateFileA(rootPath, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
208                 NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL);
209
210         int buffer_size = 10240;
211         char *buffer = new char[buffer_size];
212
213         HANDLE handles [2];
214         handles [0] = info->hStopEvent;
215         handles [1] = overlapped.hEvent;
216         while(true)
217         {
218                 int rcDir = ReadDirectoryChangesW(hRootDir, buffer, buffer_size, TRUE, 
219                         FILE_NOTIFY_CHANGE_FILE_NAME |
220                         FILE_NOTIFY_CHANGE_DIR_NAME | 
221                         FILE_NOTIFY_CHANGE_ATTRIBUTES | 
222                         FILE_NOTIFY_CHANGE_SIZE |
223                         FILE_NOTIFY_CHANGE_LAST_WRITE,
224                         NULL,
225                         &overlapped, 
226                         NULL);
227                 if (rcDir == 0)
228                 {
229                         info->bFailed = true;
230                         break;
231                 }
232
233                 int rc = WaitForMultipleObjects(2, handles, FALSE, INFINITE);
234                 if (rc == WAIT_OBJECT_0)
235                 {
236                         break;
237                 }
238                 if (rc == WAIT_OBJECT_0+1)
239                 {
240                         FILE_NOTIFY_INFORMATION *info = (FILE_NOTIFY_INFORMATION *) buffer;
241                         while(true) 
242                         {
243                                 PrintChangeInfo(rootPath, info);
244                                 if (!info->NextEntryOffset)
245                                         break;
246                                 info = (FILE_NOTIFY_INFORMATION *) ((char *) info + info->NextEntryOffset);
247                         }
248                 }
249         }
250         CloseHandle(overlapped.hEvent);
251         CloseHandle(hRootDir);
252         delete[] buffer;
253         return 0;
254 }
255
256 void MarkAllRootsUnused()
257 {
258         for(int i=0; i<ROOT_COUNT; i++)
259         {
260                 watchRootInfos [i].bUsed = false;
261         }
262 }
263
264 void StartRoot(WatchRootInfo *info)
265 {
266         info->hStopEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
267         info->hThread = CreateThread(NULL, 0, &WatcherThread, info, 0, NULL);
268         info->bInitialized = true;
269 }
270
271 void StopRoot(WatchRootInfo *info)
272 {
273         SetEvent(info->hStopEvent);
274         WaitForSingleObject(info->hThread, INFINITE);
275         CloseHandle(info->hThread);
276         CloseHandle(info->hStopEvent);
277         info->bInitialized = false;
278 }
279
280 void UpdateRoots()
281 {
282         char infoBuffer [256];
283         strcpy_s(infoBuffer, "UNWATCHEABLE\n");
284         for(int i=0; i<ROOT_COUNT; i++)
285         {
286                 if (watchRootInfos [i].bInitialized && (!watchRootInfos [i].bUsed || watchRootInfos [i].bFailed))
287                 {
288                         StopRoot(&watchRootInfos [i]);
289                         watchRootInfos [i].bFailed = false;
290                 }
291                 if (watchRootInfos [i].bUsed)
292                 {
293                         char rootPath[8];
294                         sprintf_s(rootPath, 8, "%c:\\", watchRootInfos [i].driveLetter);
295                         if (!IsWatchable(rootPath))
296                         {
297                                 strcat_s(infoBuffer, rootPath);
298                                 strcat_s(infoBuffer, "\n");
299                                 continue;
300                         }
301                         if (!watchRootInfos [i].bInitialized)
302                         {
303                                 StartRoot(&watchRootInfos [i]);
304                         }
305                 }
306         }
307         EnterCriticalSection(&csOutput);
308         fprintf(stdout, "%s", infoBuffer);
309         for(int i=0; i<ROOT_COUNT; i++)
310         {
311                 if (watchRootInfos [i].bUsed)
312                 {
313                         char rootPath[8];
314                         sprintf_s(rootPath, 8, "%c:\\", watchRootInfos [i].driveLetter);
315                         PrintMountPoints(rootPath);
316                 }
317         }
318         puts("#");
319         fflush(stdout);
320         LeaveCriticalSection(&csOutput);
321 }
322
323 int _tmain(int argc, _TCHAR* argv[])
324 {
325         InitializeCriticalSection(&csOutput);
326
327         for(int i=0; i<26; i++)
328         {
329                 watchRootInfos [i].driveLetter = 'A' + i;
330                 watchRootInfos [i].bInitialized = false;
331                 watchRootInfos [i].bUsed = false;
332         }
333
334         char buffer[8192];
335         while(true)
336         {
337                 if (!gets_s(buffer, sizeof(buffer)-1))
338                         break;
339
340                 if (!strcmp(buffer, "ROOTS"))
341                 {
342                         MarkAllRootsUnused();
343                         bool failed = false;
344                         while(true)
345                         {
346                                 if (!gets_s(buffer, sizeof(buffer)-1))
347                                 {
348                                         failed = true;
349                                         break;
350                                 }
351                                 if (buffer [0] == '#')
352                                         break;
353                                 int driveLetterPos = 0;
354                                 _strupr_s(buffer, sizeof(buffer)-1);
355                                 char driveLetter = buffer[0];
356                                 if (driveLetter == '|')
357                                         driveLetter = buffer[1];
358                                 if (driveLetter >= 'A' && driveLetter <= 'Z')
359                                 {
360                                         watchRootInfos [driveLetter-'A'].bUsed = true;
361                                 }
362                         }
363                         if (failed)
364                                 break;
365
366                         UpdateRoots();
367                 }
368                 if (!strcmp(buffer, "EXIT"))
369                         break;
370         }
371
372         MarkAllRootsUnused();
373         UpdateRoots();
374         
375         DeleteCriticalSection(&csOutput);
376 }