1 /*
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 "IcuRegistration.h"
18
19 #include <sys/mman.h>
20 #include <sys/stat.h>
21 #include <sys/types.h>
22 #include <errno.h>
23 #include <fcntl.h>
24 #include <string.h>
25 #include <unistd.h>
26
27 #include <unicode/udata.h>
28 #include <unicode/utypes.h>
29
30 #if !defined(__ANDROID__) || defined(NO_ANDROID_LIBLOG)
PriorityToLevel(char priority)31 static int PriorityToLevel(char priority) {
32 // Priority is just the array index of priority in kPriorities.
33 static const char* kPriorities = "VDIWEF";
34 static const int kLogSuppress = sizeof(kPriorities);
35 const char* matching_priority = strchr(kPriorities, toupper(priority));
36 return (matching_priority != nullptr) ? matching_priority - kPriorities : kLogSuppress;
37 }
38
GetHostLogLevel()39 static int GetHostLogLevel() {
40 const char* log_tags = getenv("ANDROID_LOG_TAGS");
41 if (log_tags == nullptr) {
42 return 0;
43 }
44 // Find the wildcard prefix if present in ANDROID_LOG_TAGS.
45 static constexpr const char kLogWildcardPrefix[] = "*:";
46 static constexpr size_t kLogWildcardPrefixLength = sizeof(kLogWildcardPrefix) - 1;
47 const char* wildcard_start = strstr(log_tags, kLogWildcardPrefix);
48 if (wildcard_start == nullptr) {
49 return 0;
50 }
51 // Priority is based on the character after the wildcard prefix.
52 char priority = *(wildcard_start + kLogWildcardPrefixLength);
53 return PriorityToLevel(priority);
54 }
55
AIcuHostShouldLog(char priority)56 bool AIcuHostShouldLog(char priority) {
57 static int g_LogLevel = GetHostLogLevel();
58 return PriorityToLevel(priority) >= g_LogLevel;
59 }
60 #endif // !defined(__ANDROID__) || defined(NO_ANDROID_LIBLOG)
61
62 namespace androidicuinit {
63 namespace impl {
64
65 #if !defined(__ANDROID__) || defined(NO_ANDROID_LIBLOG)
66 // http://b/171371690 Avoid dependency on liblog and libbase on host
67 // Simplified version of android::base::unique_fd for host.
68 class simple_unique_fd final {
69 public:
simple_unique_fd(int fd)70 simple_unique_fd(int fd) {
71 reset(fd);
72 }
~simple_unique_fd()73 ~simple_unique_fd() {
74 reset();
75 }
get()76 int get() {
77 return fd_;
78 }
79
80 private:
81 int fd_ = -1;
reset(int new_fd=-1)82 void reset(int new_fd = -1) {
83 if (fd_ != -1) {
84 close(fd_);
85 }
86 fd_ = new_fd;
87 }
88 // Disable copy constructor and assignment operator
89 simple_unique_fd(const simple_unique_fd&) = delete;
90 void operator=(const simple_unique_fd&) = delete;
91
92 };
93
94 // A copy of TEMP_FAILURE_RETRY from android-base/macros.h
95 // bionic and glibc both have TEMP_FAILURE_RETRY, but eg Mac OS' libc doesn't.
96 #ifndef TEMP_FAILURE_RETRY
97 #define TEMP_FAILURE_RETRY(exp) \
98 ({ \
99 decltype(exp) _rc; \
100 do { \
101 _rc = (exp); \
102 } while (_rc == -1 && errno == EINTR); \
103 _rc; \
104 })
105 #endif
106 #endif // !defined(__ANDROID__) || defined(NO_ANDROID_LIBLOG)
107
108 // http://b/171371690 Avoid dependency on liblog and libbase on host
109 #if defined(__ANDROID__) && !defined(NO_ANDROID_LIBLOG)
110 typedef android::base::unique_fd aicu_unique_fd;
111 #else
112 typedef simple_unique_fd aicu_unique_fd;
113 #endif
114
115 // Map in ICU data at the path, returning null to print error if it failed.
Create(const std::string & path)116 std::unique_ptr<IcuDataMap> IcuDataMap::Create(const std::string& path) {
117 std::unique_ptr<IcuDataMap> map(new IcuDataMap(path));
118
119 if (!map->TryMap()) {
120 // madvise or ICU could fail but mmap still succeeds.
121 // Destructor will take care of cleaning up a partial init.
122 return nullptr;
123 }
124
125 return map;
126 }
127
128 // Unmap the ICU data.
~IcuDataMap()129 IcuDataMap::~IcuDataMap() { TryUnmap(); }
130
TryMap()131 bool IcuDataMap::TryMap() {
132 // Open the file and get its length.
133 aicu_unique_fd fd(TEMP_FAILURE_RETRY(open(path_.c_str(), O_RDONLY)));
134
135 if (fd.get() == -1) {
136 AICU_LOGE("Couldn't open '%s': %s", path_.c_str(), strerror(errno));
137 return false;
138 }
139
140 struct stat sb;
141 if (fstat(fd.get(), &sb) == -1) {
142 AICU_LOGE("Couldn't stat '%s': %s", path_.c_str(), strerror(errno));
143 return false;
144 }
145
146 data_length_ = sb.st_size;
147
148 // Map it.
149 data_ =
150 mmap(NULL, data_length_, PROT_READ, MAP_SHARED, fd.get(), 0 /* offset */);
151 if (data_ == MAP_FAILED) {
152 AICU_LOGE("Couldn't mmap '%s': %s", path_.c_str(), strerror(errno));
153 return false;
154 }
155
156 // Tell the kernel that accesses are likely to be random rather than
157 // sequential.
158 if (madvise(data_, data_length_, MADV_RANDOM) == -1) {
159 AICU_LOGE("Couldn't madvise(MADV_RANDOM) '%s': %s", path_.c_str(),
160 strerror(errno));
161 return false;
162 }
163
164 UErrorCode status = U_ZERO_ERROR;
165
166 // Tell ICU to use our memory-mapped data.
167 udata_setCommonData(data_, &status);
168 if (status != U_ZERO_ERROR) {
169 AICU_LOGE("Couldn't initialize ICU (udata_setCommonData): %s (%s)",
170 u_errorName(status), path_.c_str());
171 return false;
172 }
173
174 return true;
175 }
176
TryUnmap()177 bool IcuDataMap::TryUnmap() {
178 // Don't need to do opposite of udata_setCommonData,
179 // u_cleanup (performed in IcuRegistration::~IcuRegistration()) takes care of
180 // it.
181
182 // Don't need to opposite of madvise, munmap will take care of it.
183
184 if (data_ != nullptr && data_ != MAP_FAILED) {
185 if (munmap(data_, data_length_) == -1) {
186 AICU_LOGE("Couldn't munmap '%s': %s", path_.c_str(), strerror(errno));
187 return false;
188 }
189 }
190
191 // Don't need to close the file, it was closed automatically during TryMap.
192 return true;
193 }
194
195 } // namespace impl
196
197 // A pointer to the instance used by Register and Deregister. Since this code
198 // is currently included in a static library this doesn't prevent duplicate
199 // initialization calls.
200 static std::unique_ptr<IcuRegistration> gIcuRegistration;
201
Register()202 void IcuRegistration::Register() {
203 CHECK(gIcuRegistration.get() == nullptr);
204
205 gIcuRegistration.reset(new IcuRegistration());
206 }
207
Deregister()208 void IcuRegistration::Deregister() {
209 gIcuRegistration.reset();
210 }
211
212 // Init ICU, configuring it and loading the data files.
IcuRegistration()213 IcuRegistration::IcuRegistration() {
214 // Note: This logic below should match the logic for ICU4J in
215 // TimeZoneDataFiles.java in external/icu/ to ensure consistent behavior between
216 // ICU4C and ICU4J.
217
218 // Check the timezone override file exists from a mounted APEX file.
219 // If it does, map it so we use its data in preference to later ones.
220 std::string tzModulePath = getTimeZoneModulePath();
221 if (pathExists(tzModulePath)) {
222 AICU_LOGD("Time zone APEX ICU file found: %s", tzModulePath.c_str());
223 icu_datamap_from_tz_module_ = impl::IcuDataMap::Create(tzModulePath);
224 if (icu_datamap_from_tz_module_ == nullptr) {
225 AICU_LOGW(
226 "TZ module override file %s exists but could not be loaded. "
227 "Skipping.",
228 tzModulePath.c_str());
229 }
230 } else {
231 AICU_LOGV("No time zone module override file found: %s", tzModulePath.c_str());
232 }
233
234 // Use the ICU data files that shipped with the i18n module for everything
235 // else.
236 std::string i18nModulePath = getI18nModulePath();
237 icu_datamap_from_i18n_module_ = impl::IcuDataMap::Create(i18nModulePath);
238 if (icu_datamap_from_i18n_module_ == nullptr) {
239 // IcuDataMap::Create() will log on error so there is no need to log here.
240 abort();
241 }
242 AICU_LOGD("I18n APEX ICU file found: %s", i18nModulePath.c_str());
243 }
244
245 // De-init ICU, unloading the data files. Do the opposite of the above function.
~IcuRegistration()246 IcuRegistration::~IcuRegistration() {
247 // Unmap ICU data files.
248 icu_datamap_from_i18n_module_.reset();
249 icu_datamap_from_tz_module_.reset();
250 }
251
pathExists(const std::string & path)252 bool IcuRegistration::pathExists(const std::string& path) {
253 struct stat sb;
254 return stat(path.c_str(), &sb) == 0;
255 }
256
257 // Returns a string containing the expected path of the (optional) /apex tz
258 // module data file
getTimeZoneModulePath()259 std::string IcuRegistration::getTimeZoneModulePath() {
260 const char* tzdataModulePathPrefix = getenv("ANDROID_TZDATA_ROOT");
261 if (tzdataModulePathPrefix == NULL) {
262 AICU_LOGE("ANDROID_TZDATA_ROOT environment variable not set");
263 abort();
264 }
265
266 std::string tzdataModulePath;
267 tzdataModulePath = tzdataModulePathPrefix;
268 tzdataModulePath += "/etc/icu/icu_tzdata.dat";
269 return tzdataModulePath;
270 }
271
getI18nModulePath()272 std::string IcuRegistration::getI18nModulePath() {
273 const char* i18nModulePathPrefix = getenv("ANDROID_I18N_ROOT");
274 if (i18nModulePathPrefix == NULL) {
275 AICU_LOGE("ANDROID_I18N_ROOT environment variable not set");
276 abort();
277 }
278
279 std::string i18nModulePath;
280 i18nModulePath = i18nModulePathPrefix;
281 i18nModulePath += "/etc/icu/" U_ICUDATA_NAME ".dat";
282 return i18nModulePath;
283 }
284
285 } // namespace androidicuinit
286
android_icu_register()287 void android_icu_register() {
288 androidicuinit::IcuRegistration::Register();
289 }
290
android_icu_deregister()291 void android_icu_deregister() {
292 androidicuinit::IcuRegistration::Deregister();
293 }
294
android_icu_is_registered()295 bool android_icu_is_registered() {
296 return androidicuinit::gIcuRegistration.get() != nullptr;
297 }
298