LCOV - code coverage report
Current view: top level - eventdispatcher - file_changed.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 0 186 0.0 %
Date: 2019-08-10 01:48:51 Functions: 0 26 0.0 %
Legend: Lines: hit not hit

          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

Generated by: LCOV version 1.12