Line data Source code
1 : // Copyright (c) 2012-2019 Made to Order Software Corp. All Rights Reserved
2 : //
3 : // https://snapwebsites.org/project/eventdispatcher
4 : // contact@m2osw.com
5 : //
6 : // This program is free software; you can redistribute it and/or modify
7 : // it under the terms of the GNU General Public License as published by
8 : // the Free Software Foundation; either version 2 of the License, or
9 : // (at your option) any later version.
10 : //
11 : // This program is distributed in the hope that it will be useful,
12 : // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 : // GNU General Public License for more details.
15 : //
16 : // You should have received a copy of the GNU General Public License
17 : // along with this program; if not, write to the Free Software
18 : // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 :
20 : /** \file
21 : * \brief Implementation of the Snap Communicator class.
22 : *
23 : * This class wraps the C poll() interface in a C++ object with many types
24 : * of objects:
25 : *
26 : * \li Server Connections; for software that want to offer a port to
27 : * which clients can connect to; the server will call accept()
28 : * once a new client connection is ready; this results in a
29 : * Server/Client connection object
30 : * \li Client Connections; for software that want to connect to
31 : * a server; these expect the IP address and port to connect to
32 : * \li Server/Client Connections; for the server when it accepts a new
33 : * connection; in this case the server gets a socket from accept()
34 : * and creates one of these objects to handle the connection
35 : *
36 : * Using the poll() function is the easiest and allows us to listen
37 : * on pretty much any number of sockets (on my server it is limited
38 : * at 16,768 and frankly over 1,000 we probably will start to have
39 : * real slowness issues on small VPN servers.)
40 : */
41 :
42 :
43 : // self
44 : //
45 : #include "eventdispatcher/file_changed.h"
46 :
47 : #include "eventdispatcher/exception.h"
48 :
49 :
50 : // snaplogger lib
51 : //
52 : #include <snaplogger/message.h>
53 :
54 :
55 : // C lib
56 : //
57 : #include <sys/inotify.h>
58 :
59 :
60 : // last include
61 : //
62 : #include <snapdev/poison.h>
63 :
64 :
65 :
66 :
67 : namespace ed
68 : {
69 :
70 :
71 0 : file_changed::event_t::event_t(std::string const & watched_path
72 : , event_mask_t events
73 : , std::string const & filename)
74 : : f_watched_path(watched_path)
75 : , f_events(events)
76 0 : , f_filename(filename)
77 : {
78 0 : if(f_watched_path.empty())
79 : {
80 0 : throw event_dispatcher_initialization_error("a snap_file_changed watch path cannot be the empty string.");
81 : }
82 :
83 0 : if(f_events == SNAP_FILE_CHANGED_EVENT_NO_EVENTS)
84 : {
85 0 : throw event_dispatcher_initialization_error("a snap_file_changed events parameter cannot be 0.");
86 : }
87 0 : }
88 :
89 :
90 0 : std::string const & file_changed::event_t::get_watched_path() const
91 : {
92 0 : return f_watched_path;
93 : }
94 :
95 :
96 0 : file_changed::event_mask_t file_changed::event_t::get_events() const
97 : {
98 0 : return f_events;
99 : }
100 :
101 :
102 0 : std::string const & file_changed::event_t::get_filename() const
103 : {
104 0 : return f_filename;
105 : }
106 :
107 :
108 0 : bool file_changed::event_t::operator < (event_t const & rhs) const
109 : {
110 0 : return f_watched_path < rhs.f_watched_path;
111 : }
112 :
113 :
114 0 : file_changed::watch_t::watch_t()
115 : {
116 0 : }
117 :
118 :
119 0 : file_changed::watch_t::watch_t(std::string const & watched_path, event_mask_t events, uint32_t add_flags)
120 : : f_watched_path(watched_path)
121 : , f_events(events)
122 0 : , f_mask(events_to_mask(events) | add_flags | IN_EXCL_UNLINK)
123 : {
124 0 : }
125 :
126 :
127 0 : void file_changed::watch_t::add_watch(int inotify)
128 : {
129 0 : f_watch = inotify_add_watch(inotify, f_watched_path.c_str(), f_mask);
130 0 : if(f_watch == -1)
131 : {
132 0 : int const e(errno);
133 : SNAP_LOG_WARNING
134 0 : << "inotify_add_watch() returned an error (errno: "
135 0 : << e
136 0 : << " -- "
137 0 : << strerror(e)
138 0 : << ").";
139 :
140 : // it did not work
141 : //
142 0 : throw event_dispatcher_initialization_error("inotify_add_watch() failed");
143 : }
144 0 : }
145 :
146 :
147 0 : void file_changed::watch_t::merge_watch(int inotify, event_mask_t const events)
148 : {
149 0 : f_mask |= events_to_mask(events);
150 :
151 : // The documentation is not 100% clear about an update so for now
152 : // I remove the existing watch and create a new one... it should
153 : // not happen very often anyway
154 : //
155 0 : if(f_watch != -1)
156 : {
157 0 : remove_watch(inotify);
158 : }
159 :
160 0 : f_watch = inotify_add_watch(inotify, f_watched_path.c_str(), f_mask);
161 0 : if(f_watch == -1)
162 : {
163 0 : int const e(errno);
164 : SNAP_LOG_WARNING
165 0 : << "inotify_raddwatch() returned an error (errno: "
166 0 : << e
167 0 : << " -- "
168 0 : << strerror(e)
169 0 : << ").";
170 :
171 : // it did not work
172 : //
173 0 : throw event_dispatcher_initialization_error("inotify_add_watch() failed");
174 : }
175 0 : }
176 :
177 :
178 0 : void file_changed::watch_t::remove_watch(int inotify)
179 : {
180 0 : if(f_watch != -1)
181 : {
182 0 : int const r(inotify_rm_watch(inotify, f_watch));
183 0 : if(r != 0)
184 : {
185 : // we output the error if one occurs, but go on as if nothing
186 : // happened
187 : //
188 0 : int const e(errno);
189 : SNAP_LOG_WARNING
190 0 : << "inotify_rm_watch() returned an error (errno: "
191 0 : << e
192 0 : << " -- "
193 0 : << strerror(e)
194 0 : << ").";
195 : }
196 :
197 : // we can remove it just once
198 : //
199 0 : f_watch = -1;
200 : }
201 0 : }
202 :
203 :
204 0 : file_changed::file_changed()
205 0 : : f_inotify(inotify_init1(IN_NONBLOCK | IN_CLOEXEC))
206 : //, f_watches() -- auto-init
207 : {
208 0 : if(f_inotify == -1)
209 : {
210 0 : throw event_dispatcher_initialization_error("snap_file_changed: inotify_init1() failed.");
211 : }
212 0 : }
213 :
214 :
215 0 : file_changed::~file_changed()
216 : {
217 : // watch_t are not RAII because we copy them in maps...
218 : // so we have to "manually" clean up here, but making them RAII would
219 : // mean creating an impl and thus hiding the problem at a different
220 : // level which is less effective...
221 : //
222 0 : for(auto & w : f_watches)
223 : {
224 0 : w.second.remove_watch(f_inotify);
225 : }
226 :
227 0 : close(f_inotify);
228 0 : }
229 :
230 :
231 : /** \brief Try to merge a new watch.
232 : *
233 : * If you attempt to watch the same path again, instead of adding a new watch,
234 : * we instead want to merge it. This is important because the system
235 : * does not generate a new watch when you do that.
236 : *
237 : * In this case, the \p events parameter is viewed as parameters being
238 : * added to the watched. If you want to replace the previous watch instead,
239 : * make sure to first remove it, then re-add it with new flags as required.
240 : *
241 : * \param[in] watched_path The path the user wants to watch.
242 : * \param[in] events The events being added to the watch.
243 : */
244 0 : bool file_changed::merge_watch(std::string const & watched_path, event_mask_t const events)
245 : {
246 : auto const & wevent(std::find_if(
247 : f_watches.begin()
248 : , f_watches.end()
249 0 : , [&watched_path](auto const & w)
250 0 : {
251 0 : return w.second.f_watched_path == watched_path;
252 0 : }));
253 0 : if(wevent == f_watches.end())
254 : {
255 : // not found
256 : //
257 0 : return false;
258 : }
259 :
260 0 : wevent->second.merge_watch(f_inotify, events);
261 :
262 0 : return true;
263 : }
264 :
265 :
266 0 : void file_changed::watch_file(std::string const & watched_path, event_mask_t const events)
267 : {
268 0 : if(!merge_watch(watched_path, events))
269 : {
270 0 : watch_t watch(watched_path, events, 0);
271 0 : watch.add_watch(f_inotify);
272 0 : f_watches[watch.f_watch] = watch;
273 : }
274 0 : }
275 :
276 :
277 0 : void file_changed::watch_symlink(std::string const & watched_path, event_mask_t const events)
278 : {
279 0 : if(!merge_watch(watched_path, events))
280 : {
281 0 : watch_t watch(watched_path, events, IN_DONT_FOLLOW);
282 0 : watch.add_watch(f_inotify);
283 0 : f_watches[watch.f_watch] = watch;
284 : }
285 0 : }
286 :
287 :
288 0 : void file_changed::watch_directory(std::string const & watched_path, event_mask_t const events)
289 : {
290 0 : if(!merge_watch(watched_path, events))
291 : {
292 0 : watch_t watch(watched_path, events, IN_ONLYDIR);
293 0 : watch.add_watch(f_inotify);
294 0 : f_watches[watch.f_watch] = watch;
295 : }
296 0 : }
297 :
298 :
299 0 : void file_changed::stop_watch(std::string const & watched_path)
300 : {
301 : // because of the merge, even though the watched_path is not the
302 : // index of our map, it will be unique so we really only need to
303 : // find one such entry
304 : //
305 : auto wevent(std::find_if(
306 : f_watches.begin()
307 : , f_watches.end()
308 0 : , [&](auto & w)
309 : {
310 0 : return w.second.f_watched_path == watched_path;
311 0 : }));
312 :
313 0 : if(wevent != f_watches.end())
314 : {
315 0 : wevent->second.remove_watch(f_inotify);
316 0 : f_watches.erase(wevent);
317 : }
318 0 : }
319 :
320 :
321 0 : bool file_changed::is_reader() const
322 : {
323 0 : return true;
324 : }
325 :
326 :
327 0 : int file_changed::get_socket() const
328 : {
329 : // if we did not add any watches, avoid adding another fd to the poll()
330 : //
331 0 : if(f_watches.empty())
332 : {
333 0 : return -1;
334 : }
335 :
336 0 : return f_inotify;
337 : }
338 :
339 :
340 0 : void file_changed::set_enable(bool enabled)
341 : {
342 0 : connection::set_enable(enabled);
343 :
344 : // TODO: inotify will continue to send us messages when disabled
345 : // and that's a total of 16K of messages! That's a lot of
346 : // memory wasted if the connection gets disabled for a long
347 : // amount of time; what we want to do instead is disconnect
348 : // completely on a disable and reconnect on a re-enable
349 0 : }
350 :
351 :
352 0 : void file_changed::process_read()
353 : {
354 : // were notifications closed in between?
355 : //
356 0 : if(f_inotify == -1)
357 : {
358 0 : return;
359 : }
360 :
361 : // WARNING: this is about 4Kb of buffer on the stack
362 : // it is NOT 256 structures because all events with a name
363 : // have the name included in themselves and that "eats"
364 : // space in the next structure
365 : //
366 : struct inotify_event buffer[256];
367 :
368 0 : for(;;)
369 : {
370 : // read a few messages in one call
371 : //
372 0 : ssize_t const len(read(f_inotify, buffer, sizeof(buffer)));
373 0 : if(len <= 0)
374 : {
375 0 : if(len == 0
376 0 : || errno == EAGAIN)
377 : {
378 : // reached the end of the current queue
379 : //
380 0 : return;
381 : }
382 :
383 : // TODO: close the inotify on errors?
384 0 : int const e(errno);
385 : SNAP_LOG_ERROR
386 0 : << "an error occurred while reading from inotify (errno: "
387 0 : << e
388 0 : << " -- "
389 0 : << strerror(e)
390 0 : << ").";
391 0 : process_error();
392 0 : return;
393 : }
394 : // convert the buffer to a character pointer to make it easier to
395 : // move the pointer to the next structure
396 : //
397 0 : char const * start(reinterpret_cast<char const *>(buffer));
398 0 : char const * end(start + len);
399 0 : while(start < end)
400 : {
401 : // get the pointer to the current inotify event
402 : //
403 0 : struct inotify_event const & ievent(*reinterpret_cast<struct inotify_event const *>(start));
404 0 : if(start + sizeof(struct inotify_event) + ievent.len > end)
405 : {
406 : // unless there is a huge bug in the inotify implementation
407 : // this exception should never happen
408 : //
409 0 : throw event_dispatcher_unexpected_data("somehow the size of this ievent does not match what we just read.");
410 : }
411 :
412 : // convert the inotify even in one of our events
413 : //
414 0 : auto const & wevent(f_watches.find(ievent.wd));
415 0 : if(wevent != f_watches.end())
416 : {
417 : // XXX: we need to know whether this flag can appear with
418 : // others (i.e. could we at the same time have a message
419 : // saying there was a read and a queue overflow?); if
420 : // so, then we need to run the else part even on
421 : // overflows
422 : //
423 0 : if((ievent.mask & IN_Q_OVERFLOW) != 0)
424 : {
425 : SNAP_LOG_ERROR
426 0 : << "Received an event queue overflow error.";
427 : }
428 : else
429 : {
430 0 : event_t const watch_event(wevent->second.f_watched_path
431 0 : , mask_to_events(ievent.mask)
432 0 : , std::string(ievent.name, ievent.len));
433 :
434 0 : process_event(watch_event);
435 :
436 : // if the event received included IN_IGNORED then we need
437 : // to remove that watch
438 : //
439 0 : if((ievent.mask & IN_IGNORED) != 0)
440 : {
441 : // before losing the wevent, make sure we disconnect
442 : // from the OS version
443 : //
444 0 : const_cast<watch_t &>(wevent->second).remove_watch(f_inotify);
445 0 : f_watches.erase(ievent.wd);
446 0 : f_watches.erase(wevent);
447 : }
448 : }
449 : }
450 : else
451 : {
452 : // we do not know about this notifier, close it
453 : // (this should never happen... unless we read the queue
454 : // for a watch that had more events and we had not read it
455 : // yet, in that case the watch was certainly already
456 : // removed... it should not hurt to re-remove it.)
457 : //
458 0 : inotify_rm_watch(f_inotify, ievent.wd);
459 : }
460 :
461 : // move the pointer to the next stucture until we reach 'end'
462 : //
463 0 : start += sizeof(struct inotify_event) + ievent.len;
464 : }
465 : }
466 : }
467 :
468 :
469 0 : uint32_t file_changed::events_to_mask(event_mask_t const events)
470 : {
471 0 : uint32_t mask(0);
472 :
473 0 : if((events & SNAP_FILE_CHANGED_EVENT_ATTRIBUTES) != 0)
474 : {
475 0 : mask |= IN_ATTRIB;
476 : }
477 :
478 0 : if((events & SNAP_FILE_CHANGED_EVENT_READ) != 0)
479 : {
480 0 : mask |= IN_ACCESS;
481 : }
482 :
483 0 : if((events & SNAP_FILE_CHANGED_EVENT_WRITE) != 0)
484 : {
485 0 : mask |= IN_MODIFY;
486 : }
487 :
488 0 : if((events & SNAP_FILE_CHANGED_EVENT_CREATED) != 0)
489 : {
490 0 : mask |= IN_CREATE | IN_MOVED_FROM | IN_MOVE_SELF;
491 : }
492 :
493 0 : if((events & SNAP_FILE_CHANGED_EVENT_DELETED) != 0)
494 : {
495 0 : mask |= IN_DELETE | IN_DELETE_SELF | IN_MOVED_TO | IN_MOVE_SELF;
496 : }
497 :
498 0 : if((events & SNAP_FILE_CHANGED_EVENT_ACCESS) != 0)
499 : {
500 0 : mask |= IN_OPEN | IN_CLOSE_WRITE | IN_CLOSE_NOWRITE;
501 : }
502 :
503 0 : if(mask == 0)
504 : {
505 0 : throw event_dispatcher_initialization_error("invalid snap_file_changed events parameter, it was not changed to any IN_... flags.");
506 : }
507 :
508 0 : return mask;
509 : }
510 :
511 :
512 0 : file_changed::event_mask_t file_changed::mask_to_events(uint32_t const mask)
513 : {
514 0 : event_mask_t events(0);
515 :
516 0 : if((mask & IN_ATTRIB) != 0)
517 : {
518 0 : events |= SNAP_FILE_CHANGED_EVENT_ATTRIBUTES;
519 : }
520 :
521 0 : if((mask & IN_ACCESS) != 0)
522 : {
523 0 : events |= SNAP_FILE_CHANGED_EVENT_READ;
524 : }
525 :
526 0 : if((mask & IN_MODIFY) != 0)
527 : {
528 0 : events |= SNAP_FILE_CHANGED_EVENT_WRITE;
529 : }
530 :
531 0 : if((mask & (IN_CREATE | IN_MOVED_FROM)) != 0)
532 : {
533 0 : events |= SNAP_FILE_CHANGED_EVENT_CREATED;
534 : }
535 :
536 0 : if((mask & (IN_DELETE | IN_DELETE_SELF | IN_MOVED_TO)) != 0)
537 : {
538 0 : events |= SNAP_FILE_CHANGED_EVENT_DELETED;
539 : }
540 :
541 0 : if((mask & (IN_OPEN | IN_CLOSE_WRITE | IN_CLOSE_NOWRITE)) != 0)
542 : {
543 0 : events |= SNAP_FILE_CHANGED_EVENT_ACCESS;
544 : }
545 :
546 : // return flags only
547 : //
548 0 : if((mask & IN_ISDIR) != 0)
549 : {
550 0 : events |= SNAP_FILE_CHANGED_EVENT_DIRECTORY;
551 : }
552 :
553 0 : if((mask & IN_IGNORED) != 0)
554 : {
555 0 : events |= SNAP_FILE_CHANGED_EVENT_GONE;
556 : }
557 :
558 0 : if((mask & IN_UNMOUNT) != 0)
559 : {
560 0 : events |= SNAP_FILE_CHANGED_EVENT_UNMOUNTED;
561 : }
562 :
563 0 : return events;
564 : }
565 :
566 :
567 :
568 : } // namespace ed
569 : // vim: ts=4 sw=4 et
|