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;
16FOR_ALL_ICU_FUNCTIONS
17#undef PER_FUNCTION_BLOCK
18
19static void* libicuuc = nullptr;
20static 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
26static const int MaxICUVersionStringLength = sizeof(VERSION_PREFIX_SUSE) + 33;
27
28#ifdef __APPLE__
29
30bool 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.
57static const int MinICUVersion = U_ICU_VERSION_MAJOR_NUM;
58static const int MaxICUVersion = MinICUVersion + 20;
59static const int MinMinorICUVersion = 1;
60static const int MaxMinorICUVersion = 5;
61static const int MinSubICUVersion = 1;
62static 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
69void 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
85bool 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
116bool 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).
148bool 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.
171bool 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.
196bool 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.
214bool 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
234bool 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
248extern "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
283extern "C" int32_t GlobalizationNative_GetICUVersion()
284{
285 int32_t version;
286 u_getVersion((uint8_t *) &version);
287 return version;
288}
289
290__attribute__((destructor))
291void 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