1 | // © 2016 and later: Unicode, Inc. and others. |
2 | // License & terms of use: http://www.unicode.org/copyright.html |
3 | /* |
4 | ****************************************************************************** |
5 | * |
6 | * Copyright (C) 1999-2013, International Business Machines |
7 | * Corporation and others. All Rights Reserved. |
8 | * |
9 | ******************************************************************************/ |
10 | |
11 | |
12 | /*---------------------------------------------------------------------------- |
13 | * |
14 | * Memory mapped file wrappers for use by the ICU Data Implementation |
15 | * All of the platform-specific implementation for mapping data files |
16 | * is here. The rest of the ICU Data implementation uses only the |
17 | * wrapper functions. |
18 | * |
19 | *----------------------------------------------------------------------------*/ |
20 | /* Defines _XOPEN_SOURCE for access to POSIX functions. |
21 | * Must be before any other #includes. */ |
22 | #include "uposixdefs.h" |
23 | |
24 | #include "unicode/putil.h" |
25 | #include "unicode/ustring.h" |
26 | #include "udatamem.h" |
27 | #include "umapfile.h" |
28 | |
29 | /* memory-mapping base definitions ------------------------------------------ */ |
30 | |
31 | #if MAP_IMPLEMENTATION==MAP_WIN32 |
32 | #ifndef WIN32_LEAN_AND_MEAN |
33 | # define WIN32_LEAN_AND_MEAN |
34 | #endif |
35 | # define VC_EXTRALEAN |
36 | # define NOUSER |
37 | # define NOSERVICE |
38 | # define NOIME |
39 | # define NOMCX |
40 | |
41 | # if U_PLATFORM_HAS_WINUWP_API == 1 |
42 | // Some previous versions of the Windows 10 SDK don't expose various APIs for UWP applications |
43 | // to use, even though UWP apps are allowed to call and use them. Temporarily change the |
44 | // WINAPI family partition below to Desktop, so that function declarations are visible for UWP. |
45 | # include <winapifamily.h> |
46 | # if !(WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP | WINAPI_PARTITION_SYSTEM)) |
47 | # pragma push_macro("WINAPI_PARTITION_DESKTOP") |
48 | # undef WINAPI_PARTITION_DESKTOP |
49 | # define WINAPI_PARTITION_DESKTOP 1 |
50 | # define CHANGED_WINAPI_PARTITION_DESKTOP_VALUE |
51 | # endif |
52 | # endif |
53 | |
54 | # include <windows.h> |
55 | |
56 | # if U_PLATFORM_HAS_WINUWP_API == 1 && defined(CHANGED_WINAPI_PARTITION_DESKTOP_VALUE) |
57 | # pragma pop_macro("WINAPI_PARTITION_DESKTOP") |
58 | # endif |
59 | |
60 | # include "cmemory.h" |
61 | |
62 | typedef HANDLE MemoryMap; |
63 | |
64 | # define IS_MAP(map) ((map)!=nullptr) |
65 | |
66 | #elif MAP_IMPLEMENTATION==MAP_POSIX || MAP_IMPLEMENTATION==MAP_390DLL |
67 | typedef size_t MemoryMap; |
68 | |
69 | # define IS_MAP(map) ((map)!=0) |
70 | |
71 | # include <unistd.h> |
72 | # include <sys/mman.h> |
73 | # include <sys/stat.h> |
74 | # include <fcntl.h> |
75 | |
76 | # ifndef MAP_FAILED |
77 | # define MAP_FAILED ((void*)-1) |
78 | # endif |
79 | |
80 | # if MAP_IMPLEMENTATION==MAP_390DLL |
81 | /* No memory mapping for 390 batch mode. Fake it using dll loading. */ |
82 | # include <dll.h> |
83 | # include "cstring.h" |
84 | # include "cmemory.h" |
85 | # include "unicode/udata.h" |
86 | # define LIB_PREFIX "lib" |
87 | # define LIB_SUFFIX ".dll" |
88 | /* This is inconvenient until we figure out what to do with U_ICUDATA_NAME in utypes.h */ |
89 | # define U_ICUDATA_ENTRY_NAME "icudt" U_ICU_VERSION_SHORT U_LIB_SUFFIX_C_NAME_STRING "_dat" |
90 | # endif |
91 | #elif MAP_IMPLEMENTATION==MAP_STDIO |
92 | # include <stdio.h> |
93 | # include "cmemory.h" |
94 | |
95 | typedef void *MemoryMap; |
96 | |
97 | # define IS_MAP(map) ((map)!=nullptr) |
98 | #endif |
99 | |
100 | /*----------------------------------------------------------------------------* |
101 | * * |
102 | * Memory Mapped File support. Platform dependent implementation of * |
103 | * functions used by the rest of the implementation.* |
104 | * * |
105 | *----------------------------------------------------------------------------*/ |
106 | #if MAP_IMPLEMENTATION==MAP_NONE |
107 | U_CFUNC UBool |
108 | uprv_mapFile(UDataMemory *pData, const char *path, UErrorCode *status) { |
109 | if (U_FAILURE(*status)) { |
110 | return FALSE; |
111 | } |
112 | UDataMemory_init(pData); /* Clear the output struct. */ |
113 | return FALSE; /* no file access */ |
114 | } |
115 | |
116 | U_CFUNC void uprv_unmapFile(UDataMemory *pData) { |
117 | /* nothing to do */ |
118 | } |
119 | #elif MAP_IMPLEMENTATION==MAP_WIN32 |
120 | U_CFUNC UBool |
121 | uprv_mapFile( |
122 | UDataMemory *pData, /* Fill in with info on the result doing the mapping. */ |
123 | /* Output only; any original contents are cleared. */ |
124 | const char *path, /* File path to be opened/mapped. */ |
125 | UErrorCode *status /* Error status, used to report out-of-memory errors. */ |
126 | ) |
127 | { |
128 | if (U_FAILURE(*status)) { |
129 | return FALSE; |
130 | } |
131 | |
132 | HANDLE map = nullptr; |
133 | HANDLE file = INVALID_HANDLE_VALUE; |
134 | |
135 | UDataMemory_init(pData); /* Clear the output struct. */ |
136 | |
137 | /* open the input file */ |
138 | #if U_PLATFORM_HAS_WINUWP_API == 0 |
139 | // Note: In the non-UWP code-path (ie: Win32), the value of the path variable might have come from |
140 | // the CRT 'getenv' function, and would be therefore be encoded in the default ANSI code page. |
141 | // This means that we can't call the *W version of API below, whereas in the UWP code-path |
142 | // there is no 'getenv' call, and thus the string will be only UTF-8/Invariant characters. |
143 | file=CreateFileA(path, GENERIC_READ, FILE_SHARE_READ, nullptr, |
144 | OPEN_EXISTING, |
145 | FILE_ATTRIBUTE_NORMAL|FILE_FLAG_RANDOM_ACCESS, nullptr); |
146 | #else |
147 | // Convert from UTF-8 string to UTF-16 string. |
148 | wchar_t utf16Path[MAX_PATH]; |
149 | int32_t pathUtf16Len = 0; |
150 | u_strFromUTF8(reinterpret_cast<UChar*>(utf16Path), static_cast<int32_t>(UPRV_LENGTHOF(utf16Path)), &pathUtf16Len, path, -1, status); |
151 | |
152 | if (U_FAILURE(*status)) { |
153 | return FALSE; |
154 | } |
155 | if (*status == U_STRING_NOT_TERMINATED_WARNING) { |
156 | // Report back an error instead of a warning. |
157 | *status = U_BUFFER_OVERFLOW_ERROR; |
158 | return FALSE; |
159 | } |
160 | |
161 | file = CreateFileW(utf16Path, GENERIC_READ, FILE_SHARE_READ, nullptr, |
162 | OPEN_EXISTING, |
163 | FILE_ATTRIBUTE_NORMAL | FILE_FLAG_RANDOM_ACCESS, nullptr); |
164 | #endif |
165 | if (file == INVALID_HANDLE_VALUE) { |
166 | // If we failed to open the file due to an out-of-memory error, then we want |
167 | // to report that error back to the caller. |
168 | if (HRESULT_FROM_WIN32(GetLastError()) == E_OUTOFMEMORY) { |
169 | *status = U_MEMORY_ALLOCATION_ERROR; |
170 | } |
171 | return FALSE; |
172 | } |
173 | |
174 | // Note: We use NULL/nullptr for lpAttributes parameter below. |
175 | // This means our handle cannot be inherited and we will get the default security descriptor. |
176 | /* create an unnamed Windows file-mapping object for the specified file */ |
177 | map = CreateFileMappingW(file, nullptr, PAGE_READONLY, 0, 0, nullptr); |
178 | |
179 | CloseHandle(file); |
180 | if (map == nullptr) { |
181 | // If we failed to create the mapping due to an out-of-memory error, then |
182 | // we want to report that error back to the caller. |
183 | if (HRESULT_FROM_WIN32(GetLastError()) == E_OUTOFMEMORY) { |
184 | *status = U_MEMORY_ALLOCATION_ERROR; |
185 | } |
186 | return FALSE; |
187 | } |
188 | |
189 | /* map a view of the file into our address space */ |
190 | pData->pHeader = reinterpret_cast<const DataHeader *>(MapViewOfFile(map, FILE_MAP_READ, 0, 0, 0)); |
191 | if (pData->pHeader == nullptr) { |
192 | CloseHandle(map); |
193 | return FALSE; |
194 | } |
195 | pData->map = map; |
196 | return TRUE; |
197 | } |
198 | |
199 | U_CFUNC void |
200 | uprv_unmapFile(UDataMemory *pData) { |
201 | if (pData != nullptr && pData->map != nullptr) { |
202 | UnmapViewOfFile(pData->pHeader); |
203 | CloseHandle(pData->map); |
204 | pData->pHeader = nullptr; |
205 | pData->map = nullptr; |
206 | } |
207 | } |
208 | |
209 | |
210 | |
211 | #elif MAP_IMPLEMENTATION==MAP_POSIX |
212 | U_CFUNC UBool |
213 | uprv_mapFile(UDataMemory *pData, const char *path, UErrorCode *status) { |
214 | int fd; |
215 | int length; |
216 | struct stat mystat; |
217 | void *data; |
218 | |
219 | if (U_FAILURE(*status)) { |
220 | return FALSE; |
221 | } |
222 | |
223 | UDataMemory_init(pData); /* Clear the output struct. */ |
224 | |
225 | /* determine the length of the file */ |
226 | if(stat(path, &mystat)!=0 || mystat.st_size<=0) { |
227 | return FALSE; |
228 | } |
229 | length=mystat.st_size; |
230 | |
231 | /* open the file */ |
232 | fd=open(path, O_RDONLY); |
233 | if(fd==-1) { |
234 | return FALSE; |
235 | } |
236 | |
237 | /* get a view of the mapping */ |
238 | #if U_PLATFORM != U_PF_HPUX |
239 | data=mmap(0, length, PROT_READ, MAP_SHARED, fd, 0); |
240 | #else |
241 | data=mmap(0, length, PROT_READ, MAP_PRIVATE, fd, 0); |
242 | #endif |
243 | close(fd); /* no longer needed */ |
244 | if(data==MAP_FAILED) { |
245 | // Possibly check the errno value for ENOMEM, and report U_MEMORY_ALLOCATION_ERROR? |
246 | return FALSE; |
247 | } |
248 | |
249 | pData->map = (char *)data + length; |
250 | pData->pHeader=(const DataHeader *)data; |
251 | pData->mapAddr = data; |
252 | #if U_PLATFORM == U_PF_IPHONE |
253 | posix_madvise(data, length, POSIX_MADV_RANDOM); |
254 | #endif |
255 | return TRUE; |
256 | } |
257 | |
258 | U_CFUNC void |
259 | uprv_unmapFile(UDataMemory *pData) { |
260 | if(pData!=nullptr && pData->map!=nullptr) { |
261 | size_t dataLen = (char *)pData->map - (char *)pData->mapAddr; |
262 | if(munmap(pData->mapAddr, dataLen)==-1) { |
263 | } |
264 | pData->pHeader=nullptr; |
265 | pData->map=0; |
266 | pData->mapAddr=nullptr; |
267 | } |
268 | } |
269 | |
270 | |
271 | |
272 | #elif MAP_IMPLEMENTATION==MAP_STDIO |
273 | /* copy of the filestrm.c/T_FileStream_size() implementation */ |
274 | static int32_t |
275 | umap_fsize(FILE *f) { |
276 | int32_t savedPos = ftell(f); |
277 | int32_t size = 0; |
278 | |
279 | /*Changes by Bertrand A. D. doesn't affect the current position |
280 | goes to the end of the file before ftell*/ |
281 | fseek(f, 0, SEEK_END); |
282 | size = (int32_t)ftell(f); |
283 | fseek(f, savedPos, SEEK_SET); |
284 | return size; |
285 | } |
286 | |
287 | U_CFUNC UBool |
288 | uprv_mapFile(UDataMemory *pData, const char *path, UErrorCode *status) { |
289 | FILE *file; |
290 | int32_t fileLength; |
291 | void *p; |
292 | |
293 | if (U_FAILURE(*status)) { |
294 | return FALSE; |
295 | } |
296 | |
297 | UDataMemory_init(pData); /* Clear the output struct. */ |
298 | /* open the input file */ |
299 | file=fopen(path, "rb" ); |
300 | if(file==nullptr) { |
301 | return FALSE; |
302 | } |
303 | |
304 | /* get the file length */ |
305 | fileLength=umap_fsize(file); |
306 | if(ferror(file) || fileLength<=20) { |
307 | fclose(file); |
308 | return FALSE; |
309 | } |
310 | |
311 | /* allocate the memory to hold the file data */ |
312 | p=uprv_malloc(fileLength); |
313 | if(p==nullptr) { |
314 | fclose(file); |
315 | *status = U_MEMORY_ALLOCATION_ERROR; |
316 | return FALSE; |
317 | } |
318 | |
319 | /* read the file */ |
320 | if(fileLength!=fread(p, 1, fileLength, file)) { |
321 | uprv_free(p); |
322 | fclose(file); |
323 | return FALSE; |
324 | } |
325 | |
326 | fclose(file); |
327 | pData->map=p; |
328 | pData->pHeader=(const DataHeader *)p; |
329 | pData->mapAddr=p; |
330 | return TRUE; |
331 | } |
332 | |
333 | U_CFUNC void |
334 | uprv_unmapFile(UDataMemory *pData) { |
335 | if(pData!=nullptr && pData->map!=nullptr) { |
336 | uprv_free(pData->map); |
337 | pData->map = nullptr; |
338 | pData->mapAddr = nullptr; |
339 | pData->pHeader = nullptr; |
340 | } |
341 | } |
342 | |
343 | |
344 | #elif MAP_IMPLEMENTATION==MAP_390DLL |
345 | /* 390 specific Library Loading. |
346 | * This is the only platform left that dynamically loads an ICU Data Library. |
347 | * All other platforms use .data files when dynamic loading is required, but |
348 | * this turn out to be awkward to support in 390 batch mode. |
349 | * |
350 | * The idea here is to hide the fact that 390 is using dll loading from the |
351 | * rest of ICU, and make it look like there is file loading happening. |
352 | * |
353 | */ |
354 | |
355 | static char *strcpy_returnEnd(char *dest, const char *src) |
356 | { |
357 | while((*dest=*src)!=0) { |
358 | ++dest; |
359 | ++src; |
360 | } |
361 | return dest; |
362 | } |
363 | |
364 | /*------------------------------------------------------------------------------ |
365 | * |
366 | * computeDirPath given a user-supplied path of an item to be opened, |
367 | * compute and return |
368 | * - the full directory path to be used |
369 | * when opening the file. |
370 | * - Pointer to null at end of above returned path |
371 | * |
372 | * Parameters: |
373 | * path: input path. Buffer is not altered. |
374 | * pathBuffer: Output buffer. Any contents are overwritten. |
375 | * |
376 | * Returns: |
377 | * Pointer to null termination in returned pathBuffer. |
378 | * |
379 | * TODO: This works the way ICU historically has, but the |
380 | * whole data fallback search path is so complicated that |
381 | * probably almost no one will ever really understand it, |
382 | * the potential for confusion is large. (It's not just |
383 | * this one function, but the whole scheme.) |
384 | * |
385 | *------------------------------------------------------------------------------*/ |
386 | static char *uprv_computeDirPath(const char *path, char *pathBuffer) |
387 | { |
388 | char *finalSlash; /* Ptr to last dir separator in input path, or null if none. */ |
389 | int32_t pathLen; /* Length of the returned directory path */ |
390 | |
391 | finalSlash = 0; |
392 | if (path != 0) { |
393 | finalSlash = uprv_strrchr(path, U_FILE_SEP_CHAR); |
394 | } |
395 | |
396 | *pathBuffer = 0; |
397 | if (finalSlash == 0) { |
398 | /* No user-supplied path. |
399 | * Copy the ICU_DATA path to the path buffer and return that*/ |
400 | const char *icuDataDir; |
401 | icuDataDir=u_getDataDirectory(); |
402 | if(icuDataDir!=nullptr && *icuDataDir!=0) { |
403 | return strcpy_returnEnd(pathBuffer, icuDataDir); |
404 | } else { |
405 | /* there is no icuDataDir either. Just return the empty pathBuffer. */ |
406 | return pathBuffer; |
407 | } |
408 | } |
409 | |
410 | /* User supplied path did contain a directory portion. |
411 | * Copy it to the output path buffer */ |
412 | pathLen = (int32_t)(finalSlash - path + 1); |
413 | uprv_memcpy(pathBuffer, path, pathLen); |
414 | *(pathBuffer+pathLen) = 0; |
415 | return pathBuffer+pathLen; |
416 | } |
417 | |
418 | |
419 | # define DATA_TYPE "dat" |
420 | |
421 | U_CFUNC UBool uprv_mapFile(UDataMemory *pData, const char *path, UErrorCode *status) { |
422 | const char *inBasename; |
423 | char *basename; |
424 | char pathBuffer[1024]; |
425 | const DataHeader *pHeader; |
426 | dllhandle *handle; |
427 | void *val=0; |
428 | |
429 | if (U_FAILURE(*status)) { |
430 | return FALSE; |
431 | } |
432 | |
433 | inBasename=uprv_strrchr(path, U_FILE_SEP_CHAR); |
434 | if(inBasename==nullptr) { |
435 | inBasename = path; |
436 | } else { |
437 | inBasename++; |
438 | } |
439 | basename=uprv_computeDirPath(path, pathBuffer); |
440 | if(uprv_strcmp(inBasename, U_ICUDATA_NAME".dat" ) != 0) { |
441 | /* must mmap file... for build */ |
442 | int fd; |
443 | int length; |
444 | struct stat mystat; |
445 | void *data; |
446 | UDataMemory_init(pData); /* Clear the output struct. */ |
447 | |
448 | /* determine the length of the file */ |
449 | if(stat(path, &mystat)!=0 || mystat.st_size<=0) { |
450 | return FALSE; |
451 | } |
452 | length=mystat.st_size; |
453 | |
454 | /* open the file */ |
455 | fd=open(path, O_RDONLY); |
456 | if(fd==-1) { |
457 | return FALSE; |
458 | } |
459 | |
460 | /* get a view of the mapping */ |
461 | data=mmap(0, length, PROT_READ, MAP_PRIVATE, fd, 0); |
462 | close(fd); /* no longer needed */ |
463 | if(data==MAP_FAILED) { |
464 | // Possibly check the errorno value for ENOMEM, and report U_MEMORY_ALLOCATION_ERROR? |
465 | return FALSE; |
466 | } |
467 | pData->map = (char *)data + length; |
468 | pData->pHeader=(const DataHeader *)data; |
469 | pData->mapAddr = data; |
470 | return TRUE; |
471 | } |
472 | |
473 | # ifdef OS390BATCH |
474 | /* ### hack: we still need to get u_getDataDirectory() fixed |
475 | for OS/390 (batch mode - always return "//"? ) |
476 | and this here straightened out with LIB_PREFIX and LIB_SUFFIX (both empty?!) |
477 | This is probably due to the strange file system on OS/390. It's more like |
478 | a database with short entry names than a typical file system. */ |
479 | /* U_ICUDATA_NAME should always have the correct name */ |
480 | /* BUT FOR BATCH MODE IT IS AN EXCEPTION BECAUSE */ |
481 | /* THE FIRST THREE LETTERS ARE PREASSIGNED TO THE */ |
482 | /* PROJECT!!!!! */ |
483 | uprv_strcpy(pathBuffer, "IXMI" U_ICU_VERSION_SHORT "DA" ); |
484 | # else |
485 | /* set up the library name */ |
486 | uprv_strcpy(basename, LIB_PREFIX U_LIBICUDATA_NAME U_ICU_VERSION_SHORT LIB_SUFFIX); |
487 | # endif |
488 | |
489 | # ifdef UDATA_DEBUG |
490 | fprintf(stderr, "dllload: %s " , pathBuffer); |
491 | # endif |
492 | |
493 | handle=dllload(pathBuffer); |
494 | |
495 | # ifdef UDATA_DEBUG |
496 | fprintf(stderr, " -> %08X\n" , handle ); |
497 | # endif |
498 | |
499 | if(handle != nullptr) { |
500 | /* we have a data DLL - what kind of lookup do we need here? */ |
501 | /* try to find the Table of Contents */ |
502 | UDataMemory_init(pData); /* Clear the output struct. */ |
503 | val=dllqueryvar((dllhandle*)handle, U_ICUDATA_ENTRY_NAME); |
504 | if(val == 0) { |
505 | /* failed... so keep looking */ |
506 | return FALSE; |
507 | } |
508 | # ifdef UDATA_DEBUG |
509 | fprintf(stderr, "dllqueryvar(%08X, %s) -> %08X\n" , handle, U_ICUDATA_ENTRY_NAME, val); |
510 | # endif |
511 | |
512 | pData->pHeader=(const DataHeader *)val; |
513 | return TRUE; |
514 | } else { |
515 | return FALSE; /* no handle */ |
516 | } |
517 | } |
518 | |
519 | U_CFUNC void uprv_unmapFile(UDataMemory *pData) { |
520 | if(pData!=nullptr && pData->map!=nullptr) { |
521 | uprv_free(pData->map); |
522 | pData->map = nullptr; |
523 | pData->mapAddr = nullptr; |
524 | pData->pHeader = nullptr; |
525 | } |
526 | } |
527 | |
528 | #else |
529 | # error MAP_IMPLEMENTATION is set incorrectly |
530 | #endif |
531 | |