OpenClonk
C4FileMonitor.cpp
Go to the documentation of this file.
1 /*
2  * OpenClonk, http://www.openclonk.org
3  *
4  * Copyright (c) 2008-2009, RedWolf Design GmbH, http://www.clonk.de/
5  * Copyright (c) 2009-2016, The OpenClonk Team and contributors
6  *
7  * Distributed under the terms of the ISC license; see accompanying file
8  * "COPYING" for details.
9  *
10  * "Clonk" is a registered trademark of Matthes Bender, used with permission.
11  * See accompanying file "TRADEMARK" for details.
12  *
13  * To redistribute this file separately, substitute the full license texts
14  * for the above references.
15  */
16 
17 // An inotify wrapper
18 
19 #include "C4Include.h"
20 #include "platform/C4FileMonitor.h"
21 
22 #include "game/C4Application.h"
23 
24 #ifdef HAVE_SYS_INOTIFY_H
25 #include <sys/inotify.h>
26 
27 C4FileMonitor::C4FileMonitor(ChangeNotify pCallback): fStarted(false), pCallback(pCallback)
28 {
29  fd = inotify_init1(IN_CLOEXEC);
30  if (fd == -1) fd = inotify_init();
31  if (fd == -1) LogF("inotify_init %s", strerror(errno));
32 }
33 
35 {
36  if (fStarted)
37  {
39  Thread.RemoveProc(this);
40  Thread.ClearCallback(Ev_FileChange, this);
41  }
42  while (close(fd) == -1 && errno == EINTR) { }
43 }
44 
46 {
47  if (fStarted) return;
49  Thread.AddProc(this);
50  Thread.SetCallback(Ev_FileChange, this);
51  fStarted = true;
52 }
53 
54 void C4FileMonitor::AddDirectory(const char * file)
55 {
56  // Add IN_CLOSE_WRITE?
57  int wd = inotify_add_watch(fd, file, IN_CREATE | IN_MODIFY | IN_MOVED_TO | IN_MOVE_SELF | IN_ONLYDIR);
58  if (wd == -1)
59  LogF("inotify_add_watch %s", strerror(errno));
60  watch_descriptors[wd] = file;
61 }
62 
63 bool C4FileMonitor::Execute(int iTimeout, pollfd * pfd) // some other thread
64 {
65  if ((pfd->revents & pfd->events) != POLLIN || pfd->fd != fd)
66  LogF("C4FileMonitor::Execute unexpectedly called %d %d %hd %hd", fd, pfd->fd, pfd->events, pfd->revents);
67  char buf[sizeof(inotify_event) + _MAX_FNAME_LEN];
68  inotify_event* event = new (buf) inotify_event;
69  if (read(fd, buf, sizeof(buf)) > 0)
70  {
71  const char * file = watch_descriptors[event->wd];
72  uint32_t mask = event->mask;
74  if (mask & IN_CREATE)
75  Thread.PushEvent(Ev_FileChange, const_cast<char*>(file));
76  if (mask & IN_MODIFY)
77  Thread.PushEvent(Ev_FileChange, const_cast<char*>(file));
78  if (mask & IN_MOVED_TO)
79  Thread.PushEvent(Ev_FileChange, const_cast<char*>(file));
80  if (mask & IN_MOVE_SELF)
81  Thread.PushEvent(Ev_FileChange, const_cast<char*>(file));
82  // FIXME: (*(inotify_event*)buf).name);
83  }
84  else
85  {
86  Log("inotify buffer too small");
87  }
88  return true;
89 }
90 
91 void C4FileMonitor::OnThreadEvent(C4InteractiveEventType eEvent, void *pEventData) // main thread
92 {
93  if (eEvent != Ev_FileChange) return;
94  pCallback((const char *)pEventData, nullptr);
95 }
96 
97 void C4FileMonitor::GetFDs(std::vector<struct pollfd> & fds)
98 {
99  pollfd pfd = { fd, POLLIN, 0 };
100  fds.push_back(pfd);
101 }
102 
103 #elif defined(_WIN32)
105 
106 C4FileMonitor::C4FileMonitor(ChangeNotify pCallback)
107  : fStarted(false), pCallback(pCallback), pWatches(nullptr)
108 {
109  hEvent = CreateEvent(nullptr, true, false, nullptr);
110 }
111 
113 {
114  if (fStarted)
115  {
117  Thread.RemoveProc(this);
118  Thread.ClearCallback(Ev_FileChange, this);
119  }
120  while (pWatches)
121  {
122  CloseHandle(pWatches->hDir);
123  TreeWatch *pDelete = pWatches;
124  pWatches = pWatches->Next;
125  delete pDelete;
126  }
127  CloseHandle(hEvent);
128 }
129 
131 {
133  Thread.AddProc(this);
134  Thread.SetCallback(Ev_FileChange, this);
135  fStarted = true;
136 }
137 
138 const DWORD C4FileMonitorNotifies = FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE;
139 
140 void C4FileMonitor::AddDirectory(const char *szDir)
141 {
142  // Create file handle
143  HANDLE hDir = CreateFileW(GetWideChar(szDir), FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, nullptr);
144  if (hDir == INVALID_HANDLE_VALUE) return;
145  // Create tree watch structure
146  TreeWatch *pWatch = new TreeWatch();
147  pWatch->hDir = hDir;
148  pWatch->DirName = szDir;
149  // Build description of async operation
150  ZeroMem(&pWatch->ov, sizeof(pWatch->ov));
151  pWatch->ov.hEvent = hEvent;
152  // Add to list
153  pWatch->Next = pWatches;
154  pWatches = pWatch;
155  // Start async directory change notification
156  if (!ReadDirectoryChangesW(hDir, pWatch->Buffer, sizeof(pWatch->Buffer), false, C4FileMonitorNotifies, nullptr, &pWatch->ov, nullptr))
157  if (GetLastError() != ERROR_IO_PENDING)
158  {
159  delete pWatch;
160  return;
161  }
162 }
163 
164 bool C4FileMonitor::Execute(int iTimeout, pollfd *)
165 {
166  // Check event
167  if (WaitForSingleObject(hEvent, iTimeout) != WAIT_OBJECT_0)
168  return true;
169  // Check handles
170  for (TreeWatch *pWatch = pWatches; pWatch; pWatch = pWatch->Next)
171  {
172  DWORD dwBytes = 0;
173  // Has a notification?
174  if (GetOverlappedResult(pWatch->hDir, &pWatch->ov, &dwBytes, false))
175  {
176  // Read notifications
177  const char *pPos = pWatch->Buffer;
178  for (;;)
179  {
180  const _FILE_NOTIFY_INFORMATION *pNotify = reinterpret_cast<const _FILE_NOTIFY_INFORMATION *>(pPos);
181  // Handle
182  HandleNotify(pWatch->DirName.getData(), pNotify);
183  // Get next entry
184  if (!pNotify->NextEntryOffset) break;
185  pPos += pNotify->NextEntryOffset;
186  if (pPos >= pWatch->Buffer + std::min<size_t>(sizeof(pWatch->Buffer), dwBytes))
187  break;
188  break;
189  }
190  // Restart directory change notification (flush queue)
191  ReadDirectoryChangesW(pWatch->hDir, pWatch->Buffer, sizeof(pWatch->Buffer), false, C4FileMonitorNotifies, nullptr, &pWatch->ov, nullptr);
192  dwBytes = 0;
193  while (GetOverlappedResult(pWatch->hDir, &pWatch->ov, &dwBytes, false))
194  {
195  ReadDirectoryChangesW(pWatch->hDir, pWatch->Buffer, sizeof(pWatch->Buffer), false, C4FileMonitorNotifies, nullptr, &pWatch->ov, nullptr);
196  dwBytes = 0;
197  }
198  }
199  }
200  ResetEvent(hEvent);
201  return true;
202 }
203 
204 void C4FileMonitor::OnThreadEvent(C4InteractiveEventType eEvent, void *pEventData) // main thread
205 {
206  if (eEvent != Ev_FileChange) return;
207  pCallback((const char *)pEventData, nullptr);
208  StdBuf::DeletePointer(pEventData);
209 }
210 
211 HANDLE C4FileMonitor::GetEvent()
212 {
213  return hEvent;
214 }
215 
216 void C4FileMonitor::HandleNotify(const char *szDir, const _FILE_NOTIFY_INFORMATION *pNotify)
217 {
218  // Get filename length
219  UINT iCodePage = CP_UTF8;
220  int iFileNameBytes = WideCharToMultiByte(iCodePage, 0,
221  pNotify->FileName, pNotify->FileNameLength / 2, nullptr, 0, nullptr, nullptr);
222  // Set up filename buffer
223  StdCopyStrBuf Path(szDir);
224  Path.AppendChar(DirectorySeparator);
225  Path.Grow(iFileNameBytes);
226  char *pFilename = Path.getMPtr(SLen(Path.getData()));
227  // Convert filename
228  int iWritten = WideCharToMultiByte(iCodePage, 0,
229  pNotify->FileName, pNotify->FileNameLength / 2,
230  pFilename, iFileNameBytes,
231  nullptr, nullptr);
232  if (iWritten != iFileNameBytes)
233  Path.Shrink(iFileNameBytes+1);
234  // Send notification
236 }
237 
238 #elif not defined(__APPLE__)
239 
240 // Stubs
241 C4FileMonitor::C4FileMonitor(ChangeNotify pCallback)
242 {
243 #ifdef HAVE_SYS_INOTIFY_H
244  C4FileMonitor::pCallback = pCallback;
245 #endif
246 }
247 
249 bool C4FileMonitor::Execute(int iTimeout, pollfd *) { return false; /* blarg... function must return a value */ }
251 void C4FileMonitor::OnThreadEvent(C4InteractiveEventType eEvent, void *pEventData) {}
252 void C4FileMonitor::AddDirectory(const char *szDir) {}
253 
254 // Signal for calling Execute()
255 #ifdef STDSCHEDULER_USE_EVENTS
256 HANDLE C4FileMonitor::GetEvent() { return 0; }
257 #else
258 void C4FileMonitor::GetFDs(std::vector<struct pollfd> & FDs) { }
259 #endif
260 
261 #endif // HAVE_SYS_INOTIFY_H
C4Application Application
Definition: C4Globals.cpp:44
C4InteractiveEventType
@ Ev_FileChange
bool Log(const char *szMessage)
Definition: C4Log.cpp:204
bool LogF(const char *strMessage,...)
Definition: C4Log.cpp:262
StdStrBuf::wchar_t_holder GetWideChar(const char *utf8, bool double_null_terminate=false)
#define DirectorySeparator
#define _MAX_FNAME_LEN
uint32_t DWORD
std::enable_if< std::is_pod< T >::value >::type ZeroMem(T *lpMem, size_t dwSize)
Definition: Standard.h:60
size_t SLen(const char *sptr)
Definition: Standard.h:74
C4InteractiveThread InteractiveThread
Definition: C4Application.h:45
void GetFDs(std::vector< struct pollfd > &FDs) override
void StartMonitoring()
void OnThreadEvent(C4InteractiveEventType eEvent, void *pEventData) override
bool Execute(int iTimeout=-1, pollfd *=nullptr) override
~C4FileMonitor() override
void AddDirectory(const char *szDir)
C4FileMonitor(ChangeNotify pCallback)
void RemoveProc(StdSchedulerProc *pProc)
void ClearCallback(C4InteractiveEventType eEvent, Callback *pnNetworkCallback)
bool AddProc(StdSchedulerProc *pProc)
bool PushEvent(C4InteractiveEventType eEventType, void *pData=nullptr)
void SetCallback(C4InteractiveEventType eEvent, Callback *pnNetworkCallback)
static void DeletePointer(void *data)
Definition: StdBuf.h:196