LCOV - code coverage report
Current view: top level - eventdispatcher - file_changed.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 0 181 0.0 %
Date: 2022-06-18 10:10:36 Functions: 0 26 0.0 %
Legend: Lines: hit not hit

          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

Generated by: LCOV version 1.13