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

Generated by: LCOV version 1.12