OSDN Git Service

Add SVN DCommit Command
[tortoisegit/TortoiseGitJp.git] / src / Utils / PathWatcher.cpp
1 // TortoiseSVN - a Windows shell extension for easy version control\r
2 \r
3 // External Cache Copyright (C) 2007 - 2007 - Stefan Kueng\r
4 \r
5 // This program is free software; you can redistribute it and/or\r
6 // modify it under the terms of the GNU General Public License\r
7 // as published by the Free Software Foundation; either version 2\r
8 // of the License, or (at your option) any later version.\r
9 \r
10 // This program is distributed in the hope that it will be useful,\r
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of\r
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
13 // GNU General Public License for more details.\r
14 \r
15 // You should have received a copy of the GNU General Public License\r
16 // along with this program; if not, write to the Free Software Foundation,\r
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\r
18 //\r
19 #include "StdAfx.h"\r
20 #include "Dbt.h"\r
21 #include "PathWatcher.h"\r
22 \r
23 CPathWatcher::CPathWatcher(void) : m_hCompPort(NULL)\r
24         , m_bRunning(TRUE)\r
25 {\r
26         // enable the required privileges for this process\r
27         \r
28         LPCTSTR arPrivelegeNames[] = {  SE_BACKUP_NAME,\r
29                                                                         SE_RESTORE_NAME,\r
30                                                                         SE_CHANGE_NOTIFY_NAME\r
31                                                                  };\r
32 \r
33         for (int i=0; i<(sizeof(arPrivelegeNames)/sizeof(LPCTSTR)); ++i)\r
34         {\r
35                 HANDLE hToken;    \r
36                 if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken))    \r
37                 {        \r
38                         TOKEN_PRIVILEGES tp = { 1 };        \r
39 \r
40                         if (LookupPrivilegeValue(NULL, arPrivelegeNames[i],  &tp.Privileges[0].Luid))\r
41                         {\r
42                                 tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;\r
43 \r
44                                 AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL);\r
45                         }\r
46                         CloseHandle(hToken);    \r
47                 }       \r
48         }\r
49 \r
50         unsigned int threadId;\r
51         m_hThread = (HANDLE)_beginthreadex(NULL,0,ThreadEntry,this,0,&threadId);\r
52 }\r
53 \r
54 CPathWatcher::~CPathWatcher(void)\r
55 {\r
56         InterlockedExchange(&m_bRunning, FALSE);\r
57         if (m_hThread != INVALID_HANDLE_VALUE)\r
58         {\r
59                 CloseHandle(m_hThread);\r
60                 m_hThread = INVALID_HANDLE_VALUE;\r
61         }\r
62         AutoLocker lock(m_critSec);\r
63         ClearInfoMap();\r
64 }\r
65 \r
66 void CPathWatcher::Stop()\r
67 {\r
68         InterlockedExchange(&m_bRunning, FALSE);\r
69         if (m_hCompPort != INVALID_HANDLE_VALUE)\r
70         {\r
71                 PostQueuedCompletionStatus(m_hCompPort, 0, NULL, NULL);\r
72         }\r
73         if (m_hThread != INVALID_HANDLE_VALUE)\r
74                 CloseHandle(m_hThread);\r
75 \r
76         m_hThread = INVALID_HANDLE_VALUE;\r
77         m_hCompPort = INVALID_HANDLE_VALUE;\r
78 }\r
79 \r
80 bool CPathWatcher::RemovePathAndChildren(const CTGitPath& path)\r
81 {\r
82         bool bRemoved = false;\r
83         AutoLocker lock(m_critSec);\r
84 repeat:\r
85         for (int i=0; i<watchedPaths.GetCount(); ++i)\r
86         {\r
87                 if (path.IsAncestorOf(watchedPaths[i]))\r
88                 {\r
89                         watchedPaths.RemovePath(watchedPaths[i]);\r
90                         bRemoved = true;\r
91                         goto repeat;\r
92                 }\r
93         }\r
94         return bRemoved;\r
95 }\r
96 \r
97 bool CPathWatcher::AddPath(const CTGitPath& path)\r
98 {\r
99         AutoLocker lock(m_critSec);\r
100         for (int i=0; i<watchedPaths.GetCount(); ++i)\r
101         {\r
102                 if (watchedPaths[i].IsAncestorOf(path))\r
103                         return false;           // already watched (recursively)\r
104         }\r
105         \r
106         // now check if with the new path we might have a new root\r
107         CTGitPath newroot;\r
108         for (int i=0; i<watchedPaths.GetCount(); ++i)\r
109         {\r
110                 const CString& watched = watchedPaths[i].GetWinPathString();\r
111                 const CString& sPath = path.GetWinPathString();\r
112                 int minlen = min(sPath.GetLength(), watched.GetLength());\r
113                 int len = 0;\r
114                 for (len = 0; len < minlen; ++len)\r
115                 {\r
116                         if (watched.GetAt(len) != sPath.GetAt(len))\r
117                         {\r
118                                 if ((len > 1)&&(len < minlen))\r
119                                 {\r
120                                         if (sPath.GetAt(len)=='\\')\r
121                                         {\r
122                                                 newroot = CTGitPath(sPath.Left(len));\r
123                                         }\r
124                                         else if (watched.GetAt(len)=='\\')\r
125                                         {\r
126                                                 newroot = CTGitPath(watched.Left(len));\r
127                                         }\r
128                                 }\r
129                                 break;\r
130                         }\r
131                 }\r
132                 if (len == minlen)\r
133                 {\r
134                         if (sPath.GetLength() == minlen)\r
135                         {\r
136                                 if (watched.GetLength() > minlen)\r
137                                 {\r
138                                         if (watched.GetAt(len)=='\\')\r
139                                         {\r
140                                                 newroot = path;\r
141                                         }\r
142                                         else if (sPath.GetLength() == 3 && sPath[1] == ':')\r
143                                         {\r
144                                                 newroot = path;\r
145                                         }\r
146                                 }\r
147                         }\r
148                         else\r
149                         {\r
150                                 if (sPath.GetLength() > minlen)\r
151                                 {\r
152                                         if (sPath.GetAt(len)=='\\')\r
153                                         {\r
154                                                 newroot = CTGitPath(watched);\r
155                                         }\r
156                                         else if (watched.GetLength() == 3 && watched[1] == ':')\r
157                                         {\r
158                                                 newroot = CTGitPath(watched);\r
159                                         }\r
160                                 }\r
161                         }\r
162                 }\r
163         }\r
164         if (!newroot.IsEmpty())\r
165         {\r
166                 ATLTRACE(_T("add path to watch %s\n"), newroot.GetWinPath());\r
167                 watchedPaths.AddPath(newroot);\r
168                 watchedPaths.RemoveChildren();\r
169                 m_hCompPort = INVALID_HANDLE_VALUE;\r
170                 return true;\r
171         }\r
172         ATLTRACE(_T("add path to watch %s\n"), path.GetWinPath());\r
173         watchedPaths.AddPath(path);\r
174         m_hCompPort = INVALID_HANDLE_VALUE;\r
175         return true;\r
176 }\r
177 \r
178 \r
179 unsigned int CPathWatcher::ThreadEntry(void* pContext)\r
180 {\r
181         ((CPathWatcher*)pContext)->WorkerThread();\r
182         return 0;\r
183 }\r
184 \r
185 void CPathWatcher::WorkerThread()\r
186 {\r
187         DWORD numBytes;\r
188         CDirWatchInfo * pdi = NULL;\r
189         LPOVERLAPPED lpOverlapped;\r
190         WCHAR buf[MAX_PATH*4] = {0};\r
191         while (m_bRunning)\r
192         {\r
193                 if (watchedPaths.GetCount())\r
194                 {\r
195                         if (!GetQueuedCompletionStatus(m_hCompPort,\r
196                                                                                         &numBytes,\r
197                                                                                         (PULONG_PTR) &pdi,\r
198                                                                                         &lpOverlapped,\r
199                                                                                         INFINITE))\r
200                         {\r
201                                 // Error retrieving changes\r
202                                 // Clear the list of watched objects and recreate that list\r
203                                 if (!m_bRunning)\r
204                                         return;\r
205                                 {\r
206                                         AutoLocker lock(m_critSec);\r
207                                         ClearInfoMap();\r
208                                 }\r
209                                 DWORD lasterr = GetLastError();\r
210                                 if ((m_hCompPort != INVALID_HANDLE_VALUE)&&(lasterr!=ERROR_SUCCESS)&&(lasterr!=ERROR_INVALID_HANDLE))\r
211                                 {\r
212                                         CloseHandle(m_hCompPort);\r
213                                         m_hCompPort = INVALID_HANDLE_VALUE;\r
214                                 }\r
215                                 // Since we pass m_hCompPort to CreateIoCompletionPort, we\r
216                                 // have to set this to NULL to have that API create a new\r
217                                 // handle.\r
218                                 m_hCompPort = NULL;\r
219                                 for (int i=0; i<watchedPaths.GetCount(); ++i)\r
220                                 {\r
221                                         HANDLE hDir = CreateFile(watchedPaths[i].GetWinPath(), \r
222                                                                                         FILE_LIST_DIRECTORY, \r
223                                                                                         FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\r
224                                                                                         NULL, //security attributes\r
225                                                                                         OPEN_EXISTING,\r
226                                                                                         FILE_FLAG_BACKUP_SEMANTICS | //required privileges: SE_BACKUP_NAME and SE_RESTORE_NAME.\r
227                                                                                         FILE_FLAG_OVERLAPPED,\r
228                                                                                         NULL);\r
229                                         if (hDir == INVALID_HANDLE_VALUE)\r
230                                         {\r
231                                                 // this could happen if a watched folder has been removed/renamed\r
232                                                 CloseHandle(m_hCompPort);\r
233                                                 m_hCompPort = INVALID_HANDLE_VALUE;\r
234                                                 AutoLocker lock(m_critSec);\r
235                                                 watchedPaths.RemovePath(watchedPaths[i]);\r
236                                                 i--; if (i<0) i=0;\r
237                                                 break;\r
238                                         }\r
239                                         \r
240                                         CDirWatchInfo * pDirInfo = new CDirWatchInfo(hDir, watchedPaths[i]);\r
241                                         m_hCompPort = CreateIoCompletionPort(hDir, m_hCompPort, (ULONG_PTR)pDirInfo, 0);\r
242                                         if (m_hCompPort == NULL)\r
243                                         {\r
244                                                 AutoLocker lock(m_critSec);\r
245                                                 ClearInfoMap();\r
246                                                 delete pDirInfo;\r
247                                                 pDirInfo = NULL;\r
248                                                 watchedPaths.RemovePath(watchedPaths[i]);\r
249                                                 i--; if (i<0) i=0;\r
250                                                 break;\r
251                                         }\r
252                                         if (!ReadDirectoryChangesW(pDirInfo->m_hDir,\r
253                                                                                                 pDirInfo->m_Buffer,\r
254                                                                                                 READ_DIR_CHANGE_BUFFER_SIZE,\r
255                                                                                                 TRUE,\r
256                                                                                                 FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE,\r
257                                                                                                 &numBytes,// not used\r
258                                                                                                 &pDirInfo->m_Overlapped,\r
259                                                                                                 NULL))  //no completion routine!\r
260                                         {\r
261                                                 AutoLocker lock(m_critSec);\r
262                                                 ClearInfoMap();\r
263                                                 delete pDirInfo;\r
264                                                 pDirInfo = NULL;\r
265                                                 watchedPaths.RemovePath(watchedPaths[i]);\r
266                                                 i--; if (i<0) i=0;\r
267                                                 break;\r
268                                         }\r
269                                         AutoLocker lock(m_critSec);\r
270                                         watchInfoMap[pDirInfo->m_hDir] = pDirInfo;\r
271                                         ATLTRACE(_T("watching path %s\n"), pDirInfo->m_DirName.GetWinPath());\r
272                                 }\r
273                         }\r
274                         else\r
275                         {\r
276                                 if (!m_bRunning)\r
277                                         return;\r
278                                 // NOTE: the longer this code takes to execute until ReadDirectoryChangesW\r
279                                 // is called again, the higher the chance that we miss some\r
280                                 // changes in the file system! \r
281                                 if (pdi)\r
282                                 {\r
283                                         if (numBytes == 0)\r
284                                         {\r
285                                                 goto continuewatching;\r
286                                         }\r
287                                         PFILE_NOTIFY_INFORMATION pnotify = (PFILE_NOTIFY_INFORMATION)pdi->m_Buffer;\r
288                                         if ((ULONG_PTR)pnotify - (ULONG_PTR)pdi->m_Buffer > READ_DIR_CHANGE_BUFFER_SIZE)\r
289                                                 goto continuewatching;\r
290                                         DWORD nOffset = pnotify->NextEntryOffset;\r
291                                         do \r
292                                         {\r
293                                                 nOffset = pnotify->NextEntryOffset;\r
294                                                 SecureZeroMemory(buf, MAX_PATH*4*sizeof(TCHAR));\r
295                                                 _tcsncpy_s(buf, MAX_PATH*4, pdi->m_DirPath, MAX_PATH*4);\r
296                                                 errno_t err = _tcsncat_s(buf+pdi->m_DirPath.GetLength(), (MAX_PATH*4)-pdi->m_DirPath.GetLength(), pnotify->FileName, _TRUNCATE);\r
297                                                 if (err == STRUNCATE)\r
298                                                 {\r
299                                                         pnotify = (PFILE_NOTIFY_INFORMATION)((LPBYTE)pnotify + nOffset);\r
300                                                         continue;\r
301                                                 }\r
302                                                 buf[min(MAX_PATH*4-1, pdi->m_DirPath.GetLength()+(pnotify->FileNameLength/sizeof(WCHAR)))] = 0;\r
303                                                 pnotify = (PFILE_NOTIFY_INFORMATION)((LPBYTE)pnotify + nOffset);\r
304                                                 ATLTRACE(_T("change notification: %s\n"), buf);\r
305                                                 m_changedPaths.AddPath(CTGitPath(buf));\r
306                                                 if ((ULONG_PTR)pnotify - (ULONG_PTR)pdi->m_Buffer > READ_DIR_CHANGE_BUFFER_SIZE)\r
307                                                         break;\r
308                                         } while (nOffset);\r
309 continuewatching:\r
310                                         SecureZeroMemory(pdi->m_Buffer, sizeof(pdi->m_Buffer));\r
311                                         SecureZeroMemory(&pdi->m_Overlapped, sizeof(OVERLAPPED));\r
312                                         if (!ReadDirectoryChangesW(pdi->m_hDir,\r
313                                                                                                 pdi->m_Buffer,\r
314                                                                                                 READ_DIR_CHANGE_BUFFER_SIZE,\r
315                                                                                                 TRUE,\r
316                                                                                                 FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE,\r
317                                                                                                 &numBytes,// not used\r
318                                                                                                 &pdi->m_Overlapped,\r
319                                                                                                 NULL))  //no completion routine!\r
320                                         {\r
321                                                 // Since the call to ReadDirectoryChangesW failed, just\r
322                                                 // wait a while. We don't want to have this thread\r
323                                                 // running using 100% CPU if something goes completely\r
324                                                 // wrong.\r
325                                                 Sleep(200);\r
326                                         }\r
327                                 }\r
328                         }\r
329                 }// if (watchedPaths.GetCount())\r
330                 else\r
331                         Sleep(200);\r
332         }// while (m_bRunning)\r
333 }\r
334 \r
335 void CPathWatcher::ClearInfoMap()\r
336 {\r
337         if (watchInfoMap.size()!=0)\r
338         {\r
339                 AutoLocker lock(m_critSec);\r
340                 for (std::map<HANDLE, CDirWatchInfo *>::iterator I = watchInfoMap.begin(); I != watchInfoMap.end(); ++I)\r
341                 {\r
342                         CPathWatcher::CDirWatchInfo * info = I->second;\r
343                         delete info;\r
344                         info = NULL;\r
345                 }\r
346         }\r
347         watchInfoMap.clear();\r
348         if (m_hCompPort != INVALID_HANDLE_VALUE)\r
349                 CloseHandle(m_hCompPort);\r
350         m_hCompPort = INVALID_HANDLE_VALUE;\r
351 }\r
352 \r
353 CPathWatcher::CDirWatchInfo::CDirWatchInfo(HANDLE hDir, const CTGitPath& DirectoryName) :\r
354         m_hDir(hDir),\r
355         m_DirName(DirectoryName)\r
356 {\r
357         ATLASSERT( hDir != INVALID_HANDLE_VALUE \r
358                 && !DirectoryName.IsEmpty());\r
359         memset(&m_Overlapped, 0, sizeof(m_Overlapped));\r
360         m_DirPath = m_DirName.GetWinPathString();\r
361         if (m_DirPath.GetAt(m_DirPath.GetLength()-1) != '\\')\r
362                 m_DirPath += _T("\\");\r
363 }\r
364 \r
365 CPathWatcher::CDirWatchInfo::~CDirWatchInfo()\r
366 {\r
367         CloseDirectoryHandle();\r
368 }\r
369 \r
370 bool CPathWatcher::CDirWatchInfo::CloseDirectoryHandle()\r
371 {\r
372         bool b = TRUE;\r
373         if( m_hDir != INVALID_HANDLE_VALUE )\r
374         {\r
375                 b = !!CloseHandle(m_hDir);\r
376                 m_hDir = INVALID_HANDLE_VALUE;\r
377         }\r
378         return b;\r
379 }\r
380 \r
381 \r
382 \r
383 \r
384 \r
385 \r
386 \r
387 \r