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