Skip to content

Commit 7421623

Browse files
Merge pull request #154 from julianmesa-gitkraken/windows-free-handle-on-access-denied
Windows free handle on access denied
2 parents c4b4c8c + c07d798 commit 7421623

File tree

4 files changed

+75
-52
lines changed

4 files changed

+75
-52
lines changed

includes/win32/Controller.h

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,6 @@ class Controller {
1818
~Controller();
1919
private:
2020
std::unique_ptr<Watcher> mWatcher;
21-
22-
HANDLE openDirectory(const std::wstring &path);
23-
HANDLE mDirectoryHandle;
2421
};
2522

2623
#endif

includes/win32/Watcher.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
class Watcher
1515
{
1616
public:
17-
Watcher(std::shared_ptr<EventQueue> queue, HANDLE dirHandle, const std::wstring &path, bool pathWasNtPrefixed);
17+
Watcher(std::shared_ptr<EventQueue> queue, const std::wstring &path, bool pathWasNtPrefixed);
1818
~Watcher();
1919

2020
bool isRunning() const { return mRunning; }
@@ -29,18 +29,21 @@ class Watcher
2929
void setError(const std::string &error);
3030
void eventCallback(DWORD errorCode);
3131
void handleEvents();
32+
void reopenWathedFolder();
33+
HANDLE openDirectory(const std::wstring &path);
3234

3335
void resizeBuffers(std::size_t size);
3436

3537
std::string getUTF8Directory(std::wstring path) ;
3638

37-
std::wstring Watcher::getWatchedPath();
39+
std::wstring Watcher::getWatchedPathFromHandle();
3840
void Watcher::checkWatchedPath();
3941

4042
std::atomic<bool> mRunning;
4143
SingleshotSemaphore mHasStartedSemaphore;
4244
SingleshotSemaphore mIsRunningSemaphore;
4345
mutable std::mutex mErrorMutex;
46+
mutable std::mutex mHandleMutex;
4447
std::string mError;
4548
std::wstring mWatchedPath;
4649

src/win32/Controller.cpp

Lines changed: 3 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -39,55 +39,27 @@ static std::wstring convertMultiByteToWideChar(const std::string &multiByte) {
3939
return wideString;
4040
}
4141

42-
HANDLE Controller::openDirectory(const std::wstring &path) {
43-
return CreateFileW(
44-
path.data(),
45-
FILE_LIST_DIRECTORY,
46-
FILE_SHARE_READ
47-
| FILE_SHARE_WRITE
48-
| FILE_SHARE_DELETE,
49-
NULL,
50-
OPEN_EXISTING,
51-
FILE_FLAG_BACKUP_SEMANTICS
52-
| FILE_FLAG_OVERLAPPED,
53-
NULL
54-
);
55-
}
56-
57-
Controller::Controller(std::shared_ptr<EventQueue> queue, const std::string &path)
58-
: mDirectoryHandle(INVALID_HANDLE_VALUE)
59-
{
42+
Controller::Controller(std::shared_ptr<EventQueue> queue, const std::string &path) {
6043
auto widePath = convertMultiByteToWideChar(path);
6144
const bool isNt = isNtPath(widePath);
6245
if (!isNt) {
6346
// We convert to an NT Path to support paths > MAX_PATH
6447
widePath = prefixWithNtPath(widePath);
6548
}
66-
mDirectoryHandle = openDirectory(widePath);
6749

68-
if (mDirectoryHandle == INVALID_HANDLE_VALUE) {
69-
return;
70-
}
71-
72-
mWatcher.reset(new Watcher(queue, mDirectoryHandle, widePath, isNt));
50+
mWatcher.reset(new Watcher(queue, widePath, isNt));
7351
}
7452

7553
Controller::~Controller() {
7654
mWatcher.reset();
77-
CancelIo(mDirectoryHandle);
78-
CloseHandle(mDirectoryHandle);
79-
mDirectoryHandle = INVALID_HANDLE_VALUE;
8055
}
8156

8257
std::string Controller::getError() {
83-
if (mDirectoryHandle == INVALID_HANDLE_VALUE) {
84-
return "Failed to open directory";
85-
}
8658
return mWatcher->getError();
8759
}
8860

8961
bool Controller::hasErrored() {
90-
return mDirectoryHandle == INVALID_HANDLE_VALUE || !mWatcher->getError().empty();
62+
return !mWatcher->getError().empty();
9163
}
9264

9365
bool Controller::isWatching() {

src/win32/Watcher.cpp

Lines changed: 67 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -104,22 +104,31 @@ std::string getUTF8FileName(std::wstring path) {
104104
return utf8Directory;
105105
}
106106

107-
Watcher::Watcher(std::shared_ptr<EventQueue> queue, HANDLE dirHandle, const std::wstring &path, bool pathWasNtPrefixed)
107+
Watcher::Watcher(std::shared_ptr<EventQueue> queue, const std::wstring &path, bool pathWasNtPrefixed)
108108
: mRunning(false),
109-
mDirectoryHandle(dirHandle),
110109
mQueue(queue),
111110
mPath(path),
112111
mPathWasNtPrefixed(pathWasNtPrefixed)
113112
{
114-
ZeroMemory(&mOverlapped, sizeof(OVERLAPPED));
115-
mOverlapped.hEvent = this;
116-
resizeBuffers(1024 * 1024);
117-
mWatchedPath = getWatchedPath();
118-
start();
113+
mDirectoryHandle = openDirectory(mPath);
114+
if (mDirectoryHandle == INVALID_HANDLE_VALUE) {
115+
setError("Failed to open directory");
116+
} else {
117+
ZeroMemory(&mOverlapped, sizeof(OVERLAPPED));
118+
mOverlapped.hEvent = this;
119+
resizeBuffers(1024 * 1024);
120+
mWatchedPath = getWatchedPathFromHandle();
121+
start();
122+
}
119123
}
120124

121125
Watcher::~Watcher() {
122126
stop();
127+
std::lock_guard<std::mutex> lock(mHandleMutex);
128+
if (mDirectoryHandle != INVALID_HANDLE_VALUE) {
129+
CancelIo(mDirectoryHandle);
130+
CloseHandle(mDirectoryHandle);
131+
}
123132
}
124133

125134
void Watcher::resizeBuffers(std::size_t size) {
@@ -140,6 +149,7 @@ bool Watcher::pollDirectoryChanges() {
140149
return false;
141150
}
142151

152+
std::lock_guard<std::mutex> lock(mHandleMutex);
143153
if (!ReadDirectoryChangesW(
144154
mDirectoryHandle,
145155
mWriteBuffer.data(),
@@ -167,6 +177,39 @@ bool Watcher::pollDirectoryChanges() {
167177
return true;
168178
}
169179

180+
HANDLE Watcher::openDirectory(const std::wstring &path) {
181+
return CreateFileW(
182+
path.data(),
183+
FILE_LIST_DIRECTORY,
184+
FILE_SHARE_READ
185+
| FILE_SHARE_WRITE
186+
| FILE_SHARE_DELETE,
187+
NULL,
188+
OPEN_EXISTING,
189+
FILE_FLAG_BACKUP_SEMANTICS
190+
| FILE_FLAG_OVERLAPPED,
191+
NULL
192+
);
193+
}
194+
195+
void Watcher::reopenWathedFolder() {
196+
std::lock_guard<std::mutex> lock(mHandleMutex);
197+
{
198+
CancelIo(mDirectoryHandle);
199+
CloseHandle(mDirectoryHandle);
200+
mDirectoryHandle = openDirectory(mPath);
201+
if (mDirectoryHandle == INVALID_HANDLE_VALUE) {
202+
if(GetFileAttributesW(mWatchedPath.data()) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_PATH_NOT_FOUND) {
203+
setError("Service shutdown: root path changed (renamed or deleted)");
204+
} else {
205+
setError("Service shutdown unexpectedly");
206+
}
207+
return;
208+
}
209+
}
210+
pollDirectoryChanges();
211+
}
212+
170213
void Watcher::eventCallback(DWORD errorCode) {
171214
if (errorCode != ERROR_SUCCESS) {
172215
if (errorCode == ERROR_NOTIFY_ENUM_DIR) {
@@ -179,7 +222,10 @@ void Watcher::eventCallback(DWORD errorCode) {
179222
setError("failed resizing buffers for network traffic");
180223
}
181224
} else if (errorCode == ERROR_ACCESS_DENIED) {
182-
pollDirectoryChanges();
225+
// Access denied can happen when we try to delete the watched folder
226+
// In Windows Server 2019 and older the folder is not removed until folder handler is released
227+
// So the code will reopen the watched folder to release the handler and it will try to open it again
228+
reopenWathedFolder();
183229
} else {
184230
setError("Service shutdown unexpectedly");
185231
}
@@ -268,11 +314,13 @@ void Watcher::start() {
268314
}
269315

270316
void Watcher::stop() {
271-
mRunning = false;
272-
// schedule a NOOP APC to force the running loop in `Watcher::run()` to wake
273-
// up, notice the changed `mRunning` and properly terminate the running loop
274-
QueueUserAPC([](__in ULONG_PTR) {}, mRunner.native_handle(), (ULONG_PTR)this);
275-
mRunner.join();
317+
if (isRunning()) {
318+
mRunning = false;
319+
// schedule a NOOP APC to force the running loop in `Watcher::run()` to wake
320+
// up, notice the changed `mRunning` and properly terminate the running loop
321+
QueueUserAPC([](__in ULONG_PTR) {}, mRunner.native_handle(), (ULONG_PTR)this);
322+
mRunner.join();
323+
}
276324
}
277325

278326
void Watcher::setError(const std::string &error) {
@@ -298,15 +346,18 @@ std::string Watcher::getError() {
298346
return mError;
299347
}
300348

301-
std::wstring Watcher::getWatchedPath() {
349+
std::wstring Watcher::getWatchedPathFromHandle() {
350+
std::lock_guard<std::mutex> lock(mHandleMutex);
351+
if (mDirectoryHandle == INVALID_HANDLE_VALUE) {
352+
return NULL;
353+
}
302354
DWORD pathLen = GetFinalPathNameByHandleW(mDirectoryHandle, NULL, 0, VOLUME_NAME_NT);
303355
if (pathLen == 0) {
304356
setError("Service shutdown: root path changed (renamed or deleted)");
305357
return NULL;
306358
}
307359

308360
WCHAR* path = new WCHAR[pathLen];
309-
310361
if (GetFinalPathNameByHandleW(mDirectoryHandle, path, pathLen, VOLUME_NAME_NT) != pathLen - 1) {
311362
setError("Service shutdown: root path changed (renamed or deleted)");
312363
delete[] path;
@@ -319,7 +370,7 @@ std::wstring Watcher::getWatchedPath() {
319370
}
320371

321372
void Watcher::checkWatchedPath() {
322-
std::wstring path = getWatchedPath();
373+
std::wstring path = getWatchedPathFromHandle();
323374
if (!path.empty() && path.compare(mWatchedPath) != 0) {
324375
setError("Service shutdown: root path changed (renamed or deleted)");
325376
}

0 commit comments

Comments
 (0)