ReactOS 0.4.15-dev-8102-g108db8f
CDirectoryWatcher.cpp
Go to the documentation of this file.
1/*
2 * PROJECT: shell32
3 * LICENSE: LGPL-2.1-or-later (https://spdx.org/licenses/LGPL-2.1-or-later)
4 * PURPOSE: Shell change notification
5 * COPYRIGHT: Copyright 2020 Katayama Hirofumi MZ (katayama.hirofumi.mz@gmail.com)
6 */
7#include "shelldesktop.h"
8#include "CDirectoryWatcher.h"
9#include <process.h> // for _beginthreadex
10#include <assert.h> // for assert
11
13
14// Notify filesystem change
15static inline void
17{
19}
20
21// The handle of the APC thread
23
24// Terminate now?
26
27// the buffer for ReadDirectoryChangesW
28#define BUFFER_SIZE 0x1000
30
31// The APC thread function for directory watch
33{
35 {
36#if 1 // FIXME: This is a HACK
38#else
40#endif
41 }
42 return 0;
43}
44
45// The APC procedure to add a CDirectoryWatcher and start the directory watch
47{
48 CDirectoryWatcher *pDirectoryWatcher = (CDirectoryWatcher *)Parameter;
49 assert(pDirectoryWatcher != NULL);
50
51 pDirectoryWatcher->RestartWatching();
52}
53
54// The APC procedure to request termination of a CDirectoryWatcher
56{
57 CDirectoryWatcher *pDirectoryWatcher = (CDirectoryWatcher *)Parameter;
58 assert(pDirectoryWatcher != NULL);
59
60 pDirectoryWatcher->QuitWatching();
61}
62
63// The APC procedure to request termination of all the directory watches
65{
69}
70
72 : m_fDead(FALSE)
73 , m_fRecursive(fSubTree)
74 , m_dir_list(pszDirectoryPath, fSubTree)
75{
76 TRACE("CDirectoryWatcher::CDirectoryWatcher: %p, '%S'\n", this, pszDirectoryPath);
77
78 lstrcpynW(m_szDirectoryPath, pszDirectoryPath, MAX_PATH);
79
80 // open the directory to watch changes (for ReadDirectoryChangesW)
85 NULL);
86}
87
88/*static*/ CDirectoryWatcher *
89CDirectoryWatcher::Create(LPCWSTR pszDirectoryPath, BOOL fSubTree)
90{
91 WCHAR szFullPath[MAX_PATH];
92 GetFullPathNameW(pszDirectoryPath, _countof(szFullPath), szFullPath, NULL);
93
94 CDirectoryWatcher *pDirectoryWatcher = new CDirectoryWatcher(szFullPath, fSubTree);
95 if (pDirectoryWatcher->m_hDirectory == INVALID_HANDLE_VALUE)
96 {
97 ERR("CreateFileW failed\n");
98 delete pDirectoryWatcher;
99 pDirectoryWatcher = NULL;
100 }
101 return pDirectoryWatcher;
102}
103
105{
106 TRACE("CDirectoryWatcher::~CDirectoryWatcher: %p, '%S'\n", this, m_szDirectoryPath);
107
110}
111
112// convert the file action to an event
113static DWORD
115{
116 switch (Action)
117 {
119 return (fDir ? SHCNE_MKDIR : SHCNE_CREATE);
121 return (fDir ? SHCNE_RMDIR : SHCNE_DELETE);
123 return (fDir ? SHCNE_UPDATEDIR : SHCNE_UPDATEITEM);
125 break;
127 return (fDir ? SHCNE_RENAMEFOLDER : SHCNE_RENAMEITEM);
128 default:
129 break;
130 }
131 return 0;
132}
133
134// Notify a filesystem notification using pDirectoryWatcher.
136{
139 DWORD dwEvent, cbName;
140 BOOL fDir;
141 TRACE("CDirectoryWatcher::ProcessNotification: enter\n");
142
143 // for each entry in s_buffer
144 szPath[0] = szTempPath[0] = 0;
145 for (;;)
146 {
147 // get name (relative from m_szDirectoryPath)
148 cbName = pInfo->FileNameLength;
149 if (sizeof(szName) - sizeof(UNICODE_NULL) < cbName)
150 {
151 ERR("pInfo->FileName is longer than szName\n");
152 break;
153 }
154 // NOTE: FILE_NOTIFY_INFORMATION.FileName is not null-terminated.
155 ZeroMemory(szName, sizeof(szName));
156 CopyMemory(szName, pInfo->FileName, cbName);
157
158 // get full path
161
162 // convert to long pathname if it contains '~'
163 if (StrChrW(szPath, L'~') != NULL)
164 {
167 {
169 }
170 }
171
172 // convert action to event
173 fDir = PathIsDirectoryW(szPath);
174 dwEvent = ConvertActionToEvent(pInfo->Action, fDir);
175
176 // convert SHCNE_DELETE to SHCNE_RMDIR if the path is a directory
177 if (!fDir && (dwEvent == SHCNE_DELETE) && m_dir_list.ContainsPath(szPath))
178 {
179 fDir = TRUE;
180 dwEvent = SHCNE_RMDIR;
181 }
182
183 // update m_dir_list
184 switch (dwEvent)
185 {
186 case SHCNE_MKDIR:
188 dwEvent = 0;
189 break;
190 case SHCNE_CREATE:
192 dwEvent = 0;
193 break;
196 dwEvent = 0;
197 break;
198 case SHCNE_RENAMEITEM:
200 dwEvent = 0;
201 break;
202 case SHCNE_RMDIR:
204 dwEvent = 0;
205 break;
206 case SHCNE_DELETE:
208 dwEvent = 0;
209 break;
210 }
211
212 if (dwEvent != 0)
213 {
214 // notify
217 else
219 }
220 else if (pInfo->Action == FILE_ACTION_RENAMED_OLD_NAME)
221 {
222 // save path for next FILE_ACTION_RENAMED_NEW_NAME
224 }
225
226 if (pInfo->NextEntryOffset == 0)
227 break; // there is no next entry
228
229 // go next entry
230 pInfo = (PFILE_NOTIFY_INFORMATION)((LPBYTE)pInfo + pInfo->NextEntryOffset);
231 }
232
233 TRACE("CDirectoryWatcher::ProcessNotification: leave\n");
234}
235
236void CDirectoryWatcher::ReadCompletion(DWORD dwErrorCode, DWORD dwNumberOfBytesTransfered)
237{
238 // If the FSD doesn't support directory change notifications, there's no
239 // no need to retry and requeue notification
240 if (dwErrorCode == ERROR_INVALID_FUNCTION)
241 {
242 ERR("ERROR_INVALID_FUNCTION\n");
243 return;
244 }
245
246 // Also, if the notify operation was canceled (like, user moved to another
247 // directory), then, don't requeue notification.
248 if (dwErrorCode == ERROR_OPERATION_ABORTED)
249 {
250 TRACE("ERROR_OPERATION_ABORTED\n");
251 if (IsDead())
252 delete this;
253 return;
254 }
255
256 // is this watch dead?
257 if (IsDead())
258 {
259 TRACE("IsDead()\n");
260 delete this;
261 return;
262 }
263
264 // This likely means overflow, so force whole directory refresh.
265 if (dwNumberOfBytesTransfered == 0)
266 {
267 // do notify a SHCNE_UPDATEDIR
269 }
270 else
271 {
272 // do notify
274 }
275
276 // restart a watch
278}
279
280// The completion routine of ReadDirectoryChangesW.
281static void CALLBACK
283 DWORD dwNumberOfBytesTransfered,
285{
286 // MSDN: The hEvent member of the OVERLAPPED structure is not used by the
287 // system in this case, so you can use it yourself. We do just this, storing
288 // a pointer to the working struct in the overlapped structure.
289 CDirectoryWatcher *pDirectoryWatcher = (CDirectoryWatcher *)lpOverlapped->hEvent;
290 assert(pDirectoryWatcher != NULL);
291
292 pDirectoryWatcher->ReadCompletion(dwErrorCode, dwNumberOfBytesTransfered);
293}
294
295// convert events to notification filter
296static DWORD
298{
299 // FIXME
304}
305
306// Restart a watch by using ReadDirectoryChangesW function
308{
309 assert(this != NULL);
310
311 if (IsDead())
312 {
313 delete this;
314 return FALSE; // the watch is dead
315 }
316
317 // initialize the buffer and the overlapped
318 ZeroMemory(s_buffer, sizeof(s_buffer));
320 m_overlapped.hEvent = (HANDLE)this;
321
322 // start the directory watch
325 m_fRecursive, dwFilter, NULL,
327 {
328 ERR("ReadDirectoryChangesW for '%S' failed (error: %ld)\n",
330 return FALSE; // failure
331 }
332
333 return TRUE; // success
334}
335
337{
338 if (s_hThreadAPC != NULL)
339 return TRUE;
340
341 unsigned tid;
344 NULL, 0, &tid);
345 return s_hThreadAPC != NULL;
346}
347
349{
350 assert(this != NULL);
351
352 // create an APC thread for directory watching
353 if (!CreateAPCThread())
354 return FALSE;
355
356 // request adding the watch
358 return TRUE;
359}
360
362{
363 assert(this != NULL);
364
365 if (s_hThreadAPC)
366 {
368 return TRUE;
369 }
370
371 return FALSE;
372}
373
375{
376 if (!s_hThreadAPC)
377 return;
378
379 // request termination of all directory watches
381}
382
384{
385 assert(this != NULL);
386
387 m_fDead = TRUE;
389}
390
392{
393 return m_fDead;
394}
static void NotifyFileSystemChange(LONG wEventId, LPCWSTR path1, LPCWSTR path2)
static void NTAPI _RequestAllTerminationAPC(ULONG_PTR Parameter)
static DWORD GetFilterFromEvents(DWORD fEvents)
static BOOL s_fTerminateAllWatchers
static BYTE s_buffer[BUFFER_SIZE]
#define BUFFER_SIZE
static void NTAPI _AddDirectoryProcAPC(ULONG_PTR Parameter)
static void CALLBACK _NotificationCompletion(DWORD dwErrorCode, DWORD dwNumberOfBytesTransfered, LPOVERLAPPED lpOverlapped)
static void NTAPI _RequestTerminationAPC(ULONG_PTR Parameter)
static DWORD ConvertActionToEvent(DWORD Action, BOOL fDir)
static HANDLE s_hThreadAPC
static unsigned __stdcall DirectoryWatcherThreadFuncAPC(void *)
#define WINE_DEFAULT_DEBUG_CHANNEL(t)
Definition: precomp.h:23
#define ERR(fmt,...)
Definition: debug.h:113
EXTERN_C void WINAPI SHChangeNotify(LONG wEventId, UINT uFlags, LPCVOID dwItem1, LPCVOID dwItem2)
BOOL AddPath(LPCWSTR pszPath)
BOOL ContainsPath(LPCWSTR pszPath) const
BOOL DeletePath(LPCWSTR pszPath)
BOOL RenamePath(LPCWSTR pszPath1, LPCWSTR pszPath2)
void ReadCompletion(DWORD dwErrorCode, DWORD dwNumberOfBytesTransfered)
static CDirectoryWatcher * Create(LPCWSTR pszDirectoryPath, BOOL fSubTree)
CDirectoryWatcher(LPCWSTR pszDirectoryPath, BOOL fSubTree)
static void RequestAllWatchersTermination()
WCHAR m_szDirectoryPath[MAX_PATH]
CDirectoryList m_dir_list
BOOL WINAPI ReadDirectoryChangesW(IN HANDLE hDirectory, IN LPVOID lpBuffer OPTIONAL, IN DWORD nBufferLength, IN BOOL bWatchSubtree, IN DWORD dwNotifyFilter, OUT LPDWORD lpBytesReturned, IN LPOVERLAPPED lpOverlapped OPTIONAL, IN LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine)
Definition: cnotify.c:253
#define ERROR_INVALID_FUNCTION
Definition: dderror.h:6
BOOL WINAPI CancelIo(IN HANDLE hFile)
Definition: deviceio.c:290
#define NULL
Definition: types.h:112
#define TRUE
Definition: types.h:120
#define FALSE
Definition: types.h:117
LPWSTR WINAPI StrChrW(LPCWSTR lpszStr, WCHAR ch)
Definition: string.c:468
#define CloseHandle
Definition: compat.h:739
#define OPEN_EXISTING
Definition: compat.h:775
#define INVALID_HANDLE_VALUE
Definition: compat.h:731
#define MAX_PATH
Definition: compat.h:34
#define CreateFileW
Definition: compat.h:741
#define CALLBACK
Definition: compat.h:35
#define FILE_SHARE_READ
Definition: compat.h:136
#define lstrcpynW
Definition: compat.h:738
DWORD WINAPI GetLongPathNameW(IN LPCWSTR lpszShortPath, OUT LPWSTR lpszLongPath, IN DWORD cchBuffer)
Definition: path.c:1456
DWORD WINAPI GetFullPathNameW(IN LPCWSTR lpFileName, IN DWORD nBufferLength, OUT LPWSTR lpBuffer, OUT LPWSTR *lpFilePart)
Definition: path.c:1106
DWORD WINAPI QueueUserAPC(IN PAPCFUNC pfnAPC, IN HANDLE hThread, IN ULONG_PTR dwData)
Definition: thread.c:959
BOOL WINAPI PathFileExistsW(LPCWSTR lpszPath)
Definition: path.c:1777
BOOL WINAPI PathIsDirectoryW(LPCWSTR lpszPath)
Definition: path.c:1723
BOOL WINAPI PathIsRelativeW(LPCWSTR lpszPath)
Definition: path.c:1579
#define assert(x)
Definition: debug.h:53
#define INFINITE
Definition: serial.h:102
unsigned int BOOL
Definition: ntddk_ex.h:94
unsigned long DWORD
Definition: ntddk_ex.h:95
#define FILE_FLAG_OVERLAPPED
Definition: disk.h:46
#define FILE_FLAG_BACKUP_SEMANTICS
Definition: disk.h:41
LPCWSTR szPath
Definition: env.c:37
static char szTempPath[MAX_PATH]
Definition: data.c:16
static TfClientId tid
static const WCHAR path1[]
Definition: path.c:28
static const WCHAR path2[]
Definition: path.c:29
_In_ HANDLE _In_ DWORD _In_ DWORD _Inout_opt_ LPOVERLAPPED lpOverlapped
Definition: mswsock.h:93
#define FILE_SHARE_WRITE
Definition: nt_native.h:681
#define FILE_LIST_DIRECTORY
Definition: nt_native.h:629
#define FILE_SHARE_DELETE
Definition: nt_native.h:682
#define UNICODE_NULL
#define L(x)
Definition: ntvdm.h:50
#define PathAppendW
Definition: pathcch.h:309
long LONG
Definition: pedump.c:60
static const WCHAR szName[]
Definition: powrprof.c:45
_CRTIMP uintptr_t __cdecl _beginthreadex(_In_opt_ void *_Security, _In_ unsigned _StackSize, _In_ unsigned(__stdcall *_StartAddress)(void *), _In_opt_ void *_ArgList, _In_ unsigned _InitFlag, _Out_opt_ unsigned *_ThrdAddr)
#define SHCNE_RMDIR
Definition: shlobj.h:1880
#define SHCNE_DELETE
Definition: shlobj.h:1878
#define SHCNE_MKDIR
Definition: shlobj.h:1879
#define SHCNE_UPDATEITEM
Definition: shlobj.h:1889
#define SHCNE_RENAMEITEM
Definition: shlobj.h:1876
#define SHCNE_UPDATEDIR
Definition: shlobj.h:1888
#define SHCNE_CREATE
Definition: shlobj.h:1877
#define SHCNE_RENAMEFOLDER
Definition: shlobj.h:1893
#define SHCNF_FLUSH
Definition: shlobj.h:1914
#define SHCNF_PATHW
Definition: shlobj.h:1911
#define SHCNE_ALLEVENTS
Definition: shlobj.h:1900
#define SHCNE_INTERRUPT
Definition: shlobj.h:1901
#define _countof(array)
Definition: sndvol32.h:70
#define TRACE(s)
Definition: solgame.cpp:4
HANDLE hEvent
Definition: winbase.h:820
DWORD WINAPI SleepEx(IN DWORD dwMilliseconds, IN BOOL bAlertable)
Definition: synch.c:802
DWORD WINAPI WaitForSingleObjectEx(IN HANDLE hHandle, IN DWORD dwMilliseconds, IN BOOL bAlertable)
Definition: synch.c:94
unsigned char * LPBYTE
Definition: typedefs.h:53
#define NTAPI
Definition: typedefs.h:36
PVOID HANDLE
Definition: typedefs.h:73
#define __stdcall
Definition: typedefs.h:25
uint32_t ULONG_PTR
Definition: typedefs.h:65
_In_ WDFIOTARGET _In_ _Strict_type_match_ WDF_IO_TARGET_SENT_IO_ACTION Action
Definition: wdfiotarget.h:510
#define ZeroMemory
Definition: winbase.h:1712
DWORD WINAPI GetLastError(void)
Definition: except.c:1042
HANDLE WINAPI GetCurrentThread(void)
Definition: proc.c:1148
#define CopyMemory
Definition: winbase.h:1710
#define ERROR_OPERATION_ABORTED
Definition: winerror.h:575
struct _FILE_NOTIFY_INFORMATION * PFILE_NOTIFY_INFORMATION
#define FILE_NOTIFY_CHANGE_SIZE
#define FILE_ACTION_MODIFIED
#define FILE_ACTION_RENAMED_OLD_NAME
#define FILE_ACTION_REMOVED
#define FILE_NOTIFY_CHANGE_FILE_NAME
#define FILE_ACTION_RENAMED_NEW_NAME
#define FILE_NOTIFY_CHANGE_CREATION
#define FILE_ACTION_ADDED
#define FILE_NOTIFY_CHANGE_DIR_NAME
_Inout_opt_ PVOID Parameter
Definition: rtltypes.h:323
__wchar_t WCHAR
Definition: xmlstorage.h:180
const WCHAR * LPCWSTR
Definition: xmlstorage.h:185
unsigned char BYTE
Definition: xxhash.c:193