1 | // Licensed to the .NET Foundation under one or more agreements. |
2 | // The .NET Foundation licenses this file to you under the MIT license. |
3 | // See the LICENSE file in the project root for more information. |
4 | // |
5 | |
6 | #include <dlfcn.h> |
7 | #include <stdlib.h> |
8 | #include <stdio.h> |
9 | #include <string.h> |
10 | #include <assert.h> |
11 | |
12 | #include "icushim.h" |
13 | |
14 | // Define pointers to all the used ICU functions |
15 | #define PER_FUNCTION_BLOCK(fn, lib) decltype(fn)* fn##_ptr; |
16 | FOR_ALL_ICU_FUNCTIONS |
17 | #undef PER_FUNCTION_BLOCK |
18 | |
19 | static void* libicuuc = nullptr; |
20 | static void* libicui18n = nullptr; |
21 | |
22 | #define VERSION_PREFIX_NONE "" |
23 | #define VERSION_PREFIX_SUSE "suse" |
24 | |
25 | // .[suse]x.x.x, considering the max number of decimal digits for each component |
26 | static const int MaxICUVersionStringLength = sizeof(VERSION_PREFIX_SUSE) + 33; |
27 | |
28 | #ifdef __APPLE__ |
29 | |
30 | bool FindICULibs(const char* versionPrefix, char* symbolName, char* symbolVersion) |
31 | { |
32 | #ifndef OSX_ICU_LIBRARY_PATH |
33 | static_assert(false, "The ICU Library path is not defined" ); |
34 | #endif // OSX_ICU_LIBRARY_PATH |
35 | |
36 | // Usually OSX_ICU_LIBRARY_PATH is "/usr/lib/libicucore.dylib" |
37 | libicuuc = dlopen(OSX_ICU_LIBRARY_PATH, RTLD_LAZY); |
38 | |
39 | if (libicuuc == nullptr) |
40 | { |
41 | return false; |
42 | } |
43 | |
44 | // in OSX all ICU APIs exist in the same library libicucore.A.dylib |
45 | libicui18n = libicuuc; |
46 | |
47 | return true; |
48 | } |
49 | |
50 | #else // __APPLE__ |
51 | |
52 | // Version ranges to search for each of the three version components |
53 | // The rationale for major version range is that we support versions higher or |
54 | // equal to the version we are built against and less or equal to that version |
55 | // plus 20 to give us enough headspace. The ICU seems to version about twice |
56 | // a year. |
57 | static const int MinICUVersion = U_ICU_VERSION_MAJOR_NUM; |
58 | static const int MaxICUVersion = MinICUVersion + 20; |
59 | static const int MinMinorICUVersion = 1; |
60 | static const int MaxMinorICUVersion = 5; |
61 | static const int MinSubICUVersion = 1; |
62 | static const int MaxSubICUVersion = 5; |
63 | |
64 | // Get filename of an ICU library with the requested version in the name |
65 | // There are three possible cases of the version components values: |
66 | // 1. Only majorVer is not equal to -1 => result is baseFileName.majorver |
67 | // 2. Only majorVer and minorVer are not equal to -1 => result is baseFileName.majorver.minorVer |
68 | // 3. All components are not equal to -1 => result is baseFileName.majorver.minorVer.subver |
69 | void GetVersionedLibFileName(const char* baseFileName, int majorVer, int minorVer, int subVer, const char* versionPrefix, char* result) |
70 | { |
71 | assert(majorVer != -1); |
72 | |
73 | int nameLen = sprintf(result, "%s.%s%d" , baseFileName, versionPrefix, majorVer); |
74 | |
75 | if (minorVer != -1) |
76 | { |
77 | nameLen += sprintf(result + nameLen, ".%d" , minorVer); |
78 | if (subVer != -1) |
79 | { |
80 | sprintf(result + nameLen, ".%d" , subVer); |
81 | } |
82 | } |
83 | } |
84 | |
85 | bool FindSymbolVersion(int majorVer, int minorVer, int subVer, char* symbolName, char* symbolVersion) |
86 | { |
87 | // Find out the format of the version string added to each symbol |
88 | // First try just the unversioned symbol |
89 | if (dlsym(libicuuc, "u_strlen" ) == nullptr) |
90 | { |
91 | // Now try just the _majorVer added |
92 | sprintf(symbolVersion, "_%d" , majorVer); |
93 | sprintf(symbolName, "u_strlen%s" , symbolVersion); |
94 | if ((dlsym(libicuuc, symbolName) == nullptr) && (minorVer != -1)) |
95 | { |
96 | // Now try the _majorVer_minorVer added |
97 | sprintf(symbolVersion, "_%d_%d" , majorVer, minorVer); |
98 | sprintf(symbolName, "u_strlen%s" , symbolVersion); |
99 | if ((dlsym(libicuuc, symbolName) == nullptr) && (subVer != -1)) |
100 | { |
101 | // Finally, try the _majorVer_minorVer_subVer added |
102 | sprintf(symbolVersion, "_%d_%d_%d" , majorVer, minorVer, subVer); |
103 | sprintf(symbolName, "u_strlen%s" , symbolVersion); |
104 | if (dlsym(libicuuc, symbolName) == nullptr) |
105 | { |
106 | return false; |
107 | } |
108 | } |
109 | } |
110 | } |
111 | |
112 | return true; |
113 | } |
114 | |
115 | // Try to open the necessary ICU libraries |
116 | bool OpenICULibraries(int majorVer, int minorVer, int subVer, const char* versionPrefix, char* symbolName, char* symbolVersion) |
117 | { |
118 | char libicuucName[64]; |
119 | char libicui18nName[64]; |
120 | |
121 | static_assert(sizeof("libicuuc.so" ) + MaxICUVersionStringLength <= sizeof(libicuucName), "The libicuucName is too small" ); |
122 | GetVersionedLibFileName("libicuuc.so" , majorVer, minorVer, subVer, versionPrefix, libicuucName); |
123 | |
124 | static_assert(sizeof("libicui18n.so" ) + MaxICUVersionStringLength <= sizeof(libicui18nName), "The libicui18nName is too small" ); |
125 | GetVersionedLibFileName("libicui18n.so" , majorVer, minorVer, subVer, versionPrefix, libicui18nName); |
126 | |
127 | libicuuc = dlopen(libicuucName, RTLD_LAZY); |
128 | if (libicuuc != nullptr) |
129 | { |
130 | if (FindSymbolVersion(majorVer, minorVer, subVer, symbolName, symbolVersion)) |
131 | { |
132 | libicui18n = dlopen(libicui18nName, RTLD_LAZY); |
133 | } |
134 | if (libicui18n == nullptr) |
135 | { |
136 | dlclose(libicuuc); |
137 | libicuuc = nullptr; |
138 | } |
139 | } |
140 | |
141 | return libicuuc != nullptr; |
142 | } |
143 | |
144 | // Select libraries using the version override specified by the CLR_ICU_VERSION_OVERRIDE |
145 | // environment variable. |
146 | // The format of the string in this variable is majorVer[.minorVer[.subVer]] (the brackets |
147 | // indicate optional parts). |
148 | bool FindLibUsingOverride(const char* versionPrefix, char* symbolName, char* symbolVersion) |
149 | { |
150 | char* versionOverride = getenv("CLR_ICU_VERSION_OVERRIDE" ); |
151 | if (versionOverride != nullptr) |
152 | { |
153 | int first = -1; |
154 | int second = -1; |
155 | int third = -1; |
156 | |
157 | int matches = sscanf(versionOverride, "%d.%d.%d" , &first, &second, &third); |
158 | if (matches > 0) |
159 | { |
160 | if (OpenICULibraries(first, second, third, versionPrefix, symbolName, symbolVersion)) |
161 | { |
162 | return true; |
163 | } |
164 | } |
165 | } |
166 | |
167 | return false; |
168 | } |
169 | |
170 | // Search for library files with names including the major version. |
171 | bool FindLibWithMajorVersion(const char* versionPrefix, char* symbolName, char* symbolVersion) |
172 | { |
173 | // ICU packaging documentation (http://userguide.icu-project.org/packaging) |
174 | // describes applications link against the major (e.g. libicuuc.so.54). |
175 | |
176 | // Select the version of ICU present at build time. |
177 | if (OpenICULibraries(MinICUVersion, -1, -1, versionPrefix, symbolName, symbolVersion)) |
178 | { |
179 | return true; |
180 | } |
181 | |
182 | // Select the highest supported version of ICU present on the local machine |
183 | for (int i = MaxICUVersion; i > MinICUVersion; i--) |
184 | { |
185 | if (OpenICULibraries(i, -1, -1, versionPrefix, symbolName, symbolVersion)) |
186 | { |
187 | return true; |
188 | } |
189 | } |
190 | |
191 | return false; |
192 | } |
193 | |
194 | // Select the highest supported version of ICU present on the local machine |
195 | // Search for library files with names including the major and minor version. |
196 | bool FindLibWithMajorMinorVersion(const char* versionPrefix, char* symbolName, char* symbolVersion) |
197 | { |
198 | for (int i = MaxICUVersion; i >= MinICUVersion; i--) |
199 | { |
200 | for (int j = MaxMinorICUVersion; j >= MinMinorICUVersion; j--) |
201 | { |
202 | if (OpenICULibraries(i, j, -1, versionPrefix, symbolName, symbolVersion)) |
203 | { |
204 | return true; |
205 | } |
206 | } |
207 | } |
208 | |
209 | return false; |
210 | } |
211 | |
212 | // Select the highest supported version of ICU present on the local machine |
213 | // Search for library files with names including the major, minor and sub version. |
214 | bool FindLibWithMajorMinorSubVersion(const char* versionPrefix, char* symbolName, char* symbolVersion) |
215 | { |
216 | for (int i = MaxICUVersion; i >= MinICUVersion; i--) |
217 | { |
218 | for (int j = MaxMinorICUVersion; j >= MinMinorICUVersion; j--) |
219 | { |
220 | for (int k = MaxSubICUVersion; k >= MinSubICUVersion; k--) |
221 | { |
222 | if (OpenICULibraries(i, j, k, versionPrefix, symbolName, symbolVersion)) |
223 | { |
224 | return true; |
225 | } |
226 | } |
227 | } |
228 | } |
229 | |
230 | return false; |
231 | } |
232 | |
233 | |
234 | bool FindICULibs(const char* versionPrefix, char* symbolName, char* symbolVersion) |
235 | { |
236 | return FindLibUsingOverride(versionPrefix, symbolName, symbolVersion) || |
237 | FindLibWithMajorVersion(versionPrefix, symbolName, symbolVersion) || |
238 | FindLibWithMajorMinorVersion(versionPrefix, symbolName, symbolVersion) || |
239 | FindLibWithMajorMinorSubVersion(versionPrefix, symbolName, symbolVersion); |
240 | } |
241 | |
242 | #endif // __APPLE__ |
243 | |
244 | // GlobalizationNative_LoadICU |
245 | // This method get called from the managed side during the globalization initialization. |
246 | // This method shouldn't get called at all if we are running in globalization invariant mode |
247 | // return 0 if failed to load ICU and 1 otherwise |
248 | extern "C" int32_t GlobalizationNative_LoadICU() |
249 | { |
250 | char symbolName[128]; |
251 | char symbolVersion[MaxICUVersionStringLength + 1] = "" ; |
252 | |
253 | if (!FindICULibs(VERSION_PREFIX_NONE, symbolName, symbolVersion)) |
254 | { |
255 | #ifndef __APPLE__ |
256 | if (!FindICULibs(VERSION_PREFIX_SUSE, symbolName, symbolVersion)) |
257 | #endif |
258 | { |
259 | return 0; |
260 | } |
261 | } |
262 | |
263 | // Get pointers to all the ICU functions that are needed |
264 | #define PER_FUNCTION_BLOCK(fn, lib) \ |
265 | static_assert((sizeof(#fn) + MaxICUVersionStringLength + 1) <= sizeof(symbolName), "The symbolName is too small for symbol " #fn); \ |
266 | sprintf(symbolName, #fn "%s", symbolVersion); \ |
267 | fn##_ptr = (decltype(fn)*)dlsym(lib, symbolName); \ |
268 | if (fn##_ptr == NULL) { fprintf(stderr, "Cannot get symbol %s from " #lib "\nError: %s\n", symbolName, dlerror()); abort(); } |
269 | |
270 | FOR_ALL_ICU_FUNCTIONS |
271 | #undef PER_FUNCTION_BLOCK |
272 | |
273 | #ifdef __APPLE__ |
274 | // libicui18n initialized with libicuuc so we null it to avoid double closing same handle |
275 | libicui18n = nullptr; |
276 | #endif // __APPLE__ |
277 | |
278 | return 1; |
279 | } |
280 | |
281 | // GlobalizationNative_GetICUVersion |
282 | // return the current loaded ICU version |
283 | extern "C" int32_t GlobalizationNative_GetICUVersion() |
284 | { |
285 | int32_t version; |
286 | u_getVersion((uint8_t *) &version); |
287 | return version; |
288 | } |
289 | |
290 | __attribute__((destructor)) |
291 | void ShutdownICUShim() |
292 | { |
293 | if (libicuuc != nullptr) |
294 | { |
295 | dlclose(libicuuc); |
296 | libicuuc = nullptr; |
297 | } |
298 | |
299 | if (libicui18n != nullptr) |
300 | { |
301 | dlclose(libicui18n); |
302 | libicui18n = nullptr; |
303 | } |
304 | } |
305 | |