1 // TortoiseGit - a Windows shell extension for easy version control
3 // External Cache Copyright (C) 2005 - 2007 - TortoiseSVN
4 // Copyright (C) 2008-2011 - TortoiseGit
6 // This program is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU General Public License
8 // as published by the Free Software Foundation; either version 2
9 // of the License, or (at your option) any later version.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software Foundation,
18 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22 #include "GitStatusCache.h"
23 #include "directorywatcher.h"
27 CDirectoryWatcher::CDirectoryWatcher(void) : m_hCompPort(NULL
)
29 , m_FolderCrawler(NULL
)
32 // enable the required privileges for this process
34 LPCTSTR arPrivelegeNames
[] = { SE_BACKUP_NAME
,
39 for (int i
=0; i
<(sizeof(arPrivelegeNames
)/sizeof(LPCTSTR
)); ++i
)
42 if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES
, &hToken
))
44 TOKEN_PRIVILEGES tp
= { 1 };
46 if (LookupPrivilegeValue(NULL
, arPrivelegeNames
[i
], &tp
.Privileges
[0].Luid
))
48 tp
.Privileges
[0].Attributes
= SE_PRIVILEGE_ENABLED
;
50 AdjustTokenPrivileges(hToken
, FALSE
, &tp
, sizeof(tp
), NULL
, NULL
);
56 unsigned int threadId
;
57 m_hThread
= (HANDLE
)_beginthreadex(NULL
,0,ThreadEntry
,this,0,&threadId
);
60 CDirectoryWatcher::~CDirectoryWatcher(void)
62 InterlockedExchange(&m_bRunning
, FALSE
);
63 if (m_hThread
!= INVALID_HANDLE_VALUE
)
65 CloseHandle(m_hThread
);
66 m_hThread
= INVALID_HANDLE_VALUE
;
68 AutoLocker
lock(m_critSec
);
72 void CDirectoryWatcher::Stop()
74 InterlockedExchange(&m_bRunning
, FALSE
);
75 if (m_hThread
!= INVALID_HANDLE_VALUE
)
76 CloseHandle(m_hThread
);
77 m_hThread
= INVALID_HANDLE_VALUE
;
78 if (m_hCompPort
!= INVALID_HANDLE_VALUE
)
79 CloseHandle(m_hCompPort
);
80 m_hCompPort
= INVALID_HANDLE_VALUE
;
83 void CDirectoryWatcher::SetFolderCrawler(CFolderCrawler
* crawler
)
85 m_FolderCrawler
= crawler
;
88 bool CDirectoryWatcher::RemovePathAndChildren(const CTGitPath
& path
)
90 bool bRemoved
= false;
91 AutoLocker
lock(m_critSec
);
93 for (int i
=0; i
<watchedPaths
.GetCount(); ++i
)
95 if (path
.IsAncestorOf(watchedPaths
[i
]))
97 watchedPaths
.RemovePath(watchedPaths
[i
]);
105 void CDirectoryWatcher::BlockPath(const CTGitPath
& path
)
108 // block the path from being watched for 4 seconds
109 blockTickCount
= GetTickCount()+4000;
110 ATLTRACE(_T("Blocking path: %s\n"), path
.GetWinPath());
113 bool CDirectoryWatcher::AddPath(const CTGitPath
& path
)
115 if (!CGitStatusCache::Instance().IsPathAllowed(path
))
117 if ((!blockedPath
.IsEmpty())&&(blockedPath
.IsAncestorOf(path
)))
119 if (GetTickCount() < blockTickCount
)
121 ATLTRACE(_T("Path %s prevented from being watched\n"), path
.GetWinPath());
125 AutoLocker
lock(m_critSec
);
126 for (int i
=0; i
<watchedPaths
.GetCount(); ++i
)
128 if (watchedPaths
[i
].IsAncestorOf(path
))
129 return false; // already watched (recursively)
132 // now check if with the new path we might have a new root
134 for (int i
=0; i
<watchedPaths
.GetCount(); ++i
)
136 const CString
& watched
= watchedPaths
[i
].GetWinPathString();
137 const CString
& sPath
= path
.GetWinPathString();
138 int minlen
= min(sPath
.GetLength(), watched
.GetLength());
140 for (len
= 0; len
< minlen
; ++len
)
142 if (watched
.GetAt(len
) != sPath
.GetAt(len
))
144 if ((len
> 1)&&(len
< minlen
))
146 if (sPath
.GetAt(len
)=='\\')
148 newroot
= CTGitPath(sPath
.Left(len
));
150 else if (watched
.GetAt(len
)=='\\')
152 newroot
= CTGitPath(watched
.Left(len
));
160 if (sPath
.GetLength() == minlen
)
162 if (watched
.GetLength() > minlen
)
164 if (watched
.GetAt(len
)=='\\')
168 else if (sPath
.GetLength() == 3 && sPath
[1] == ':')
176 if (sPath
.GetLength() > minlen
)
178 if (sPath
.GetAt(len
)=='\\')
180 newroot
= CTGitPath(watched
);
182 else if (watched
.GetLength() == 3 && watched
[1] == ':')
184 newroot
= CTGitPath(watched
);
190 if (!newroot
.IsEmpty())
192 ATLTRACE(_T("add path to watch %s\n"), newroot
.GetWinPath());
193 watchedPaths
.AddPath(newroot
);
194 watchedPaths
.RemoveChildren();
196 m_hCompPort
= INVALID_HANDLE_VALUE
;
199 ATLTRACE(_T("add path to watch %s\n"), path
.GetWinPath());
200 watchedPaths
.AddPath(path
);
202 m_hCompPort
= INVALID_HANDLE_VALUE
;
206 bool CDirectoryWatcher::IsPathWatched(const CTGitPath
& path
)
208 AutoLocker
lock(m_critSec
);
209 for (int i
=0; i
<watchedPaths
.GetCount(); ++i
)
211 if (watchedPaths
[i
].IsAncestorOf(path
))
217 unsigned int CDirectoryWatcher::ThreadEntry(void* pContext
)
219 ((CDirectoryWatcher
*)pContext
)->WorkerThread();
223 void CDirectoryWatcher::WorkerThread()
226 CDirWatchInfo
* pdi
= NULL
;
227 LPOVERLAPPED lpOverlapped
;
228 WCHAR buf
[READ_DIR_CHANGE_BUFFER_SIZE
] = {0};
229 WCHAR
* pFound
= NULL
;
234 if (watchedPaths
.GetCount())
236 if (!GetQueuedCompletionStatus(m_hCompPort
,
242 // Error retrieving changes
243 // Clear the list of watched objects and recreate that list
247 AutoLocker
lock(m_critSec
);
250 DWORD lasterr
= GetLastError();
251 if ((m_hCompPort
!= INVALID_HANDLE_VALUE
)&&(lasterr
!=ERROR_SUCCESS
)&&(lasterr
!=ERROR_INVALID_HANDLE
))
253 CloseHandle(m_hCompPort
);
254 m_hCompPort
= INVALID_HANDLE_VALUE
;
256 // Since we pass m_hCompPort to CreateIoCompletionPort, we
257 // have to set this to NULL to have that API create a new
260 for (int i
=0; i
<watchedPaths
.GetCount(); ++i
)
262 CTGitPath watchedPath
= watchedPaths
[i
];
264 HANDLE hDir
= CreateFile(watchedPath
.GetWinPath(),
266 FILE_SHARE_READ
| FILE_SHARE_WRITE
| FILE_SHARE_DELETE
,
267 NULL
, //security attributes
269 FILE_FLAG_BACKUP_SEMANTICS
| //required privileges: SE_BACKUP_NAME and SE_RESTORE_NAME.
270 FILE_FLAG_OVERLAPPED
,
272 if (hDir
== INVALID_HANDLE_VALUE
)
274 // this could happen if a watched folder has been removed/renamed
275 ATLTRACE(_T("CDirectoryWatcher: CreateFile failed. Can't watch directory %s\n"), watchedPaths
[i
].GetWinPath());
276 CloseHandle(m_hCompPort
);
277 m_hCompPort
= INVALID_HANDLE_VALUE
;
278 AutoLocker
lock(m_critSec
);
279 watchedPaths
.RemovePath(watchedPath
);
284 DEV_BROADCAST_HANDLE NotificationFilter
;
285 SecureZeroMemory(&NotificationFilter
, sizeof(NotificationFilter
));
286 NotificationFilter
.dbch_size
= sizeof(DEV_BROADCAST_HANDLE
);
287 NotificationFilter
.dbch_devicetype
= DBT_DEVTYP_HANDLE
;
288 NotificationFilter
.dbch_handle
= hDir
;
289 NotificationFilter
.dbch_hdevnotify
= RegisterDeviceNotification(hWnd
, &NotificationFilter
, DEVICE_NOTIFY_WINDOW_HANDLE
);
291 CDirWatchInfo
* pDirInfo
= new CDirWatchInfo(hDir
, watchedPath
);
292 pDirInfo
->m_hDevNotify
= NotificationFilter
.dbch_hdevnotify
;
293 m_hCompPort
= CreateIoCompletionPort(hDir
, m_hCompPort
, (ULONG_PTR
)pDirInfo
, 0);
294 if (m_hCompPort
== NULL
)
296 ATLTRACE(_T("CDirectoryWatcher: CreateIoCompletionPort failed. Can't watch directory %s\n"), watchedPath
.GetWinPath());
297 AutoLocker
lock(m_critSec
);
301 watchedPaths
.RemovePath(watchedPath
);
305 if (!ReadDirectoryChangesW(pDirInfo
->m_hDir
,
307 READ_DIR_CHANGE_BUFFER_SIZE
,
309 FILE_NOTIFY_CHANGE_FILE_NAME
| FILE_NOTIFY_CHANGE_DIR_NAME
| FILE_NOTIFY_CHANGE_LAST_WRITE
,
310 &numBytes
,// not used
311 &pDirInfo
->m_Overlapped
,
312 NULL
)) //no completion routine!
314 ATLTRACE(_T("CDirectoryWatcher: ReadDirectoryChangesW failed. Can't watch directory %s\n"), watchedPath
.GetWinPath());
315 AutoLocker
lock(m_critSec
);
319 watchedPaths
.RemovePath(watchedPath
);
323 AutoLocker
lock(m_critSec
);
324 watchInfoMap
[pDirInfo
->m_hDir
] = pDirInfo
;
325 ATLTRACE(_T("watching path %s\n"), pDirInfo
->m_DirName
.GetWinPath());
332 // NOTE: the longer this code takes to execute until ReadDirectoryChangesW
333 // is called again, the higher the chance that we miss some
334 // changes in the file system!
339 goto continuewatching
;
341 PFILE_NOTIFY_INFORMATION pnotify
= (PFILE_NOTIFY_INFORMATION
)pdi
->m_Buffer
;
342 if ((ULONG_PTR
)pnotify
- (ULONG_PTR
)pdi
->m_Buffer
> READ_DIR_CHANGE_BUFFER_SIZE
)
343 goto continuewatching
;
344 DWORD nOffset
= pnotify
->NextEntryOffset
;
348 nOffset
= pnotify
->NextEntryOffset
;
349 if (pnotify
->FileNameLength
>= (READ_DIR_CHANGE_BUFFER_SIZE
*sizeof(TCHAR
)))
351 SecureZeroMemory(buf
, READ_DIR_CHANGE_BUFFER_SIZE
*sizeof(TCHAR
));
352 _tcsncpy_s(buf
, READ_DIR_CHANGE_BUFFER_SIZE
, pdi
->m_DirPath
, READ_DIR_CHANGE_BUFFER_SIZE
);
353 errno_t err
= _tcsncat_s(buf
+pdi
->m_DirPath
.GetLength(), READ_DIR_CHANGE_BUFFER_SIZE
-pdi
->m_DirPath
.GetLength(), pnotify
->FileName
, _TRUNCATE
);
354 if (err
== STRUNCATE
)
356 pnotify
= (PFILE_NOTIFY_INFORMATION
)((LPBYTE
)pnotify
+ nOffset
);
359 buf
[(pnotify
->FileNameLength
/sizeof(TCHAR
))+pdi
->m_DirPath
.GetLength()] = 0;
360 pnotify
= (PFILE_NOTIFY_INFORMATION
)((LPBYTE
)pnotify
+ nOffset
);
363 if ((pFound
= wcsstr(buf
, L
"\\tmp"))!=NULL
)
366 if (((*pFound
)=='\\')||((*pFound
)=='\0'))
368 if ((ULONG_PTR
)pnotify
- (ULONG_PTR
)pdi
->m_Buffer
> READ_DIR_CHANGE_BUFFER_SIZE
)
373 if ((pFound
= wcsstr(buf
, L
":\\RECYCLER\\"))!=NULL
)
375 if ((pFound
-buf
) < 5)
377 // a notification for the recycle bin - ignore it
378 if ((ULONG_PTR
)pnotify
- (ULONG_PTR
)pdi
->m_Buffer
> READ_DIR_CHANGE_BUFFER_SIZE
)
383 if ((pFound
= wcsstr(buf
, L
":\\$Recycle.Bin\\"))!=NULL
)
385 if ((pFound
-buf
) < 5)
387 // a notification for the recycle bin - ignore it
388 if ((ULONG_PTR
)pnotify
- (ULONG_PTR
)pdi
->m_Buffer
> READ_DIR_CHANGE_BUFFER_SIZE
)
393 if ((pFound
= wcsstr(buf
, L
".tmp"))!=NULL
)
395 // assume files with a .tmp extension are not versioned and interesting,
397 if ((ULONG_PTR
)pnotify
- (ULONG_PTR
)pdi
->m_Buffer
> READ_DIR_CHANGE_BUFFER_SIZE
)
401 bool isIndex
= false;
402 if ((pFound
= wcsstr(buf
, L
".git"))!=NULL
)
404 // omit repository data change except .git/index.lock- or .git/HEAD.lock-files
405 if ((ULONG_PTR
)pnotify
- (ULONG_PTR
)pdi
->m_Buffer
> READ_DIR_CHANGE_BUFFER_SIZE
)
408 if ((wcsstr(pFound
, L
"index.lock") != NULL
&& wcsstr(pFound
, L
"HEAD.lock") != NULL
) && pnotify
->Action
== FILE_ACTION_ADDED
)
410 m_FolderCrawler
->BlockPath(CTGitPath(buf
).GetContainingDirectory().GetContainingDirectory()); // optimize here, and use general BlockPath with priorities
413 else if ((wcsstr(pFound
, L
"index.lock") != NULL
&& wcsstr(pFound
, L
"HEAD.lock") != NULL
) && pnotify
->Action
== FILE_ACTION_REMOVED
)
416 m_FolderCrawler
->BlockPath(CTGitPath(buf
).GetContainingDirectory().GetContainingDirectory(), 1);
424 path
.SetFromWin(buf
);
425 if(!path
.HasAdminDir() && !isIndex
)
428 ATLTRACE(_T("change notification: %s\n"), buf
);
429 m_FolderCrawler
->AddPathForUpdate(CTGitPath(buf
));
431 if ((ULONG_PTR
)pnotify
- (ULONG_PTR
)pdi
->m_Buffer
> READ_DIR_CHANGE_BUFFER_SIZE
)
435 SecureZeroMemory(pdi
->m_Buffer
, sizeof(pdi
->m_Buffer
));
436 SecureZeroMemory(&pdi
->m_Overlapped
, sizeof(OVERLAPPED
));
437 if (!ReadDirectoryChangesW(pdi
->m_hDir
,
439 READ_DIR_CHANGE_BUFFER_SIZE
,
441 FILE_NOTIFY_CHANGE_FILE_NAME
| FILE_NOTIFY_CHANGE_DIR_NAME
| FILE_NOTIFY_CHANGE_LAST_WRITE
,
442 &numBytes
,// not used
444 NULL
)) //no completion routine!
446 // Since the call to ReadDirectoryChangesW failed, just
447 // wait a while. We don't want to have this thread
448 // running using 100% CPU if something goes completely
454 } // if (watchedPaths.GetCount())
457 } // while (m_bRunning)
460 void CDirectoryWatcher::ClearInfoMap()
462 if (watchInfoMap
.size()!=0)
464 AutoLocker
lock(m_critSec
);
465 for (std::map
<HANDLE
, CDirWatchInfo
*>::iterator I
= watchInfoMap
.begin(); I
!= watchInfoMap
.end(); ++I
)
467 CDirectoryWatcher::CDirWatchInfo
* info
= I
->second
;
472 watchInfoMap
.clear();
473 if (m_hCompPort
!= INVALID_HANDLE_VALUE
)
474 CloseHandle(m_hCompPort
);
475 m_hCompPort
= INVALID_HANDLE_VALUE
;
478 CTGitPath
CDirectoryWatcher::CloseInfoMap(HDEVNOTIFY hdev
)
481 if (watchInfoMap
.size() == 0)
483 AutoLocker
lock(m_critSec
);
484 for (std::map
<HANDLE
, CDirWatchInfo
*>::iterator I
= watchInfoMap
.begin(); I
!= watchInfoMap
.end(); ++I
)
486 CDirectoryWatcher::CDirWatchInfo
* info
= I
->second
;
487 if (info
->m_hDevNotify
== hdev
)
489 path
= info
->m_DirName
;
490 RemovePathAndChildren(path
);
493 info
->CloseDirectoryHandle();
495 watchInfoMap
.clear();
496 if (m_hCompPort
!= INVALID_HANDLE_VALUE
)
497 CloseHandle(m_hCompPort
);
498 m_hCompPort
= INVALID_HANDLE_VALUE
;
502 bool CDirectoryWatcher::CloseHandlesForPath(const CTGitPath
& path
)
504 if (watchInfoMap
.size() == 0)
506 AutoLocker
lock(m_critSec
);
507 for (std::map
<HANDLE
, CDirWatchInfo
*>::iterator I
= watchInfoMap
.begin(); I
!= watchInfoMap
.end(); ++I
)
509 CDirectoryWatcher::CDirWatchInfo
* info
= I
->second
;
510 CTGitPath p
= CTGitPath(info
->m_DirPath
);
511 if (path
.IsAncestorOf(p
))
513 RemovePathAndChildren(p
);
516 info
->CloseDirectoryHandle();
518 watchInfoMap
.clear();
519 if (m_hCompPort
!= INVALID_HANDLE_VALUE
)
520 CloseHandle(m_hCompPort
);
521 m_hCompPort
= INVALID_HANDLE_VALUE
;
525 CDirectoryWatcher::CDirWatchInfo::CDirWatchInfo(HANDLE hDir
, const CTGitPath
& DirectoryName
) :
527 m_DirName(DirectoryName
)
529 ATLASSERT( hDir
!= INVALID_HANDLE_VALUE
530 && !DirectoryName
.IsEmpty());
531 memset(&m_Overlapped
, 0, sizeof(m_Overlapped
));
532 m_DirPath
= m_DirName
.GetWinPathString();
533 if (m_DirPath
.GetAt(m_DirPath
.GetLength()-1) != '\\')
534 m_DirPath
+= _T("\\");
535 m_hDevNotify
= INVALID_HANDLE_VALUE
;
538 CDirectoryWatcher::CDirWatchInfo::~CDirWatchInfo()
540 CloseDirectoryHandle();
543 bool CDirectoryWatcher::CDirWatchInfo::CloseDirectoryHandle()
546 if( m_hDir
!= INVALID_HANDLE_VALUE
)
548 b
= !!CloseHandle(m_hDir
);
549 m_hDir
= INVALID_HANDLE_VALUE
;
551 if (m_hDevNotify
!= INVALID_HANDLE_VALUE
)
553 UnregisterDeviceNotification(m_hDevNotify
);