ReactOS  0.4.15-dev-2996-gf777e6b
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  {
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;
194  case SHCNE_RENAMEFOLDER:
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:
207  if (PathFileExistsW(szPath))
208  dwEvent = 0;
209  break;
210  }
211 
212  if (dwEvent != 0)
213  {
214  // notify
215  if (pInfo->Action == FILE_ACTION_RENAMED_NEW_NAME)
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 
236 void 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
277  RestartWatching();
278 }
279 
280 // The completion routine of ReadDirectoryChangesW.
281 static 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
296 static 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 }
#define SHCNE_MKDIR
Definition: shlobj.h:1732
BOOL WINAPI PathIsDirectoryW(LPCWSTR lpszPath)
Definition: path.c:1722
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
BOOL WINAPI PathIsRelativeW(LPCWSTR lpszPath)
Definition: path.c:1578
#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:1040
#define ZeroMemory
Definition: winbase.h:1664
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:126
#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:814
static void CALLBACK _NotificationCompletion(DWORD dwErrorCode, DWORD dwNumberOfBytesTransfered, LPOVERLAPPED lpOverlapped)
BOOL WINAPI CancelIo(IN HANDLE hFile)
Definition: deviceio.c:290
#define FILE_ACTION_REMOVED
BOOL WINAPI PathFileExistsW(LPCWSTR lpszPath)
Definition: path.c:1776
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)
#define MAX_PATH
Definition: compat.h:34
#define CopyMemory
Definition: winbase.h:1662
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
_In_ WDFIOTARGET _In_ _Strict_type_match_ WDF_IO_TARGET_SENT_IO_ACTION Action
Definition: wdfiotarget.h:506
LPCWSTR szPath
Definition: env.c:37
#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)
#define NULL
Definition: types.h:112
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)