1 // TortoiseSVN - a Windows shell extension for easy version control
3 // External Cache Copyright (C) 2007 - 2007 - Stefan Kueng
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software Foundation,
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 #include "PathWatcher.h"
23 CPathWatcher::CPathWatcher(void) : m_hCompPort(NULL
)
26 // enable the required privileges for this process
27 LPCTSTR arPrivelegeNames
[] = { SE_BACKUP_NAME
,
32 for (int i
=0; i
<(sizeof(arPrivelegeNames
)/sizeof(LPCTSTR
)); ++i
)
35 if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES
, &hToken
))
37 TOKEN_PRIVILEGES tp
= { 1 };
39 if (LookupPrivilegeValue(NULL
, arPrivelegeNames
[i
], &tp
.Privileges
[0].Luid
))
41 tp
.Privileges
[0].Attributes
= SE_PRIVILEGE_ENABLED
;
43 AdjustTokenPrivileges(hToken
, FALSE
, &tp
, sizeof(tp
), NULL
, NULL
);
49 unsigned int threadId
;
50 m_hThread
= (HANDLE
)_beginthreadex(NULL
,0,ThreadEntry
,this,0,&threadId
);
53 CPathWatcher::~CPathWatcher(void)
55 InterlockedExchange(&m_bRunning
, FALSE
);
56 if (m_hThread
!= INVALID_HANDLE_VALUE
)
58 CloseHandle(m_hThread
);
59 m_hThread
= INVALID_HANDLE_VALUE
;
61 AutoLocker
lock(m_critSec
);
65 void CPathWatcher::Stop()
67 InterlockedExchange(&m_bRunning
, FALSE
);
68 if (m_hCompPort
!= INVALID_HANDLE_VALUE
)
70 PostQueuedCompletionStatus(m_hCompPort
, 0, NULL
, NULL
);
73 if (m_hThread
!= INVALID_HANDLE_VALUE
)
75 if( WaitForSingleObject(m_hThread
, 1000) != WAIT_OBJECT_0
)
77 TerminateThread(m_hThread
, (DWORD
)-1);
79 CloseHandle(m_hThread
);
81 m_hThread
= INVALID_HANDLE_VALUE
;
82 m_hCompPort
= INVALID_HANDLE_VALUE
;
85 bool CPathWatcher::RemovePathAndChildren(const CTGitPath
& path
)
87 bool bRemoved
= false;
88 AutoLocker
lock(m_critSec
);
90 for (int i
=0; i
<watchedPaths
.GetCount(); ++i
)
92 if (path
.IsAncestorOf(watchedPaths
[i
]))
94 watchedPaths
.RemovePath(watchedPaths
[i
]);
102 bool CPathWatcher::AddPath(const CTGitPath
& path
)
104 AutoLocker
lock(m_critSec
);
105 for (int i
=0; i
<watchedPaths
.GetCount(); ++i
)
107 if (watchedPaths
[i
].IsAncestorOf(path
))
108 return false; // already watched (recursively)
111 // now check if with the new path we might have a new root
113 for (int i
=0; i
<watchedPaths
.GetCount(); ++i
)
115 const CString
& watched
= watchedPaths
[i
].GetWinPathString();
116 const CString
& sPath
= path
.GetWinPathString();
117 int minlen
= min(sPath
.GetLength(), watched
.GetLength());
119 for (len
= 0; len
< minlen
; ++len
)
121 if (watched
.GetAt(len
) != sPath
.GetAt(len
))
123 if ((len
> 1)&&(len
< minlen
))
125 if (sPath
.GetAt(len
)=='\\')
127 newroot
= CTGitPath(sPath
.Left(len
));
129 else if (watched
.GetAt(len
)=='\\')
131 newroot
= CTGitPath(watched
.Left(len
));
139 if (sPath
.GetLength() == minlen
)
141 if (watched
.GetLength() > minlen
)
143 if (watched
.GetAt(len
)=='\\')
147 else if (sPath
.GetLength() == 3 && sPath
[1] == ':')
155 if (sPath
.GetLength() > minlen
)
157 if (sPath
.GetAt(len
)=='\\')
159 newroot
= CTGitPath(watched
);
161 else if (watched
.GetLength() == 3 && watched
[1] == ':')
163 newroot
= CTGitPath(watched
);
169 if (!newroot
.IsEmpty())
171 ATLTRACE(_T("add path to watch %s\n"), newroot
.GetWinPath());
172 watchedPaths
.AddPath(newroot
);
173 watchedPaths
.RemoveChildren();
174 m_hCompPort
= INVALID_HANDLE_VALUE
;
177 ATLTRACE(_T("add path to watch %s\n"), path
.GetWinPath());
178 watchedPaths
.AddPath(path
);
179 m_hCompPort
= INVALID_HANDLE_VALUE
;
184 unsigned int CPathWatcher::ThreadEntry(void* pContext
)
186 ((CPathWatcher
*)pContext
)->WorkerThread();
190 void CPathWatcher::WorkerThread()
193 CDirWatchInfo
* pdi
= NULL
;
194 LPOVERLAPPED lpOverlapped
;
195 WCHAR buf
[MAX_PATH
*4] = {0};
198 if (watchedPaths
.GetCount())
200 if (!GetQueuedCompletionStatus(m_hCompPort
,
206 // Error retrieving changes
207 // Clear the list of watched objects and recreate that list
211 AutoLocker
lock(m_critSec
);
214 DWORD lasterr
= GetLastError();
215 if ((m_hCompPort
!= INVALID_HANDLE_VALUE
)&&(lasterr
!=ERROR_SUCCESS
)&&(lasterr
!=ERROR_INVALID_HANDLE
))
217 CloseHandle(m_hCompPort
);
218 m_hCompPort
= INVALID_HANDLE_VALUE
;
220 // Since we pass m_hCompPort to CreateIoCompletionPort, we
221 // have to set this to NULL to have that API create a new
224 for (int i
=0; i
<watchedPaths
.GetCount(); ++i
)
226 HANDLE hDir
= CreateFile(watchedPaths
[i
].GetWinPath(),
228 FILE_SHARE_READ
| FILE_SHARE_WRITE
| FILE_SHARE_DELETE
,
229 NULL
, //security attributes
231 FILE_FLAG_BACKUP_SEMANTICS
| //required privileges: SE_BACKUP_NAME and SE_RESTORE_NAME.
232 FILE_FLAG_OVERLAPPED
,
234 if (hDir
== INVALID_HANDLE_VALUE
)
236 // this could happen if a watched folder has been removed/renamed
237 CloseHandle(m_hCompPort
);
238 m_hCompPort
= INVALID_HANDLE_VALUE
;
239 AutoLocker
lock(m_critSec
);
240 watchedPaths
.RemovePath(watchedPaths
[i
]);
245 CDirWatchInfo
* pDirInfo
= new CDirWatchInfo(hDir
, watchedPaths
[i
]);
246 m_hCompPort
= CreateIoCompletionPort(hDir
, m_hCompPort
, (ULONG_PTR
)pDirInfo
, 0);
247 if (m_hCompPort
== NULL
)
249 AutoLocker
lock(m_critSec
);
253 watchedPaths
.RemovePath(watchedPaths
[i
]);
257 if (!ReadDirectoryChangesW(pDirInfo
->m_hDir
,
259 READ_DIR_CHANGE_BUFFER_SIZE
,
261 FILE_NOTIFY_CHANGE_FILE_NAME
| FILE_NOTIFY_CHANGE_DIR_NAME
| FILE_NOTIFY_CHANGE_LAST_WRITE
,
262 &numBytes
,// not used
263 &pDirInfo
->m_Overlapped
,
264 NULL
)) //no completion routine!
266 AutoLocker
lock(m_critSec
);
270 watchedPaths
.RemovePath(watchedPaths
[i
]);
274 AutoLocker
lock(m_critSec
);
275 watchInfoMap
[pDirInfo
->m_hDir
] = pDirInfo
;
276 ATLTRACE(_T("watching path %s\n"), pDirInfo
->m_DirName
.GetWinPath());
283 // NOTE: the longer this code takes to execute until ReadDirectoryChangesW
284 // is called again, the higher the chance that we miss some
285 // changes in the file system!
290 goto continuewatching
;
292 PFILE_NOTIFY_INFORMATION pnotify
= (PFILE_NOTIFY_INFORMATION
)pdi
->m_Buffer
;
293 if ((ULONG_PTR
)pnotify
- (ULONG_PTR
)pdi
->m_Buffer
> READ_DIR_CHANGE_BUFFER_SIZE
)
294 goto continuewatching
;
295 DWORD nOffset
= pnotify
->NextEntryOffset
;
298 nOffset
= pnotify
->NextEntryOffset
;
299 SecureZeroMemory(buf
, MAX_PATH
*4*sizeof(TCHAR
));
300 _tcsncpy_s(buf
, MAX_PATH
*4, pdi
->m_DirPath
, MAX_PATH
*4);
301 errno_t err
= _tcsncat_s(buf
+pdi
->m_DirPath
.GetLength(), (MAX_PATH
*4)-pdi
->m_DirPath
.GetLength(), pnotify
->FileName
, _TRUNCATE
);
302 if (err
== STRUNCATE
)
304 pnotify
= (PFILE_NOTIFY_INFORMATION
)((LPBYTE
)pnotify
+ nOffset
);
307 buf
[min(MAX_PATH
*4-1, pdi
->m_DirPath
.GetLength()+(pnotify
->FileNameLength
/sizeof(WCHAR
)))] = 0;
308 pnotify
= (PFILE_NOTIFY_INFORMATION
)((LPBYTE
)pnotify
+ nOffset
);
309 ATLTRACE(_T("change notification: %s\n"), buf
);
310 m_changedPaths
.AddPath(CTGitPath(buf
));
311 if ((ULONG_PTR
)pnotify
- (ULONG_PTR
)pdi
->m_Buffer
> READ_DIR_CHANGE_BUFFER_SIZE
)
315 SecureZeroMemory(pdi
->m_Buffer
, sizeof(pdi
->m_Buffer
));
316 SecureZeroMemory(&pdi
->m_Overlapped
, sizeof(OVERLAPPED
));
317 if (!ReadDirectoryChangesW(pdi
->m_hDir
,
319 READ_DIR_CHANGE_BUFFER_SIZE
,
321 FILE_NOTIFY_CHANGE_FILE_NAME
| FILE_NOTIFY_CHANGE_DIR_NAME
| FILE_NOTIFY_CHANGE_LAST_WRITE
,
322 &numBytes
,// not used
324 NULL
)) //no completion routine!
326 // Since the call to ReadDirectoryChangesW failed, just
327 // wait a while. We don't want to have this thread
328 // running using 100% CPU if something goes completely
334 }// if (watchedPaths.GetCount())
337 }// while (m_bRunning)
340 void CPathWatcher::ClearInfoMap()
342 if (watchInfoMap
.size()!=0)
344 AutoLocker
lock(m_critSec
);
345 for (std::map
<HANDLE
, CDirWatchInfo
*>::iterator I
= watchInfoMap
.begin(); I
!= watchInfoMap
.end(); ++I
)
347 CPathWatcher::CDirWatchInfo
* info
= I
->second
;
352 watchInfoMap
.clear();
353 if (m_hCompPort
!= INVALID_HANDLE_VALUE
)
354 CloseHandle(m_hCompPort
);
355 m_hCompPort
= INVALID_HANDLE_VALUE
;
358 CPathWatcher::CDirWatchInfo::CDirWatchInfo(HANDLE hDir
, const CTGitPath
& DirectoryName
) :
360 m_DirName(DirectoryName
)
362 ATLASSERT( hDir
!= INVALID_HANDLE_VALUE
363 && !DirectoryName
.IsEmpty());
364 memset(&m_Overlapped
, 0, sizeof(m_Overlapped
));
365 m_DirPath
= m_DirName
.GetWinPathString();
366 if (m_DirPath
.GetAt(m_DirPath
.GetLength()-1) != '\\')
367 m_DirPath
+= _T("\\");
370 CPathWatcher::CDirWatchInfo::~CDirWatchInfo()
372 CloseDirectoryHandle();
375 bool CPathWatcher::CDirWatchInfo::CloseDirectoryHandle()
378 if( m_hDir
!= INVALID_HANDLE_VALUE
)
380 b
= !!CloseHandle(m_hDir
);
381 m_hDir
= INVALID_HANDLE_VALUE
;