From c337f6fae51b987ce7dbed1fd9bea41e6073efbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20K=2E=20H=C3=BCttel?= Date: Sat, 10 Oct 2020 19:26:13 +0200 Subject: [PATCH 1/2] Revert "Cache system zone ID when fetched from the file-system" This reverts commit c70ce3d042025c858faffe661f85d2482a2a0d8c. --- src/corelib/time/qtimezoneprivate_tz.cpp | 205 +++++++---------------- 1 file changed, 64 insertions(+), 141 deletions(-) diff --git a/src/corelib/time/qtimezoneprivate_tz.cpp b/src/corelib/time/qtimezoneprivate_tz.cpp index c5c70b7364..01f9a6cce0 100644 --- a/src/corelib/time/qtimezoneprivate_tz.cpp +++ b/src/corelib/time/qtimezoneprivate_tz.cpp @@ -1,6 +1,5 @@ /**************************************************************************** ** -** Copyright (C) 2020 The Qt Company Ltd. ** Copyright (C) 2019 Crimson AS ** Copyright (C) 2013 John Layt ** Contact: https://www.qt.io/licensing/ @@ -43,19 +42,18 @@ #include "qtimezoneprivate_p.h" #include "private/qlocale_tools_p.h" -#include -#include #include -#include #include +#include +#include +#include #include -#include #include #include #include -#ifndef Q_OS_INTEGRITY +#if !defined(Q_OS_INTEGRITY) #include // to use MAXSYMLINKS constant #endif #include // to use _SC_SYMLOOP_MAX constant @@ -1102,146 +1100,28 @@ QTimeZonePrivate::Data QTzTimeZonePrivate::previousTransition(qint64 beforeMSecs return last > tranCache().cbegin() ? dataForTzTransition(*--last) : invalidData(); } -bool QTzTimeZonePrivate::isTimeZoneIdAvailable(const QByteArray &ianaId) const -{ - return tzZones->contains(ianaId); -} - -QList QTzTimeZonePrivate::availableTimeZoneIds() const -{ - QList result = tzZones->keys(); - std::sort(result.begin(), result.end()); - return result; -} - -QList QTzTimeZonePrivate::availableTimeZoneIds(QLocale::Country country) const +static long getSymloopMax() { - // TODO AnyCountry - QList result; - for (auto it = tzZones->cbegin(), end = tzZones->cend(); it != end; ++it) { - if (it.value().country == country) - result << it.key(); - } - std::sort(result.begin(), result.end()); - return result; -} - -// Getting the system zone's ID: - -namespace { -class ZoneNameReader : public QObject -{ -public: - QByteArray name() - { - /* Assumptions: - a) Systems don't change which of localtime and TZ they use without a - reboot. - b) When they change, they use atomic renames, hence a new device and - inode for the new file. - c) If we change which *name* is used for a zone, while referencing - the same final zoneinfo file, we don't care about the change of - name (e.g. if Europe/Oslo and Europe/Berlin are both symlinks to - the same CET file, continuing to use the old name, after - /etc/localtime changes which of the two it points to, is - harmless). - - The alternative would be to use a file-system watcher, but they are a - scarce resource. - */ - const StatIdent local = identify("/etc/localtime"); - const StatIdent tz = identify("/etc/TZ"); - if (!m_name.isEmpty() && m_last.isValid() && (m_last == local || m_last == tz)) - return m_name; - - m_name = etcLocalTime(); - if (!m_name.isEmpty()) { - m_last = local; - return m_name; - } - - m_name = etcTZ(); - m_last = m_name.isEmpty() ? StatIdent() : tz; - return m_name; - } - - -private: - QByteArray m_name; - struct StatIdent - { - static constexpr unsigned long bad = ~0ul; - unsigned long m_dev, m_ino; - StatIdent() : m_dev(bad), m_ino(bad) {} - StatIdent(const QT_STATBUF &data) : m_dev(data.st_dev), m_ino(data.st_ino) {} - bool isValid() { return m_dev != bad || m_ino != bad; } - bool operator==(const StatIdent &other) - { return other.m_dev == m_dev && other.m_ino == m_ino; } - }; - StatIdent m_last; - - static StatIdent identify(const char *path) - { - QT_STATBUF data; - return QT_STAT(path, &data) == -1 ? StatIdent() : StatIdent(data); - } - - static QByteArray etcLocalTime() - { - // On most distros /etc/localtime is a symlink to a real file so extract - // name from the path - const QLatin1String zoneinfo("/zoneinfo/"); - QString path = QStringLiteral("/etc/localtime"); - long iteration = getSymloopMax(); - // Symlink may point to another symlink etc. before being under zoneinfo/ - // We stop on the first path under /zoneinfo/, even if it is itself a - // symlink, like America/Montreal pointing to America/Toronto - do { - path = QFile::symLinkTarget(path); - int index = path.indexOf(zoneinfo); - if (index >= 0) // Found zoneinfo file; extract zone name from path: - return path.midRef(index + zoneinfo.size()).toUtf8(); - } while (!path.isEmpty() && --iteration > 0); - - return QByteArray(); - } - - static QByteArray etcTZ() - { - // Some systems (e.g. uClibc) have a default value for $TZ in /etc/TZ: - const QString path = QStringLiteral("/etc/TZ"); - QFile zone(path); - if (zone.open(QIODevice::ReadOnly)) - return zone.readAll().trimmed(); - - return QByteArray(); - } - - // Any chain of symlinks longer than this is assumed to be a loop: - static long getSymloopMax() - { -#ifdef SYMLOOP_MAX - // If defined, at runtime it can only be greater than this, so this is a safe bet: - return SYMLOOP_MAX; +#if defined(SYMLOOP_MAX) + return SYMLOOP_MAX; // if defined, at runtime it can only be greater than this, so this is a safe bet #else - errno = 0; - long result = sysconf(_SC_SYMLOOP_MAX); - if (result >= 0) - return result; - // result is -1, meaning either error or no limit - Q_ASSERT(!errno); // ... but it can't be an error, POSIX mandates _SC_SYMLOOP_MAX - - // therefore we can make up our own limit -# ifdef MAXSYMLINKS - return MAXSYMLINKS; + errno = 0; + long result = sysconf(_SC_SYMLOOP_MAX); + if (result >= 0) + return result; + // result is -1, meaning either error or no limit + Q_ASSERT(!errno); // ... but it can't be an error, POSIX mandates _SC_SYMLOOP_MAX + + // therefore we can make up our own limit +# if defined(MAXSYMLINKS) + return MAXSYMLINKS; # else - return 8; + return 8; # endif #endif - } -}; } +// TODO Could cache the value and monitor the required files for any changes QByteArray QTzTimeZonePrivate::systemTimeZoneId() const { // Check TZ env var first, if not populated try find it @@ -1256,9 +1136,28 @@ QByteArray QTzTimeZonePrivate::systemTimeZoneId() const else if (ianaId.startsWith(':')) ianaId = ianaId.mid(1); + // On most distros /etc/localtime is a symlink to a real file so extract name from the path + if (ianaId.isEmpty()) { + const QLatin1String zoneinfo("/zoneinfo/"); + QString path = QFile::symLinkTarget(QStringLiteral("/etc/localtime")); + int index = -1; + long iteration = getSymloopMax(); + // Symlink may point to another symlink etc. before being under zoneinfo/ + // We stop on the first path under /zoneinfo/, even if it is itself a + // symlink, like America/Montreal pointing to America/Toronto + while (iteration-- > 0 && !path.isEmpty() && (index = path.indexOf(zoneinfo)) < 0) + path = QFile::symLinkTarget(path); + if (index >= 0) { + // /etc/localtime is a symlink to the current TZ file, so extract from path + ianaId = path.midRef(index + zoneinfo.size()).toUtf8(); + } + } + + // Some systems (e.g. uClibc) have a default value for $TZ in /etc/TZ: if (ianaId.isEmpty()) { - thread_local static ZoneNameReader reader; - ianaId = reader.name(); + QFile zone(QStringLiteral("/etc/TZ")); + if (zone.open(QIODevice::ReadOnly)) + ianaId = zone.readAll().trimmed(); } // Give up for now and return UTC @@ -1268,4 +1167,28 @@ QByteArray QTzTimeZonePrivate::systemTimeZoneId() const return ianaId; } +bool QTzTimeZonePrivate::isTimeZoneIdAvailable(const QByteArray &ianaId) const +{ + return tzZones->contains(ianaId); +} + +QList QTzTimeZonePrivate::availableTimeZoneIds() const +{ + QList result = tzZones->keys(); + std::sort(result.begin(), result.end()); + return result; +} + +QList QTzTimeZonePrivate::availableTimeZoneIds(QLocale::Country country) const +{ + // TODO AnyCountry + QList result; + for (auto it = tzZones->cbegin(), end = tzZones->cend(); it != end; ++it) { + if (it.value().country == country) + result << it.key(); + } + std::sort(result.begin(), result.end()); + return result; +} + QT_END_NAMESPACE -- 2.28.0