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