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