1 // TortoiseSVN - a Windows shell extension for easy version control
\r
3 // External Cache Copyright (C) 2005 - 2007 - TortoiseSVN
\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
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
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
21 #include "GitStatusCache.h"
\r
22 #include ".\directorywatcher.h"
\r
26 CDirectoryWatcher::CDirectoryWatcher(void) : m_hCompPort(NULL)
\r
28 , m_FolderCrawler(NULL)
\r
31 // enable the required privileges for this process
\r
33 LPCTSTR arPrivelegeNames[] = { SE_BACKUP_NAME,
\r
35 SE_CHANGE_NOTIFY_NAME
\r
38 for (int i=0; i<(sizeof(arPrivelegeNames)/sizeof(LPCTSTR)); ++i)
\r
41 if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken))
\r
43 TOKEN_PRIVILEGES tp = { 1 };
\r
45 if (LookupPrivilegeValue(NULL, arPrivelegeNames[i], &tp.Privileges[0].Luid))
\r
47 tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
\r
49 AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL);
\r
51 CloseHandle(hToken);
\r
55 unsigned int threadId;
\r
56 m_hThread = (HANDLE)_beginthreadex(NULL,0,ThreadEntry,this,0,&threadId);
\r
59 CDirectoryWatcher::~CDirectoryWatcher(void)
\r
61 InterlockedExchange(&m_bRunning, FALSE);
\r
62 if (m_hThread != INVALID_HANDLE_VALUE)
\r
64 CloseHandle(m_hThread);
\r
65 m_hThread = INVALID_HANDLE_VALUE;
\r
67 AutoLocker lock(m_critSec);
\r
71 void CDirectoryWatcher::Stop()
\r
73 InterlockedExchange(&m_bRunning, FALSE);
\r
74 if (m_hThread != INVALID_HANDLE_VALUE)
\r
75 CloseHandle(m_hThread);
\r
76 m_hThread = INVALID_HANDLE_VALUE;
\r
77 if (m_hCompPort != INVALID_HANDLE_VALUE)
\r
78 CloseHandle(m_hCompPort);
\r
79 m_hCompPort = INVALID_HANDLE_VALUE;
\r
82 void CDirectoryWatcher::SetFolderCrawler(CFolderCrawler * crawler)
\r
84 m_FolderCrawler = crawler;
\r
87 bool CDirectoryWatcher::RemovePathAndChildren(const CTGitPath& path)
\r
89 bool bRemoved = false;
\r
90 AutoLocker lock(m_critSec);
\r
92 for (int i=0; i<watchedPaths.GetCount(); ++i)
\r
94 if (path.IsAncestorOf(watchedPaths[i]))
\r
96 watchedPaths.RemovePath(watchedPaths[i]);
\r
104 void CDirectoryWatcher::BlockPath(const CTGitPath& path)
\r
106 blockedPath = path;
\r
107 // block the path from being watched for 4 seconds
\r
108 blockTickCount = GetTickCount()+4000;
\r
109 ATLTRACE(_T("Blocking path: %s\n"), path.GetWinPath());
\r
112 bool CDirectoryWatcher::AddPath(const CTGitPath& path)
\r
114 if (!CGitStatusCache::Instance().IsPathAllowed(path))
\r
116 if ((!blockedPath.IsEmpty())&&(blockedPath.IsAncestorOf(path)))
\r
118 if (GetTickCount() < blockTickCount)
\r
120 ATLTRACE(_T("Path %s prevented from being watched\n"), path.GetWinPath());
\r
124 AutoLocker lock(m_critSec);
\r
125 for (int i=0; i<watchedPaths.GetCount(); ++i)
\r
127 if (watchedPaths[i].IsAncestorOf(path))
\r
128 return false; // already watched (recursively)
\r
131 // now check if with the new path we might have a new root
\r
133 for (int i=0; i<watchedPaths.GetCount(); ++i)
\r
135 const CString& watched = watchedPaths[i].GetWinPathString();
\r
136 const CString& sPath = path.GetWinPathString();
\r
137 int minlen = min(sPath.GetLength(), watched.GetLength());
\r
139 for (len = 0; len < minlen; ++len)
\r
141 if (watched.GetAt(len) != sPath.GetAt(len))
\r
143 if ((len > 1)&&(len < minlen))
\r
145 if (sPath.GetAt(len)=='\\')
\r
147 newroot = CTGitPath(sPath.Left(len));
\r
149 else if (watched.GetAt(len)=='\\')
\r
151 newroot = CTGitPath(watched.Left(len));
\r
159 if (sPath.GetLength() == minlen)
\r
161 if (watched.GetLength() > minlen)
\r
163 if (watched.GetAt(len)=='\\')
\r
167 else if (sPath.GetLength() == 3 && sPath[1] == ':')
\r
175 if (sPath.GetLength() > minlen)
\r
177 if (sPath.GetAt(len)=='\\')
\r
179 newroot = CTGitPath(watched);
\r
181 else if (watched.GetLength() == 3 && watched[1] == ':')
\r
183 newroot = CTGitPath(watched);
\r
189 if (!newroot.IsEmpty())
\r
191 ATLTRACE(_T("add path to watch %s\n"), newroot.GetWinPath());
\r
192 watchedPaths.AddPath(newroot);
\r
193 watchedPaths.RemoveChildren();
\r
195 m_hCompPort = INVALID_HANDLE_VALUE;
\r
198 ATLTRACE(_T("add path to watch %s\n"), path.GetWinPath());
\r
199 watchedPaths.AddPath(path);
\r
201 m_hCompPort = INVALID_HANDLE_VALUE;
\r
205 bool CDirectoryWatcher::IsPathWatched(const CTGitPath& path)
\r
207 for (int i=0; i<watchedPaths.GetCount(); ++i)
\r
209 if (watchedPaths[i].IsAncestorOf(path))
\r
215 unsigned int CDirectoryWatcher::ThreadEntry(void* pContext)
\r
217 ((CDirectoryWatcher*)pContext)->WorkerThread();
\r
221 void CDirectoryWatcher::WorkerThread()
\r
224 CDirWatchInfo * pdi = NULL;
\r
225 LPOVERLAPPED lpOverlapped;
\r
226 WCHAR buf[READ_DIR_CHANGE_BUFFER_SIZE] = {0};
\r
227 WCHAR * pFound = NULL;
\r
230 if (watchedPaths.GetCount())
\r
232 if (!GetQueuedCompletionStatus(m_hCompPort,
\r
238 // Error retrieving changes
\r
239 // Clear the list of watched objects and recreate that list
\r
243 AutoLocker lock(m_critSec);
\r
246 DWORD lasterr = GetLastError();
\r
247 if ((m_hCompPort != INVALID_HANDLE_VALUE)&&(lasterr!=ERROR_SUCCESS)&&(lasterr!=ERROR_INVALID_HANDLE))
\r
249 CloseHandle(m_hCompPort);
\r
250 m_hCompPort = INVALID_HANDLE_VALUE;
\r
252 // Since we pass m_hCompPort to CreateIoCompletionPort, we
\r
253 // have to set this to NULL to have that API create a new
\r
255 m_hCompPort = NULL;
\r
256 for (int i=0; i<watchedPaths.GetCount(); ++i)
\r
258 CTGitPath watchedPath = watchedPaths[i];
\r
260 HANDLE hDir = CreateFile(watchedPath.GetWinPath(),
\r
261 FILE_LIST_DIRECTORY,
\r
262 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
\r
263 NULL, //security attributes
\r
265 FILE_FLAG_BACKUP_SEMANTICS | //required privileges: SE_BACKUP_NAME and SE_RESTORE_NAME.
\r
266 FILE_FLAG_OVERLAPPED,
\r
268 if (hDir == INVALID_HANDLE_VALUE)
\r
270 // this could happen if a watched folder has been removed/renamed
\r
271 ATLTRACE(_T("CDirectoryWatcher: CreateFile failed. Can't watch directory %s\n"), watchedPaths[i].GetWinPath());
\r
272 CloseHandle(m_hCompPort);
\r
273 m_hCompPort = INVALID_HANDLE_VALUE;
\r
274 AutoLocker lock(m_critSec);
\r
275 watchedPaths.RemovePath(watchedPath);
\r
280 DEV_BROADCAST_HANDLE NotificationFilter;
\r
281 SecureZeroMemory(&NotificationFilter, sizeof(NotificationFilter));
\r
282 NotificationFilter.dbch_size = sizeof(DEV_BROADCAST_HANDLE);
\r
283 NotificationFilter.dbch_devicetype = DBT_DEVTYP_HANDLE;
\r
284 NotificationFilter.dbch_handle = hDir;
\r
285 NotificationFilter.dbch_hdevnotify = RegisterDeviceNotification(hWnd, &NotificationFilter, DEVICE_NOTIFY_WINDOW_HANDLE);
\r
287 CDirWatchInfo * pDirInfo = new CDirWatchInfo(hDir, watchedPath);
\r
288 pDirInfo->m_hDevNotify = NotificationFilter.dbch_hdevnotify;
\r
289 m_hCompPort = CreateIoCompletionPort(hDir, m_hCompPort, (ULONG_PTR)pDirInfo, 0);
\r
290 if (m_hCompPort == NULL)
\r
292 ATLTRACE(_T("CDirectoryWatcher: CreateIoCompletionPort failed. Can't watch directory %s\n"), watchedPath.GetWinPath());
\r
293 AutoLocker lock(m_critSec);
\r
297 watchedPaths.RemovePath(watchedPath);
\r
301 if (!ReadDirectoryChangesW(pDirInfo->m_hDir,
\r
302 pDirInfo->m_Buffer,
\r
303 READ_DIR_CHANGE_BUFFER_SIZE,
\r
305 FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE,
\r
306 &numBytes,// not used
\r
307 &pDirInfo->m_Overlapped,
\r
308 NULL)) //no completion routine!
\r
310 ATLTRACE(_T("CDirectoryWatcher: ReadDirectoryChangesW failed. Can't watch directory %s\n"), watchedPath.GetWinPath());
\r
311 AutoLocker lock(m_critSec);
\r
315 watchedPaths.RemovePath(watchedPath);
\r
319 AutoLocker lock(m_critSec);
\r
320 watchInfoMap[pDirInfo->m_hDir] = pDirInfo;
\r
321 ATLTRACE(_T("watching path %s\n"), pDirInfo->m_DirName.GetWinPath());
\r
328 // NOTE: the longer this code takes to execute until ReadDirectoryChangesW
\r
329 // is called again, the higher the chance that we miss some
\r
330 // changes in the file system!
\r
335 goto continuewatching;
\r
337 PFILE_NOTIFY_INFORMATION pnotify = (PFILE_NOTIFY_INFORMATION)pdi->m_Buffer;
\r
338 if ((ULONG_PTR)pnotify - (ULONG_PTR)pdi->m_Buffer > READ_DIR_CHANGE_BUFFER_SIZE)
\r
339 goto continuewatching;
\r
340 DWORD nOffset = pnotify->NextEntryOffset;
\r
343 nOffset = pnotify->NextEntryOffset;
\r
344 if (pnotify->FileNameLength >= (READ_DIR_CHANGE_BUFFER_SIZE*sizeof(TCHAR)))
\r
346 SecureZeroMemory(buf, READ_DIR_CHANGE_BUFFER_SIZE*sizeof(TCHAR));
\r
347 _tcsncpy_s(buf, READ_DIR_CHANGE_BUFFER_SIZE, pdi->m_DirPath, READ_DIR_CHANGE_BUFFER_SIZE);
\r
348 errno_t err = _tcsncat_s(buf+pdi->m_DirPath.GetLength(), READ_DIR_CHANGE_BUFFER_SIZE-pdi->m_DirPath.GetLength(), pnotify->FileName, _TRUNCATE);
\r
349 if (err == STRUNCATE)
\r
351 pnotify = (PFILE_NOTIFY_INFORMATION)((LPBYTE)pnotify + nOffset);
\r
354 buf[(pnotify->FileNameLength/sizeof(TCHAR))+pdi->m_DirPath.GetLength()] = 0;
\r
355 pnotify = (PFILE_NOTIFY_INFORMATION)((LPBYTE)pnotify + nOffset);
\r
356 if (m_FolderCrawler)
\r
358 if ((pFound = wcsstr(buf, L"\\tmp"))!=NULL)
\r
361 if (((*pFound)=='\\')||((*pFound)=='\0'))
\r
363 if ((ULONG_PTR)pnotify - (ULONG_PTR)pdi->m_Buffer > READ_DIR_CHANGE_BUFFER_SIZE)
\r
368 if ((pFound = wcsstr(buf, L":\\RECYCLER\\"))!=NULL)
\r
370 if ((pFound-buf) < 5)
\r
372 // a notification for the recycle bin - ignore it
\r
373 if ((ULONG_PTR)pnotify - (ULONG_PTR)pdi->m_Buffer > READ_DIR_CHANGE_BUFFER_SIZE)
\r
378 if ((pFound = wcsstr(buf, L":\\$Recycle.Bin\\"))!=NULL)
\r
380 if ((pFound-buf) < 5)
\r
382 // a notification for the recycle bin - ignore it
\r
383 if ((ULONG_PTR)pnotify - (ULONG_PTR)pdi->m_Buffer > READ_DIR_CHANGE_BUFFER_SIZE)
\r
388 if ((pFound = wcsstr(buf, L".tmp"))!=NULL)
\r
390 // assume files with a .tmp extension are not versioned and interesting,
\r
392 if ((ULONG_PTR)pnotify - (ULONG_PTR)pdi->m_Buffer > READ_DIR_CHANGE_BUFFER_SIZE)
\r
396 ATLTRACE(_T("change notification: %s\n"), buf);
\r
397 m_FolderCrawler->AddPathForUpdate(CTGitPath(buf));
\r
399 if ((ULONG_PTR)pnotify - (ULONG_PTR)pdi->m_Buffer > READ_DIR_CHANGE_BUFFER_SIZE)
\r
403 SecureZeroMemory(pdi->m_Buffer, sizeof(pdi->m_Buffer));
\r
404 SecureZeroMemory(&pdi->m_Overlapped, sizeof(OVERLAPPED));
\r
405 if (!ReadDirectoryChangesW(pdi->m_hDir,
\r
407 READ_DIR_CHANGE_BUFFER_SIZE,
\r
409 FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE,
\r
410 &numBytes,// not used
\r
411 &pdi->m_Overlapped,
\r
412 NULL)) //no completion routine!
\r
414 // Since the call to ReadDirectoryChangesW failed, just
\r
415 // wait a while. We don't want to have this thread
\r
416 // running using 100% CPU if something goes completely
\r
422 }// if (watchedPaths.GetCount())
\r
425 }// while (m_bRunning)
\r
428 void CDirectoryWatcher::ClearInfoMap()
\r
430 if (watchInfoMap.size()!=0)
\r
432 AutoLocker lock(m_critSec);
\r
433 for (std::map<HANDLE, CDirWatchInfo *>::iterator I = watchInfoMap.begin(); I != watchInfoMap.end(); ++I)
\r
435 CDirectoryWatcher::CDirWatchInfo * info = I->second;
\r
440 watchInfoMap.clear();
\r
441 if (m_hCompPort != INVALID_HANDLE_VALUE)
\r
442 CloseHandle(m_hCompPort);
\r
443 m_hCompPort = INVALID_HANDLE_VALUE;
\r
446 CTGitPath CDirectoryWatcher::CloseInfoMap(HDEVNOTIFY hdev)
\r
449 if (watchInfoMap.size() == 0)
\r
451 AutoLocker lock(m_critSec);
\r
452 for (std::map<HANDLE, CDirWatchInfo *>::iterator I = watchInfoMap.begin(); I != watchInfoMap.end(); ++I)
\r
454 CDirectoryWatcher::CDirWatchInfo * info = I->second;
\r
455 if (info->m_hDevNotify == hdev)
\r
457 path = info->m_DirName;
\r
458 RemovePathAndChildren(path);
\r
461 info->CloseDirectoryHandle();
\r
463 watchInfoMap.clear();
\r
464 if (m_hCompPort != INVALID_HANDLE_VALUE)
\r
465 CloseHandle(m_hCompPort);
\r
466 m_hCompPort = INVALID_HANDLE_VALUE;
\r
470 bool CDirectoryWatcher::CloseHandlesForPath(const CTGitPath& path)
\r
472 if (watchInfoMap.size() == 0)
\r
474 AutoLocker lock(m_critSec);
\r
475 for (std::map<HANDLE, CDirWatchInfo *>::iterator I = watchInfoMap.begin(); I != watchInfoMap.end(); ++I)
\r
477 CDirectoryWatcher::CDirWatchInfo * info = I->second;
\r
478 CTGitPath p = CTGitPath(info->m_DirPath);
\r
479 if (path.IsAncestorOf(p))
\r
481 RemovePathAndChildren(p);
\r
484 info->CloseDirectoryHandle();
\r
486 watchInfoMap.clear();
\r
487 if (m_hCompPort != INVALID_HANDLE_VALUE)
\r
488 CloseHandle(m_hCompPort);
\r
489 m_hCompPort = INVALID_HANDLE_VALUE;
\r
493 CDirectoryWatcher::CDirWatchInfo::CDirWatchInfo(HANDLE hDir, const CTGitPath& DirectoryName) :
\r
495 m_DirName(DirectoryName)
\r
497 ATLASSERT( hDir != INVALID_HANDLE_VALUE
\r
498 && !DirectoryName.IsEmpty());
\r
499 memset(&m_Overlapped, 0, sizeof(m_Overlapped));
\r
500 m_DirPath = m_DirName.GetWinPathString();
\r
501 if (m_DirPath.GetAt(m_DirPath.GetLength()-1) != '\\')
\r
502 m_DirPath += _T("\\");
\r
503 m_hDevNotify = INVALID_HANDLE_VALUE;
\r
506 CDirectoryWatcher::CDirWatchInfo::~CDirWatchInfo()
\r
508 CloseDirectoryHandle();
\r
511 bool CDirectoryWatcher::CDirWatchInfo::CloseDirectoryHandle()
\r
514 if( m_hDir != INVALID_HANDLE_VALUE )
\r
516 b = !!CloseHandle(m_hDir);
\r
517 m_hDir = INVALID_HANDLE_VALUE;
\r
519 if (m_hDevNotify != INVALID_HANDLE_VALUE)
\r
521 UnregisterDeviceNotification(m_hDevNotify);
\r