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 #ifndef __ANDROID__
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  // __ANDROID__
61 
62 namespace androidicuinit {
63 namespace impl {
64 
65 #ifndef __ANDROID__
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 // #ifndef __ANDROID__
107 
108 // http://b/171371690 Avoid dependency on liblog and libbase on host
109 #ifdef __ANDROID__
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 libcore/ to ensure consistent behavior between
216   // ICU4C and ICU4J.
217 
218   // Check the timezone /data override file exists from the "Time zone update
219   // via APK" feature.
220   // https://source.android.com/devices/tech/config/timezone-rules
221   // If it does, map it first so we use its data in preference to later ones.
222   std::string dataPath = getDataTimeZonePath();
223   if (pathExists(dataPath)) {
224     AICU_LOGD("Time zone override file found: %s", dataPath.c_str());
225     icu_datamap_from_data_ = impl::IcuDataMap::Create(dataPath);
226     if (icu_datamap_from_data_ == nullptr) {
227       AICU_LOGW(
228           "TZ override /data file %s exists but could not be loaded. Skipping.",
229           dataPath.c_str());
230     }
231   } else {
232     AICU_LOGV("No timezone override /data file found: %s", dataPath.c_str());
233   }
234 
235   // Check the timezone override file exists from a mounted APEX file.
236   // If it does, map it next so we use its data in preference to later ones.
237   std::string tzModulePath = getTimeZoneModulePath();
238   if (pathExists(tzModulePath)) {
239     AICU_LOGD("Time zone APEX ICU file found: %s", tzModulePath.c_str());
240     icu_datamap_from_tz_module_ = impl::IcuDataMap::Create(tzModulePath);
241     if (icu_datamap_from_tz_module_ == nullptr) {
242       AICU_LOGW(
243           "TZ module override file %s exists but could not be loaded. "
244           "Skipping.",
245           tzModulePath.c_str());
246     }
247   } else {
248     AICU_LOGV("No time zone module override file found: %s", tzModulePath.c_str());
249   }
250 
251   // Use the ICU data files that shipped with the i18n module for everything
252   // else.
253   std::string i18nModulePath = getI18nModulePath();
254   icu_datamap_from_i18n_module_ = impl::IcuDataMap::Create(i18nModulePath);
255   if (icu_datamap_from_i18n_module_ == nullptr) {
256     // IcuDataMap::Create() will log on error so there is no need to log here.
257     abort();
258   }
259   AICU_LOGD("I18n APEX ICU file found: %s", i18nModulePath.c_str());
260 }
261 
262 // De-init ICU, unloading the data files. Do the opposite of the above function.
~IcuRegistration()263 IcuRegistration::~IcuRegistration() {
264   // Unmap ICU data files.
265   icu_datamap_from_i18n_module_.reset();
266   icu_datamap_from_tz_module_.reset();
267   icu_datamap_from_data_.reset();
268 }
269 
pathExists(const std::string & path)270 bool IcuRegistration::pathExists(const std::string& path) {
271   struct stat sb;
272   return stat(path.c_str(), &sb) == 0;
273 }
274 
275 // Returns a string containing the expected path of the (optional) /data tz data
276 // file
getDataTimeZonePath()277 std::string IcuRegistration::getDataTimeZonePath() {
278   const char* dataPathPrefix = getenv("ANDROID_DATA");
279   if (dataPathPrefix == NULL) {
280     AICU_LOGE("ANDROID_DATA environment variable not set");
281     abort();
282   }
283   std::string dataPath;
284   dataPath = dataPathPrefix;
285   dataPath += "/misc/zoneinfo/current/icu/icu_tzdata.dat";
286 
287   return dataPath;
288 }
289 
290 // Returns a string containing the expected path of the (optional) /apex tz
291 // module data file
getTimeZoneModulePath()292 std::string IcuRegistration::getTimeZoneModulePath() {
293   const char* tzdataModulePathPrefix = getenv("ANDROID_TZDATA_ROOT");
294   if (tzdataModulePathPrefix == NULL) {
295     AICU_LOGE("ANDROID_TZDATA_ROOT environment variable not set");
296     abort();
297   }
298 
299   std::string tzdataModulePath;
300   tzdataModulePath = tzdataModulePathPrefix;
301   tzdataModulePath += "/etc/icu/icu_tzdata.dat";
302   return tzdataModulePath;
303 }
304 
getI18nModulePath()305 std::string IcuRegistration::getI18nModulePath() {
306   const char* i18nModulePathPrefix = getenv("ANDROID_I18N_ROOT");
307   if (i18nModulePathPrefix == NULL) {
308     AICU_LOGE("ANDROID_I18N_ROOT environment variable not set");
309     abort();
310   }
311 
312   std::string i18nModulePath;
313   i18nModulePath = i18nModulePathPrefix;
314   i18nModulePath += "/etc/icu/" U_ICUDATA_NAME ".dat";
315   return i18nModulePath;
316 }
317 
318 }  // namespace androidicuinit
319 
android_icu_register()320 void android_icu_register() {
321   androidicuinit::IcuRegistration::Register();
322 }
323 
android_icu_deregister()324 void android_icu_deregister() {
325   androidicuinit::IcuRegistration::Deregister();
326 }
327 
android_icu_is_registered()328 bool android_icu_is_registered() {
329   return androidicuinit::gIcuRegistration.get() != nullptr;
330 }
331