Skip to content

Commit c07d798

Browse files
Free handle on access denied in Windows
Access denied can happen when we try to delete the watched folder In windows server the folder is not removed until folder handler is released So the code will reopen the watched folder to release the handler and it will try to open it again
1 parent 687da68 commit c07d798

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)