blob: b3c96e3c77bfe496a9eb6f7d2bf76d9e997a6ea2 [file] [log] [blame]
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "androidicuinit/android_icu_reg.h"
#include "IcuRegistration.h"
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <android-base/logging.h>
#include <android-base/unique_fd.h>
#include <log/log.h>
#include <unicode/uclean.h>
#include <unicode/udata.h>
#include <unicode/utypes.h>
namespace androidicuinit {
namespace impl {
// Map in ICU data at the path, returning null if it failed (prints error to
// ALOGE).
std::unique_ptr<IcuDataMap> IcuDataMap::Create(const std::string& path) {
std::unique_ptr<IcuDataMap> map(new IcuDataMap(path));
if (!map->TryMap()) {
// madvise or ICU could fail but mmap still succeeds.
// Destructor will take care of cleaning up a partial init.
return nullptr;
}
return map;
}
// Unmap the ICU data.
IcuDataMap::~IcuDataMap() { TryUnmap(); }
bool IcuDataMap::TryMap() {
// Open the file and get its length.
android::base::unique_fd fd(
TEMP_FAILURE_RETRY(open(path_.c_str(), O_RDONLY)));
if (fd.get() == -1) {
ALOGE("Couldn't open '%s': %s", path_.c_str(), strerror(errno));
return false;
}
struct stat sb;
if (fstat(fd.get(), &sb) == -1) {
ALOGE("Couldn't stat '%s': %s", path_.c_str(), strerror(errno));
return false;
}
data_length_ = sb.st_size;
// Map it.
data_ =
mmap(NULL, data_length_, PROT_READ, MAP_SHARED, fd.get(), 0 /* offset */);
if (data_ == MAP_FAILED) {
ALOGE("Couldn't mmap '%s': %s", path_.c_str(), strerror(errno));
return false;
}
// Tell the kernel that accesses are likely to be random rather than
// sequential.
if (madvise(data_, data_length_, MADV_RANDOM) == -1) {
ALOGE("Couldn't madvise(MADV_RANDOM) '%s': %s", path_.c_str(),
strerror(errno));
return false;
}
UErrorCode status = U_ZERO_ERROR;
// Tell ICU to use our memory-mapped data.
udata_setCommonData(data_, &status);
if (status != U_ZERO_ERROR) {
ALOGE("Couldn't initialize ICU (udata_setCommonData): %s (%s)",
u_errorName(status), path_.c_str());
return false;
}
return true;
}
bool IcuDataMap::TryUnmap() {
// Don't need to do opposite of udata_setCommonData,
// u_cleanup (performed in IcuRegistration::~IcuRegistration()) takes care of
// it.
// Don't need to opposite of madvise, munmap will take care of it.
if (data_ != nullptr && data_ != MAP_FAILED) {
if (munmap(data_, data_length_) == -1) {
ALOGE("Couldn't munmap '%s': %s", path_.c_str(), strerror(errno));
return false;
}
}
// Don't need to close the file, it was closed automatically during TryMap.
return true;
}
} // namespace impl
// A pointer to the instance used by Register and Deregister. Since this code
// is currently included in a static library this doesn't prevent duplicate
// initialization calls.
static std::unique_ptr<IcuRegistration> gIcuRegistration;
void IcuRegistration::Register() {
CHECK(gIcuRegistration.get() == nullptr);
gIcuRegistration.reset(new IcuRegistration());
}
void IcuRegistration::Deregister() {
gIcuRegistration.reset();
}
// Init ICU, configuring it and loading the data files.
IcuRegistration::IcuRegistration() {
UErrorCode status = U_ZERO_ERROR;
// Tell ICU it can *only* use our memory-mapped data.
udata_setFileAccess(UDATA_NO_FILES, &status);
if (status != U_ZERO_ERROR) {
ALOGE("Couldn't initialize ICU (s_setFileAccess): %s", u_errorName(status));
abort();
}
// Note: This logic below should match the logic for ICU4J in
// TimeZoneDataFiles.java in libcore/ to ensure consistent behavior between
// ICU4C and ICU4J.
// Check the timezone /data override file exists from the "Time zone update
// via APK" feature.
// https://source.android.com/devices/tech/config/timezone-rules
// If it does, map it first so we use its data in preference to later ones.
std::string dataPath = getDataTimeZonePath();
if (pathExists(dataPath)) {
ALOGD("Time zone override file found: %s", dataPath.c_str());
icu_datamap_from_data_ = impl::IcuDataMap::Create(dataPath);
if (icu_datamap_from_data_ == nullptr) {
ALOGW(
"TZ override /data file %s exists but could not be loaded. Skipping.",
dataPath.c_str());
}
} else {
ALOGV("No timezone override /data file found: %s", dataPath.c_str());
}
// Check the timezone override file exists from a mounted APEX file.
// If it does, map it next so we use its data in preference to later ones.
std::string tzModulePath = getTimeZoneModulePath();
if (pathExists(tzModulePath)) {
ALOGD("Time zone APEX ICU file found: %s", tzModulePath.c_str());
icu_datamap_from_tz_module_ = impl::IcuDataMap::Create(tzModulePath);
if (icu_datamap_from_tz_module_ == nullptr) {
ALOGW(
"TZ module override file %s exists but could not be loaded. "
"Skipping.",
tzModulePath.c_str());
}
} else {
ALOGV("No time zone module override file found: %s", tzModulePath.c_str());
}
// Use the ICU data files that shipped with the i18n module for everything
// else.
std::string i18nModulePath = getI18nModulePath();
icu_datamap_from_i18n_module_ = impl::IcuDataMap::Create(i18nModulePath);
if (icu_datamap_from_i18n_module_ == nullptr) {
// IcuDataMap::Create() will log on error so there is no need to log here.
abort();
}
ALOGD("I18n APEX ICU file found: %s", i18nModulePath.c_str());
// Failures to find the ICU data tend to be somewhat obscure because ICU loads
// its data on first use, which can be anywhere. Force initialization up front
// so we can report a nice clear error and bail.
u_init(&status);
if (status != U_ZERO_ERROR) {
ALOGE("Couldn't initialize ICU (u_init): %s", u_errorName(status));
abort();
}
}
// De-init ICU, unloading the data files. Do the opposite of the above function.
IcuRegistration::~IcuRegistration() {
// Reset libicu state to before it was loaded.
u_cleanup();
// Unmap ICU data files.
icu_datamap_from_i18n_module_.reset();
icu_datamap_from_tz_module_.reset();
icu_datamap_from_data_.reset();
// We don't need to call udata_setFileAccess because u_cleanup takes care of
// it.
}
bool IcuRegistration::pathExists(const std::string& path) {
struct stat sb;
return stat(path.c_str(), &sb) == 0;
}
// Returns a string containing the expected path of the (optional) /data tz data
// file
std::string IcuRegistration::getDataTimeZonePath() {
const char* dataPathPrefix = getenv("ANDROID_DATA");
if (dataPathPrefix == NULL) {
ALOGE("ANDROID_DATA environment variable not set");
abort();
}
std::string dataPath;
dataPath = dataPathPrefix;
dataPath += "/misc/zoneinfo/current/icu/icu_tzdata.dat";
return dataPath;
}
// Returns a string containing the expected path of the (optional) /apex tz
// module data file
std::string IcuRegistration::getTimeZoneModulePath() {
const char* tzdataModulePathPrefix = getenv("ANDROID_TZDATA_ROOT");
if (tzdataModulePathPrefix == NULL) {
ALOGE("ANDROID_TZDATA_ROOT environment variable not set");
abort();
}
std::string tzdataModulePath;
tzdataModulePath = tzdataModulePathPrefix;
tzdataModulePath += "/etc/icu/icu_tzdata.dat";
return tzdataModulePath;
}
std::string IcuRegistration::getI18nModulePath() {
const char* i18nModulePathPrefix = getenv("ANDROID_I18N_ROOT");
if (i18nModulePathPrefix == NULL) {
ALOGE("ANDROID_I18N_ROOT environment variable not set");
abort();
}
std::string i18nModulePath;
i18nModulePath = i18nModulePathPrefix;
i18nModulePath += "/etc/icu/" U_ICUDATA_NAME ".dat";
return i18nModulePath;
}
} // namespace androidicuinit
extern "C" {
void android_icu_register() {
androidicuinit::IcuRegistration::Register();
}
void android_icu_deregister() {
androidicuinit::IcuRegistration::Deregister();
}
}