LCOV - code coverage report
Current view: top level - source/lib/sysdep/os/linux - dir_watch_inotify.cpp (source / functions) Hit Total Coverage
Test: 0 A.D. test coverage report Lines: 3 109 2.8 %
Date: 2023-01-19 00:18:29 Functions: 2 13 15.4 %

          Line data    Source code
       1             : /* Copyright (C) 2015 Wildfire Games.
       2             :  *
       3             :  * Permission is hereby granted, free of charge, to any person obtaining
       4             :  * a copy of this software and associated documentation files (the
       5             :  * "Software"), to deal in the Software without restriction, including
       6             :  * without limitation the rights to use, copy, modify, merge, publish,
       7             :  * distribute, sublicense, and/or sell copies of the Software, and to
       8             :  * permit persons to whom the Software is furnished to do so, subject to
       9             :  * the following conditions:
      10             :  *
      11             :  * The above copyright notice and this permission notice shall be included
      12             :  * in all copies or substantial portions of the Software.
      13             :  *
      14             :  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
      15             :  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
      16             :  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
      17             :  * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
      18             :  * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
      19             :  * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
      20             :  * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
      21             :  */
      22             : 
      23             : #include "precompiled.h"
      24             : 
      25             : #include "lib/sysdep/dir_watch.h"
      26             : #include "lib/sysdep/sysdep.h"
      27             : #include "ps/CLogger.h"
      28             : 
      29             : #include <map>
      30             : #include <string>
      31             : #include <sys/inotify.h>
      32             : 
      33             : 
      34           0 : struct NotificationEvent
      35             : {
      36             :     std::string filename;
      37             :     uint32_t code;
      38             :     int wd;
      39             : };
      40             : 
      41             : // To avoid deadlocks and slow synchronous reads, it's necessary to use a
      42             : // separate thread for reading events from inotify.
      43             : // So we just spawn a thread to push events into this list, then swap it out
      44             : // when someone calls dir_watch_Poll.
      45             : // (We assume STL memory allocation is thread-safe.)
      46           1 : static std::vector<NotificationEvent> g_notifications;
      47             : static pthread_t g_event_loop_thread;
      48             : 
      49             : // Mutex must wrap all accesses of g_notifications
      50             : // while the event loop thread is running
      51             : static pthread_mutex_t g_mutex = PTHREAD_MUTEX_INITIALIZER;
      52             : 
      53             : // trool; -1 = init failed and all operations will be aborted silently.
      54             : // this is so that each dir_* call doesn't complain if the system's
      55             : // inotify is broken or unavailable.
      56             : static int initialized = 0;
      57             : 
      58             : // Inotify file descriptor
      59             : static int inotifyfd;
      60             : 
      61             : // With inotify, using a map seems to be a good alternative to FAM's userdata
      62             : typedef std::map<int, PDirWatch> DirWatchMap;
      63           1 : static DirWatchMap g_paths;
      64             : 
      65             : struct DirWatch
      66             : {
      67           0 :     DirWatch()
      68           0 :         : reqnum(-1)
      69             :     {
      70           0 :     }
      71             : 
      72           0 :     ~DirWatch()
      73           0 :     {
      74           0 :         ENSURE(initialized > 0);
      75           0 :         inotify_rm_watch(inotifyfd, reqnum);
      76           0 :     }
      77             : 
      78             :     OsPath path;
      79             :     int reqnum;
      80             : };
      81             : 
      82             : // for atexit
      83           0 : static void inotify_deinit()
      84             : {
      85           0 :     close(inotifyfd);
      86             : 
      87             : #ifdef __BIONIC__
      88             :     #warning TODO: pthread_cancel not supported on Bionic
      89             : #else
      90           0 :     pthread_cancel(g_event_loop_thread);
      91             : #endif
      92             :     // NOTE: POSIX threads are (by default) only cancellable inside particular
      93             :     // functions (like 'select'), so this should safely terminate while it's
      94             :     // in select/etc (and won't e.g. cancel while it's holding the
      95             :     // mutex)
      96             : 
      97             :     // Wait for the thread to finish
      98           0 :     pthread_join(g_event_loop_thread, NULL);
      99           0 : }
     100             : 
     101           0 : static void inotify_event_loop_process_events()
     102             : {
     103             :     // Buffer for reading the events.
     104             :     // Need to be careful about overflow here.
     105           0 :     char buffer[65535] = {0};
     106             : 
     107             :     // Event iterator
     108           0 :     ssize_t buffer_i = 0;
     109             : 
     110             :     // Total size of all the events
     111             :     ssize_t r;
     112             : 
     113             :     // Size & struct for the current event
     114             :     size_t event_size;
     115             :     struct inotify_event *pevent;
     116             : 
     117           0 :     r = read(inotifyfd, buffer, 65535);
     118           0 :     if(r <= 0)
     119           0 :         return;
     120             : 
     121           0 :     while(buffer_i < r)
     122             :     {
     123           0 :         NotificationEvent ne;
     124           0 :         pevent = (struct inotify_event *) &buffer[buffer_i];
     125             : 
     126           0 :         event_size = offsetof(struct inotify_event, name) + pevent->len;
     127           0 :         ne.wd = pevent->wd;
     128           0 :         ne.filename = pevent->name;
     129           0 :         ne.code = pevent->mask;
     130             : 
     131           0 :         pthread_mutex_lock(&g_mutex);
     132           0 :         g_notifications.push_back(ne);
     133           0 :         pthread_mutex_unlock(&g_mutex);
     134             : 
     135           0 :         buffer_i += event_size;
     136             :     }
     137             : }
     138             : 
     139           0 : static void* inotify_event_loop(void*)
     140             : {
     141             :     while(true)
     142             :     {
     143             :         fd_set fdrset;
     144           0 :         FD_ZERO(&fdrset);
     145           0 :         FD_SET(inotifyfd, &fdrset);
     146           0 :         errno = 0;
     147             :         // Block with select until there's events waiting
     148           0 :         while(select(inotifyfd+1, &fdrset, NULL, NULL, NULL) < 0)
     149             :         {
     150           0 :             if(errno == EINTR)
     151             :             {
     152             :                 // interrupted - try again
     153           0 :                 FD_ZERO(&fdrset);
     154           0 :                 FD_SET(inotifyfd, &fdrset);
     155             :             }
     156           0 :             else if(errno == EBADF)
     157             :             {
     158             :                 // probably just lost the connection to inotify - kill the thread
     159           0 :                 debug_printf("inotify_event_loop: Invalid file descriptor inotifyfd=%d\n", inotifyfd);
     160           0 :                 return NULL;
     161             :             }
     162             :             else
     163             :             {
     164             :                 // oops
     165           0 :                 debug_printf("inotify_event_loop: select error errno=%d\n", errno);
     166           0 :                 return NULL;
     167             :             }
     168           0 :             errno = 0;
     169             :         }
     170           0 :         if(FD_ISSET(inotifyfd, &fdrset))
     171           0 :             inotify_event_loop_process_events();
     172           0 :     }
     173             : }
     174             : 
     175           0 : Status dir_watch_Add(const OsPath& path, PDirWatch& dirWatch)
     176             : {
     177             :     char resolved[PATH_MAX + 1];
     178             : 
     179             :     // init already failed; don't try again or complain
     180           0 :     if(initialized == -1)
     181           0 :         return ERR::FAIL;   // NOWARN
     182             : 
     183           0 :     if(!initialized)
     184             :     {
     185           0 :         errno = 0;
     186           0 :         if((inotifyfd = inotify_init()) < 0)
     187             :         {
     188             :             // Check for error ?
     189           0 :             int err = errno;
     190           0 :             initialized = -1;
     191           0 :             LOGERROR("Error initializing inotify file descriptor; hotloading will be disabled, errno=%d", err);
     192           0 :             errno = err;
     193           0 :             return StatusFromErrno();   // NOWARN
     194             :         }
     195             : 
     196           0 :         errno = 0;
     197           0 :         int ret = pthread_create(&g_event_loop_thread, NULL, &inotify_event_loop, NULL);
     198           0 :         if (ret != 0)
     199             :         {
     200           0 :             initialized = -1;
     201           0 :             LOGERROR("Error creating inotify event loop thread; hotloading will be disabled, err=%d", ret);
     202           0 :             errno = ret;
     203           0 :             return StatusFromErrno();   // NOWARN
     204             :         }
     205             : 
     206           0 :         initialized = 1;
     207           0 :         atexit(inotify_deinit);
     208             :     }
     209             : 
     210           0 :     PDirWatch tmpDirWatch(new DirWatch);
     211           0 :     errno = 0;
     212           0 :     int wd = inotify_add_watch(inotifyfd, realpath(OsString(path).c_str(), resolved), IN_CREATE | IN_DELETE | IN_CLOSE_WRITE);
     213           0 :     if (wd < 0)
     214           0 :         WARN_RETURN(StatusFromErrno());
     215             : 
     216           0 :     dirWatch.swap(tmpDirWatch);
     217           0 :     dirWatch->path = path;
     218           0 :     dirWatch->reqnum = wd;
     219           0 :     g_paths.insert(std::make_pair(wd, dirWatch));
     220             : 
     221           0 :     return INFO::OK;
     222             : }
     223             : 
     224           0 : Status dir_watch_Poll(DirWatchNotifications& notifications)
     225             : {
     226           0 :     if(initialized == -1)
     227           0 :         return ERR::FAIL;   // NOWARN
     228           0 :     if(!initialized) // XXX Fix Atlas instead of suppressing the warning
     229           0 :         return ERR::FAIL; //WARN_RETURN(ERR::LOGIC);
     230             : 
     231           0 :     std::vector<NotificationEvent> polled_notifications;
     232             : 
     233           0 :     pthread_mutex_lock(&g_mutex);
     234           0 :     g_notifications.swap(polled_notifications);
     235           0 :     pthread_mutex_unlock(&g_mutex);
     236             : 
     237           0 :     for(size_t i = 0; i < polled_notifications.size(); ++i)
     238             :     {
     239             :         DirWatchNotification::EType type;
     240             :         // TODO: code is actually a bitmask, so this is slightly incorrect
     241           0 :         switch(polled_notifications[i].code)
     242             :         {
     243           0 :         case IN_CLOSE_WRITE:
     244           0 :             type = DirWatchNotification::Changed;
     245           0 :             break;
     246           0 :         case IN_CREATE:
     247           0 :             type = DirWatchNotification::Created;
     248           0 :             break;
     249           0 :         case IN_DELETE:
     250           0 :             type = DirWatchNotification::Deleted;
     251           0 :             break;
     252           0 :         default:
     253           0 :             continue;
     254             :         }
     255             : 
     256           0 :         DirWatchMap::iterator it = g_paths.find(polled_notifications[i].wd);
     257           0 :         if(it != g_paths.end())
     258             :         {
     259           0 :             OsPath filename = Path(OsString(it->second->path).append(polled_notifications[i].filename));
     260           0 :             notifications.push_back(DirWatchNotification(filename, type));
     261             :         }
     262             :         else
     263             :         {
     264           0 :             debug_printf("dir_watch_Poll: Notification with invalid watch descriptor wd=%d\n", polled_notifications[i].wd);
     265             :         }
     266             :     }
     267             : 
     268             :     // nothing new; try again later
     269           0 :     return INFO::OK;
     270           3 : }
     271             : 

Generated by: LCOV version 1.13