From 913019eeec830421a48b12adca7c8d863920a550 Mon Sep 17 00:00:00 2001 From: Konstantin Pozin Date: Wed, 13 Oct 2021 11:45:33 -0700 Subject: [PATCH] [reland] Add basic support for Fuchsia (#202) - Implement `FuchsiaZoneInfoSource` to read tzif2 files from their designated locations on Fuchsia systems. - Implement calls to Fuchsia APIs in `local_time_zone()`. - Disable a time conversion edge case that is unsupported on Fuchsia. This reverts commit 63b7130d4b3e43e6d09342470fb3732025555f50. --- src/time_zone_info.cc | 64 ++++++++++++++++++++++++++++++++++++ src/time_zone_lookup.cc | 34 +++++++++++++++++++ src/time_zone_lookup_test.cc | 4 +++ 3 files changed, 102 insertions(+) diff --git a/src/time_zone_info.cc b/src/time_zone_info.cc index e48d7fea..6e4545d3 100644 --- a/src/time_zone_info.cc +++ b/src/time_zone_info.cc @@ -39,6 +39,7 @@ #include #include #include +#include #include #include #include @@ -722,6 +723,68 @@ std::unique_ptr AndroidZoneInfoSource::Open( return nullptr; } +// A zoneinfo source for use inside Fuchsia components. This attempts to +// read zoneinfo files from one of several known paths in a component's +// incoming namespace. [Config data][1] is preferred, but package-specific +// resources are also supported. +// +// Fuchsia's implementation supports `FileZoneInfoSource::Version()`. +// +// [1]: https://fuchsia.dev/fuchsia-src/development/components/data#using_config_data_in_your_component +class FuchsiaZoneInfoSource : public FileZoneInfoSource { + public: + static std::unique_ptr Open(const std::string& name); + std::string Version() const override { return version_; } + + private: + explicit FuchsiaZoneInfoSource(FilePtr fp, std::string version) + : FileZoneInfoSource(std::move(fp)), version_(std::move(version)) {} + std::string version_; +}; + +std::unique_ptr FuchsiaZoneInfoSource::Open( + const std::string& name) { + // Use of the "file:" prefix is intended for testing purposes only. + const std::size_t pos = (name.compare(0, 5, "file:") == 0) ? 5 : 0; + + // Prefixes where a Fuchsia component might find zoneinfo files, + // in descending order of preference. + const auto kTzdataPrefixes = { + "/config/data/tzdata/", + "/pkg/data/tzdata/", + "/data/tzdata/", + }; + const auto kEmptyPrefix = {""}; + const bool name_absolute = (pos != name.size() && name[pos] == '/'); + const auto prefixes = name_absolute ? kEmptyPrefix : kTzdataPrefixes; + + // Fuchsia builds place zoneinfo files at "". + for (const std::string prefix : prefixes) { + std::string path = prefix; + if (!prefix.empty()) path += "zoneinfo/tzif2/"; // format + path.append(name, pos, std::string::npos); + + auto fp = FOpen(path.c_str(), "rb"); + if (fp.get() == nullptr) continue; + + std::string version; + if (!prefix.empty()) { + // Fuchsia builds place the version in "revision.txt". + std::ifstream version_stream(prefix + "revision.txt"); + if (version_stream.is_open()) { + // revision.txt should contain no newlines, but to be + // defensive we read just the first line. + std::getline(version_stream, version); + } + } + + return std::unique_ptr( + new FuchsiaZoneInfoSource(std::move(fp), std::move(version))); + } + + return nullptr; +} + } // namespace bool TimeZoneInfo::Load(const std::string& name) { @@ -739,6 +802,7 @@ bool TimeZoneInfo::Load(const std::string& name) { name, [](const std::string& n) -> std::unique_ptr { if (auto z = FileZoneInfoSource::Open(n)) return z; if (auto z = AndroidZoneInfoSource::Open(n)) return z; + if (auto z = FuchsiaZoneInfoSource::Open(n)) return z; return nullptr; }); return zip != nullptr && Load(zip.get()); diff --git a/src/time_zone_lookup.cc b/src/time_zone_lookup.cc index 92eb2ae7..b1338caf 100644 --- a/src/time_zone_lookup.cc +++ b/src/time_zone_lookup.cc @@ -26,6 +26,14 @@ #include #endif +#if defined(__Fuchsia__) +#include +#include +#include +#include +#include +#endif + #include #include #include @@ -139,6 +147,32 @@ time_zone local_time_zone() { } CFRelease(tz_default); #endif +#if defined(__Fuchsia__) + std::string primary_tz; + { + const zx::duration kTimeout = zx::msec(500); + + async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread); + std::unique_ptr context = + sys::ComponentContext::Create(); + // Note: We can't use the synchronous FIDL API here because it doesn't + // allow timeouts; if the FIDL call failed, local_time_zone() would never + // return. + auto intl_provider = context->svc()->Connect(); + intl_provider->GetProfile( + [&loop, &primary_tz](fuchsia::intl::Profile profile) { + if (!profile.time_zones().empty()) { + primary_tz = profile.time_zones()[0].id; + } + loop.Quit(); + }); + loop.Run(zx::deadline_after(kTimeout)); + + if (!primary_tz.empty()) { + zone = primary_tz.c_str(); + } + } +#endif // Allow ${TZ} to override to default zone. char* tz_env = nullptr; diff --git a/src/time_zone_lookup_test.cc b/src/time_zone_lookup_test.cc index a5699741..f28511c6 100644 --- a/src/time_zone_lookup_test.cc +++ b/src/time_zone_lookup_test.cc @@ -1023,7 +1023,11 @@ TEST(MakeTime, SysSecondsLimits) { #endif const year_t min_tm_year = year_t{std::numeric_limits::min()} + 1900; tp = convert(civil_second(min_tm_year, 1, 1, 0, 0, 0), cut); +#if defined(__Fuchsia__) + // Fuchsia's gmtime_r() fails on extreme negative values (fxbug.dev/78527). +#else EXPECT_EQ("-2147481748-01-01T00:00:00+00:00", format(RFC3339, tp, cut)); +#endif #endif } }