kde-workspace/ktimezoned/ktimezoned.cpp

365 lines
11 KiB
C++
Raw Normal View History

2014-11-15 04:16:00 +02:00
/*
This file is part of the KDE libraries
Copyright (c) 2005-2010 David Jarvie <djarvie@kde.org>
Copyright (c) 2005 S.R.Haque <srhaque@iee.org>.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "ktimezoned.moc"
#include "ktimezonedbase.moc"
#include <climits>
#include <cstdlib>
#include <QFile>
#include <QFileInfo>
#include <QDir>
#include <QRegExp>
#include <QStringList>
#include <QTextStream>
#include <QtDBus/QtDBus>
#include <kglobal.h>
#include <klocale.h>
#include <kcodecs.h>
#include <kstandarddirs.h>
#include <kstringhandler.h>
#include <ktemporaryfile.h>
#include <kdebug.h>
#include <kconfiggroup.h>
#include <kpluginfactory.h>
#include <kpluginloader.h>
K_PLUGIN_FACTORY(KTimeZonedFactory,
registerPlugin<KTimeZoned>();
)
K_EXPORT_PLUGIN(KTimeZonedFactory("ktimezoned"))
// The maximum allowed length for reading a zone.tab line. This is set to
// provide plenty of leeway, given that the maximum length of lines in a valid
// zone.tab will be around 100 - 120 characters.
const int MAX_ZONE_TAB_LINE_LENGTH = 2000;
// Config file entry names
const char ZONEINFO_DIR[] = "ZoneinfoDir"; // path to zoneinfo/ directory
const char ZONE_TAB[] = "Zonetab"; // path & name of zone.tab
const char LOCAL_ZONE[] = "LocalZone"; // name of local time zone
KTimeZoned::KTimeZoned(QObject* parent, const QList<QVariant>& l)
: KTimeZonedBase(parent, l),
mSource(0),
mZonetabWatch(0),
mDirWatch(0)
{
init(false);
}
KTimeZoned::~KTimeZoned()
{
delete mSource;
mSource = 0;
delete mZonetabWatch;
mZonetabWatch = 0;
delete mDirWatch;
mDirWatch = 0;
}
void KTimeZoned::init(bool restart)
{
if (restart)
{
kDebug(1221) << "KTimeZoned::init(restart)";
delete mSource;
mSource = 0;
delete mZonetabWatch;
mZonetabWatch = 0;
delete mDirWatch;
mDirWatch = 0;
}
KConfig config(QLatin1String("ktimezonedrc"));
if (restart)
config.reparseConfiguration();
KConfigGroup group(&config, "TimeZones");
mZoneinfoDir = group.readEntry(ZONEINFO_DIR);
mZoneTab = group.readEntry(ZONE_TAB);
mConfigLocalZone = group.readEntry(LOCAL_ZONE);
if (mZoneinfoDir.length() > 1 && mZoneinfoDir.endsWith('/'))
mZoneinfoDir.truncate(mZoneinfoDir.length() - 1); // strip trailing '/'
// Open zone.tab if we already know where it is
QFile f;
2014-12-06 03:58:16 +02:00
// Search for zone.tab
if (!findZoneTab(f))
return;
mZoneTab = f.fileName();
2014-11-15 04:16:00 +02:00
// Read zone.tab and create a collection of KTimeZone instances
readZoneTab(f);
mZonetabWatch = new KDirWatch(this);
mZonetabWatch->addFile(mZoneTab);
connect(mZonetabWatch, SIGNAL(dirty(const QString&)), SLOT(zonetab_Changed(const QString&)));
2014-12-06 03:58:16 +02:00
// Respect the config above all
if (!mConfigLocalZone.isEmpty()) {
// We know the local zone from last time.
// Check whether the file still matches it.
KTimeZone local = mZones.zone(mConfigLocalZone);
if (local.isValid())
{
mLocalZone = mConfigLocalZone;
mLocalZoneDataFile = mZoneinfoDir + "/" + mConfigLocalZone;
}
}
2014-11-15 04:16:00 +02:00
// Find the local system time zone and set up file monitors to detect changes
findLocalZone();
}
2014-12-06 03:58:16 +02:00
// Check if the local zone has been updated, and if so, notify interested parties.
2014-11-15 04:16:00 +02:00
void KTimeZoned::updateLocalZone()
{
2014-12-06 03:58:16 +02:00
qDebug() << "updating timezone" << mLocalZone;
KConfig config(QLatin1String("ktimezonedrc"));
KConfigGroup group(&config, "TimeZones");
mConfigLocalZone = mLocalZone;
group.writeEntry(LOCAL_ZONE, mConfigLocalZone);
group.sync();
QDBusMessage message = QDBusMessage::createSignal("/Daemon", "org.kde.KTimeZoned", "configChanged");
QDBusConnection::sessionBus().send(message);
2014-11-15 04:16:00 +02:00
}
/*
* Find the location of the zoneinfo files and store in mZoneinfoDir.
* Open or if necessary create zone.tab.
*/
bool KTimeZoned::findZoneTab(QFile& f)
{
QString ZONE_TAB_FILE = "/zone.tab";
QString ZONE_INFO_DIR;
if (QDir("/usr/share/zoneinfo").exists()) {
ZONE_INFO_DIR = "/usr/share/zoneinfo";
} else if (QDir("/usr/lib/zoneinfo").exists()) {
ZONE_INFO_DIR = "/usr/lib/zoneinfo";
} else if (QDir("/share/zoneinfo").exists()) {
ZONE_INFO_DIR = "/share/zoneinfo";
} else if (QDir("/lib/zoneinfo").exists()) {
ZONE_INFO_DIR = "/lib/zoneinfo";
} else {
// /usr is kind of standard
ZONE_INFO_DIR = "/usr/share/zoneinfo";
}
2014-11-15 04:16:00 +02:00
// Find and open zone.tab - it's all easy except knowing where to look.
QDir dir;
QString zoneinfoDir = ZONE_INFO_DIR;
// make a note if the dir exists; whether it contains zone.tab or not
if (dir.exists(zoneinfoDir))
{
mZoneinfoDir = zoneinfoDir;
f.setFileName(zoneinfoDir + ZONE_TAB_FILE);
if (f.open(QIODevice::ReadOnly))
2014-12-06 03:58:16 +02:00
mZoneTab = zoneinfoDir + ZONE_TAB_FILE;
2014-11-15 04:16:00 +02:00
return true;
kDebug(1221) << "Can't open " << f.fileName();
}
zoneinfoDir = ::getenv("TZDIR");
if (!zoneinfoDir.isEmpty() && dir.exists(zoneinfoDir))
{
mZoneinfoDir = zoneinfoDir;
f.setFileName(zoneinfoDir + ZONE_TAB_FILE);
if (f.open(QIODevice::ReadOnly))
2014-12-06 03:58:16 +02:00
mZoneTab = zoneinfoDir + ZONE_TAB_FILE;
2014-11-15 04:16:00 +02:00
return true;
kDebug(1221) << "Can't open " << f.fileName();
}
return false;
}
// Parse zone.tab and for each time zone, create a KSystemTimeZone instance.
// Note that only data needed by this module is specified to KSystemTimeZone.
void KTimeZoned::readZoneTab(QFile &f)
{
2014-12-06 03:58:16 +02:00
qDebug() << "reading zone.tab";
2014-11-15 04:16:00 +02:00
// Parse the already open real or fake zone.tab.
QRegExp lineSeparator("[ \t]");
if (!mSource)
mSource = new KSystemTimeZoneSource;
mZones.clear();
QTextStream str(&f);
while (!str.atEnd())
{
// Read the next line, but limit its length to guard against crashing
// due to a corrupt very large zone.tab (see KDE bug 224868).
QString line = str.readLine(MAX_ZONE_TAB_LINE_LENGTH);
if (line.isEmpty() || line[0] == '#')
continue;
QStringList tokens = KStringHandler::perlSplit(lineSeparator, line, 4);
int n = tokens.count();
if (n < 3)
{
kError(1221) << "readZoneTab(): invalid record: " << line << endl;
continue;
}
// Add entry to list.
if (tokens[0] == "??")
tokens[0] = "";
else if (!tokens[0].isEmpty())
mHaveCountryCodes = true;
mZones.add(KSystemTimeZone(mSource, tokens[2], tokens[0]));
}
f.close();
}
// Find the local time zone, starting from scratch.
void KTimeZoned::findLocalZone()
{
delete mDirWatch;
mDirWatch = 0;
mLocalZone.clear();
mLocalZoneDataFile.clear();
2014-12-06 03:58:16 +02:00
// First try the checking for TZ envinomental variable.
2014-11-15 04:16:00 +02:00
const char *envtz = ::getenv("TZ");
2014-12-06 03:58:16 +02:00
checkEnv(envtz);
2014-11-15 04:16:00 +02:00
2014-12-06 03:58:16 +02:00
if (mLocalZone.isEmpty() && !mZoneinfoDir.isEmpty()) {
2014-11-15 04:16:00 +02:00
// Try to match /etc/localtime against the list of zoneinfo files.
2014-12-06 03:58:16 +02:00
// Resolve symlink
QFileInfo fi(QFile("/etc/localtime"));
if (fi.isSymLink()) {
checkLocaltime(fi.canonicalFilePath());
} else {
checkLocaltime(QLatin1String("/etc/localtime"));
}
2014-11-15 04:16:00 +02:00
}
2014-12-06 03:58:16 +02:00
// Failsafe, fallback to UTC
if (mLocalZone.isEmpty()) {
mLocalZone = KTimeZone::utc().name();
kDebug(1221) << "Failsafe: " << mLocalZone;
2014-11-15 04:16:00 +02:00
}
2014-12-06 03:58:16 +02:00
// Watch for changes in the file defining the local time zone
2014-11-15 04:16:00 +02:00
mDirWatch = new KDirWatch(this);
2014-12-06 03:58:16 +02:00
mDirWatch->addFile("/etc/localtime");
2014-11-15 04:16:00 +02:00
if (!mLocalZoneDataFile.isEmpty())
mDirWatch->addFile(mLocalZoneDataFile);
2014-12-06 03:58:16 +02:00
qDebug() << "watching" << mLocalZoneDataFile;
2014-11-15 04:16:00 +02:00
connect(mDirWatch, SIGNAL(dirty(const QString&)), SLOT(localChanged(const QString&)));
connect(mDirWatch, SIGNAL(deleted(const QString&)), SLOT(localChanged(const QString&)));
connect(mDirWatch, SIGNAL(created(const QString&)), SLOT(localChanged(const QString&)));
2014-12-06 03:58:16 +02:00
mDirWatch->startScan();
2014-11-15 04:16:00 +02:00
2014-12-06 03:58:16 +02:00
// Finally, if the local zone identity has changed, let the world know
2014-11-15 04:16:00 +02:00
updateLocalZone();
}
// Called when KDirWatch detects a change in zone.tab
void KTimeZoned::zonetab_Changed(const QString& path)
{
2014-12-06 03:58:16 +02:00
qDebug() << "zone.tab changed";
if (path != mZoneTab) {
2014-11-15 04:16:00 +02:00
kError(1221) << "Wrong path (" << path << ") for zone.tab";
return;
}
QDBusMessage message = QDBusMessage::createSignal("/Daemon", "org.kde.KTimeZoned", "zonetabChanged");
QList<QVariant> args;
args += mZoneTab;
message.setArguments(args);
QDBusConnection::sessionBus().send(message);
// Reread zone.tab and recreate the collection of KTimeZone instances,
// in case any zones have been created or deleted and one of them
// subsequently becomes the local zone.
QFile f;
f.setFileName(mZoneTab);
2014-12-06 03:58:16 +02:00
if (!f.open(QIODevice::ReadOnly)) {
2014-11-15 04:16:00 +02:00
kError(1221) << "Could not open zone.tab (" << mZoneTab << ") to reread";
2014-12-06 03:58:16 +02:00
} else {
2014-11-15 04:16:00 +02:00
readZoneTab(f);
2014-12-06 03:58:16 +02:00
}
2014-11-15 04:16:00 +02:00
}
// Called when KDirWatch detects a change
void KTimeZoned::localChanged(const QString& path)
{
2014-12-06 03:58:16 +02:00
kDebug(1221) << path << "changed";
findLocalZone();
2014-11-15 04:16:00 +02:00
}
2014-12-06 03:58:16 +02:00
bool KTimeZoned::checkEnv(const char *envZone)
2014-11-15 04:16:00 +02:00
{
2014-12-06 03:58:16 +02:00
if (!mLocalZone.isEmpty()) {
// It has most likely already been set by preferences in the config
return true;
2014-11-15 04:16:00 +02:00
}
2014-12-06 03:58:16 +02:00
if (envZone) {
if (envZone[0] == ':') {
// TZ specified, either relative to zoneinfo/ or absolute.
QString tz = QFile::decodeName(envZone + 1);
KTimeZone local = mZones.zone(tz);
if (!local.isValid()) {
return false;
2014-11-15 04:16:00 +02:00
}
2014-12-06 03:58:16 +02:00
mLocalZone = tz;
if (QFile(tz).exists()) {
// Full path.
mLocalZoneDataFile = tz;
} else {
// Relative path.
mLocalZoneDataFile = mZoneinfoDir + "/" + tz;
2014-11-15 04:16:00 +02:00
}
}
}
return false;
}
2014-12-06 03:58:16 +02:00
bool KTimeZoned::checkLocaltime(const QString &path)
2014-11-15 04:16:00 +02:00
{
2014-12-06 03:58:16 +02:00
if (!mLocalZone.isEmpty()) {
// It has most likely already been set by preferences in the config
return true;
2014-11-15 04:16:00 +02:00
}
2014-12-06 03:58:16 +02:00
if (mZoneinfoDir.isEmpty() && !path.indexOf(mZoneinfoDir)) {
qDebug() << "relative path passed to checkLocaltime()";
2014-11-15 04:16:00 +02:00
return false;
}
2014-12-06 03:58:16 +02:00
// The path could be full path, remove zoneinfo directory reference
QString rel = path.mid(mZoneinfoDir.length() + 1);
2014-11-15 04:16:00 +02:00
2014-12-06 03:58:16 +02:00
KTimeZone local = mZones.zone(rel);
if (!local.isValid()) {
return false;
2014-11-15 04:16:00 +02:00
}
2014-12-06 03:58:16 +02:00
mLocalZone = rel;
mLocalZoneDataFile = path;
kDebug(1221) << mLocalZone;
return true;
2014-11-15 04:16:00 +02:00
}