VirtualBox

Changeset 98068 in vbox for trunk/src/VBox/Main


Ignore:
Timestamp:
Jan 13, 2023 4:14:20 AM (2 years ago)
Author:
vboxsync
Message:

Main/HostDnsServiceLinux.cpp: Worked out a bunch of bugs and issues with HostDnsServiceLinux::monitorThreadProc. Main problems: 1. dropping events because of only processing the first one read returned when read can return lots of them. 2. modifying directory watch config means races. 3. if symlinked we must also watch the directory where the actual resolv.conf file is. bugref:10255

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/VBox/Main/src-server/linux/HostDnsServiceLinux.cpp

    r98067 r98068  
    2626 */
    2727
     28
     29/*********************************************************************************************************************************
     30*   Header Files                                                                                                                 *
     31*********************************************************************************************************************************/
     32#define LOG_GROUP LOG_GROUP_MAIN_HOST
    2833#include <iprt/assert.h>
    2934#include <iprt/errcore.h>
    3035#include <iprt/initterm.h>
    3136#include <iprt/file.h>
    32 #include <iprt/log.h>
     37#include <VBox/log.h>
    3338#include <iprt/stream.h>
    3439#include <iprt/string.h>
     
    5358#include <sys/types.h>
    5459#include <sys/socket.h>
    55 
    56 #include <iprt/sanitized/string>
    57 #include <vector>
     60#include <sys/stat.h>
     61
    5862#include "../HostDnsService.h"
    5963
    6064
     65/*********************************************************************************************************************************
     66*   Global Variables                                                                                                             *
     67*********************************************************************************************************************************/
    6168static int g_DnsMonitorStop[2];
    6269
    63 static const std::string g_EtcFolder = "/etc";
    64 static const std::string g_ResolvConf = "resolv.conf";
    65 static const std::string g_ResolvConfFullPath = "/etc/resolv.conf";
     70static const char g_szEtcFolder[]          = "/etc";
     71static const char g_szResolvConfPath[]     = "/etc/resolv.conf";
     72static const char g_szResolvConfFilename[] = "resolv.conf";
     73
    6674
    6775class FileDescriptor
     
    94102};
    95103
    96 struct InotifyEventWithName
    97 {
    98     struct inotify_event e;
    99     char name[NAME_MAX];
    100 };
    101 
    102104HostDnsServiceLinux::~HostDnsServiceLinux()
    103105{
     
    119121}
    120122
     123#ifdef LOG_ENABLED
     124/**
     125 * Format the notifcation event mask into a buffer for logging purposes.
     126 */
     127static const char *InotifyMaskToStr(char *psz, size_t cb, uint32_t fMask)
     128{
     129    static struct { const char *pszName; uint32_t cchName, fFlag; } const s_aFlags[] =
     130    {
     131# define ENTRY(fFlag)   { #fFlag, sizeof(#fFlag) - 1, fFlag }
     132        ENTRY(IN_ACCESS),
     133        ENTRY(IN_MODIFY),
     134        ENTRY(IN_ATTRIB),
     135        ENTRY(IN_CLOSE_WRITE),
     136        ENTRY(IN_CLOSE_NOWRITE),
     137        ENTRY(IN_OPEN),
     138        ENTRY(IN_MOVED_FROM),
     139        ENTRY(IN_MOVED_TO),
     140        ENTRY(IN_CREATE),
     141        ENTRY(IN_DELETE),
     142        ENTRY(IN_DELETE_SELF),
     143        ENTRY(IN_MOVE_SELF),
     144        ENTRY(IN_Q_OVERFLOW),
     145        ENTRY(IN_IGNORED),
     146        ENTRY(IN_UNMOUNT),
     147        ENTRY(IN_ISDIR),
     148    };
     149    size_t offDst = 0;
     150    for (size_t i = 0; i < RT_ELEMENTS(s_aFlags); i++)
     151        if (fMask & s_aFlags[i].fFlag)
     152        {
     153            if (offDst && offDst < cb)
     154                psz[offDst++] = ' ';
     155            if (offDst < cb)
     156            {
     157                size_t cbToCopy = RT_MIN(s_aFlags[i].cchName, cb - offDst);
     158                memcpy(&psz[offDst], s_aFlags[i].pszName, cbToCopy);
     159                offDst += cbToCopy;
     160            }
     161
     162            fMask &= ~s_aFlags[i].fFlag;
     163            if (!fMask)
     164                break;
     165        }
     166    if (fMask && offDst < cb)
     167        RTStrPrintf(&psz[offDst], cb - offDst, offDst ? " %#x" : "%#x", fMask);
     168    else
     169        psz[RT_MIN(offDst, cb - 1)] = '\0';
     170    return psz;
     171}
     172#endif
     173
     174
     175/**
     176 * Helper for HostDnsServiceLinux::monitorThreadProc.
     177 */
     178static int monitorSymlinkedDir(int iInotifyFd, char szRealResolvConf[PATH_MAX], size_t *poffFilename)
     179{
     180    RT_BZERO(szRealResolvConf, PATH_MAX);
     181
     182    /* Check that it's a symlink first. */
     183    struct stat st;
     184    if (   lstat(g_szResolvConfPath, &st) >= 0
     185        && S_ISLNK(st.st_mode))
     186    {
     187        /* If realpath fails, the file must've been deleted while we were busy: */
     188        if (   realpath(g_szResolvConfPath, szRealResolvConf)
     189            && strchr(szRealResolvConf, '/'))
     190        {
     191            /* Cut of the filename part. We only need that for deletion checks and such. */
     192            size_t const offFilename = strrchr(szRealResolvConf, '/') - &szRealResolvConf[0];
     193            *poffFilename = offFilename + 1;
     194            szRealResolvConf[offFilename] = '\0';
     195
     196            /* Try set up directory monitoring. (File monitoring is done via the symlink.) */
     197            return inotify_add_watch(iInotifyFd, szRealResolvConf, IN_MOVE | IN_CREATE | IN_DELETE);
     198        }
     199    }
     200
     201    *poffFilename = 0;
     202    szRealResolvConf[0] = '\0';
     203    return -1;
     204}
     205
    121206int HostDnsServiceLinux::monitorThreadProc(void)
    122207{
    123208    /*
    124      * inotify initialization
     209     * inotify initialization.
     210     *
     211     * The order here helps keep the descriptor values stable.
    125212     *
    126213     * Note! Ignoring failures here is safe, because poll will ignore entires
    127214     *       with negative fd values.
    128215     */
    129     AutoNotify a;
    130     int wd[2];
    131     wd[0] = inotify_add_watch(a.fileDescriptor(),
    132                               g_ResolvConfFullPath.c_str(), IN_CLOSE_WRITE | IN_DELETE_SELF);
    133 
    134     /* If /etc/resolv.conf exists we want to listen for movements: because
    135        # mv /etc/resolv.conf ...
    136        won't arm IN_DELETE_SELF on wd[0] instead it will fire IN_MOVE_FROM on wd[1].
    137 
    138        Because on some distributions /etc/resolv.conf is a symlink, wd[0] can't detect
    139        deletion, it's recognizible on directory level (wd[1]) only. */
    140     wd[1] = inotify_add_watch(a.fileDescriptor(), g_EtcFolder.c_str(),
    141                               wd[0] == -1 ? IN_MOVED_TO | IN_CREATE : IN_MOVED_FROM | IN_DELETE);
     216    AutoNotify Notify;
     217
     218    /* Monitor the /etc directory so we can detect moves, unliking and creations
     219       involving /etc/resolv.conf:  */
     220    int const iWdDir = inotify_add_watch(Notify.fileDescriptor(), g_szEtcFolder, IN_MOVE | IN_CREATE | IN_DELETE);
     221
     222    /* In case g_szResolvConfPath is a symbolic link, monitor the target directory
     223       too for changes to what it links to. */
     224    char   szRealResolvConf[PATH_MAX];
     225    size_t offRealResolvConfName = 0;
     226    int iWdSymDir = ::monitorSymlinkedDir(Notify.fileDescriptor(), szRealResolvConf, &offRealResolvConfName);
     227
     228    /* Monitor the resolv.conf itself if it exists, following all symlinks. */
     229    int iWdFile = inotify_add_watch(Notify.fileDescriptor(), g_szResolvConfPath, IN_CLOSE_WRITE | IN_DELETE_SELF);
     230
     231    Log5Func(("iWdDir=%d iWdSymDir=%d iWdFile=%d\n", iWdDir, iWdSymDir, iWdFile));
    142232
    143233    /*
     
    154244     * poll initialization:
    155245     */
    156     pollfd polls[2];
    157     RT_ZERO(polls);
    158 
    159     polls[0].fd = a.fileDescriptor();
    160     polls[0].events = POLLIN;
    161 
    162     polls[1].fd = g_DnsMonitorStop[1];
    163     polls[1].events = POLLIN;
     246    pollfd aFdPolls[2];
     247    RT_ZERO(aFdPolls);
     248
     249    aFdPolls[0].fd = Notify.fileDescriptor();
     250    aFdPolls[0].events = POLLIN;
     251
     252    aFdPolls[1].fd = g_DnsMonitorStop[1];
     253    aFdPolls[1].events = POLLIN;
    164254
    165255    onMonitorThreadInitDone();
     
    170260    for (;;)
    171261    {
    172         rc = poll(polls, RT_ELEMENTS(polls), -1 /*infinite timeout*/);
     262        /*
     263         * Wait for something to happen.
     264         */
     265        rc = poll(aFdPolls, RT_ELEMENTS(aFdPolls), -1 /*infinite timeout*/);
    173266        if (rc == -1)
     267        {
     268            LogRelMax(32, ("HostDnsServiceLinux::monitorThreadProc: poll failed %d: errno=%d\n", rc, errno));
     269            RTThreadSleep(1);
    174270            continue;
    175 
    176         AssertMsgReturn(   (polls[0].revents & (POLLERR | POLLNVAL)) == 0
    177                         && (polls[1].revents & (POLLERR | POLLNVAL)) == 0, ("Debug Me"), VERR_INTERNAL_ERROR);
    178 
    179         if (polls[1].revents & POLLIN)
    180             return VINF_SUCCESS; /* time to shutdown */
    181 
    182         if (polls[0].revents & POLLIN)
     271        }
     272        Log5Func(("poll returns %d: [0]=%#x [1]=%#x\n", rc, aFdPolls[1].revents, aFdPolls[0].revents));
     273
     274        AssertMsgReturn(   (aFdPolls[0].revents & (POLLERR | POLLNVAL)) == 0
     275                        && (aFdPolls[1].revents & (POLLERR | POLLNVAL)) == 0, ("Debug Me"), VERR_INTERNAL_ERROR);
     276
     277
     278        /*
     279         * Check for shutdown first.
     280         */
     281        if (aFdPolls[1].revents & POLLIN)
     282            return VINF_SUCCESS;
     283
     284        if (aFdPolls[0].revents & POLLIN)
    183285        {
    184286            /*
    185287             * Read the notification event.
    186288             */
    187             /** @todo r=bird: This is buggy in that it somehow assumes we'll only get
    188              *        one event here.  But since we're waiting on two different DELETE
    189              *        events for both a specific file and its parent directory, we're likely
    190              *        to get two DELETE events at the same time. */
    191             struct InotifyEventWithName combo;
    192             RT_ZERO(combo);
    193             combo.e.wd = -42; /* avoid confusion on the offchance that wd[0] or wd[1] is zero. */
    194 
    195             ssize_t r = read(polls[0].fd, &combo, sizeof(combo));
    196             RT_NOREF(r);
    197 
    198             if (combo.e.wd == wd[0])
     289#define INOTIFY_EVENT_SIZE  (RT_UOFFSETOF(struct inotify_event, name))
     290            union
    199291            {
    200                 if (combo.e.mask & IN_CLOSE_WRITE)
    201                     readResolvConf();
    202                 else if (combo.e.mask & IN_DELETE_SELF)
     292                uint8_t     abBuf[(INOTIFY_EVENT_SIZE * 2 - 1 + NAME_MAX) / INOTIFY_EVENT_SIZE * INOTIFY_EVENT_SIZE * 4];
     293                uint64_t    uAlignTrick[2];
     294            } uEvtBuf;
     295
     296            ssize_t cbEvents = read(Notify.fileDescriptor(), &uEvtBuf, sizeof(uEvtBuf));
     297            Log5Func(("read(inotify) -> %zd\n", cbEvents));
     298            if (cbEvents > 0)
     299                Log5(("%.*Rhxd\n", cbEvents, &uEvtBuf));
     300
     301            /*
     302             * Process the events.
     303             *
     304             * We'll keep the old watch descriptor number till after we're done
     305             * parsing this block of events.  Even so, the removal of watches
     306             * isn't race free, as they'll get automatically removed when what
     307             * is being watched is unliked.
     308             */
     309            int                         iWdFileNew   = iWdFile;
     310            int                         iWdSymDirNew = iWdSymDir;
     311            bool                        fTryReRead   = false;
     312            struct inotify_event const *pCurEvt      = (struct inotify_event const *)&uEvtBuf;
     313            while (cbEvents >= (ssize_t)INOTIFY_EVENT_SIZE)
     314            {
     315#ifdef LOG_ENABLED
     316                char szTmp[64];
     317                if (pCurEvt->len == 0)
     318                    Log5Func(("event: wd=%#x mask=%#x (%s) cookie=%#x\n", pCurEvt->wd, pCurEvt->mask,
     319                              InotifyMaskToStr(szTmp, sizeof(szTmp), pCurEvt->mask), pCurEvt->cookie));
     320                else
     321                    Log5Func(("event: wd=%#x mask=%#x (%s) cookie=%#x len=%#x '%s'\n",
     322                              pCurEvt->wd, pCurEvt->mask, InotifyMaskToStr(szTmp, sizeof(szTmp), pCurEvt->mask),
     323                              pCurEvt->cookie, pCurEvt->len, pCurEvt->name));
     324#endif
     325
     326                /*
     327                 * The file itself (symlinks followed, remember):
     328                 */
     329                if (pCurEvt->wd == iWdFile)
    203330                {
    204                     inotify_rm_watch(a.fileDescriptor(), wd[0]); /* removes file watcher */
    205                     int wd2 = inotify_add_watch(a.fileDescriptor(), g_EtcFolder.c_str(),
    206                                                 IN_MOVED_TO|IN_CREATE); /* alter folder watcher */
    207                     Assert(wd2 == wd[1]); RT_NOREF(wd2); /* ASSUMES wd[1] will be updated */
     331                    if (pCurEvt->mask & IN_CLOSE_WRITE)
     332                    {
     333                        Log5Func(("file: close-after-write => trigger re-read\n"));
     334                        fTryReRead = true;
     335                    }
     336                    else if (pCurEvt->mask & IN_DELETE_SELF)
     337                    {
     338                        Log5Func(("file: deleted self\n"));
     339                        if (iWdFileNew != -1)
     340                        {
     341                            rc = inotify_rm_watch(Notify.fileDescriptor(), iWdFileNew);
     342                            AssertMsg(rc >= 0, ("%d/%d\n", rc, errno));
     343                            iWdFileNew = -1;
     344                        }
     345                    }
     346                    else if (pCurEvt->mask & IN_IGNORED)
     347                        iWdFileNew = -1; /* file deleted */
     348                    else
     349                        AssertMsgFailed(("file: mask=%#x\n", pCurEvt->mask));
    208350                }
    209                 else if (combo.e.mask & IN_IGNORED)
    210                     wd[0] = -1; /* we want receive any events on this watch */
     351                /*
     352                 * The /etc directory
     353                 *
     354                 * We only care about events relating to the creation, deletion and
     355                 * renaming of 'resolv.conf'.  We'll restablish both the direct file
     356                 * watching and the watching of any symlinked directory on all of
     357                 * these events, although for the former we'll delay the re-starting
     358                 * of the watching till all events have been processed.
     359                 */
     360                else if (pCurEvt->wd == iWdDir)
     361                {
     362                    if (   pCurEvt->len > 0
     363                        && strcmp(g_szResolvConfFilename, pCurEvt->name) == 0)
     364                    {
     365                        if (pCurEvt->mask & (IN_MOVE | IN_CREATE | IN_DELETE))
     366                        {
     367                            if (iWdFileNew >= 0)
     368                            {
     369                                rc = inotify_rm_watch(Notify.fileDescriptor(), iWdFileNew);
     370                                Log5Func(("dir: moved / created / deleted: dropped file watch (%d - rc=%d/err=%d)\n",
     371                                          iWdFileNew, rc, errno));
     372                                iWdFileNew = -1;
     373                            }
     374                            if (iWdSymDirNew >= 0)
     375                            {
     376                                rc = inotify_rm_watch(Notify.fileDescriptor(), iWdSymDirNew);
     377                                Log5Func(("dir: moved / created / deleted: dropped symlinked dir watch (%d - %s/%s - rc=%d/err=%d)\n",
     378                                          iWdSymDirNew, szRealResolvConf, &szRealResolvConf[offRealResolvConfName], rc, errno));
     379                                iWdSymDirNew = -1;
     380                                offRealResolvConfName = 0;
     381                            }
     382                            if (pCurEvt->mask & (IN_MOVED_TO | IN_CREATE))
     383                            {
     384                                Log5Func(("dir: moved_to / created: trigger re-read\n"));
     385                                fTryReRead = true;
     386
     387                                iWdSymDirNew = ::monitorSymlinkedDir(Notify.fileDescriptor(),
     388                                                                     szRealResolvConf, &offRealResolvConfName);
     389                                if (iWdSymDirNew < 0)
     390                                    Log5Func(("dir: moved_to / created: re-stablished symlinked-directory monitoring: iWdSymDir=%d (%s/%s)\n",
     391                                              iWdSymDirNew, szRealResolvConf, &szRealResolvConf[offRealResolvConfName]));
     392                            }
     393                        }
     394                        else
     395                            AssertMsgFailed(("dir: %#x\n", pCurEvt->mask));
     396                    }
     397                }
     398                /*
     399                 * The directory of a symlinked resolv.conf.
     400                 *
     401                 * Where we only care when the symlink target is created, moved_to,
     402                 * deleted or moved_from - i.e. a minimal version of the /etc event
     403                 * processing above.
     404                 *
     405                 * Note! Since we re-statablish monitoring above, szRealResolvConf
     406                 *       might not match the event we're processing.  Fortunately,
     407                 *       this shouldn't be important except for debug logging.
     408                 */
     409                else if (pCurEvt->wd == iWdSymDir)
     410                {
     411                    if (   pCurEvt->len > 0
     412                        && offRealResolvConfName > 0
     413                        && strcmp(&szRealResolvConf[offRealResolvConfName], pCurEvt->name) == 0)
     414                    {
     415                        if (iWdFileNew >= 0)
     416                        {
     417                            rc = inotify_rm_watch(Notify.fileDescriptor(), iWdFileNew);
     418                            Log5Func(("symdir: moved / created / deleted: drop file watch (%d - rc=%d/err=%d)\n",
     419                                      iWdFileNew, rc, errno));
     420                            iWdFileNew = -1;
     421                        }
     422                        if (pCurEvt->mask & (IN_MOVED_TO | IN_CREATE))
     423                        {
     424                            Log5Func(("symdir: moved_to / created: trigger re-read\n"));
     425                            fTryReRead = true;
     426                        }
     427                    }
     428                }
     429                /* We can get here it seems if our inotify_rm_watch calls above takes
     430                   place after new events relating to the two descriptors happens. */
    211431                else
     432                    Log5Func(("Unknown (obsoleted) wd value: %d (mask=%#x cookie=%#x len=%#x)\n",
     433                              pCurEvt->wd, pCurEvt->mask, pCurEvt->cookie, pCurEvt->len));
     434
     435                /* advance to the next event */
     436                Assert(pCurEvt->len / INOTIFY_EVENT_SIZE * INOTIFY_EVENT_SIZE == pCurEvt->len);
     437                size_t const cbCurEvt = INOTIFY_EVENT_SIZE + pCurEvt->len;
     438                pCurEvt   = (struct inotify_event const *)((uintptr_t)pCurEvt + cbCurEvt);
     439                cbEvents -= cbCurEvt;
     440            }
     441
     442            /*
     443             * Commit the new watch descriptor numbers now that we're
     444             * done processing event using the old ones.
     445             */
     446            iWdFile   = iWdFileNew;
     447            iWdSymDir = iWdSymDirNew;
     448
     449            /*
     450             * If the resolv.conf watch descriptor is -1, try restablish it here.
     451             */
     452            if (iWdFile == -1)
     453            {
     454                iWdFile = inotify_add_watch(Notify.fileDescriptor(), g_szResolvConfPath, IN_CLOSE_WRITE | IN_DELETE_SELF);
     455                if (iWdFile >= 0)
    212456                {
    213                     /*
    214                      * It shouldn't happen, in release we will just ignore in debug
    215                      * we will have to chance to look at into inotify_event
    216                      */
    217                     AssertMsgFailed(("Debug Me!!!"));
     457                    Log5Func(("Re-established file watcher: iWdFile=%d\n", iWdFile));
     458                    fTryReRead = true;
    218459                }
    219460            }
    220             else if (combo.e.wd == wd[1])
     461
     462            /*
     463             * If any of the events indicate that we should re-read the file, we
     464             * do so now.  Should reduce number of unnecessary re-reads.
     465             */
     466            if (fTryReRead)
    221467            {
    222                 if (combo.e.mask & (IN_DELETE | IN_MOVED_FROM))
    223                 {
    224                     if (g_ResolvConf == combo.e.name)
    225                     {
    226                         /*
    227                          * Our file has been moved or deleted so we should change watching mode.
    228                          */
    229                         inotify_rm_watch(a.fileDescriptor(), wd[0]);
    230                         wd[1] = inotify_add_watch(a.fileDescriptor(), g_EtcFolder.c_str(),
    231                                                   IN_MOVED_TO | IN_CREATE);
    232                         AssertMsg(wd[1] != -1,
    233                                   ("It shouldn't happen, further investigation is needed\n"));
    234                     }
    235                 }
    236                 else
    237                 {
    238                     AssertMsg(combo.e.mask & (IN_MOVED_TO | IN_CREATE),
    239                               ("%RX32 event isn't expected, we are waiting for IN_MOVED|IN_CREATE\n", combo.e.mask));
    240                     if (g_ResolvConf == combo.e.name)
    241                     {
    242                         AssertMsg(wd[0] == -1, ("We haven't removed file watcher first\n"));
    243 
    244                         /* alter folder watcher: */
    245                         wd[1] = inotify_add_watch(a.fileDescriptor(),
    246                                                   g_EtcFolder.c_str(),
    247                                                   IN_MOVED_FROM | IN_DELETE);
    248                         AssertMsg(wd[1] != -1, ("It shouldn't happen.\n"));
    249 
    250                         wd[0] = inotify_add_watch(a.fileDescriptor(),
    251                                                   g_ResolvConfFullPath.c_str(),
    252                                                   IN_CLOSE_WRITE | IN_DELETE_SELF);
    253                         AssertMsg(wd[0] != -1, ("Adding watcher to file (%s) has been failed!\n",
    254                                                 g_ResolvConfFullPath.c_str()));
    255 
    256                         /* Notify our listeners */
    257                         readResolvConf();
    258                     }
    259                 }
    260             }
    261             else
    262             {
    263                 /* It shouldn't happen */
    264                 AssertMsgFailed(("Shouldn't happen! Please debug me!"));
     468                Log5Func(("Calling readResolvConf()...\n"));
     469                readResolvConf();
    265470            }
    266471        }
Note: See TracChangeset for help on using the changeset viewer.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette