diff options
Diffstat (limited to 'src/network/kernel/qhostinfo_unix.cpp')
-rw-r--r-- | src/network/kernel/qhostinfo_unix.cpp | 228 |
1 files changed, 77 insertions, 151 deletions
diff --git a/src/network/kernel/qhostinfo_unix.cpp b/src/network/kernel/qhostinfo_unix.cpp index 12d8c04d10..80d386a13d 100644 --- a/src/network/kernel/qhostinfo_unix.cpp +++ b/src/network/kernel/qhostinfo_unix.cpp @@ -3,140 +3,78 @@ //#define QHOSTINFO_DEBUG -#include "qplatformdefs.h" - #include "qhostinfo_p.h" -#include "private/qnativesocketengine_p.h" -#include "qiodevice.h" + #include <qbytearray.h> -#if QT_CONFIG(library) -#include <qlibrary.h> -#endif -#include <qbasicatomic.h> -#include <qurl.h> #include <qfile.h> -#include <private/qnet_unix_p.h> - -#include "QtCore/qapplicationstatic.h" +#include <qplatformdefs.h> +#include <qurl.h> #include <sys/types.h> #include <netdb.h> -#include <arpa/inet.h> -#if defined(Q_OS_VXWORKS) -# include <hostLib.h> -#else -# include <resolv.h> -#endif +#include <netinet/in.h> -#if defined(__GNU_LIBRARY__) && !defined(__UCLIBC__) -# include <gnu/lib-names.h> +#if QT_CONFIG(libresolv) +# include <resolv.h> #endif -#if defined(Q_OS_FREEBSD) || QT_CONFIG(dlopen) -# include <dlfcn.h> +#ifndef _PATH_RESCONF +# define _PATH_RESCONF "/etc/resolv.conf" #endif QT_BEGIN_NAMESPACE using namespace Qt::StringLiterals; -enum LibResolvFeature { - NeedResInit, - NeedResNInit -}; - -typedef struct __res_state *res_state_ptr; - -typedef int (*res_init_proto)(void); -static res_init_proto local_res_init = nullptr; -typedef int (*res_ninit_proto)(res_state_ptr); -static res_ninit_proto local_res_ninit = nullptr; -typedef void (*res_nclose_proto)(res_state_ptr); -static res_nclose_proto local_res_nclose = nullptr; -static res_state_ptr local_res = nullptr; - -#if QT_CONFIG(library) && !defined(Q_OS_QNX) -namespace { -struct LibResolv -{ - enum { -#ifdef RES_NORELOAD - // If RES_NORELOAD is defined, then the libc is capable of watching - // /etc/resolv.conf for changes and reloading as necessary. So accept - // whatever is configured. - ReinitNecessary = false -#else - ReinitNecessary = true -#endif - }; - - QLibrary lib; - LibResolv(); - ~LibResolv() { lib.unload(); } -}; -} - -static QFunctionPointer resolveSymbol(QLibrary &lib, const char *sym) -{ - if (lib.isLoaded()) - return lib.resolve(sym); - -#if defined(RTLD_DEFAULT) && (defined(Q_OS_FREEBSD) || QT_CONFIG(dlopen)) - return reinterpret_cast<QFunctionPointer>(dlsym(RTLD_DEFAULT, sym)); -#else - return nullptr; -#endif -} - -LibResolv::LibResolv() +static void maybeRefreshResolver() { -#ifdef LIBRESOLV_SO - lib.setFileName(QStringLiteral(LIBRESOLV_SO)); - if (!lib.load()) +#if defined(RES_NORELOAD) + // If RES_NORELOAD is defined, then the libc is capable of watching + // /etc/resolv.conf for changes and reloading as necessary. So accept + // whatever is configured. + return; +#elif defined(Q_OS_DARWIN) + // Apple's libsystem_info.dylib:getaddrinfo() uses the + // libsystem_dnssd.dylib to resolve hostnames. Using res_init() has no + // effect on it and is thread-unsafe. + return; +#elif defined(Q_OS_FREEBSD) + // FreeBSD automatically refreshes: + // https://github.com/freebsd/freebsd-src/blob/b3fe5d932264445cbf9a1c4eab01afb6179b499b/lib/libc/resolv/res_state.c#L69 + return; +#elif defined(Q_OS_OPENBSD) + // OpenBSD automatically refreshes: + // https://github.com/ligurio/openbsd-src/blob/b1ce0da17da254cc15b8aff25b3d55d3c7a82cec/lib/libc/asr/asr.c#L367 + return; +#elif defined(Q_OS_QNX) + // res_init() is not thread-safe; executing it leads to state corruption. + // Whether it reloads resolv.conf on its own is unknown. + return; #endif - { - lib.setFileName("resolv"_L1); - lib.load(); - } - - // res_ninit is required for localDomainName() - local_res_ninit = res_ninit_proto(resolveSymbol(lib, "__res_ninit")); - if (!local_res_ninit) - local_res_ninit = res_ninit_proto(resolveSymbol(lib, "res_ninit")); - if (local_res_ninit) { - // we must now find res_nclose - local_res_nclose = res_nclose_proto(resolveSymbol(lib, "res_nclose")); - if (!local_res_nclose) - local_res_nclose = res_nclose_proto(resolveSymbol(lib, "__res_nclose")); - if (!local_res_nclose) - local_res_ninit = nullptr; - } - - if (ReinitNecessary || !local_res_ninit) { - local_res_init = res_init_proto(resolveSymbol(lib, "__res_init")); - if (!local_res_init) - local_res_init = res_init_proto(resolveSymbol(lib, "res_init")); - if (local_res_init && !local_res_ninit) { - // if we can't get a thread-safe context, we have to use the global _res state - local_res = res_state_ptr(resolveSymbol(lib, "_res")); +#if QT_CONFIG(libresolv) + // OSes known or thought to reach here: AIX, NetBSD, Solaris, + // Linux with MUSL (though res_init() does nothing and is unnecessary) + + Q_CONSTINIT static QT_STATBUF lastStat = {}; + Q_CONSTINIT static QBasicMutex mutex = {}; + if (QT_STATBUF st; QT_STAT(_PATH_RESCONF, &st) == 0) { + QMutexLocker locker(&mutex); + bool refresh = false; + if ((_res.options & RES_INIT) == 0) + refresh = true; + else if (lastStat.st_ctime != st.st_ctime) + refresh = true; // file was updated + else if (lastStat.st_dev != st.st_dev || lastStat.st_ino != st.st_ino) + refresh = true; // file was replaced + if (refresh) { + lastStat = st; + res_init(); } } +#endif } -Q_APPLICATION_STATIC(LibResolv, libResolv) - -static void resolveLibrary(LibResolvFeature f) -{ - if (LibResolv::ReinitNecessary || f == NeedResNInit) - libResolv(); -} -#else // QT_CONFIG(library) || Q_OS_QNX -static void resolveLibrary(LibResolvFeature) -{ -} -#endif // QT_CONFIG(library) || Q_OS_QNX - QHostInfo QHostInfoAgent::fromName(const QString &hostName) { QHostInfo results; @@ -146,12 +84,7 @@ QHostInfo QHostInfoAgent::fromName(const QString &hostName) hostName.toLatin1().constData()); #endif - // Load res_init on demand. - resolveLibrary(NeedResInit); - - // If res_init is available, poll it. - if (local_res_init) - local_res_init(); + maybeRefreshResolver(); QHostAddress address; if (address.setAddress(hostName)) @@ -162,56 +95,49 @@ QHostInfo QHostInfoAgent::fromName(const QString &hostName) QString QHostInfo::localDomainName() { -#if !defined(Q_OS_VXWORKS) && !defined(Q_OS_ANDROID) - resolveLibrary(NeedResNInit); - if (local_res_ninit) { - // using thread-safe version - res_state_ptr state = res_state_ptr(malloc(sizeof(*state))); - Q_CHECK_PTR(state); - memset(state, 0, sizeof(*state)); - local_res_ninit(state); - QString domainName = QUrl::fromAce(state->defdname); +#if QT_CONFIG(libresolv) + auto domainNameFromRes = [](res_state r) { + QString domainName; + if (r->defdname[0]) + domainName = QUrl::fromAce(r->defdname); if (domainName.isEmpty()) - domainName = QUrl::fromAce(state->dnsrch[0]); - local_res_nclose(state); - free(state); - + domainName = QUrl::fromAce(r->dnsrch[0]); return domainName; + }; + std::remove_pointer_t<res_state> state = {}; + if (res_ninit(&state) == 0) { + // using thread-safe version + auto guard = qScopeGuard([&] { res_nclose(&state); }); + return domainNameFromRes(&state); } - if (local_res_init && local_res) { - // using thread-unsafe version + // using thread-unsafe version + maybeRefreshResolver(); + return domainNameFromRes(&_res); +#endif // !QT_CONFIG(libresolv) - local_res_init(); - QString domainName = QUrl::fromAce(local_res->defdname); - if (domainName.isEmpty()) - domainName = QUrl::fromAce(local_res->dnsrch[0]); - return domainName; - } -#endif // nothing worked, try doing it by ourselves: QFile resolvconf; -#if defined(_PATH_RESCONF) - resolvconf.setFileName(QFile::decodeName(_PATH_RESCONF)); -#else - resolvconf.setFileName("/etc/resolv.conf"_L1); -#endif + resolvconf.setFileName(_PATH_RESCONF ""_L1); if (!resolvconf.open(QIODevice::ReadOnly)) return QString(); // failure QString domainName; while (!resolvconf.atEnd()) { - QByteArray line = resolvconf.readLine().trimmed(); - if (line.startsWith("domain ")) - return QUrl::fromAce(line.mid(sizeof "domain " - 1).trimmed()); + const QByteArray lineArray = resolvconf.readLine(); + QByteArrayView line = QByteArrayView(lineArray).trimmed(); + constexpr QByteArrayView domainWithSpace = "domain "; + if (line.startsWith(domainWithSpace)) + return QUrl::fromAce(line.mid(domainWithSpace.size()).trimmed().toByteArray()); // in case there's no "domain" line, fall back to the first "search" entry - if (domainName.isEmpty() && line.startsWith("search ")) { - QByteArray searchDomain = line.mid(sizeof "search " - 1).trimmed(); + constexpr QByteArrayView searchWithSpace = "search "; + if (domainName.isEmpty() && line.startsWith(searchWithSpace)) { + QByteArrayView searchDomain = line.mid(searchWithSpace.size()).trimmed(); int pos = searchDomain.indexOf(' '); if (pos != -1) searchDomain.truncate(pos); - domainName = QUrl::fromAce(searchDomain); + domainName = QUrl::fromAce(searchDomain.toByteArray()); } } |