/* $Id: HostDnsServiceLinux.cpp 98067 2023-01-12 23:04:34Z vboxsync $ */ /** @file * Linux specific DNS information fetching. */ /* * Copyright (C) 2013-2022 Oracle and/or its affiliates. * * This file is part of VirtualBox base platform packages, as * available from https://www.virtualbox.org. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation, in version 3 of the * License. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . * * SPDX-License-Identifier: GPL-3.0-only */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* Workaround for defining __flexarr to [] which beats us in * struct inotify_event (char name __flexarr). */ #include #undef __flexarr #define __flexarr [0] #include #include #include #include #include #include "../HostDnsService.h" static int g_DnsMonitorStop[2]; static const std::string g_EtcFolder = "/etc"; static const std::string g_ResolvConf = "resolv.conf"; static const std::string g_ResolvConfFullPath = "/etc/resolv.conf"; class FileDescriptor { public: FileDescriptor(int d = -1) : fd(d) {} virtual ~FileDescriptor() { if (fd != -1) close(fd); } int fileDescriptor() const {return fd;} protected: int fd; }; class AutoNotify : public FileDescriptor { public: AutoNotify() { FileDescriptor::fd = inotify_init(); AssertReturnVoid(FileDescriptor::fd != -1); } }; struct InotifyEventWithName { struct inotify_event e; char name[NAME_MAX]; }; HostDnsServiceLinux::~HostDnsServiceLinux() { } HRESULT HostDnsServiceLinux::init(HostDnsMonitorProxy *pProxy) { return HostDnsServiceResolvConf::init(pProxy, "/etc/resolv.conf"); } int HostDnsServiceLinux::monitorThreadShutdown(RTMSINTERVAL uTimeoutMs) { RT_NOREF(uTimeoutMs); send(g_DnsMonitorStop[0], "", 1, 0); /** @todo r=andy Do we have to wait for something here? Can this fail? */ return VINF_SUCCESS; } int HostDnsServiceLinux::monitorThreadProc(void) { /* * inotify initialization * * Note! Ignoring failures here is safe, because poll will ignore entires * with negative fd values. */ AutoNotify a; int wd[2]; wd[0] = inotify_add_watch(a.fileDescriptor(), g_ResolvConfFullPath.c_str(), IN_CLOSE_WRITE | IN_DELETE_SELF); /* If /etc/resolv.conf exists we want to listen for movements: because # mv /etc/resolv.conf ... won't arm IN_DELETE_SELF on wd[0] instead it will fire IN_MOVE_FROM on wd[1]. Because on some distributions /etc/resolv.conf is a symlink, wd[0] can't detect deletion, it's recognizible on directory level (wd[1]) only. */ wd[1] = inotify_add_watch(a.fileDescriptor(), g_EtcFolder.c_str(), wd[0] == -1 ? IN_MOVED_TO | IN_CREATE : IN_MOVED_FROM | IN_DELETE); /* * Create a socket pair for signalling shutdown via (see monitorThreadShutdown). */ int rc = socketpair(AF_LOCAL, SOCK_DGRAM, 0, g_DnsMonitorStop); AssertMsgReturn(rc == 0, ("socketpair: failed (%d: %s)\n", errno, strerror(errno)), E_FAIL); /* automatic cleanup tricks */ FileDescriptor stopper0(g_DnsMonitorStop[0]); FileDescriptor stopper1(g_DnsMonitorStop[1]); /* * poll initialization: */ pollfd polls[2]; RT_ZERO(polls); polls[0].fd = a.fileDescriptor(); polls[0].events = POLLIN; polls[1].fd = g_DnsMonitorStop[1]; polls[1].events = POLLIN; onMonitorThreadInitDone(); /* * The monitoring loop. */ for (;;) { rc = poll(polls, RT_ELEMENTS(polls), -1 /*infinite timeout*/); if (rc == -1) continue; AssertMsgReturn( (polls[0].revents & (POLLERR | POLLNVAL)) == 0 && (polls[1].revents & (POLLERR | POLLNVAL)) == 0, ("Debug Me"), VERR_INTERNAL_ERROR); if (polls[1].revents & POLLIN) return VINF_SUCCESS; /* time to shutdown */ if (polls[0].revents & POLLIN) { /* * Read the notification event. */ /** @todo r=bird: This is buggy in that it somehow assumes we'll only get * one event here. But since we're waiting on two different DELETE * events for both a specific file and its parent directory, we're likely * to get two DELETE events at the same time. */ struct InotifyEventWithName combo; RT_ZERO(combo); combo.e.wd = -42; /* avoid confusion on the offchance that wd[0] or wd[1] is zero. */ ssize_t r = read(polls[0].fd, &combo, sizeof(combo)); RT_NOREF(r); if (combo.e.wd == wd[0]) { if (combo.e.mask & IN_CLOSE_WRITE) readResolvConf(); else if (combo.e.mask & IN_DELETE_SELF) { inotify_rm_watch(a.fileDescriptor(), wd[0]); /* removes file watcher */ int wd2 = inotify_add_watch(a.fileDescriptor(), g_EtcFolder.c_str(), IN_MOVED_TO|IN_CREATE); /* alter folder watcher */ Assert(wd2 == wd[1]); RT_NOREF(wd2); /* ASSUMES wd[1] will be updated */ } else if (combo.e.mask & IN_IGNORED) wd[0] = -1; /* we want receive any events on this watch */ else { /* * It shouldn't happen, in release we will just ignore in debug * we will have to chance to look at into inotify_event */ AssertMsgFailed(("Debug Me!!!")); } } else if (combo.e.wd == wd[1]) { if (combo.e.mask & (IN_DELETE | IN_MOVED_FROM)) { if (g_ResolvConf == combo.e.name) { /* * Our file has been moved or deleted so we should change watching mode. */ inotify_rm_watch(a.fileDescriptor(), wd[0]); wd[1] = inotify_add_watch(a.fileDescriptor(), g_EtcFolder.c_str(), IN_MOVED_TO | IN_CREATE); AssertMsg(wd[1] != -1, ("It shouldn't happen, further investigation is needed\n")); } } else { AssertMsg(combo.e.mask & (IN_MOVED_TO | IN_CREATE), ("%RX32 event isn't expected, we are waiting for IN_MOVED|IN_CREATE\n", combo.e.mask)); if (g_ResolvConf == combo.e.name) { AssertMsg(wd[0] == -1, ("We haven't removed file watcher first\n")); /* alter folder watcher: */ wd[1] = inotify_add_watch(a.fileDescriptor(), g_EtcFolder.c_str(), IN_MOVED_FROM | IN_DELETE); AssertMsg(wd[1] != -1, ("It shouldn't happen.\n")); wd[0] = inotify_add_watch(a.fileDescriptor(), g_ResolvConfFullPath.c_str(), IN_CLOSE_WRITE | IN_DELETE_SELF); AssertMsg(wd[0] != -1, ("Adding watcher to file (%s) has been failed!\n", g_ResolvConfFullPath.c_str())); /* Notify our listeners */ readResolvConf(); } } } else { /* It shouldn't happen */ AssertMsgFailed(("Shouldn't happen! Please debug me!")); } } } }