Ticket #1316: inotify2.patch

File inotify2.patch, 10.6 KB (added by historic_bruno, 12 years ago)
  • build/premake/premake4.lua

     
    55newoption { trigger = "gles", description = "Use non-working OpenGL ES 2.0 mode" }
    66newoption { trigger = "icc", description = "Use Intel C++ Compiler (Linux only; should use either \"--cc icc\" or --without-pch too, and then set CXX=icpc before calling make)" }
    77newoption { trigger = "outpath", description = "Location for generated project files" }
    8 newoption { trigger = "without-fam", description = "Disable use of FAM API on Linux" }
    98newoption { trigger = "without-audio", description = "Disable use of OpenAL/Ogg/Vorbis APIs" }
    109newoption { trigger = "minimal-flags", description = "Only set compiler/linker flags that are really needed. Has no effect on Windows builds" }
    1110newoption { trigger = "without-nvtt", description = "Disable use of NVTT" }
     
    156155        defines { "CONFIG2_GLES=1" }
    157156    end
    158157
    159     if _OPTIONS["without-fam"] then
    160         defines { "CONFIG2_FAM=0" }
    161     end
    162 
    163158    if _OPTIONS["without-audio"] then
    164159        defines { "CONFIG2_AUDIO=0" }
    165160    end
     
    780775
    781776    elseif os.is("linux") or os.is("bsd") then
    782777
    783         if not _OPTIONS["without-fam"] then
    784             links { "fam" }
    785         end
    786 
    787778        if not _OPTIONS["android"] and not (os.getversion().description == "OpenBSD") then
    788779            links { "rt" }
    789780        end
     
    12051196
    12061197    elseif os.is("linux") or os.is("bsd") then
    12071198
    1208         if not _OPTIONS["without-fam"] then
    1209             links { "fam" }
    1210         end
    1211 
    12121199        if not _OPTIONS["android"] and not (os.getversion().description == "OpenBSD") then
    12131200            links { "rt" }
    12141201        end
  • source/lib/sysdep/os/linux/dir_watch_fam.cpp

     
    2222
    2323#include "precompiled.h"
    2424
    25 #include <string>
    26 
    27 #include "lib/config2.h"
     25#include "lib/sysdep/dir_watch.h"
    2826#include "lib/sysdep/sysdep.h"
    29 #include "lib/sysdep/dir_watch.h"
    3027#include "ps/CLogger.h"
    3128
    32 #if !CONFIG2_FAM
     29#include <map>
     30#include <string>
     31#include <sys/inotify.h>
    3332
    34 // stub implementations
    3533
    36 Status dir_watch_Add(const OsPath& UNUSED(path), PDirWatch& UNUSED(dirWatch))
    37 {
    38     return INFO::OK;
    39 }
    40 
    41 Status dir_watch_Poll(DirWatchNotifications& UNUSED(notifications))
    42 {
    43     return INFO::OK;
    44 }
    45 
    46 #else
    47 
    48 #include <fam.h>
    49 
    50 // FAMEvent is large (~4KB), so define a smaller structure to store events
    5134struct NotificationEvent
    5235{
    5336    std::string filename;
    54     void *userdata;
    55     FAMCodes code;
     37    uint32_t code;
     38    int wd;
    5639};
    5740
    5841// To avoid deadlocks and slow synchronous reads, it's necessary to use a
    59 // separate thread for reading events from FAM.
     42// separate thread for reading events from inotify.
    6043// So we just spawn a thread to push events into this list, then swap it out
    6144// when someone calls dir_watch_Poll.
    6245// (We assume STL memory allocation is thread-safe.)
     
    6952
    7053// trool; -1 = init failed and all operations will be aborted silently.
    7154// this is so that each dir_* call doesn't complain if the system's
    72 // FAM is broken or unavailable.
     55// inotify is broken or unavailable.
    7356static int initialized = 0;
    7457
    75 static FAMConnection fc;
     58// Inotify file descriptor
     59static int inotifyfd;
     60 
     61// With inotify, using a map seems to be a good alternative to FAM's userdata
     62typedef std::map<int, PDirWatch> DirWatchMap;
     63static DirWatchMap g_paths;
    7664
    7765struct DirWatch
    7866{
     
    8472    ~DirWatch()
    8573    {
    8674        ENSURE(initialized > 0);
    87 
    88         FAMRequest req;
    89         req.reqnum = reqnum;
    90         FAMCancelMonitor(&fc, &req);
     75        inotify_rm_watch(inotifyfd, reqnum);
    9176    }
    9277
    9378    OsPath path;
    9479    int reqnum;
    9580};
    9681
    97 
    9882// for atexit
    99 static void fam_deinit()
     83static void inotify_deinit()
    10084{
    101     FAMClose(&fc);
     85    close(inotifyfd);
    10286
    10387    pthread_cancel(g_event_loop_thread);
    10488    // NOTE: POSIX threads are (by default) only cancellable inside particular
    10589    // functions (like 'select'), so this should safely terminate while it's
    106     // in select/FAMNextEvent/etc (and won't e.g. cancel while it's holding the
     90    // in select/etc (and won't e.g. cancel while it's holding the
    10791    // mutex)
    10892
    10993    // Wait for the thread to finish
    11094    pthread_join(g_event_loop_thread, NULL);
    11195}
    11296
    113 static void fam_event_loop_process_events()
     97static void inotify_event_loop_process_events()
    11498{
    115     while(FAMPending(&fc) > 0)
     99    // Buffer for reading the events.
     100    // Need to be careful about overflow here.
     101    char buffer[65535];
     102   
     103    // Event iterator
     104    ssize_t buffer_i = 0;
     105
     106    // Total size of all the events
     107    ssize_t r;
     108   
     109    // Size & struct for the current event
     110    size_t event_size;
     111    struct inotify_event *pevent;
     112       
     113    r = read(inotifyfd, buffer, 65535);
     114    if(r <= 0)
     115        return;
     116   
     117    while(buffer_i < r)
    116118    {
    117         FAMEvent e;
    118         if(FAMNextEvent(&fc, &e) < 0)
    119         {
    120             debug_printf(L"FAMNextEvent error");
    121             return;
    122         }
    123 
    124119        NotificationEvent ne;
    125         ne.filename = e.filename;
    126         ne.userdata = e.userdata;
    127         ne.code = e.code;
    128 
    129         pthread_mutex_lock(&g_mutex);
    130         g_notifications.push_back(ne);
    131         pthread_mutex_unlock(&g_mutex);
     120        pevent = (struct inotify_event *) &buffer[buffer_i];
     121       
     122        event_size = offsetof(struct inotify_event, name) + pevent->len;
     123        ne.wd = pevent->wd;
     124        ne.filename = pevent->name;
     125        ne.code = pevent->mask;
     126       
     127        pthread_mutex_lock(&g_mutex);
     128        g_notifications.push_back(ne);
     129        pthread_mutex_unlock(&g_mutex);
     130       
     131        buffer_i += event_size;
    132132    }
    133133}
    134134
    135 static void* fam_event_loop(void*)
     135static void* inotify_event_loop(void*)
    136136{
    137     int famfd = FAMCONNECTION_GETFD(&fc);
    138 
    139137    while(true)
    140138    {
    141139        fd_set fdrset;
    142140        FD_ZERO(&fdrset);
    143         FD_SET(famfd, &fdrset);
    144 
     141        FD_SET(inotifyfd, &fdrset);
     142        errno = 0;
    145143        // Block with select until there's events waiting
    146         // (Mustn't just block inside FAMNextEvent since fam will deadlock)
    147         while(select(famfd+1, &fdrset, NULL, NULL, NULL) < 0)
     144        while(select(inotifyfd+1, &fdrset, NULL, NULL, NULL) < 0)
    148145        {
    149146            if(errno == EINTR)
    150147            {
    151148                // interrupted - try again
    152149                FD_ZERO(&fdrset);
    153                 FD_SET(famfd, &fdrset);
     150                FD_SET(inotifyfd, &fdrset);
    154151            }
    155152            else if(errno == EBADF)
    156153            {
    157                 // probably just lost the connection to FAM - kill the thread
    158                 debug_printf(L"lost connection to FAM");
     154                // probably just lost the connection to inotify - kill the thread
     155                debug_printf(L"inotify_event_loop: Invalid file descriptor inotifyfd=%d\n", inotifyfd);
    159156                return NULL;
    160157            }
    161158            else
    162159            {
    163160                // oops
    164                 debug_printf(L"select error %d", errno);
     161                debug_printf(L"inotify_event_loop: select error errno=%d\n", errno);
    165162                return NULL;
    166163            }
     164            errno = 0;
    167165        }
    168 
    169         if(FD_ISSET(famfd, &fdrset))
    170             fam_event_loop_process_events();
     166        if(FD_ISSET(inotifyfd, &fdrset))
     167            inotify_event_loop_process_events();
    171168    }
    172169}
    173170
    174171Status dir_watch_Add(const OsPath& path, PDirWatch& dirWatch)
    175172{
    176     // init already failed; don't try again or complain
     173    char resolved[PATH_MAX + 1];
     174   
     175    // init already failed; don't try again or complain
    177176    if(initialized == -1)
    178177        return ERR::FAIL;   // NOWARN
    179178
    180179    if(!initialized)
    181180    {
    182         if(FAMOpen2(&fc, "lib_res"))
     181        errno = 0;
     182        if((inotifyfd = inotify_init()) < 0)
    183183        {
     184            // Check for error ?
     185            int err = errno;
    184186            initialized = -1;
    185             LOGERROR(L"Error initializing FAM; hotloading will be disabled");
    186             return ERR::FAIL;   // NOWARN
     187            LOGERROR(L"Error initializing inotify file descriptor; hotloading will be disabled, errno=%d", err);
     188            errno = err;
     189            return StatusFromErrno();   // NOWARN
    187190        }
    188191       
    189         if (pthread_create(&g_event_loop_thread, NULL, &fam_event_loop, NULL))
     192        errno = 0;
     193        int ret = pthread_create(&g_event_loop_thread, NULL, &inotify_event_loop, NULL);
     194        if (ret != 0)
    190195        {
    191196            initialized = -1;
    192             LOGERROR(L"Error creating FAM event loop thread; hotloading will be disabled");
    193             return ERR::FAIL;   // NOWARN
     197            LOGERROR(L"Error creating inotify event loop thread; hotloading will be disabled, err=%d", ret);
     198            errno = ret;
     199            return StatusFromErrno();   // NOWARN
    194200        }
    195201
    196202        initialized = 1;
    197         atexit(fam_deinit);
     203        atexit(inotify_deinit);
    198204    }
    199205
    200206    PDirWatch tmpDirWatch(new DirWatch);
    201 
    202     // NOTE: It would be possible to use FAMNoExists iff we're building with Gamin
    203     // (not FAM), to avoid a load of boring notifications when we add a directory,
    204     // but it would only save tens of milliseconds of CPU time, so it's probably
    205     // not worthwhile
    206 
    207     FAMRequest req;
    208     if(FAMMonitorDirectory(&fc, OsString(path).c_str(), &req, tmpDirWatch.get()) < 0)
    209     {
    210         debug_warn(L"res_watch_dir failed!");
    211         WARN_RETURN(ERR::FAIL); // no way of getting error code?
    212     }
    213 
     207    errno = 0;
     208    int wd = inotify_add_watch(inotifyfd, realpath(OsString(path).c_str(), resolved), IN_CREATE | IN_DELETE | IN_CLOSE_WRITE);
     209    if (wd < 0)
     210        WARN_RETURN(StatusFromErrno());
     211   
    214212    dirWatch.swap(tmpDirWatch);
    215213    dirWatch->path = path;
    216     dirWatch->reqnum = req.reqnum;
    217 
     214    dirWatch->reqnum = wd;
     215    g_paths.insert(std::make_pair(wd, dirWatch));
     216   
    218217    return INFO::OK;
    219218}
    220219
    221 
    222 
    223220Status dir_watch_Poll(DirWatchNotifications& notifications)
    224221{
    225222    if(initialized == -1)
     
    236233    for(size_t i = 0; i < polled_notifications.size(); ++i)
    237234    {
    238235        DirWatchNotification::EType type;
     236        // TODO: code is actually a bitmask, so this is slightly incorrect
    239237        switch(polled_notifications[i].code)
    240238        {
    241         case FAMChanged:
     239        case IN_CLOSE_WRITE:
    242240            type = DirWatchNotification::Changed;
    243241            break;
    244         case FAMCreated:
     242        case IN_CREATE:
    245243            type = DirWatchNotification::Created;
    246244            break;
    247         case FAMDeleted:
     245        case IN_DELETE:
    248246            type = DirWatchNotification::Deleted;
    249247            break;
    250248        default:
    251249            continue;
    252250        }
    253         DirWatch* dirWatch = (DirWatch*)polled_notifications[i].userdata;
    254         OsPath pathname = dirWatch->path / polled_notifications[i].filename;
    255         notifications.push_back(DirWatchNotification(pathname, type));
     251
     252        DirWatchMap::iterator it = g_paths.find(polled_notifications[i].wd);
     253        if(it != g_paths.end())
     254        {
     255            OsPath filename = Path(OsString(it->second->path).append(polled_notifications[i].filename));
     256            notifications.push_back(DirWatchNotification(filename, type));
     257        }
     258        else
     259        {
     260            debug_printf(L"dir_watch_Poll: Notification with invalid watch descriptor wd=%d\n", polled_notifications[i].wd);
     261        }
    256262    }
    257263
    258264    // nothing new; try again later
    259265    return INFO::OK;
    260266}
    261267
    262 #endif  // CONFIG2_FAM
  • source/lib/config2.h

     
    101101# define CONFIG2_GLES 0
    102102#endif
    103103
    104 // allow use of FAM API on Linux
    105 #ifndef CONFIG2_FAM
    106 # define CONFIG2_FAM 0
    107 #endif
    108 
    109104// allow use of OpenAL/Ogg/Vorbis APIs
    110105#ifndef CONFIG2_AUDIO
    111106# define CONFIG2_AUDIO 1