Support the TZDIR environment variable

On Linux / glibc, this overrides the default system location for the
zone info. So check for files there first. Break out a function to
manage the trying of (now three) zoneinfo directories when opening a
file by name relative to there.

Pick-to: 6.6 6.5
Task-number: QTBUG-116017
Change-Id: I1f97107aabd9015c0a5543639870f1d70654ca67
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
Edward Welbourne 2023-09-11 11:41:39 +02:00
parent c0cb616c1d
commit 569dc0de50

View File

@ -51,17 +51,41 @@ typedef QHash<QByteArray, QTzTimeZone> QTzTimeZoneHash;
static bool isTzFile(const QString &name);
// Open a named file under the zone info directory:
static bool openZoneInfo(QString name, QFile *file)
{
// At least on Linux / glibc (see man 3 tzset), $TZDIR overrides the system
// default location for zone info:
const QString tzdir = qEnvironmentVariable("TZDIR");
if (!tzdir.isEmpty()) {
file->setFileName(QDir(tzdir).filePath(name));
if (file->open(QIODevice::ReadOnly))
return true;
}
// Try modern system path first:
constexpr auto zoneShare = "/usr/share/zoneinfo/"_L1;
if (tzdir != zoneShare && tzdir != zoneShare.chopped(1)) {
file->setFileName(zoneShare + name);
if (file->open(QIODevice::ReadOnly))
return true;
}
// Fall back to legacy system path:
constexpr auto zoneLib = "/usr/lib/zoneinfo/"_L1;
if (tzdir != zoneLib && tzdir != zoneLib.chopped(1)) {
file->setFileName(zoneShare + name);
if (file->open(QIODevice::ReadOnly))
return true;
}
return false;
}
// Parse zone.tab table for territory information, read directories to ensure we
// find all installed zones (many are omitted from zone.tab; even more from
// zone1970.tab).
static QTzTimeZoneHash loadTzTimeZones()
{
QString path = QStringLiteral("/usr/share/zoneinfo/zone.tab");
if (!QFile::exists(path))
path = QStringLiteral("/usr/lib/zoneinfo/zone.tab");
QFile tzif(path);
if (!tzif.open(QIODevice::ReadOnly))
QFile tzif;
if (!openZoneInfo("zone.tab"_L1, &tzif))
return QTzTimeZoneHash();
QTzTimeZoneHash zonesHash;
@ -91,6 +115,7 @@ static QTzTimeZoneHash loadTzTimeZones()
}
}
const QString path = tzif.fileName();
const qsizetype cut = path.lastIndexOf(u'/');
Q_ASSERT(cut > 0);
const QDir zoneDir = QDir(path.first(cut));
@ -772,20 +797,13 @@ QTzTimeZoneCacheEntry QTzTimeZoneCache::findEntry(const QByteArray &ianaId)
tzif.setFileName(QStringLiteral("/etc/localtime"));
if (!tzif.open(QIODevice::ReadOnly))
return ret;
} else {
// Open named tz, try modern path first, if fails try legacy path
tzif.setFileName("/usr/share/zoneinfo/"_L1 + QString::fromLocal8Bit(ianaId));
if (!tzif.open(QIODevice::ReadOnly)) {
tzif.setFileName("/usr/lib/zoneinfo/"_L1 + QString::fromLocal8Bit(ianaId));
if (!tzif.open(QIODevice::ReadOnly)) {
// ianaId may be a POSIX rule, taken from $TZ or /etc/TZ
auto check = validatePosixRule(ianaId);
if (check.isValid) {
ret.m_hasDst = check.hasDst;
ret.m_posixRule = ianaId;
}
return ret;
}
} else if (!openZoneInfo(QString::fromLocal8Bit(ianaId), &tzif)) {
// ianaId may be a POSIX rule, taken from $TZ or /etc/TZ
auto check = validatePosixRule(ianaId);
if (check.isValid) {
ret.m_hasDst = check.hasDst;
ret.m_posixRule = ianaId;
return ret;
}
}
@ -1332,7 +1350,8 @@ private:
{
// On most distros /etc/localtime is a symlink to a real file so extract
// name from the path
const auto zoneinfo = "/zoneinfo/"_L1;
const QString tzdir = qEnvironmentVariable("TZDIR");
constexpr auto zoneinfo = "/zoneinfo/"_L1;
QString path = QStringLiteral("/etc/localtime");
long iteration = getSymloopMax();
// Symlink may point to another symlink etc. before being under zoneinfo/
@ -1340,8 +1359,14 @@ private:
// 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:
// If it's a zoneinfo file, extract the zone name from its path:
int index = tzdir.isEmpty() ? -1 : path.indexOf(tzdir);
if (index >= 0) {
const auto tail = QStringView{ path }.sliced(index + tzdir.size()).toUtf8();
return tail.startsWith(u'/') ? tail.sliced(1) : tail;
}
index = path.indexOf(zoneinfo);
if (index >= 0)
return QStringView{ path }.sliced(index + zoneinfo.size()).toUtf8();
} while (!path.isEmpty() && --iteration > 0);