LCOV - code coverage report
Current view: top level - eventdispatcher - file_changed.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 0 182 0.0 %
Date: 2021-09-19 09:06:58 Functions: 0 26 0.0 %
Legend: Lines: hit not hit

          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

Generated by: LCOV version 1.13