Line data Source code
1 : // Copyright (c) 2012-2022 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 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 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 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 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 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 : * \return true if the merge happened.
253 : */
254 0 : bool file_changed::merge_watch(
255 : std::string const & watched_path
256 : , event_mask_t const events)
257 : {
258 : auto const & wevent(std::find_if(
259 : f_watches.begin()
260 : , f_watches.end()
261 0 : , [&watched_path](auto const & w)
262 0 : {
263 0 : return w.second.f_watched_path == watched_path;
264 0 : }));
265 0 : if(wevent == f_watches.end())
266 : {
267 : // not found
268 : //
269 0 : return false;
270 : }
271 :
272 0 : wevent->second.merge_watch(f_inotify, events);
273 :
274 0 : return true;
275 : }
276 :
277 :
278 0 : void file_changed::watch_file(std::string const & watched_path, event_mask_t const events)
279 : {
280 0 : if(!merge_watch(watched_path, events))
281 : {
282 0 : watch_t watch(watched_path, events, 0);
283 0 : watch.add_watch(f_inotify);
284 0 : f_watches[watch.f_watch] = watch;
285 : }
286 0 : }
287 :
288 :
289 0 : void file_changed::watch_symlink(std::string const & watched_path, event_mask_t const events)
290 : {
291 0 : if(!merge_watch(watched_path, events))
292 : {
293 0 : watch_t watch(watched_path, events, IN_DONT_FOLLOW);
294 0 : watch.add_watch(f_inotify);
295 0 : f_watches[watch.f_watch] = watch;
296 : }
297 0 : }
298 :
299 :
300 0 : void file_changed::watch_directory(std::string const & watched_path, event_mask_t const events)
301 : {
302 0 : if(!merge_watch(watched_path, events))
303 : {
304 0 : watch_t watch(watched_path, events, IN_ONLYDIR);
305 0 : watch.add_watch(f_inotify);
306 0 : f_watches[watch.f_watch] = watch;
307 : }
308 0 : }
309 :
310 :
311 0 : void file_changed::stop_watch(std::string const & watched_path)
312 : {
313 : // because of the merge, even though the watched_path is not the
314 : // index of our map, it will be unique so we really only need to
315 : // find one such entry
316 : //
317 0 : auto wevent(std::find_if(
318 : f_watches.begin()
319 : , f_watches.end()
320 0 : , [&](auto & w)
321 : {
322 0 : return w.second.f_watched_path == watched_path;
323 0 : }));
324 :
325 0 : if(wevent != f_watches.end())
326 : {
327 0 : wevent->second.remove_watch(f_inotify);
328 0 : f_watches.erase(wevent);
329 : }
330 0 : }
331 :
332 :
333 0 : bool file_changed::is_reader() const
334 : {
335 0 : return true;
336 : }
337 :
338 :
339 0 : int file_changed::get_socket() const
340 : {
341 : // if we did not add any watches, avoid adding another fd to the poll()
342 : //
343 0 : if(f_watches.empty())
344 : {
345 0 : return -1;
346 : }
347 :
348 0 : return f_inotify;
349 : }
350 :
351 :
352 0 : void file_changed::set_enable(bool enabled)
353 : {
354 0 : connection::set_enable(enabled);
355 :
356 : // TODO: inotify will continue to send us messages when disabled
357 : // and that's a total of 16K of messages! That's a lot of
358 : // memory wasted if the connection gets disabled for a long
359 : // amount of time; what we want to do instead is disconnect
360 : // completely on a disable and reconnect on a re-enable
361 0 : }
362 :
363 :
364 0 : void file_changed::process_read()
365 : {
366 : // were notifications closed in between?
367 : //
368 0 : if(f_inotify == -1)
369 : {
370 0 : return;
371 : }
372 :
373 : // WARNING: this is about 4Kb of buffer on the stack
374 : // it is NOT 256 structures because all events with a name
375 : // have the name included in themselves and that "eats"
376 : // space in the next structure
377 : //
378 0 : struct inotify_event buffer[256];
379 :
380 : for(;;)
381 : {
382 : // read a few messages in one call
383 : //
384 0 : ssize_t const len(read(f_inotify, buffer, sizeof(buffer)));
385 0 : if(len <= 0)
386 : {
387 0 : if(len == 0
388 0 : || errno == EAGAIN)
389 : {
390 : // reached the end of the current queue
391 : //
392 0 : return;
393 : }
394 :
395 : // TODO: close the inotify on errors?
396 0 : int const e(errno);
397 0 : SNAP_LOG_ERROR
398 0 : << "an error occurred while reading from inotify (errno: "
399 : << e
400 : << " -- "
401 0 : << strerror(e)
402 : << ")."
403 : << SNAP_LOG_SEND;
404 0 : process_error();
405 0 : return;
406 : }
407 : // convert the buffer to a character pointer to make it easier to
408 : // move the pointer to the next structure
409 : //
410 0 : char const * start(reinterpret_cast<char const *>(buffer));
411 0 : char const * end(start + len);
412 0 : while(start < end)
413 : {
414 : // get the pointer to the current inotify event
415 : //
416 0 : struct inotify_event const & ievent(*reinterpret_cast<struct inotify_event const *>(start));
417 0 : if(start + sizeof(struct inotify_event) + ievent.len > end)
418 : {
419 : // unless there is a huge bug in the inotify implementation
420 : // this exception should never happen
421 : //
422 0 : throw unexpected_data("somehow the size of this ievent does not match what we just read.");
423 : }
424 :
425 : // convert the inotify even in one of our events
426 : //
427 0 : auto const & wevent(f_watches.find(ievent.wd));
428 0 : if(wevent != f_watches.end())
429 : {
430 : // XXX: we need to know whether this flag can appear with
431 : // others (i.e. could we at the same time have a message
432 : // saying there was a read and a queue overflow?); if
433 : // so, then we need to run the else part even on
434 : // overflows
435 : //
436 0 : if((ievent.mask & IN_Q_OVERFLOW) != 0)
437 : {
438 0 : SNAP_LOG_ERROR
439 : << "Received an event queue overflow error."
440 : << SNAP_LOG_SEND;
441 : }
442 : else
443 : {
444 0 : event_t const watch_event(wevent->second.f_watched_path
445 0 : , mask_to_events(ievent.mask)
446 0 : , std::string(ievent.name, ievent.len));
447 :
448 0 : process_event(watch_event);
449 :
450 : // if the event received included IN_IGNORED then we need
451 : // to remove that watch
452 : //
453 0 : if((ievent.mask & IN_IGNORED) != 0)
454 : {
455 : // before losing the wevent, make sure we disconnect
456 : // from the OS version
457 : //
458 0 : const_cast<watch_t &>(wevent->second).remove_watch(f_inotify);
459 0 : f_watches.erase(ievent.wd);
460 0 : f_watches.erase(wevent);
461 : }
462 : }
463 : }
464 : else
465 : {
466 : // we do not know about this notifier, close it
467 : // (this should never happen... unless we read the queue
468 : // for a watch that had more events and we had not read it
469 : // yet, in that case the watch was certainly already
470 : // removed... it should not hurt to re-remove it.)
471 : //
472 0 : inotify_rm_watch(f_inotify, ievent.wd);
473 : }
474 :
475 : // move the pointer to the next structure until we reach 'end'
476 : //
477 0 : start += sizeof(struct inotify_event) + ievent.len;
478 : }
479 0 : }
480 : }
481 :
482 :
483 0 : uint32_t file_changed::events_to_mask(event_mask_t const events)
484 : {
485 0 : uint32_t mask(0);
486 :
487 0 : if((events & SNAP_FILE_CHANGED_EVENT_ATTRIBUTES) != 0)
488 : {
489 0 : mask |= IN_ATTRIB;
490 : }
491 :
492 0 : if((events & SNAP_FILE_CHANGED_EVENT_READ) != 0)
493 : {
494 0 : mask |= IN_ACCESS;
495 : }
496 :
497 0 : if((events & SNAP_FILE_CHANGED_EVENT_WRITE) != 0)
498 : {
499 0 : mask |= IN_MODIFY;
500 : }
501 :
502 0 : if((events & SNAP_FILE_CHANGED_EVENT_CREATED) != 0)
503 : {
504 0 : mask |= IN_CREATE | IN_MOVED_FROM | IN_MOVE_SELF;
505 : }
506 :
507 0 : if((events & SNAP_FILE_CHANGED_EVENT_DELETED) != 0)
508 : {
509 0 : mask |= IN_DELETE | IN_DELETE_SELF | IN_MOVED_TO | IN_MOVE_SELF;
510 : }
511 :
512 0 : if((events & SNAP_FILE_CHANGED_EVENT_ACCESS) != 0)
513 : {
514 0 : mask |= IN_OPEN | IN_CLOSE_WRITE | IN_CLOSE_NOWRITE;
515 : }
516 :
517 0 : if(mask == 0)
518 : {
519 0 : throw initialization_error("invalid file_changed events parameter, it was not changed to any IN_... flags.");
520 : }
521 :
522 0 : return mask;
523 : }
524 :
525 :
526 0 : file_changed::event_mask_t file_changed::mask_to_events(uint32_t const mask)
527 : {
528 0 : event_mask_t events(0);
529 :
530 0 : if((mask & IN_ATTRIB) != 0)
531 : {
532 0 : events |= SNAP_FILE_CHANGED_EVENT_ATTRIBUTES;
533 : }
534 :
535 0 : if((mask & IN_ACCESS) != 0)
536 : {
537 0 : events |= SNAP_FILE_CHANGED_EVENT_READ;
538 : }
539 :
540 0 : if((mask & IN_MODIFY) != 0)
541 : {
542 0 : events |= SNAP_FILE_CHANGED_EVENT_WRITE;
543 : }
544 :
545 0 : if((mask & (IN_CREATE | IN_MOVED_FROM)) != 0)
546 : {
547 0 : events |= SNAP_FILE_CHANGED_EVENT_CREATED;
548 : }
549 :
550 0 : if((mask & (IN_DELETE | IN_DELETE_SELF | IN_MOVED_TO)) != 0)
551 : {
552 0 : events |= SNAP_FILE_CHANGED_EVENT_DELETED;
553 : }
554 :
555 0 : if((mask & (IN_OPEN | IN_CLOSE_WRITE | IN_CLOSE_NOWRITE)) != 0)
556 : {
557 0 : events |= SNAP_FILE_CHANGED_EVENT_ACCESS;
558 : }
559 :
560 : // return flags only
561 : //
562 0 : if((mask & IN_ISDIR) != 0)
563 : {
564 0 : events |= SNAP_FILE_CHANGED_EVENT_DIRECTORY;
565 : }
566 :
567 0 : if((mask & IN_IGNORED) != 0)
568 : {
569 0 : events |= SNAP_FILE_CHANGED_EVENT_GONE;
570 : }
571 :
572 0 : if((mask & IN_UNMOUNT) != 0)
573 : {
574 0 : events |= SNAP_FILE_CHANGED_EVENT_UNMOUNTED;
575 : }
576 :
577 0 : return events;
578 : }
579 :
580 :
581 :
582 : } // namespace ed
583 : // vim: ts=4 sw=4 et
|