2015-09-29 19:19:54 +00:00
|
|
|
#include <common/DateLUT.h>
|
2015-06-26 17:57:49 +00:00
|
|
|
|
2017-03-23 14:41:04 +00:00
|
|
|
#include <boost/filesystem.hpp>
|
2017-01-22 09:27:51 +00:00
|
|
|
#include <Poco/Exception.h>
|
2017-03-23 14:41:04 +00:00
|
|
|
#include <Poco/SHA1Engine.h>
|
|
|
|
#include <Poco/DigestStream.h>
|
|
|
|
#include <fstream>
|
2017-01-22 09:27:51 +00:00
|
|
|
|
2019-07-02 00:12:00 +00:00
|
|
|
|
2017-03-23 14:41:04 +00:00
|
|
|
namespace
|
|
|
|
{
|
2009-05-28 18:19:51 +00:00
|
|
|
|
2017-03-23 14:41:04 +00:00
|
|
|
Poco::DigestEngine::Digest calcSHA1(const std::string & path)
|
|
|
|
{
|
2017-04-01 07:20:54 +00:00
|
|
|
std::ifstream stream(path);
|
|
|
|
if (!stream)
|
2019-07-01 19:55:35 +00:00
|
|
|
throw Poco::Exception("Error while opening file: '" + path + "'.");
|
2017-04-01 07:20:54 +00:00
|
|
|
Poco::SHA1Engine digest_engine;
|
|
|
|
Poco::DigestInputStream digest_stream(digest_engine, stream);
|
|
|
|
digest_stream.ignore(std::numeric_limits<std::streamsize>::max());
|
|
|
|
if (!stream.eof())
|
2019-07-01 19:55:35 +00:00
|
|
|
throw Poco::Exception("Error while reading file: '" + path + "'.");
|
2017-04-01 07:20:54 +00:00
|
|
|
return digest_engine.digest();
|
2017-03-23 14:41:04 +00:00
|
|
|
}
|
2017-01-22 09:27:51 +00:00
|
|
|
|
2019-07-01 22:36:05 +00:00
|
|
|
|
2017-03-23 14:41:04 +00:00
|
|
|
std::string determineDefaultTimeZone()
|
2009-05-28 18:19:51 +00:00
|
|
|
{
|
2017-04-01 07:20:54 +00:00
|
|
|
namespace fs = boost::filesystem;
|
|
|
|
|
|
|
|
const char * tzdir_env_var = std::getenv("TZDIR");
|
|
|
|
fs::path tz_database_path = tzdir_env_var ? tzdir_env_var : "/usr/share/zoneinfo/";
|
|
|
|
|
|
|
|
fs::path tz_file_path;
|
|
|
|
std::string error_prefix;
|
|
|
|
const char * tz_env_var = std::getenv("TZ");
|
2019-05-28 11:33:21 +00:00
|
|
|
|
|
|
|
/// In recent tzdata packages some files now are symlinks and canonical path resolution
|
|
|
|
/// may give wrong timezone names - store the name as it is, if possible.
|
|
|
|
std::string tz_name;
|
|
|
|
|
2017-04-01 07:20:54 +00:00
|
|
|
if (tz_env_var)
|
|
|
|
{
|
2019-07-01 19:55:35 +00:00
|
|
|
error_prefix = std::string("Could not determine time zone from TZ variable value: '") + tz_env_var + "': ";
|
2017-04-01 07:20:54 +00:00
|
|
|
|
|
|
|
if (*tz_env_var == ':')
|
|
|
|
++tz_env_var;
|
|
|
|
|
|
|
|
tz_file_path = tz_env_var;
|
2019-05-28 11:33:21 +00:00
|
|
|
tz_name = tz_env_var;
|
2017-04-01 07:20:54 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
error_prefix = "Could not determine local time zone: ";
|
2019-07-02 00:12:00 +00:00
|
|
|
tz_file_path = "/etc/localtime";
|
|
|
|
|
2019-07-01 22:36:05 +00:00
|
|
|
/// No TZ variable and no tzdata installed (e.g. Docker)
|
|
|
|
if (!fs::exists(tz_file_path))
|
|
|
|
return "UTC";
|
2019-07-02 00:12:50 +00:00
|
|
|
|
2019-07-02 00:12:00 +00:00
|
|
|
/// Read symlink but not transitive.
|
|
|
|
/// Example:
|
|
|
|
/// /etc/localtime -> /usr/share/zoneinfo//UTC
|
|
|
|
/// /usr/share/zoneinfo//UTC -> UCT
|
|
|
|
/// But the preferred time zone name is pointed by the first link (UTC), and the second link is just an internal detail.
|
|
|
|
if (fs::is_symlink(tz_file_path))
|
|
|
|
tz_file_path = fs::read_symlink(tz_file_path);
|
2017-04-01 07:20:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
try
|
|
|
|
{
|
|
|
|
tz_database_path = fs::canonical(tz_database_path);
|
|
|
|
|
|
|
|
/// The tzdata file exists. If it is inside the tz_database_dir,
|
|
|
|
/// then the relative path is the time zone id.
|
2019-07-02 00:12:00 +00:00
|
|
|
{
|
|
|
|
fs::path relative_path = tz_file_path.lexically_relative(tz_database_path);
|
|
|
|
|
|
|
|
if (!relative_path.empty() && *relative_path.begin() != ".." && *relative_path.begin() != ".")
|
|
|
|
return tz_name.empty() ? relative_path.string() : tz_name;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Try the same with full symlinks resolution
|
|
|
|
{
|
|
|
|
tz_file_path = fs::canonical(tz_file_path, tz_database_path);
|
|
|
|
|
|
|
|
fs::path relative_path = tz_file_path.lexically_relative(tz_database_path);
|
|
|
|
if (!relative_path.empty() && *relative_path.begin() != ".." && *relative_path.begin() != ".")
|
|
|
|
return tz_name.empty() ? relative_path.string() : tz_name;
|
|
|
|
}
|
2017-04-01 07:20:54 +00:00
|
|
|
|
2019-07-02 00:12:00 +00:00
|
|
|
/// The file is not inside the tz_database_dir, so we hope that it was copied (not symlinked)
|
|
|
|
/// and try to find the file with exact same contents in the database.
|
2017-04-01 07:20:54 +00:00
|
|
|
|
|
|
|
size_t tzfile_size = fs::file_size(tz_file_path);
|
2018-03-23 16:05:14 +00:00
|
|
|
Poco::SHA1Engine::Digest tzfile_sha1 = calcSHA1(tz_file_path.string());
|
2017-04-01 07:20:54 +00:00
|
|
|
|
|
|
|
fs::recursive_directory_iterator begin(tz_database_path);
|
|
|
|
fs::recursive_directory_iterator end;
|
|
|
|
for (auto candidate_it = begin; candidate_it != end; ++candidate_it)
|
|
|
|
{
|
|
|
|
const auto & path = candidate_it->path();
|
|
|
|
if (path.filename() == "posix" || path.filename() == "right")
|
|
|
|
{
|
|
|
|
/// Some timezone databases contain copies of toplevel tzdata files in the posix/ directory
|
|
|
|
/// and tzdata files with leap seconds in the right/ directory. Skip them.
|
|
|
|
candidate_it.no_push();
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (candidate_it->status().type() != fs::regular_file || path.filename() == "localtime")
|
|
|
|
continue;
|
|
|
|
|
2018-03-23 16:05:14 +00:00
|
|
|
if (fs::file_size(path) == tzfile_size && calcSHA1(path.string()) == tzfile_sha1)
|
|
|
|
return path.lexically_relative(tz_database_path).string();
|
2017-04-01 07:20:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
catch (const Poco::Exception & ex)
|
|
|
|
{
|
|
|
|
throw Poco::Exception(error_prefix + ex.message(), ex);
|
|
|
|
}
|
|
|
|
catch (const std::exception & ex)
|
|
|
|
{
|
|
|
|
throw Poco::Exception(error_prefix + ex.what());
|
|
|
|
}
|
|
|
|
|
|
|
|
throw Poco::Exception(error_prefix + "custom time zone file used.");
|
2017-03-23 14:41:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
DateLUT::DateLUT()
|
|
|
|
{
|
2017-04-01 07:20:54 +00:00
|
|
|
/// Initialize the pointer to the default DateLUTImpl.
|
|
|
|
std::string default_time_zone = determineDefaultTimeZone();
|
|
|
|
default_impl.store(&getImplementation(default_time_zone), std::memory_order_release);
|
2009-05-28 18:19:51 +00:00
|
|
|
}
|
2017-01-22 09:27:51 +00:00
|
|
|
|
|
|
|
|
|
|
|
const DateLUTImpl & DateLUT::getImplementation(const std::string & time_zone) const
|
|
|
|
{
|
2017-04-01 07:20:54 +00:00
|
|
|
std::lock_guard<std::mutex> lock(mutex);
|
2017-01-22 09:27:51 +00:00
|
|
|
|
2017-04-01 07:20:54 +00:00
|
|
|
auto it = impls.emplace(time_zone, nullptr).first;
|
|
|
|
if (!it->second)
|
|
|
|
it->second = std::make_unique<DateLUTImpl>(time_zone);
|
2017-01-22 09:27:51 +00:00
|
|
|
|
2017-04-01 07:20:54 +00:00
|
|
|
return *it->second;
|
2017-01-22 09:27:51 +00:00
|
|
|
}
|