blob: cd0dbbfb65814c8ee3c8a6fa87bae334f01af25d [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "garnet/bin/timezone/timezone.h"
#include <zircon/syscalls.h>
#include <algorithm>
#include <fstream>
#include <memory>
#include <lib/async-loop/cpp/loop.h>
#include "lib/app/cpp/environment_services.h"
#include "lib/fsl/vmo/file.h"
#include "lib/fsl/vmo/sized_vmo.h"
#include "lib/fxl/logging.h"
#include "lib/fxl/strings/string_number_conversions.h"
#include "third_party/icu/source/common/unicode/errorcode.h"
#include "third_party/icu/source/common/unicode/udata.h"
#include "third_party/icu/source/i18n/unicode/timezone.h"
static constexpr char kTzIdPath[] = "/data/tz_id";
static constexpr char kIcuDataPath[] = "/pkg/data/icudtl.dat";
static constexpr char kDefaultTimezone[] = "UTC";
static constexpr int32_t kMillisecondsInMinute = 60000;
namespace time_zone {
TimezoneImpl::TimezoneImpl() : valid_(Init()) {}
TimezoneImpl::~TimezoneImpl() = default;
bool TimezoneImpl::Init() {
fsl::SizedVmo icu_data;
if (!fsl::VmoFromFilename(kIcuDataPath, &icu_data)) {
FXL_LOG(ERROR) << "Unable to load ICU data. Timezone data unavailable.";
return false;
}
// Maps the ICU VMO into this process.
uintptr_t icu_data_ptr = 0;
if (zx_vmar_map(zx_vmar_root_self(), 0, icu_data.vmo().get(), 0,
icu_data.size(), ZX_VM_FLAG_PERM_READ,
&icu_data_ptr) != ZX_OK) {
FXL_LOG(ERROR) << "Unable to map ICU data into process.";
return false;
}
// ICU-related initialization.
UErrorCode icu_set_data_status;
udata_setCommonData(reinterpret_cast<void*>(icu_data_ptr),
&icu_set_data_status);
if (icu_set_data_status != U_ZERO_ERROR) {
FXL_LOG(ERROR) << "Unable to set common ICU data. "
<< "Timezone data unavailable.";
return false;
}
FXL_LOG(INFO) << "Time zone data initialized successfully.";
return true;
}
void TimezoneImpl::GetTimezoneOffsetMinutes(
int64_t milliseconds_since_epoch,
GetTimezoneOffsetMinutesCallback callback) {
if (!valid_) {
callback(0, 0);
return;
}
fidl::StringPtr timezone_id = GetTimezoneIdImpl();
std::unique_ptr<icu::TimeZone> timezone(
icu::TimeZone::createTimeZone(timezone_id.get().c_str()));
int32_t local_offset = 0, dst_offset = 0;
UErrorCode error;
// Local time is set to false, and local_offset/dst_offset/error are mutated
// via non-const references.
timezone->getOffset(static_cast<UDate>(milliseconds_since_epoch), false,
local_offset, dst_offset, error);
if (error != U_ZERO_ERROR) {
// For an as-of-yet-unknown reason, this code simply existing appears to
// prevent [SY-516] from happening. Without it, getting the offset fails
// consistently.
icu::ErrorCode icuError;
icuError.set(error);
FXL_LOG(ERROR) << "Unable to get correct offset: error code " << error
<< " " << icuError.errorName();
callback(0, 0);
return;
}
local_offset /= kMillisecondsInMinute;
dst_offset /= kMillisecondsInMinute;
callback(local_offset, dst_offset);
}
void TimezoneImpl::NotifyWatchers(const fidl::StringPtr& new_timezone_id) {
for (auto& watcher : watchers_) {
watcher->OnTimezoneOffsetChange(new_timezone_id);
}
}
bool TimezoneImpl::IsValidTimezoneId(const fidl::StringPtr& timezone_id) {
std::unique_ptr<icu::TimeZone> timezone(
icu::TimeZone::createTimeZone(timezone_id.get().c_str()));
if ((*timezone) == icu::TimeZone::getUnknown()) {
return false;
}
return true;
}
void TimezoneImpl::SetTimezone(fidl::StringPtr timezone_id,
SetTimezoneCallback callback) {
if (!valid_) {
FXL_LOG(ERROR) << "Time service is not valid.";
callback(false);
return;
}
if (!IsValidTimezoneId(timezone_id)) {
FXL_LOG(ERROR) << "Timezone '" << timezone_id << "' is not valid.";
callback(false);
return;
}
std::ofstream out_fstream(kTzIdPath, std::ofstream::trunc);
if (!out_fstream.is_open()) {
FXL_LOG(ERROR) << "Unable to open file for write '" << kTzIdPath << "'";
callback(false);
return;
}
out_fstream << timezone_id;
out_fstream.close();
NotifyWatchers(timezone_id);
callback(true);
}
void TimezoneImpl::GetTimezoneId(GetTimezoneIdCallback callback) {
callback(GetTimezoneIdImpl());
}
fidl::StringPtr TimezoneImpl::GetTimezoneIdImpl() {
if (!valid_) {
return kDefaultTimezone;
}
std::ifstream in_fstream(kTzIdPath);
if (!in_fstream.is_open()) {
return kDefaultTimezone;
}
std::string id_str;
in_fstream >> id_str;
in_fstream.close();
if (id_str.empty()) {
FXL_LOG(ERROR) << "TZ file empty at '" << kTzIdPath << "'";
return kDefaultTimezone;
}
if (!IsValidTimezoneId(id_str)) {
FXL_LOG(ERROR) << "Saved TZ ID invalid: '" << id_str << "'";
return kDefaultTimezone;
}
return id_str;
}
void TimezoneImpl::ReleaseWatcher(fuchsia::timezone::TimezoneWatcher* watcher) {
auto predicate = [watcher](const auto& target) {
return target.get() == watcher;
};
watchers_.erase(
std::remove_if(watchers_.begin(), watchers_.end(), predicate));
}
void TimezoneImpl::Watch(
fidl::InterfaceHandle<fuchsia::timezone::TimezoneWatcher> watcher) {
fuchsia::timezone::TimezoneWatcherPtr watcher_proxy = watcher.Bind();
fuchsia::timezone::TimezoneWatcher* proxy_raw_ptr = watcher_proxy.get();
watcher_proxy.set_error_handler(
[this, proxy_raw_ptr] { ReleaseWatcher(proxy_raw_ptr); });
watchers_.push_back(std::move(watcher_proxy));
}
void TimezoneImpl::AddBinding(fidl::InterfaceRequest<Timezone> request) {
bindings_.AddBinding(this, std::move(request));
}
} // namespace time_zone