summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEdward Welbourne <edward.welbourne@qt.io>2020-04-03 12:01:01 +0200
committerEdward Welbourne <edward.welbourne@qt.io>2020-05-28 15:13:05 +0200
commitf20f0b03d57b9517ac34043b873665bd41c098d2 (patch)
treed676db0fdfeda37dbdd3789c39ff9bd3cb239271
parentbd877e76a80ca7522f2e1a912508e884dda4c14a (diff)
Cache system zone ID when fetched from the file-system
Record the device and inode numbers when found and assume we can safely re-use the name if those have not changed. Tidy up some preprocessor trivia in the process and moved zone-availability check functions to before the system name lookup. Pick-to: 5.15 Task-number: QTBUG-75585 Change-Id: I3660922ef3c94b553a20f887a676e8921693b30f Reviewed-by: MÃ¥rten Nordheim <marten.nordheim@qt.io> Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
-rw-r--r--src/corelib/time/qtimezoneprivate_tz.cpp205
1 files changed, 141 insertions, 64 deletions
diff --git a/src/corelib/time/qtimezoneprivate_tz.cpp b/src/corelib/time/qtimezoneprivate_tz.cpp
index d40f4612d5..2d113d1582 100644
--- a/src/corelib/time/qtimezoneprivate_tz.cpp
+++ b/src/corelib/time/qtimezoneprivate_tz.cpp
@@ -1,5 +1,6 @@
/****************************************************************************
**
+** Copyright (C) 2020 The Qt Company Ltd.
** Copyright (C) 2019 Crimson AS <info@crimson.no>
** Copyright (C) 2013 John Layt <jlayt@kde.org>
** Contact: https://www.qt.io/licensing/
@@ -42,18 +43,19 @@
#include "qtimezoneprivate_p.h"
#include "private/qlocale_tools_p.h"
-#include <QtCore/QFile>
-#include <QtCore/QMutex>
-#include <QtCore/QHash>
#include <QtCore/QDataStream>
#include <QtCore/QDateTime>
+#include <QtCore/QFile>
+#include <QtCore/QHash>
+#include <QtCore/QMutex>
#include <qdebug.h>
+#include <qplatformdefs.h>
#include <algorithm>
#include <errno.h>
#include <limits.h>
-#if !defined(Q_OS_INTEGRITY)
+#ifndef Q_OS_INTEGRITY
#include <sys/param.h> // to use MAXSYMLINKS constant
#endif
#include <unistd.h> // to use _SC_SYMLOOP_MAX constant
@@ -1100,28 +1102,146 @@ QTimeZonePrivate::Data QTzTimeZonePrivate::previousTransition(qint64 beforeMSecs
return last > tranCache().cbegin() ? dataForTzTransition(*--last) : invalidData();
}
-static long getSymloopMax()
+bool QTzTimeZonePrivate::isTimeZoneIdAvailable(const QByteArray &ianaId) const
{
-#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
+ return tzZones->contains(ianaId);
+}
+
+QList<QByteArray> QTzTimeZonePrivate::availableTimeZoneIds() const
+{
+ QList<QByteArray> result = tzZones->keys();
+ std::sort(result.begin(), result.end());
+ return result;
+}
- // therefore we can make up our own limit
-# if defined(MAXSYMLINKS)
- return MAXSYMLINKS;
+QList<QByteArray> QTzTimeZonePrivate::availableTimeZoneIds(QLocale::Country country) const
+{
+ // TODO AnyCountry
+ QList<QByteArray> 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;
+#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;
# 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
@@ -1136,28 +1256,9 @@ 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()) {
- QFile zone(QStringLiteral("/etc/TZ"));
- if (zone.open(QIODevice::ReadOnly))
- ianaId = zone.readAll().trimmed();
+ thread_local static ZoneNameReader reader;
+ ianaId = reader.name();
}
// Give up for now and return UTC
@@ -1167,28 +1268,4 @@ QByteArray QTzTimeZonePrivate::systemTimeZoneId() const
return ianaId;
}
-bool QTzTimeZonePrivate::isTimeZoneIdAvailable(const QByteArray &ianaId) const
-{
- return tzZones->contains(ianaId);
-}
-
-QList<QByteArray> QTzTimeZonePrivate::availableTimeZoneIds() const
-{
- QList<QByteArray> result = tzZones->keys();
- std::sort(result.begin(), result.end());
- return result;
-}
-
-QList<QByteArray> QTzTimeZonePrivate::availableTimeZoneIds(QLocale::Country country) const
-{
- // TODO AnyCountry
- QList<QByteArray> 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