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