ReactOS  0.4.15-dev-1201-gb2cf5a4
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
15 static 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)
81  m_hDirectory = CreateFileW(pszDirectoryPath, FILE_LIST_DIRECTORY,
85  NULL);
86 }
87 
88 /*static*/ CDirectoryWatcher *
89 CDirectoryWatcher::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
113 static DWORD
115 {
116  switch (Action)
117  {
118  case FILE_ACTION_ADDED:
119  return (fDir ? SHCNE_MKDIR : SHCNE_CREATE);
120  case FILE_ACTION_REMOVED:
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  }
168 
169  // convert action to event
170  fDir = PathIsDirectoryW(szPath);
171  dwEvent = ConvertActionToEvent(pInfo->Action, fDir);
172 
173  // convert SHCNE_DELETE to SHCNE_RMDIR if the path is a directory
174  if (!fDir && (dwEvent == SHCNE_DELETE) && m_dir_list.ContainsPath(szPath))
175  {
176  fDir = TRUE;
177  dwEvent = SHCNE_RMDIR;
178  }
179 
180  // update m_dir_list
181  switch (dwEvent)
182  {
183  case SHCNE_MKDIR:
185  dwEvent = 0;
186  break;
187  case SHCNE_CREATE:
189  dwEvent = 0;
190  break;
191  case SHCNE_RENAMEFOLDER:
193  dwEvent = 0;
194  break;
195  case SHCNE_RENAMEITEM:
197  dwEvent = 0;
198  break;
199  case SHCNE_RMDIR:
201  dwEvent = 0;
202  break;
203  case SHCNE_DELETE:
204  if (PathFileExistsW(szPath))
205  dwEvent = 0;
206  break;
207  }
208 
209  if (dwEvent != 0)
210  {
211  // notify
212  if (pInfo->Action == FILE_ACTION_RENAMED_NEW_NAME)
214  else
216  }
217  else if (pInfo->Action == FILE_ACTION_RENAMED_OLD_NAME)
218  {
219  // save path for next FILE_ACTION_RENAMED_NEW_NAME
221  }
222 
223  if (pInfo->NextEntryOffset == 0)
224  break; // there is no next entry
225 
226  // go next entry
227  pInfo = (PFILE_NOTIFY_INFORMATION)((LPBYTE)pInfo + pInfo->NextEntryOffset);
228  }
229 
230  TRACE("CDirectoryWatcher::ProcessNotification: leave\n");
231 }
232 
233 void CDirectoryWatcher::ReadCompletion(DWORD dwErrorCode, DWORD dwNumberOfBytesTransfered)
234 {
235  // If the FSD doesn't support directory change notifications, there's no
236  // no need to retry and requeue notification
237  if (dwErrorCode == ERROR_INVALID_FUNCTION)
238  {
239  ERR("ERROR_INVALID_FUNCTION\n");
240  return;
241  }
242 
243  // Also, if the notify operation was canceled (like, user moved to another
244  // directory), then, don't requeue notification.
245  if (dwErrorCode == ERROR_OPERATION_ABORTED)
246  {
247  TRACE("ERROR_OPERATION_ABORTED\n");
248  if (IsDead())
249  delete this;
250  return;
251  }
252 
253  // is this watch dead?
254  if (IsDead())
255  {
256  TRACE("IsDead()\n");
257  delete this;
258  return;
259  }
260 
261  // This likely means overflow, so force whole directory refresh.
262  if (dwNumberOfBytesTransfered == 0)
263  {
264  // do notify a SHCNE_UPDATEDIR
266  }
267  else
268  {
269  // do notify
271  }
272 
273  // restart a watch
274  RestartWatching();
275 }
276 
277 // The completion routine of ReadDirectoryChangesW.
278 static void CALLBACK
280  DWORD dwNumberOfBytesTransfered,
282 {
283  // MSDN: The hEvent member of the OVERLAPPED structure is not used by the
284  // system in this case, so you can use it yourself. We do just this, storing
285  // a pointer to the working struct in the overlapped structure.
286  CDirectoryWatcher *pDirectoryWatcher = (CDirectoryWatcher *)lpOverlapped->hEvent;
287  assert(pDirectoryWatcher != NULL);
288 
289  pDirectoryWatcher->ReadCompletion(dwErrorCode, dwNumberOfBytesTransfered);
290 }
291 
292 // convert events to notification filter
293 static DWORD
295 {
296  // FIXME
301 }
302 
303 // Restart a watch by using ReadDirectoryChangesW function
305 {
306  assert(this != NULL);
307 
308  if (IsDead())
309  {
310  delete this;
311  return FALSE; // the watch is dead
312  }
313 
314  // initialize the buffer and the overlapped
315  ZeroMemory(s_buffer, sizeof(s_buffer));
317  m_overlapped.hEvent = (HANDLE)this;
318 
319  // start the directory watch
322  m_fRecursive, dwFilter, NULL,
324  {
325  ERR("ReadDirectoryChangesW for '%S' failed (error: %ld)\n",
327  return FALSE; // failure
328  }
329 
330  return TRUE; // success
331 }
332 
334 {
335  if (s_hThreadAPC != NULL)
336  return TRUE;
337 
338  unsigned tid;
341  NULL, 0, &tid);
342  return s_hThreadAPC != NULL;
343 }
344 
346 {
347  assert(this != NULL);
348 
349  // create an APC thread for directory watching
350  if (!CreateAPCThread())
351  return FALSE;
352 
353  // request adding the watch
355  return TRUE;
356 }
357 
359 {
360  assert(this != NULL);
361 
362  if (s_hThreadAPC)
363  {
365  return TRUE;
366  }
367 
368  return FALSE;
369 }
370 
372 {
373  if (!s_hThreadAPC)
374  return;
375 
376  // request termination of all directory watches
378 }
379 
381 {
382  assert(this != NULL);
383 
384  m_fDead = TRUE;
386 }
387 
389 {
390  return m_fDead;
391 }
#define SHCNE_MKDIR
Definition: shlobj.h:1732
BOOL WINAPI PathIsDirectoryW(LPCWSTR lpszPath)
Definition: path.c:1702
static const WCHAR path2[]
Definition: path.c:29
DWORD WINAPI WaitForSingleObjectEx(IN HANDLE hHandle, IN DWORD dwMilliseconds, IN BOOL bAlertable)
Definition: synch.c:94
static HANDLE s_hThreadAPC
WCHAR m_szDirectoryPath[MAX_PATH]
#define SHCNE_RMDIR
Definition: shlobj.h:1733
#define CloseHandle
Definition: compat.h:598
#define FILE_ACTION_RENAMED_OLD_NAME
const WCHAR * LPCWSTR
Definition: xmlstorage.h:185
#define ERROR_INVALID_FUNCTION
Definition: dderror.h:6
#define TRUE
Definition: types.h:120
static unsigned __stdcall DirectoryWatcherThreadFuncAPC(void *)
static void NTAPI _RequestAllTerminationAPC(ULONG_PTR Parameter)
#define SHCNE_RENAMEFOLDER
Definition: shlobj.h:1746
#define CALLBACK
Definition: compat.h:35
#define SHCNE_INTERRUPT
Definition: shlobj.h:1754
static void NTAPI _RequestTerminationAPC(ULONG_PTR Parameter)
#define INVALID_HANDLE_VALUE
Definition: compat.h:590
#define assert(x)
Definition: debug.h:53
DWORD WINAPI GetLastError(VOID)
Definition: except.c:1044
#define ZeroMemory
Definition: winbase.h:1648
DWORD WINAPI GetFullPathNameW(IN LPCWSTR lpFileName, IN DWORD nBufferLength, OUT LPWSTR lpBuffer, OUT LPWSTR *lpFilePart)
Definition: path.c:1105
#define FILE_NOTIFY_CHANGE_SIZE
#define FILE_NOTIFY_CHANGE_FILE_NAME
static BYTE s_buffer[BUFFER_SIZE]
#define FILE_SHARE_WRITE
Definition: nt_native.h:681
_In_ PVOID Parameter
Definition: ldrtypes.h:241
_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_RENAMEITEM
Definition: shlobj.h:1729
#define FILE_NOTIFY_CHANGE_DIR_NAME
WINE_DEFAULT_DEBUG_CHANNEL(shcn)
BOOL RenamePath(LPCWSTR pszPath1, LPCWSTR pszPath2)
#define FILE_SHARE_READ
Definition: compat.h:136
#define lstrcpynW
Definition: compat.h:597
BOOL WINAPI PathAppendW(LPWSTR lpszPath, LPCWSTR lpszAppend)
Definition: path.c:121
#define ERROR_OPERATION_ABORTED
Definition: winerror.h:575
uint32_t ULONG_PTR
Definition: typedefs.h:65
DWORD WINAPI QueueUserAPC(IN PAPCFUNC pfnAPC, IN HANDLE hThread, IN ULONG_PTR dwData)
Definition: thread.c:947
unsigned char * LPBYTE
Definition: typedefs.h:53
NTSTATUS(* NTAPI)(IN PFILE_FULL_EA_INFORMATION EaBuffer, IN ULONG EaLength, OUT PULONG ErrorOffset)
Definition: IoEaTest.cpp:117
#define FALSE
Definition: types.h:117
#define UNICODE_NULL
HANDLE WINAPI GetCurrentThread(VOID)
Definition: proc.c:1148
BOOL AddPath(LPCWSTR pszPath)
unsigned int BOOL
Definition: ntddk_ex.h:94
#define SHCNE_ALLEVENTS
Definition: shlobj.h:1753
long LONG
Definition: pedump.c:60
#define FILE_ACTION_MODIFIED
HANDLE hEvent
Definition: winbase.h:798
static void CALLBACK _NotificationCompletion(DWORD dwErrorCode, DWORD dwNumberOfBytesTransfered, LPOVERLAPPED lpOverlapped)
BOOL WINAPI CancelIo(IN HANDLE hFile)
Definition: deviceio.c:290
smooth NULL
Definition: ftsmooth.c:416
#define FILE_ACTION_REMOVED
BOOL WINAPI PathFileExistsW(LPCWSTR lpszPath)
Definition: path.c:1756
static void RequestAllWatchersTermination()
#define OPEN_EXISTING
Definition: compat.h:634
#define FILE_NOTIFY_CHANGE_CREATION
BOOL ContainsPath(LPCWSTR pszPath) const
static const WCHAR path1[]
Definition: path.c:28
static TCHAR szTempPath[MAX_PATH]
Definition: CImage.cpp:25
static CDirectoryWatcher * Create(LPCWSTR pszDirectoryPath, BOOL fSubTree)
static DWORD GetFilterFromEvents(DWORD fEvents)
DWORD WINAPI GetLongPathNameW(IN LPCWSTR lpszShortPath, OUT LPWSTR lpszLongPath, IN DWORD cchBuffer)
Definition: path.c:1455
#define TRACE(s)
Definition: solgame.cpp:4
DWORD WINAPI SleepEx(IN DWORD dwMilliseconds, IN BOOL bAlertable)
Definition: synch.c:802
#define FILE_LIST_DIRECTORY
Definition: nt_native.h:629
__wchar_t WCHAR
Definition: xmlstorage.h:180
#define _countof(array)
Definition: sndvol32.h:68
static void NTAPI _AddDirectoryProcAPC(ULONG_PTR Parameter)
_In_ PLIST_ENTRY _In_ PSTRING _In_ USHORT _In_opt_ PSTRING _In_opt_ PSTRING _In_ ULONG _In_ ULONG Action
Definition: fsrtlfuncs.h:738
#define MAX_PATH
Definition: compat.h:34
#define CopyMemory
Definition: winbase.h:1646
PVOID HANDLE
Definition: typedefs.h:73
unsigned long DWORD
Definition: ntddk_ex.h:95
#define __stdcall
Definition: typedefs.h:25
#define BUFFER_SIZE
EXTERN_C void WINAPI SHChangeNotify(LONG wEventId, UINT uFlags, LPCVOID dwItem1, LPCVOID dwItem2)
#define FILE_SHARE_DELETE
Definition: nt_native.h:682
static const WCHAR L[]
Definition: oid.c:1250
#define SHCNF_FLUSH
Definition: shlobj.h:1767
#define FILE_ACTION_RENAMED_NEW_NAME
unsigned char BYTE
Definition: xxhash.c:193
#define SHCNF_PATHW
Definition: shlobj.h:1764
#define ERR(fmt,...)
Definition: debug.h:110
_In_ HANDLE _In_ DWORD _In_ DWORD _Inout_opt_ LPOVERLAPPED lpOverlapped
Definition: mswsock.h:90
LPCWSTR szPath
Definition: env.c:35
#define SHCNE_DELETE
Definition: shlobj.h:1731
#define SHCNE_UPDATEDIR
Definition: shlobj.h:1741
LPWSTR WINAPI StrChrW(LPCWSTR lpszStr, WCHAR ch)
Definition: string.c:468
BOOL DeletePath(LPCWSTR pszPath)
static void NotifyFileSystemChange(LONG wEventId, LPCWSTR path1, LPCWSTR path2)
#define FILE_ACTION_ADDED
#define CreateFileW
Definition: compat.h:600
static const WCHAR szName[]
Definition: msipriv.h:1194
#define FILE_FLAG_BACKUP_SEMANTICS
Definition: disk.h:41
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
CDirectoryWatcher(LPCWSTR pszDirectoryPath, BOOL fSubTree)
struct _FILE_NOTIFY_INFORMATION * PFILE_NOTIFY_INFORMATION
#define SHCNE_CREATE
Definition: shlobj.h:1730
#define SHCNE_UPDATEITEM
Definition: shlobj.h:1742
CDirectoryList m_dir_list
static BOOL s_fTerminateAllWatchers
#define FILE_FLAG_OVERLAPPED
Definition: disk.h:46
#define INFINITE
Definition: serial.h:102
static DWORD ConvertActionToEvent(DWORD Action, BOOL fDir)
static TfClientId tid
void ReadCompletion(DWORD dwErrorCode, DWORD dwNumberOfBytesTransfered)