1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2013, 2016 - TortoiseGit
4 // External Cache Copyright (C) 2007-2012 - TortoiseSVN
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 "PathWatcher.h"
24 CPathWatcher::CPathWatcher(void)
25 : m_hCompPort(nullptr)
27 , m_bLimitReached(false)
29 // enable the required privileges for this process
30 LPCTSTR arPrivelegeNames
[] = { SE_BACKUP_NAME
,
35 for (int i
=0; i
<(sizeof(arPrivelegeNames
)/sizeof(LPCTSTR
)); ++i
)
37 CAutoGeneralHandle hToken
;
38 if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES
, hToken
.GetPointer()))
40 TOKEN_PRIVILEGES tp
= { 1 };
42 if (LookupPrivilegeValue(nullptr, arPrivelegeNames
[i
], &tp
.Privileges
[0].Luid
))
44 tp
.Privileges
[0].Attributes
= SE_PRIVILEGE_ENABLED
;
46 AdjustTokenPrivileges(hToken
, FALSE
, &tp
, sizeof(tp
), nullptr, nullptr);
51 unsigned int threadId
= 0;
52 m_hThread
= (HANDLE
)_beginthreadex(nullptr, 0, ThreadEntry
, this, 0, &threadId
);
55 CPathWatcher::~CPathWatcher(void)
58 AutoLocker
lock(m_critSec
);
62 void CPathWatcher::Stop()
64 InterlockedExchange(&m_bRunning
, FALSE
);
67 PostQueuedCompletionStatus(m_hCompPort
, 0, NULL
, nullptr);
68 m_hCompPort
.CloseHandle();
73 // the background thread sleeps for 200ms,
74 // so lets wait for it to finish for 1000 ms.
76 WaitForSingleObject(m_hThread
, 1000);
77 m_hThread
.CloseHandle();
81 bool CPathWatcher::RemovePathAndChildren(const CTGitPath
& path
)
83 bool bRemoved
= false;
84 AutoLocker
lock(m_critSec
);
86 for (int i
=0; i
<watchedPaths
.GetCount(); ++i
)
88 if (path
.IsAncestorOf(watchedPaths
[i
]))
90 watchedPaths
.RemovePath(watchedPaths
[i
]);
98 bool CPathWatcher::AddPath(const CTGitPath
& path
)
100 AutoLocker
lock(m_critSec
);
101 for (int i
=0; i
<watchedPaths
.GetCount(); ++i
)
103 if (watchedPaths
[i
].IsAncestorOf(path
))
104 return false; // already watched (recursively)
107 // now check if with the new path we might have a new root
109 for (int i
=0; i
<watchedPaths
.GetCount(); ++i
)
111 const CString
& watched
= watchedPaths
[i
].GetWinPathString();
112 const CString
& sPath
= path
.GetWinPathString();
113 int minlen
= min(sPath
.GetLength(), watched
.GetLength());
115 for (len
= 0; len
< minlen
; ++len
)
117 if (watched
.GetAt(len
) != sPath
.GetAt(len
))
119 if ((len
> 1)&&(len
< minlen
))
121 if (sPath
.GetAt(len
)=='\\')
123 newroot
= CTGitPath(sPath
.Left(len
));
125 else if (watched
.GetAt(len
)=='\\')
127 newroot
= CTGitPath(watched
.Left(len
));
135 if (sPath
.GetLength() == minlen
)
137 if (watched
.GetLength() > minlen
)
139 if (watched
.GetAt(len
)=='\\')
143 else if (sPath
.GetLength() == 3 && sPath
[1] == ':')
151 if (sPath
.GetLength() > minlen
)
153 if (sPath
.GetAt(len
)=='\\')
155 newroot
= CTGitPath(watched
);
157 else if (watched
.GetLength() == 3 && watched
[1] == ':')
159 newroot
= CTGitPath(watched
);
165 if (!newroot
.IsEmpty())
167 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__
) L
": add path to watch %s\n", newroot
.GetWinPath());
168 watchedPaths
.AddPath(newroot
);
169 watchedPaths
.RemoveChildren();
170 m_hCompPort
.CloseHandle();
173 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__
) L
": add path to watch %s\n", path
.GetWinPath());
174 watchedPaths
.AddPath(path
);
175 m_hCompPort
.CloseHandle();
180 unsigned int CPathWatcher::ThreadEntry(void* pContext
)
182 reinterpret_cast<CPathWatcher
*>(pContext
)->WorkerThread();
186 void CPathWatcher::WorkerThread()
189 CDirWatchInfo
* pdi
= nullptr;
190 LPOVERLAPPED lpOverlapped
;
191 const int bufferSize
= MAX_PATH
* 4;
192 TCHAR buf
[bufferSize
] = {0};
195 if (!watchedPaths
.IsEmpty())
197 if (!m_hCompPort
|| !GetQueuedCompletionStatus(m_hCompPort
,
203 // Error retrieving changes
204 // Clear the list of watched objects and recreate that list
208 AutoLocker
lock(m_critSec
);
211 DWORD lasterr
= GetLastError();
212 if ((m_hCompPort
)&&(lasterr
!=ERROR_SUCCESS
)&&(lasterr
!=ERROR_INVALID_HANDLE
))
214 m_hCompPort
.CloseHandle();
216 for (int i
=0; i
<watchedPaths
.GetCount(); ++i
)
218 CAutoFile hDir
= CreateFile(watchedPaths
[i
].GetWinPath(),
220 FILE_SHARE_READ
| FILE_SHARE_WRITE
| FILE_SHARE_DELETE
,
221 nullptr, //security attributes
223 FILE_FLAG_BACKUP_SEMANTICS
| //required privileges: SE_BACKUP_NAME and SE_RESTORE_NAME.
224 FILE_FLAG_OVERLAPPED
,
228 // this could happen if a watched folder has been removed/renamed
229 m_hCompPort
.CloseHandle();
230 AutoLocker
lock(m_critSec
);
231 watchedPaths
.RemovePath(watchedPaths
[i
]);
236 CDirWatchInfo
* pDirInfo
= new CDirWatchInfo(hDir
.Detach(), watchedPaths
[i
]); // the new CDirWatchInfo object owns the handle now
237 m_hCompPort
= CreateIoCompletionPort(pDirInfo
->m_hDir
, m_hCompPort
, (ULONG_PTR
)pDirInfo
, 0);
240 AutoLocker
lock(m_critSec
);
244 watchedPaths
.RemovePath(watchedPaths
[i
]);
248 if (!ReadDirectoryChangesW(pDirInfo
->m_hDir
,
252 FILE_NOTIFY_CHANGE_FILE_NAME
| FILE_NOTIFY_CHANGE_DIR_NAME
| FILE_NOTIFY_CHANGE_LAST_WRITE
,
253 &numBytes
,// not used
254 &pDirInfo
->m_Overlapped
,
255 nullptr)) //no completion routine!
257 AutoLocker
lock(m_critSec
);
261 watchedPaths
.RemovePath(watchedPaths
[i
]);
265 AutoLocker
lock(m_critSec
);
266 watchInfoMap
[pDirInfo
->m_hDir
] = pDirInfo
;
267 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__
) L
": watching path %s\n", pDirInfo
->m_DirName
.GetWinPath());
274 // NOTE: the longer this code takes to execute until ReadDirectoryChangesW
275 // is called again, the higher the chance that we miss some
276 // changes in the file system!
281 goto continuewatching
;
283 PFILE_NOTIFY_INFORMATION pnotify
= (PFILE_NOTIFY_INFORMATION
)pdi
->m_Buffer
;
284 if ((ULONG_PTR
)pnotify
- (ULONG_PTR
)pdi
->m_Buffer
> bufferSize
)
285 goto continuewatching
;
286 DWORD nOffset
= pnotify
->NextEntryOffset
;
289 nOffset
= pnotify
->NextEntryOffset
;
290 SecureZeroMemory(buf
, bufferSize
*sizeof(TCHAR
));
291 wcsncpy_s(buf
, bufferSize
, pdi
->m_DirPath
, bufferSize
- 1);
292 errno_t err
= wcsncat_s(buf
+ pdi
->m_DirPath
.GetLength(), bufferSize
- pdi
->m_DirPath
.GetLength(), pnotify
->FileName
, min(bufferSize
- pdi
->m_DirPath
.GetLength(), int(pnotify
->FileNameLength
/ sizeof(TCHAR
))));
293 if (err
== STRUNCATE
)
295 pnotify
= (PFILE_NOTIFY_INFORMATION
)((LPBYTE
)pnotify
+ nOffset
);
298 buf
[min(bufferSize
- 1, pdi
->m_DirPath
.GetLength() + (pnotify
->FileNameLength
/sizeof(WCHAR
)))] = L
'\0';
299 pnotify
= (PFILE_NOTIFY_INFORMATION
)((LPBYTE
)pnotify
+ nOffset
);
300 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__
) L
": change notification: %s\n", buf
);
302 AutoLocker
lock(m_critSec
);
303 if (m_changedPaths
.GetCount() < MAX_CHANGED_PATHS
)
304 m_changedPaths
.AddPath(CTGitPath(buf
));
306 m_bLimitReached
= true;
308 if ((ULONG_PTR
)pnotify
- (ULONG_PTR
)pdi
->m_Buffer
> bufferSize
)
313 AutoLocker
lock(m_critSec
);
314 m_changedPaths
.RemoveDuplicates();
316 SecureZeroMemory(pdi
->m_Buffer
, sizeof(pdi
->m_Buffer
));
317 SecureZeroMemory(&pdi
->m_Overlapped
, sizeof(OVERLAPPED
));
318 if (!ReadDirectoryChangesW(pdi
->m_hDir
,
322 FILE_NOTIFY_CHANGE_FILE_NAME
| FILE_NOTIFY_CHANGE_DIR_NAME
| FILE_NOTIFY_CHANGE_LAST_WRITE
,
323 &numBytes
,// not used
325 nullptr)) //no completion routine!
327 // Since the call to ReadDirectoryChangesW failed, just
328 // wait a while. We don't want to have this thread
329 // running using 100% CPU if something goes completely
335 }// if (!watchedPaths.IsEmpty())
338 }// while (m_bRunning)
341 void CPathWatcher::ClearInfoMap()
343 if (!watchInfoMap
.empty())
345 AutoLocker
lock(m_critSec
);
346 for (std::map
<HANDLE
, CDirWatchInfo
*>::iterator I
= watchInfoMap
.begin(); I
!= watchInfoMap
.end(); ++I
)
348 CPathWatcher::CDirWatchInfo
* info
= I
->second
;
353 watchInfoMap
.clear();
354 m_hCompPort
.CloseHandle();
357 CPathWatcher::CDirWatchInfo::CDirWatchInfo(HANDLE hDir
, const CTGitPath
& DirectoryName
)
358 : m_hDir(std::move(hDir
))
359 , m_DirName(DirectoryName
)
361 ATLASSERT( hDir
&& !DirectoryName
.IsEmpty());
363 memset(&m_Overlapped
, 0, sizeof(m_Overlapped
));
364 m_DirPath
= m_DirName
.GetWinPathString();
365 if (m_DirPath
.GetAt(m_DirPath
.GetLength()-1) != '\\')
369 CPathWatcher::CDirWatchInfo::~CDirWatchInfo()
371 CloseDirectoryHandle();
374 bool CPathWatcher::CDirWatchInfo::CloseDirectoryHandle()
376 return m_hDir
.CloseHandle();