summaryrefslogtreecommitdiff
path: root/libandroidicuinit/IcuRegistration.cpp
blob: 1d68e4c05cca7483a86a5b83b3636e6c69435ce5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
/*
 * 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 "IcuRegistration.h"

#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>

#include <unicode/udata.h>
#include <unicode/utypes.h>

#if !defined(__ANDROID__) || defined(NO_ANDROID_LIBLOG)
static int PriorityToLevel(char priority) {
  // Priority is just the array index of priority in kPriorities.
  static const char* kPriorities = "VDIWEF";
  static const int kLogSuppress = sizeof(kPriorities);
  const char* matching_priority = strchr(kPriorities, toupper(priority));
  return (matching_priority != nullptr) ? matching_priority - kPriorities : kLogSuppress;
}

static int GetHostLogLevel() {
  const char* log_tags = getenv("ANDROID_LOG_TAGS");
  if (log_tags == nullptr) {
    return 0;
  }
  // Find the wildcard prefix if present in ANDROID_LOG_TAGS.
  static constexpr const char kLogWildcardPrefix[] = "*:";
  static constexpr size_t kLogWildcardPrefixLength = sizeof(kLogWildcardPrefix) - 1;
  const char* wildcard_start = strstr(log_tags, kLogWildcardPrefix);
  if (wildcard_start == nullptr) {
    return 0;
  }
  // Priority is based on the character after the wildcard prefix.
  char priority = *(wildcard_start + kLogWildcardPrefixLength);
  return PriorityToLevel(priority);
}

bool AIcuHostShouldLog(char priority) {
  static int g_LogLevel = GetHostLogLevel();
  return PriorityToLevel(priority) >= g_LogLevel;
}
#endif  // !defined(__ANDROID__) || defined(NO_ANDROID_LIBLOG)

namespace androidicuinit {
namespace impl {

#if !defined(__ANDROID__) || defined(NO_ANDROID_LIBLOG)
// http://b/171371690 Avoid dependency on liblog and libbase on host
// Simplified version of android::base::unique_fd for host.
class simple_unique_fd final {
 public:
  simple_unique_fd(int fd) {
    reset(fd);
  }
  ~simple_unique_fd() {
    reset();
  }
  int get() {
    return fd_;
  }

 private:
  int fd_ = -1;
  void reset(int new_fd = -1) {
    if (fd_ != -1) {
      close(fd_);
    }
    fd_ = new_fd;
  }
  // Disable copy constructor and assignment operator
  simple_unique_fd(const simple_unique_fd&) = delete;
  void operator=(const simple_unique_fd&) = delete;

};

  // A copy of TEMP_FAILURE_RETRY from android-base/macros.h
  // bionic and glibc both have TEMP_FAILURE_RETRY, but eg Mac OS' libc doesn't.
  #ifndef TEMP_FAILURE_RETRY
    #define TEMP_FAILURE_RETRY(exp)            \
      ({                                       \
        decltype(exp) _rc;                     \
        do {                                   \
          _rc = (exp);                         \
        } while (_rc == -1 && errno == EINTR); \
        _rc;                                   \
      })
  #endif
#endif // !defined(__ANDROID__) || defined(NO_ANDROID_LIBLOG)

// http://b/171371690 Avoid dependency on liblog and libbase on host
#if defined(__ANDROID__) && !defined(NO_ANDROID_LIBLOG)
  typedef android::base::unique_fd aicu_unique_fd;
#else
  typedef simple_unique_fd aicu_unique_fd;
#endif

// Map in ICU data at the path, returning null to print error if it failed.
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.
  aicu_unique_fd fd(TEMP_FAILURE_RETRY(open(path_.c_str(), O_RDONLY)));

  if (fd.get() == -1) {
    AICU_LOGE("Couldn't open '%s': %s", path_.c_str(), strerror(errno));
    return false;
  }

  struct stat sb;
  if (fstat(fd.get(), &sb) == -1) {
    AICU_LOGE("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) {
    AICU_LOGE("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) {
    AICU_LOGE("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) {
    AICU_LOGE("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) {
      AICU_LOGE("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() {
  // 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)) {
    AICU_LOGD("Time zone override file found: %s", dataPath.c_str());
    icu_datamap_from_data_ = impl::IcuDataMap::Create(dataPath);
    if (icu_datamap_from_data_ == nullptr) {
      AICU_LOGW(
          "TZ override /data file %s exists but could not be loaded. Skipping.",
          dataPath.c_str());
    }
  } else {
    AICU_LOGV("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)) {
    AICU_LOGD("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) {
      AICU_LOGW(
          "TZ module override file %s exists but could not be loaded. "
          "Skipping.",
          tzModulePath.c_str());
    }
  } else {
    AICU_LOGV("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();
  }
  AICU_LOGD("I18n APEX ICU file found: %s", i18nModulePath.c_str());
}

// De-init ICU, unloading the data files. Do the opposite of the above function.
IcuRegistration::~IcuRegistration() {
  // Unmap ICU data files.
  icu_datamap_from_i18n_module_.reset();
  icu_datamap_from_tz_module_.reset();
  icu_datamap_from_data_.reset();
}

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) {
    AICU_LOGE("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) {
    AICU_LOGE("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) {
    AICU_LOGE("ANDROID_I18N_ROOT environment variable not set");
    abort();
  }

  std::string i18nModulePath;
  i18nModulePath = i18nModulePathPrefix;
  i18nModulePath += "/etc/icu/" U_ICUDATA_NAME ".dat";
  return i18nModulePath;
}

}  // namespace androidicuinit

void android_icu_register() {
  androidicuinit::IcuRegistration::Register();
}

void android_icu_deregister() {
  androidicuinit::IcuRegistration::Deregister();
}

bool android_icu_is_registered() {
  return androidicuinit::gIcuRegistration.get() != nullptr;
}