Pyrogenesis  13997
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
dir_watch_inotify.cpp
Go to the documentation of this file.
1 /* Copyright (c) 2012 Wildfire Games
2  *
3  * Permission is hereby granted, free of charge, to any person obtaining
4  * a copy of this software and associated documentation files (the
5  * "Software"), to deal in the Software without restriction, including
6  * without limitation the rights to use, copy, modify, merge, publish,
7  * distribute, sublicense, and/or sell copies of the Software, and to
8  * permit persons to whom the Software is furnished to do so, subject to
9  * the following conditions:
10  *
11  * The above copyright notice and this permission notice shall be included
12  * in all copies or substantial portions of the Software.
13  *
14  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17  * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
18  * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19  * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
20  * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21  */
22 
23 #include "precompiled.h"
24 
25 #include "lib/sysdep/dir_watch.h"
26 #include "lib/sysdep/sysdep.h"
27 #include "ps/CLogger.h"
28 
29 #include <map>
30 #include <string>
31 #include <sys/inotify.h>
32 
33 
35 {
36  std::string filename;
38  int wd;
39 };
40 
41 // To avoid deadlocks and slow synchronous reads, it's necessary to use a
42 // separate thread for reading events from inotify.
43 // So we just spawn a thread to push events into this list, then swap it out
44 // when someone calls dir_watch_Poll.
45 // (We assume STL memory allocation is thread-safe.)
46 static std::vector<NotificationEvent> g_notifications;
48 
49 // Mutex must wrap all accesses of g_notifications
50 // while the event loop thread is running
52 
53 // trool; -1 = init failed and all operations will be aborted silently.
54 // this is so that each dir_* call doesn't complain if the system's
55 // inotify is broken or unavailable.
56 static int initialized = 0;
57 
58 // Inotify file descriptor
59 static int inotifyfd;
60 
61 // With inotify, using a map seems to be a good alternative to FAM's userdata
62 typedef std::map<int, PDirWatch> DirWatchMap;
64 
65 struct DirWatch
66 {
68  : reqnum(-1)
69  {
70  }
71 
73  {
74  ENSURE(initialized > 0);
75  inotify_rm_watch(inotifyfd, reqnum);
76  }
77 
79  int reqnum;
80 };
81 
82 // for atexit
83 static void inotify_deinit()
84 {
85  close(inotifyfd);
86 
88  // NOTE: POSIX threads are (by default) only cancellable inside particular
89  // functions (like 'select'), so this should safely terminate while it's
90  // in select/etc (and won't e.g. cancel while it's holding the
91  // mutex)
92 
93  // Wait for the thread to finish
95 }
96 
98 {
99  // Buffer for reading the events.
100  // Need to be careful about overflow here.
101  char buffer[65535];
102 
103  // Event iterator
104  ssize_t buffer_i = 0;
105 
106  // Total size of all the events
107  ssize_t r;
108 
109  // Size & struct for the current event
110  size_t event_size;
111  struct inotify_event *pevent;
112 
113  r = read(inotifyfd, buffer, 65535);
114  if(r <= 0)
115  return;
116 
117  while(buffer_i < r)
118  {
120  pevent = (struct inotify_event *) &buffer[buffer_i];
121 
122  event_size = offsetof(struct inotify_event, name) + pevent->len;
123  ne.wd = pevent->wd;
124  ne.filename = pevent->name;
125  ne.code = pevent->mask;
126 
128  g_notifications.push_back(ne);
130 
131  buffer_i += event_size;
132  }
133 }
134 
135 static void* inotify_event_loop(void*)
136 {
137  while(true)
138  {
139  fd_set fdrset;
140  FD_ZERO(&fdrset);
141  FD_SET(inotifyfd, &fdrset);
142  errno = 0;
143  // Block with select until there's events waiting
144  while(select(inotifyfd+1, &fdrset, NULL, NULL, NULL) < 0)
145  {
146  if(errno == EINTR)
147  {
148  // interrupted - try again
149  FD_ZERO(&fdrset);
150  FD_SET(inotifyfd, &fdrset);
151  }
152  else if(errno == EBADF)
153  {
154  // probably just lost the connection to inotify - kill the thread
155  debug_printf(L"inotify_event_loop: Invalid file descriptor inotifyfd=%d\n", inotifyfd);
156  return NULL;
157  }
158  else
159  {
160  // oops
161  debug_printf(L"inotify_event_loop: select error errno=%d\n", errno);
162  return NULL;
163  }
164  errno = 0;
165  }
166  if(FD_ISSET(inotifyfd, &fdrset))
168  }
169 }
170 
171 Status dir_watch_Add(const OsPath& path, PDirWatch& dirWatch)
172 {
173  char resolved[PATH_MAX + 1];
174 
175  // init already failed; don't try again or complain
176  if(initialized == -1)
177  return ERR::FAIL; // NOWARN
178 
179  if(!initialized)
180  {
181  errno = 0;
182  if((inotifyfd = inotify_init()) < 0)
183  {
184  // Check for error ?
185  int err = errno;
186  initialized = -1;
187  LOGERROR(L"Error initializing inotify file descriptor; hotloading will be disabled, errno=%d", err);
188  errno = err;
189  return StatusFromErrno(); // NOWARN
190  }
191 
192  errno = 0;
193  int ret = pthread_create(&g_event_loop_thread, NULL, &inotify_event_loop, NULL);
194  if (ret != 0)
195  {
196  initialized = -1;
197  LOGERROR(L"Error creating inotify event loop thread; hotloading will be disabled, err=%d", ret);
198  errno = ret;
199  return StatusFromErrno(); // NOWARN
200  }
201 
202  initialized = 1;
203  atexit(inotify_deinit);
204  }
205 
206  PDirWatch tmpDirWatch(new DirWatch);
207  errno = 0;
208  int wd = inotify_add_watch(inotifyfd, realpath(OsString(path).c_str(), resolved), IN_CREATE | IN_DELETE | IN_CLOSE_WRITE);
209  if (wd < 0)
211 
212  dirWatch.swap(tmpDirWatch);
213  dirWatch->path = path;
214  dirWatch->reqnum = wd;
215  g_paths.insert(std::make_pair(wd, dirWatch));
216 
217  return INFO::OK;
218 }
219 
221 {
222  if(initialized == -1)
223  return ERR::FAIL; // NOWARN
224  if(!initialized) // XXX Fix Atlas instead of suppressing the warning
225  return ERR::FAIL; //WARN_RETURN(ERR::LOGIC);
226 
227  std::vector<NotificationEvent> polled_notifications;
228 
230  g_notifications.swap(polled_notifications);
232 
233  for(size_t i = 0; i < polled_notifications.size(); ++i)
234  {
236  // TODO: code is actually a bitmask, so this is slightly incorrect
237  switch(polled_notifications[i].code)
238  {
239  case IN_CLOSE_WRITE:
241  break;
242  case IN_CREATE:
244  break;
245  case IN_DELETE:
247  break;
248  default:
249  continue;
250  }
251 
252  DirWatchMap::iterator it = g_paths.find(polled_notifications[i].wd);
253  if(it != g_paths.end())
254  {
255  OsPath filename = Path(OsString(it->second->path).append(polled_notifications[i].filename));
256  notifications.push_back(DirWatchNotification(filename, type));
257  }
258  else
259  {
260  debug_printf(L"dir_watch_Poll: Notification with invalid watch descriptor wd=%d\n", polled_notifications[i].wd);
261  }
262  }
263 
264  // nothing new; try again later
265  return INFO::OK;
266 }
267 
void * pthread_mutex_t
Definition: wpthread.h:82
std::vector< DirWatchNotification > DirWatchNotifications
Definition: dir_watch.h:84
static DirWatchMap g_paths
static void * inotify_event_loop(void *)
static pthread_t g_event_loop_thread
const Status OK
Definition: status.h:386
#define LOGERROR
Definition: CLogger.h:35
static int inotifyfd
std::map< int, PDirWatch > DirWatchMap
LIB_API Status dir_watch_Add(const OsPath &path, PDirWatch &dirWatch)
start watching a single directory for changes.
Definition: dir_watch.cpp:28
static std::vector< NotificationEvent > g_notifications
static void inotify_deinit()
#define ENSURE(expr)
ensure the expression &lt;expr&gt; evaluates to non-zero.
Definition: debug.h:282
int pthread_create(pthread_t *thread_id, const void *attr, void *(*func)(void *), void *arg)
Definition: wpthread.cpp:636
static void inotify_event_loop_process_events()
shared_ptr< DirWatch > PDirWatch
Definition: dir_watch.h:32
Definition: path.h:75
LIB_API Status dir_watch_Poll(DirWatchNotifications &notifications)
return all pending directory watch notifications.
Definition: dir_watch.cpp:33
int read(int fd, void *buf, size_t nbytes)
int pthread_mutex_lock(pthread_mutex_t *m)
Definition: wpthread.cpp:329
static int initialized
i64 Status
Error handling system.
Definition: status.h:171
#define PATH_MAX
Definition: wposix_types.h:101
intptr_t ssize_t
Definition: wposix_types.h:82
Status StatusFromErrno()
Definition: status.cpp:105
static pthread_mutex_t g_mutex
uintptr_t pthread_t
Definition: wpthread.h:63
unsigned int uint32_t
Definition: wposix_types.h:53
int pthread_mutex_unlock(pthread_mutex_t *m)
Definition: wpthread.cpp:347
#define WARN_RETURN(status)
Definition: status.h:255
const Status FAIL
Definition: status.h:406
int pthread_join(pthread_t thread, void **value_ptr)
Definition: wpthread.cpp:679
#define PTHREAD_MUTEX_INITIALIZER
Definition: wpthread.h:84
int pthread_cancel(pthread_t thread)
Definition: wpthread.cpp:670
void debug_printf(const wchar_t *fmt,...)
write a formatted string to the debug channel, subject to filtering (see below).
Definition: debug.cpp:142
static std::string OsString(const OsPath &path)
Definition: os_path.h:42