blob: 10e419b8d73e9540a0579abacfdda157ba0172fd [file] [log] [blame]
Neil Fullere24629b2019-06-12 13:02:34 +01001/*
2 * Copyright (C) 2019 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include "androidicuinit/IcuRegistration.h"
Victor Chang72321a62019-09-11 17:49:13 +010018#include "androidicuinit/android_icu_reg.h"
Neil Fullere24629b2019-06-12 13:02:34 +010019
20#include <sys/mman.h>
21#include <sys/stat.h>
22#include <sys/types.h>
23#include <unistd.h>
24
25#include <android-base/logging.h>
26#include <android-base/unique_fd.h>
27#include <log/log.h>
28#include <unicode/uclean.h>
29#include <unicode/udata.h>
30#include <unicode/utypes.h>
31
32namespace androidicuinit {
33namespace impl {
34
35// Map in ICU data at the path, returning null if it failed (prints error to
36// ALOGE).
37std::unique_ptr<IcuDataMap> IcuDataMap::Create(const std::string& path) {
38 std::unique_ptr<IcuDataMap> map(new IcuDataMap(path));
39
40 if (!map->TryMap()) {
41 // madvise or ICU could fail but mmap still succeeds.
42 // Destructor will take care of cleaning up a partial init.
43 return nullptr;
44 }
45
46 return map;
47}
48
49// Unmap the ICU data.
50IcuDataMap::~IcuDataMap() { TryUnmap(); }
51
52bool IcuDataMap::TryMap() {
53 // Open the file and get its length.
54 android::base::unique_fd fd(
55 TEMP_FAILURE_RETRY(open(path_.c_str(), O_RDONLY)));
56
57 if (fd.get() == -1) {
58 ALOGE("Couldn't open '%s': %s", path_.c_str(), strerror(errno));
59 return false;
60 }
61
62 struct stat sb;
63 if (fstat(fd.get(), &sb) == -1) {
64 ALOGE("Couldn't stat '%s': %s", path_.c_str(), strerror(errno));
65 return false;
66 }
67
68 data_length_ = sb.st_size;
69
70 // Map it.
71 data_ =
72 mmap(NULL, data_length_, PROT_READ, MAP_SHARED, fd.get(), 0 /* offset */);
73 if (data_ == MAP_FAILED) {
74 ALOGE("Couldn't mmap '%s': %s", path_.c_str(), strerror(errno));
75 return false;
76 }
77
78 // Tell the kernel that accesses are likely to be random rather than
79 // sequential.
80 if (madvise(data_, data_length_, MADV_RANDOM) == -1) {
81 ALOGE("Couldn't madvise(MADV_RANDOM) '%s': %s", path_.c_str(),
82 strerror(errno));
83 return false;
84 }
85
86 UErrorCode status = U_ZERO_ERROR;
87
88 // Tell ICU to use our memory-mapped data.
89 udata_setCommonData(data_, &status);
90 if (status != U_ZERO_ERROR) {
91 ALOGE("Couldn't initialize ICU (udata_setCommonData): %s (%s)",
92 u_errorName(status), path_.c_str());
93 return false;
94 }
95
96 return true;
97}
98
99bool IcuDataMap::TryUnmap() {
100 // Don't need to do opposite of udata_setCommonData,
101 // u_cleanup (performed in IcuRegistration::~IcuRegistration()) takes care of
102 // it.
103
104 // Don't need to opposite of madvise, munmap will take care of it.
105
106 if (data_ != nullptr && data_ != MAP_FAILED) {
107 if (munmap(data_, data_length_) == -1) {
108 ALOGE("Couldn't munmap '%s': %s", path_.c_str(), strerror(errno));
109 return false;
110 }
111 }
112
113 // Don't need to close the file, it was closed automatically during TryMap.
114 return true;
115}
116
117} // namespace impl
118
119// A pointer to the instance used by Register and Deregister. Since this code
120// is currently included in a static library this doesn't prevent duplicate
121// initialization calls.
122static std::unique_ptr<IcuRegistration> gIcuRegistration;
123
124void IcuRegistration::Register() {
125 CHECK(gIcuRegistration.get() == nullptr);
126
127 gIcuRegistration.reset(new IcuRegistration());
128}
129
130void IcuRegistration::Deregister() {
131 gIcuRegistration.reset();
132}
133
134// Init ICU, configuring it and loading the data files.
135IcuRegistration::IcuRegistration() {
136 UErrorCode status = U_ZERO_ERROR;
137 // Tell ICU it can *only* use our memory-mapped data.
138 udata_setFileAccess(UDATA_NO_FILES, &status);
139 if (status != U_ZERO_ERROR) {
140 ALOGE("Couldn't initialize ICU (s_setFileAccess): %s", u_errorName(status));
141 abort();
142 }
143
144 // Note: This logic below should match the logic for ICU4J in
145 // TimeZoneDataFiles.java in libcore/ to ensure consistent behavior between
146 // ICU4C and ICU4J.
147
148 // Check the timezone /data override file exists from the "Time zone update
149 // via APK" feature.
150 // https://source.android.com/devices/tech/config/timezone-rules
151 // If it does, map it first so we use its data in preference to later ones.
152 std::string dataPath = getDataTimeZonePath();
153 if (pathExists(dataPath)) {
154 ALOGD("Time zone override file found: %s", dataPath.c_str());
155 icu_datamap_from_data_ = impl::IcuDataMap::Create(dataPath);
156 if (icu_datamap_from_data_ == nullptr) {
157 ALOGW(
158 "TZ override /data file %s exists but could not be loaded. Skipping.",
159 dataPath.c_str());
160 }
161 } else {
162 ALOGV("No timezone override /data file found: %s", dataPath.c_str());
163 }
164
165 // Check the timezone override file exists from a mounted APEX file.
166 // If it does, map it next so we use its data in preference to later ones.
167 std::string tzModulePath = getTimeZoneModulePath();
168 if (pathExists(tzModulePath)) {
169 ALOGD("Time zone APEX ICU file found: %s", tzModulePath.c_str());
170 icu_datamap_from_tz_module_ = impl::IcuDataMap::Create(tzModulePath);
171 if (icu_datamap_from_tz_module_ == nullptr) {
172 ALOGW(
173 "TZ module override file %s exists but could not be loaded. "
174 "Skipping.",
175 tzModulePath.c_str());
176 }
177 } else {
178 ALOGV("No time zone module override file found: %s", tzModulePath.c_str());
179 }
180
Victor Changde8ba8a2019-07-05 16:33:55 +0100181 // Use the ICU data files that shipped with the i18n module for everything
Neil Fullere24629b2019-06-12 13:02:34 +0100182 // else.
Victor Changde8ba8a2019-07-05 16:33:55 +0100183 std::string i18nModulePath = getI18nModulePath();
184 icu_datamap_from_i18n_module_ = impl::IcuDataMap::Create(i18nModulePath);
185 if (icu_datamap_from_i18n_module_ == nullptr) {
Neil Fullere24629b2019-06-12 13:02:34 +0100186 // IcuDataMap::Create() will log on error so there is no need to log here.
187 abort();
188 }
Victor Changde8ba8a2019-07-05 16:33:55 +0100189 ALOGD("I18n APEX ICU file found: %s", i18nModulePath.c_str());
Neil Fullere24629b2019-06-12 13:02:34 +0100190
191 // Failures to find the ICU data tend to be somewhat obscure because ICU loads
192 // its data on first use, which can be anywhere. Force initialization up front
193 // so we can report a nice clear error and bail.
194 u_init(&status);
195 if (status != U_ZERO_ERROR) {
196 ALOGE("Couldn't initialize ICU (u_init): %s", u_errorName(status));
197 abort();
198 }
199}
200
201// De-init ICU, unloading the data files. Do the opposite of the above function.
202IcuRegistration::~IcuRegistration() {
203 // Reset libicu state to before it was loaded.
204 u_cleanup();
205
206 // Unmap ICU data files.
Victor Changde8ba8a2019-07-05 16:33:55 +0100207 icu_datamap_from_i18n_module_.reset();
Neil Fullere24629b2019-06-12 13:02:34 +0100208 icu_datamap_from_tz_module_.reset();
209 icu_datamap_from_data_.reset();
210
211 // We don't need to call udata_setFileAccess because u_cleanup takes care of
212 // it.
213}
214
Greg Kaiser60affcd2019-07-11 12:39:54 -0700215bool IcuRegistration::pathExists(const std::string& path) {
Neil Fullere24629b2019-06-12 13:02:34 +0100216 struct stat sb;
217 return stat(path.c_str(), &sb) == 0;
218}
219
220// Returns a string containing the expected path of the (optional) /data tz data
221// file
222std::string IcuRegistration::getDataTimeZonePath() {
223 const char* dataPathPrefix = getenv("ANDROID_DATA");
224 if (dataPathPrefix == NULL) {
225 ALOGE("ANDROID_DATA environment variable not set");
226 abort();
227 }
228 std::string dataPath;
229 dataPath = dataPathPrefix;
230 dataPath += "/misc/zoneinfo/current/icu/icu_tzdata.dat";
231
232 return dataPath;
233}
234
235// Returns a string containing the expected path of the (optional) /apex tz
236// module data file
237std::string IcuRegistration::getTimeZoneModulePath() {
238 const char* tzdataModulePathPrefix = getenv("ANDROID_TZDATA_ROOT");
239 if (tzdataModulePathPrefix == NULL) {
240 ALOGE("ANDROID_TZDATA_ROOT environment variable not set");
241 abort();
242 }
243
244 std::string tzdataModulePath;
245 tzdataModulePath = tzdataModulePathPrefix;
246 tzdataModulePath += "/etc/icu/icu_tzdata.dat";
247 return tzdataModulePath;
248}
249
Victor Changde8ba8a2019-07-05 16:33:55 +0100250std::string IcuRegistration::getI18nModulePath() {
251 const char* i18nModulePathPrefix = getenv("ANDROID_I18N_ROOT");
252 if (i18nModulePathPrefix == NULL) {
253 ALOGE("ANDROID_I18N_ROOT environment variable not set");
Neil Fullere24629b2019-06-12 13:02:34 +0100254 abort();
255 }
256
Victor Changde8ba8a2019-07-05 16:33:55 +0100257 std::string i18nModulePath;
258 i18nModulePath = i18nModulePathPrefix;
259 i18nModulePath += "/etc/icu/" U_ICUDATA_NAME ".dat";
260 return i18nModulePath;
Neil Fullere24629b2019-06-12 13:02:34 +0100261}
262
263} // namespace androidicuinit
Victor Chang72321a62019-09-11 17:49:13 +0100264
265extern "C" {
266void android_icu_register() {
267 androidicuinit::IcuRegistration::Register();
268}
269
270
271void android_icu_deregister() {
272 androidicuinit::IcuRegistration::Deregister();
273}
274}