1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "base/i18n/icu_util.h"
6
7#if defined(OS_WIN)
8#include <windows.h>
9#endif
10
11#include <string>
12
13#include "base/files/file_path.h"
14#include "base/files/memory_mapped_file.h"
15#include "base/logging.h"
16#include "base/path_service.h"
17#include "base/strings/string_util.h"
18#include "base/strings/sys_string_conversions.h"
19#include "third_party/icu/source/common/unicode/putil.h"
20#include "third_party/icu/source/common/unicode/udata.h"
21
22#if defined(OS_MACOSX)
23#include "base/mac/foundation_util.h"
24#endif
25
26#define ICU_UTIL_DATA_FILE   0
27#define ICU_UTIL_DATA_SHARED 1
28#define ICU_UTIL_DATA_STATIC 2
29
30#if ICU_UTIL_DATA_IMPL == ICU_UTIL_DATA_FILE
31// Use an unversioned file name to simplify a icu version update down the road.
32// No need to change the filename in multiple places (gyp files, windows
33// build pkg configurations, etc). 'l' stands for Little Endian.
34#define ICU_UTIL_DATA_FILE_NAME "icudtl.dat"
35#elif ICU_UTIL_DATA_IMPL == ICU_UTIL_DATA_SHARED
36#define ICU_UTIL_DATA_SYMBOL "icudt" U_ICU_VERSION_SHORT "_dat"
37#if defined(OS_WIN)
38#define ICU_UTIL_DATA_SHARED_MODULE_NAME "icudt.dll"
39#endif
40#endif
41
42namespace base {
43namespace i18n {
44
45namespace {
46
47#if !defined(NDEBUG)
48// Assert that we are not called more than once.  Even though calling this
49// function isn't harmful (ICU can handle it), being called twice probably
50// indicates a programming error.
51bool g_called_once = false;
52bool g_check_called_once = true;
53#endif
54}
55
56
57#if defined(OS_ANDROID)
58bool InitializeICUWithFileDescriptor(int data_fd) {
59#if !defined(NDEBUG)
60  DCHECK(!g_check_called_once || !g_called_once);
61  g_called_once = true;
62#endif
63
64#if (ICU_UTIL_DATA_IMPL == ICU_UTIL_DATA_STATIC)
65  // The ICU data is statically linked.
66  return true;
67#elif (ICU_UTIL_DATA_IMPL == ICU_UTIL_DATA_FILE)
68  CR_DEFINE_STATIC_LOCAL(base::MemoryMappedFile, mapped_file, ());
69  if (!mapped_file.IsValid()) {
70    if (!mapped_file.Initialize(base::File(data_fd))) {
71      LOG(ERROR) << "Couldn't mmap icu data file";
72      return false;
73    }
74  }
75  UErrorCode err = U_ZERO_ERROR;
76  udata_setCommonData(const_cast<uint8*>(mapped_file.data()), &err);
77  return err == U_ZERO_ERROR;
78#endif // ICU_UTIL_DATA_FILE
79}
80#endif
81
82
83bool InitializeICU() {
84#if !defined(NDEBUG)
85  DCHECK(!g_check_called_once || !g_called_once);
86  g_called_once = true;
87#endif
88
89#if (ICU_UTIL_DATA_IMPL == ICU_UTIL_DATA_SHARED)
90  // We expect to find the ICU data module alongside the current module.
91  FilePath data_path;
92  PathService::Get(base::DIR_MODULE, &data_path);
93  data_path = data_path.AppendASCII(ICU_UTIL_DATA_SHARED_MODULE_NAME);
94
95  HMODULE module = LoadLibrary(data_path.value().c_str());
96  if (!module) {
97    LOG(ERROR) << "Failed to load " << ICU_UTIL_DATA_SHARED_MODULE_NAME;
98    return false;
99  }
100
101  FARPROC addr = GetProcAddress(module, ICU_UTIL_DATA_SYMBOL);
102  if (!addr) {
103    LOG(ERROR) << ICU_UTIL_DATA_SYMBOL << ": not found in "
104               << ICU_UTIL_DATA_SHARED_MODULE_NAME;
105    return false;
106  }
107
108  UErrorCode err = U_ZERO_ERROR;
109  udata_setCommonData(reinterpret_cast<void*>(addr), &err);
110  return err == U_ZERO_ERROR;
111#elif (ICU_UTIL_DATA_IMPL == ICU_UTIL_DATA_STATIC)
112  // The ICU data is statically linked.
113  return true;
114#elif (ICU_UTIL_DATA_IMPL == ICU_UTIL_DATA_FILE)
115  // If the ICU data directory is set, ICU won't actually load the data until
116  // it is needed.  This can fail if the process is sandboxed at that time.
117  // Instead, we map the file in and hand off the data so the sandbox won't
118  // cause any problems.
119
120  // Chrome doesn't normally shut down ICU, so the mapped data shouldn't ever
121  // be released.
122  CR_DEFINE_STATIC_LOCAL(base::MemoryMappedFile, mapped_file, ());
123  if (!mapped_file.IsValid()) {
124#if !defined(OS_MACOSX)
125    FilePath data_path;
126#if defined(OS_WIN)
127    // The data file will be in the same directory as the current module.
128    bool path_ok = PathService::Get(base::DIR_MODULE, &data_path);
129#elif defined(OS_ANDROID)
130    bool path_ok = PathService::Get(base::DIR_ANDROID_APP_DATA, &data_path);
131#else
132    // For now, expect the data file to be alongside the executable.
133    // This is sufficient while we work on unit tests, but will eventually
134    // likely live in a data directory.
135    bool path_ok = PathService::Get(base::DIR_EXE, &data_path);
136#endif
137    DCHECK(path_ok);
138    data_path = data_path.AppendASCII(ICU_UTIL_DATA_FILE_NAME);
139#else
140    // Assume it is in the framework bundle's Resources directory.
141    FilePath data_path =
142      base::mac::PathForFrameworkBundleResource(CFSTR(ICU_UTIL_DATA_FILE_NAME));
143    if (data_path.empty()) {
144      LOG(ERROR) << ICU_UTIL_DATA_FILE_NAME << " not found in bundle";
145      return false;
146    }
147#endif  // OS check
148    if (!mapped_file.Initialize(data_path)) {
149      LOG(ERROR) << "Couldn't mmap " << data_path.AsUTF8Unsafe();
150      return false;
151    }
152  }
153  UErrorCode err = U_ZERO_ERROR;
154  udata_setCommonData(const_cast<uint8*>(mapped_file.data()), &err);
155  return err == U_ZERO_ERROR;
156#endif
157}
158
159void AllowMultipleInitializeCallsForTesting() {
160#if !defined(NDEBUG)
161  g_check_called_once = false;
162#endif
163}
164
165}  // namespace i18n
166}  // namespace base
167