1 // TortoiseSVN - a Windows shell extension for easy version control
\r
3 // External Cache Copyright (C) 2007 - 2007 - Stefan Kueng
\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 "PathWatcher.h"
\r
23 CPathWatcher::CPathWatcher(void) : m_hCompPort(NULL)
\r
26 // enable the required privileges for this process
\r
28 LPCTSTR arPrivelegeNames[] = { SE_BACKUP_NAME,
\r
30 SE_CHANGE_NOTIFY_NAME
\r
33 for (int i=0; i<(sizeof(arPrivelegeNames)/sizeof(LPCTSTR)); ++i)
\r
36 if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken))
\r
38 TOKEN_PRIVILEGES tp = { 1 };
\r
40 if (LookupPrivilegeValue(NULL, arPrivelegeNames[i], &tp.Privileges[0].Luid))
\r
42 tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
\r
44 AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL);
\r
46 CloseHandle(hToken);
\r
50 unsigned int threadId;
\r
51 m_hThread = (HANDLE)_beginthreadex(NULL,0,ThreadEntry,this,0,&threadId);
\r
54 CPathWatcher::~CPathWatcher(void)
\r
56 InterlockedExchange(&m_bRunning, FALSE);
\r
57 if (m_hThread != INVALID_HANDLE_VALUE)
\r
59 CloseHandle(m_hThread);
\r
60 m_hThread = INVALID_HANDLE_VALUE;
\r
62 AutoLocker lock(m_critSec);
\r
66 void CPathWatcher::Stop()
\r
68 InterlockedExchange(&m_bRunning, FALSE);
\r
69 if (m_hCompPort != INVALID_HANDLE_VALUE)
\r
71 PostQueuedCompletionStatus(m_hCompPort, 0, NULL, NULL);
\r
73 if (m_hThread != INVALID_HANDLE_VALUE)
\r
74 CloseHandle(m_hThread);
\r
76 m_hThread = INVALID_HANDLE_VALUE;
\r
77 m_hCompPort = INVALID_HANDLE_VALUE;
\r
80 bool CPathWatcher::RemovePathAndChildren(const CTGitPath& path)
\r
82 bool bRemoved = false;
\r
83 AutoLocker lock(m_critSec);
\r
85 for (int i=0; i<watchedPaths.GetCount(); ++i)
\r
87 if (path.IsAncestorOf(watchedPaths[i]))
\r
89 watchedPaths.RemovePath(watchedPaths[i]);
\r
97 bool CPathWatcher::AddPath(const CTGitPath& path)
\r
99 AutoLocker lock(m_critSec);
\r
100 for (int i=0; i<watchedPaths.GetCount(); ++i)
\r
102 if (watchedPaths[i].IsAncestorOf(path))
\r
103 return false; // already watched (recursively)
\r
106 // now check if with the new path we might have a new root
\r
108 for (int i=0; i<watchedPaths.GetCount(); ++i)
\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
114 for (len = 0; len < minlen; ++len)
\r
116 if (watched.GetAt(len) != sPath.GetAt(len))
\r
118 if ((len > 1)&&(len < minlen))
\r
120 if (sPath.GetAt(len)=='\\')
\r
122 newroot = CTGitPath(sPath.Left(len));
\r
124 else if (watched.GetAt(len)=='\\')
\r
126 newroot = CTGitPath(watched.Left(len));
\r
134 if (sPath.GetLength() == minlen)
\r
136 if (watched.GetLength() > minlen)
\r
138 if (watched.GetAt(len)=='\\')
\r
142 else if (sPath.GetLength() == 3 && sPath[1] == ':')
\r
150 if (sPath.GetLength() > minlen)
\r
152 if (sPath.GetAt(len)=='\\')
\r
154 newroot = CTGitPath(watched);
\r
156 else if (watched.GetLength() == 3 && watched[1] == ':')
\r
158 newroot = CTGitPath(watched);
\r
164 if (!newroot.IsEmpty())
\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
172 ATLTRACE(_T("add path to watch %s\n"), path.GetWinPath());
\r
173 watchedPaths.AddPath(path);
\r
174 m_hCompPort = INVALID_HANDLE_VALUE;
\r
179 unsigned int CPathWatcher::ThreadEntry(void* pContext)
\r
181 ((CPathWatcher*)pContext)->WorkerThread();
\r
185 void CPathWatcher::WorkerThread()
\r
188 CDirWatchInfo * pdi = NULL;
\r
189 LPOVERLAPPED lpOverlapped;
\r
190 WCHAR buf[MAX_PATH*4] = {0};
\r
193 if (watchedPaths.GetCount())
\r
195 if (!GetQueuedCompletionStatus(m_hCompPort,
\r
201 // Error retrieving changes
\r
202 // Clear the list of watched objects and recreate that list
\r
206 AutoLocker lock(m_critSec);
\r
209 DWORD lasterr = GetLastError();
\r
210 if ((m_hCompPort != INVALID_HANDLE_VALUE)&&(lasterr!=ERROR_SUCCESS)&&(lasterr!=ERROR_INVALID_HANDLE))
\r
212 CloseHandle(m_hCompPort);
\r
213 m_hCompPort = INVALID_HANDLE_VALUE;
\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
218 m_hCompPort = NULL;
\r
219 for (int i=0; i<watchedPaths.GetCount(); ++i)
\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
226 FILE_FLAG_BACKUP_SEMANTICS | //required privileges: SE_BACKUP_NAME and SE_RESTORE_NAME.
\r
227 FILE_FLAG_OVERLAPPED,
\r
229 if (hDir == INVALID_HANDLE_VALUE)
\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
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
244 AutoLocker lock(m_critSec);
\r
248 watchedPaths.RemovePath(watchedPaths[i]);
\r
252 if (!ReadDirectoryChangesW(pDirInfo->m_hDir,
\r
253 pDirInfo->m_Buffer,
\r
254 READ_DIR_CHANGE_BUFFER_SIZE,
\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
261 AutoLocker lock(m_critSec);
\r
265 watchedPaths.RemovePath(watchedPaths[i]);
\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
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
285 goto continuewatching;
\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
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
299 pnotify = (PFILE_NOTIFY_INFORMATION)((LPBYTE)pnotify + nOffset);
\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
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
314 READ_DIR_CHANGE_BUFFER_SIZE,
\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
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
329 }// if (watchedPaths.GetCount())
\r
332 }// while (m_bRunning)
\r
335 void CPathWatcher::ClearInfoMap()
\r
337 if (watchInfoMap.size()!=0)
\r
339 AutoLocker lock(m_critSec);
\r
340 for (std::map<HANDLE, CDirWatchInfo *>::iterator I = watchInfoMap.begin(); I != watchInfoMap.end(); ++I)
\r
342 CPathWatcher::CDirWatchInfo * info = I->second;
\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
353 CPathWatcher::CDirWatchInfo::CDirWatchInfo(HANDLE hDir, const CTGitPath& DirectoryName) :
\r
355 m_DirName(DirectoryName)
\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
365 CPathWatcher::CDirWatchInfo::~CDirWatchInfo()
\r
367 CloseDirectoryHandle();
\r
370 bool CPathWatcher::CDirWatchInfo::CloseDirectoryHandle()
\r
373 if( m_hDir != INVALID_HANDLE_VALUE )
\r
375 b = !!CloseHandle(m_hDir);
\r
376 m_hDir = INVALID_HANDLE_VALUE;
\r