blob: 0228df5a9b0621acdefe7e3e5fec6c8f8b78b955 [file] [log] [blame]
// Copyright 2015 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 "src/lib/icu_data/cpp/icu_data.h"
#include <lib/syslog/cpp/macros.h>
#include <lib/zx/vmar.h>
#include <zircon/errors.h>
#include <fstream>
#include <optional>
#include <string>
#include <src/lib/files/directory.h>
#include "src/lib/fsl/vmo/file.h"
#include "src/lib/fsl/vmo/sized_vmo.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"
namespace icu_data {
namespace {
static constexpr char kIcuDataPath[] = "/pkg/data/icudtl.dat";
static uintptr_t g_icu_data_ptr = 0u;
static size_t g_icu_data_size = 0;
// Maximum number of bytes a revision ID length may be (e.g. "2019c").
static const size_t kRevisionIdLength = 5;
// Map the memory into the process and return a pointer to the memory.
// |size_out| is required and is set with the size of the mapped memory
// region.
uintptr_t GetData(const fsl::SizedVmo& icu_data, size_t* size_out) {
if (!size_out)
return 0u;
uint64_t data_size = icu_data.size();
if (data_size > std::numeric_limits<size_t>::max())
return 0u;
uintptr_t data = 0u;
zx_status_t status = zx::vmar::root_self()->map(
ZX_VM_PERM_READ, 0, icu_data.vmo(), 0, static_cast<size_t>(data_size), &data);
if (status == ZX_OK) {
*size_out = static_cast<size_t>(data_size);
return data;
}
return 0u;
}
} // namespace
zx_status_t Initialize() { return InitializeWithTzResourceDir(nullptr); }
zx_status_t InitializeWithTzResourceDir(const char tz_files_dir[]) {
return InitializeWithTzResourceDirAndValidate(tz_files_dir, nullptr);
}
zx_status_t InitializeWithTzResourceDirAndValidate(const char tz_files_dir[],
const char tz_revision_file_path[]) {
if (g_icu_data_ptr) {
// Don't allow calling Initialize twice.
return ZX_ERR_ALREADY_BOUND;
}
if (tz_files_dir && !files::IsDirectory(tz_files_dir)) {
return ZX_ERR_NOT_DIR;
}
if (tz_files_dir) {
// This is how we configure ICU to load time zone resource files from a
// separate directory. See
// http://userguide.icu-project.org/datetime/timezone#TOC-ICU4C-TZ-Update-with-Drop-in-.res-files-ICU-54-and-newer-
setenv("ICU_TIMEZONE_FILES_DIR", tz_files_dir,
/* overwrite existing env variable */ 1);
}
std::optional<std::string> expected_tz_revision_id;
if (tz_revision_file_path) {
std::ifstream in(tz_revision_file_path);
if (in.fail()) {
FX_LOGS(WARNING) << "could not read: " << tz_revision_file_path;
return ZX_ERR_IO;
}
expected_tz_revision_id =
std::string(std::istreambuf_iterator<char>(in), std::istreambuf_iterator<char>());
if (expected_tz_revision_id->length() != kRevisionIdLength) {
FX_LOGS(WARNING) << "corrupted time zone revision ID: " << expected_tz_revision_id.value();
return ZX_ERR_IO_DATA_INTEGRITY;
}
} else {
expected_tz_revision_id = std::nullopt;
}
fsl::SizedVmo icu_data;
if (!fsl::VmoFromFilename(kIcuDataPath, &icu_data)) {
FX_LOGS(ERROR) << "could not create VMO from filename: " << kIcuDataPath;
return ZX_ERR_IO;
}
size_t data_size = 0;
uintptr_t data = GetData(icu_data, &data_size);
// Pass the data to ICU.
if (data) {
UErrorCode err = U_ZERO_ERROR;
udata_setCommonData(reinterpret_cast<const char*>(data), &err);
g_icu_data_ptr = data;
g_icu_data_size = data_size;
if (err != U_ZERO_ERROR) {
icu::ErrorCode ec;
ec.set(err);
FX_LOGS(WARNING) << "failed to set common data: " << ec.errorName();
return ZX_ERR_INTERNAL;
}
// Validate tz revision ID if requested
if (expected_tz_revision_id.has_value()) {
const char* actual_tz_revision_id = icu::TimeZone::getTZDataVersion(err);
if (err != U_ZERO_ERROR) {
icu::ErrorCode ec;
ec.set(err);
FX_LOGS(WARNING) << "could not get tzdata version: " << ec.errorName();
return ZX_ERR_INTERNAL;
}
if (expected_tz_revision_id != std::string(actual_tz_revision_id, kRevisionIdLength)) {
FX_LOGS(WARNING) << "mismatched revision id: " << actual_tz_revision_id
<< ", expected: " << expected_tz_revision_id.value();
return ZX_ERR_IO_DATA_INTEGRITY;
}
}
return ZX_OK;
} else {
Release();
return ZX_ERR_INTERNAL;
}
}
zx_status_t Release() {
if (g_icu_data_ptr) {
// Unmap the ICU data.
zx_status_t status = zx::vmar::root_self()->unmap(g_icu_data_ptr, g_icu_data_size);
g_icu_data_ptr = 0u;
g_icu_data_size = 0;
return status;
} else {
return ZX_ERR_BAD_STATE;
}
}
} // namespace icu_data