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