Merge pull request #2930 from sivarv/retfix
[platform/upstream/coreclr.git] / src / utilcode / sortversioning.cpp
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 //  File:    SortVersioning.cpp
7 //
8
9
10 //  Purpose:  Provides access of the sort versioning functionality on
11 //            downlevel (pre-Win7) machines.
12 //
13 //       
14 //            This is not used on CoreCLR, where we always go to the OS
15 //            for sorting.
16 //            
17 ////////////////////////////////////////////////////////////////////////////
18
19 #include "stdafx.h"
20 #include "sortversioning.h"
21 #include "newapis.h"
22
23 #include "mscoree.h"
24 #include "clrconfig.h"
25
26 #define SORT_VERSION_V4         0x00060101
27 #define SORT_VERSION_WHIDBEY    0x00001000
28 #define SORT_VERSION_DEFAULT    SORT_VERSION_V4
29 #define SORT_DEFAULT_DLL_NAME   MAKEDLLNAME(W("nlssorting"))
30
31 namespace SortVersioning
32 {
33 #define SORT_HASH_TBL_SIZE  128
34
35     //
36     // Forward Declarations
37     //
38     PSORTHANDLE MakeSortHashNode(
39         __in LPCWSTR    pSortName,
40         __in DWORD      dwVersion);
41
42
43     PSORTHANDLE InsertSortHashNode(
44         __in PSORTHANDLE pHashN);
45
46     static PSORTHANDLE     g_pSortHash[SORT_HASH_TBL_SIZE];    // Sort node hash table
47
48     static HMODULE g_hSortDefault = (HMODULE)-1;
49
50     __encoded_pointer static SORTGETHANDLE   g_pDefaultGetHandle;
51     __encoded_pointer static SORTCLOSEHANDLE g_pDefaultCloseHandle;
52
53     static HMODULE g_hSortCompatV2 = (HMODULE)-1;
54
55     __encoded_pointer static SORTGETHANDLE   g_pV2GetHandle;
56     __encoded_pointer static SORTCLOSEHANDLE g_pV2CloseHandle;
57
58     static HMODULE g_hSortCompatV4 = (HMODULE)-1;
59
60     __encoded_pointer static SORTGETHANDLE   g_pV4GetHandle;
61     __encoded_pointer static SORTCLOSEHANDLE g_pV4CloseHandle;
62
63
64     ////////////////////////////////////////////////////////////////////////////
65     //
66     //  NlsCompareInvariantNoCase
67     //
68     //  This routine does fast caseless comparison without needing the tables.
69     //  This helps us do the comparisons we need to load the tables :-)
70     //
71     //  Returns 0 if identical, <0 if pFirst if first string sorts first.
72     //
73     //  This is only intended to help with our locale name comparisons,
74     //  which are effectively limited to A-Z, 0-9, a-z and - where A-Z and a-z
75     //  compare as equal.
76     //
77     //  WARNING: [\]^_` will be less than A-Z because we make everything lower
78     //           case before comparing them.
79     //
80     //  When bNullEnd is TRUE, both of the strings should be null-terminator to be considered equal.
81     //  When bNullEnd is FALSE, the strings are considered equal when we reach the number of characters specifed by size
82     //  or when null terminators are reached, whichever happens first (strncmp-like behavior)
83     //
84     ////////////////////////////////////////////////////////////////////////////
85     int NlsCompareInvariantNoCase(
86         LPCWSTR pFirst,
87         LPCWSTR pSecond,
88         int     size,
89         BOOL bNullEnd)
90     {
91         int i=0;
92         WCHAR first;
93         WCHAR second;
94
95         for (;
96              size > 0 && (first = *pFirst) != 0 && (second = *pSecond) != 0;
97              size--, pFirst++, pSecond++)
98         {
99             // Make them lower case
100             if ((first >= 'A') && (first <= 'Z')) first |= 0x20;
101             if ((second >= 'A') && (second <= 'Z')) second |= 0x20;
102
103             // Get the diff
104             i = (first - second);
105
106             // Are they the same?
107             if (i == 0)
108                 continue;
109
110             // Otherwise the difference.  Remember we made A-Z into lower case, so
111             // the characters [\]^_` will sort < A-Z and also < a-z.  (Those are the
112             // characters between A-Z and a-Z in ascii)
113             return i;
114         }
115
116         // When we are here, one of these holds:
117         //    size == 0
118         //    or one of the strings has a null terminator
119         //    or both of the string reaches null terminator
120
121         if (bNullEnd || size != 0)
122         {
123             // If bNullEnd is TRUE, always check for null terminator.
124             // If bNullEnd is FALSE, we still have to check if one of the strings is terminated eariler
125             // than another (hense the size != 0 check).
126
127             // See if one string ended first
128             if (*pFirst != 0 || *pSecond != 0)
129             {
130                 // Which one?
131                 return *pFirst == 0 ? -1 : 1;
132             }
133         }
134
135         // Return our difference (0)
136         return i;
137     }
138
139 #if !defined(FEATURE_CORECLR) && !defined(CROSSGEN_COMPILE)
140     ////////////////////////////////////////////////////////////////////////////
141     //
142     //  LoadSortModuleAndInvariant()
143     //
144     //  Attempts to load the given dll. If that is successful, attempts to
145     //  load the invariant data. If that is successful, returns the module handle
146     //  and the addresses of the SortGetHandle function and SortCloseHandle function
147     //
148     //  failure is indicated by returning NULL for the module handle and the
149     //  function addresses
150     //
151     ////////////////////////////////////////////////////////////////////////////
152
153     HMODULE LoadSortModuleAndInvariant(
154         __in LPCWSTR sDllName,
155         __in DWORD dwVersion,
156         __out SORTGETHANDLE* ppGetHandle,
157         __out SORTCLOSEHANDLE* ppCloseHandle
158         )
159     {
160         *ppGetHandle = NULL;
161         *ppCloseHandle = NULL;
162         HMODULE hSort;
163
164         if(FAILED(UtilCode::LoadLibraryShim(sDllName, NULL, NULL, &hSort))) 
165         {            
166             return NULL;
167         }
168
169         SORTGETHANDLE pGetHandle = (SORTGETHANDLE)GetProcAddress(hSort, "SortGetHandle");
170         SORTCLOSEHANDLE pCloseHandle = (SORTCLOSEHANDLE)GetProcAddress(hSort, "SortCloseHandle");
171
172         // If we didn't load the procs, then remember that
173         if (pCloseHandle == NULL || pGetHandle == NULL)
174         {
175             ::FreeLibrary(hSort);
176             return NULL;
177         }
178
179         // Verify that the data file's available
180         NLSVERSIONINFO  sortVersion;
181         sortVersion.dwNLSVersionInfoSize = sizeof(NLSVERSIONINFO);
182         sortVersion.dwNLSVersion = dwVersion;
183         sortVersion.dwDefinedVersion = dwVersion;
184
185         // Invariant must be there and is kinda common, so we'll just get it
186         PSORTHANDLE pSort = pGetHandle(W(""), &sortVersion, NULL);
187         if (!pSort)
188         {
189             // Yikes, invariant failed, forget about it.
190             ::FreeLibrary(hSort);
191             return NULL;
192         }
193
194         // Since we found it, may as well remember it.
195         const SORTHANDLE * const pSortInHash = InsertSortHashNode(pSort);
196
197         // If we got a different one back then free the one we added
198         if (pSortInHash != pSort && pSortInHash)
199         {
200             // We got a different one from the hash (someone beat us to the cache)
201             // so use that and discard the new one.
202             pCloseHandle(pSort);
203         }
204
205         *ppGetHandle = pGetHandle;
206         *ppCloseHandle = pCloseHandle;
207         return hSort;
208     }
209
210     // Attempts to load a Sort DLL.  If this fails, the values of the __out parameters are unchanged.
211     __success(return)
212     BOOL LoadSortDllAndPublish(
213         __in  LPCWSTR sDllName, 
214         __in  DWORD dwVersion, 
215         __out __encoded_pointer SORTGETHANDLE* ppGetHandle,
216         __out __encoded_pointer SORTCLOSEHANDLE* ppCloseHandle,
217         __out HMODULE* phSortDll)
218     {
219         HMODULE hSortDll;
220         SORTGETHANDLE   pGetHandle;
221         SORTCLOSEHANDLE pCloseHandle;
222         
223         hSortDll = LoadSortModuleAndInvariant(sDllName, dwVersion, &pGetHandle, &pCloseHandle);        
224
225         if(hSortDll != NULL) {
226             *phSortDll = hSortDll;
227             *ppGetHandle = (SORTGETHANDLE)EncodePointer(pGetHandle);
228             *ppCloseHandle = (SORTCLOSEHANDLE)EncodePointer(pCloseHandle);
229             return TRUE;
230         }
231
232         return FALSE;
233     }
234
235     ////////////////////////////////////////////////////////////////////////////
236     //
237     //  GetSortGetHandle()
238     //
239     //  Get the SortGetHandle() function for the proper dll version.
240     //
241     ////////////////////////////////////////////////////////////////////////////
242     SORTGETHANDLE GetSortGetHandle(__in DWORD dwVersion)
243     {
244         if(dwVersion == SORT_VERSION_DEFAULT)
245         {
246             // If we haven't tried to load the module/proc before do so now
247             if (g_hSortDefault == (HMODULE)-1)
248             {
249                 LoadSortDllAndPublish(SORT_DEFAULT_DLL_NAME, SORT_VERSION_DEFAULT, &g_pDefaultGetHandle, &g_pDefaultCloseHandle, &g_hSortDefault);
250             }
251
252             // This check is necessary because the LoadSortDllAndPublish call may have failed since some platforms
253             // won't have nlssorting.dll (e.g. Windows 8 and above).
254             if (g_hSortDefault != (HMODULE)-1) 
255             {
256                 return (SORTGETHANDLE)DecodePointer(g_pDefaultGetHandle);
257             }
258         }
259
260         HMODULE* pHSortModule; 
261         SORTGETHANDLE*   ppGetHandle;
262         SORTCLOSEHANDLE* ppCloseHandle;
263
264         if(dwVersion == SORT_VERSION_V4)
265         {
266             ppGetHandle = &g_pV4GetHandle;
267             ppCloseHandle = &g_pV4CloseHandle;
268             pHSortModule = &g_hSortCompatV4;
269         } 
270         else if(dwVersion == SORT_VERSION_WHIDBEY) 
271         {
272             ppGetHandle = &g_pV2GetHandle;
273             ppCloseHandle = &g_pV2CloseHandle;
274             pHSortModule = &g_hSortCompatV2;
275         }
276         else
277         {
278             // Unsupported sorting version.
279             return NULL;
280         }
281
282         if(*pHSortModule == (HMODULE) -1)
283         {
284             // get module name - the module name should be "Sort"+dwVersion.ToString("x8")+".dll"
285             WCHAR moduleName[] = W("Sort00000000.dll");
286             // replace the "00000000" with the hexadecimal of dwVersion
287             LPCWSTR hex = W("0123456789abcdef");
288             WCHAR* p = &moduleName[4+8]; // position at end of number part of dll name
289
290             unsigned int value = dwVersion;
291
292             while (value != 0 && p != moduleName) {
293                 int digit = value & 0xF;
294                 *--p = hex[digit];
295                 value >>= 4;
296             }
297
298             if(!LoadSortDllAndPublish(&moduleName[0], dwVersion, ppGetHandle, ppCloseHandle, pHSortModule)) 
299             {
300                 // We failed to load a versioned sort dll, try to fall back to the current version.
301                 // If we haven't tried to load the module/proc before do so now
302                 if (g_hSortDefault == (HMODULE)-1)
303                 {
304                     LoadSortDllAndPublish(SORT_DEFAULT_DLL_NAME, SORT_VERSION_DEFAULT, &g_pDefaultGetHandle, &g_pDefaultCloseHandle, &g_hSortDefault);
305                 }
306
307                 *pHSortModule = g_hSortDefault;
308                 *ppCloseHandle = g_pDefaultCloseHandle;
309                 *ppGetHandle = g_pDefaultGetHandle;
310             }           
311         }
312
313         // At this point, we've either loaded a sorting dll or we've exausted all options.
314         if(*pHSortModule == (HMODULE) -1)
315         {
316             // Couldn't find anything, give up.
317             return NULL;
318         }
319         else
320         {
321             return (SORTGETHANDLE)DecodePointer(*ppGetHandle);
322         }
323
324         // Unknown version requested
325         return NULL;
326     }
327
328     void DoSortCloseHandle(__in DWORD dwVersion, __in PSORTHANDLE pSort)
329     {
330         if (dwVersion == SORT_VERSION_DEFAULT)
331         {
332             SORTCLOSEHANDLE pDefaultCloseHandle = (SORTCLOSEHANDLE)DecodePointer(g_pDefaultCloseHandle);
333             if(pDefaultCloseHandle != NULL)
334             {
335                 (pDefaultCloseHandle)(pSort);
336                 return;
337             }
338         }
339
340         if (dwVersion == SORT_VERSION_V4)
341         {
342             SORTCLOSEHANDLE pV4CloseHandle = (SORTCLOSEHANDLE)DecodePointer(g_pV4CloseHandle);
343             if(pV4CloseHandle != NULL)
344             {
345                 (pV4CloseHandle)(pSort);
346                 return;
347             }
348         }
349
350
351         if (dwVersion == SORT_VERSION_WHIDBEY) 
352         {
353             SORTCLOSEHANDLE pV2CloseHandle = (SORTCLOSEHANDLE)DecodePointer(g_pV2CloseHandle);
354             if(pV2CloseHandle != NULL)
355             {
356                 (pV2CloseHandle)(pSort);
357                 return;
358             }
359         }
360     }
361
362 #else // !defined(FEATURE_CORECLR) && !defined(CROSSGEN_COMPILE)
363
364     SORTGETHANDLE GetSortGetHandle(__in DWORD dwVersion)
365     {
366         return NULL;
367     }
368
369     void DoSortCloseHandle(__in DWORD dwVersion, __in PSORTHANDLE pSort)
370     {
371     }
372
373 #endif // !defined(FEATURE_CORECLR) && !defined(CROSSGEN_COMPILE)
374
375     ////////////////////////////////////////////////////////////////////////////
376     //
377     //  GetSortHashValue
378     //
379     //  Returns the hash value for given sort name & version.
380     //
381     //  WARNING: This must be case insensitive.  Currently we're expecting only
382     //           a-z, A-Z, 0-9 & -.
383     //
384     ////////////////////////////////////////////////////////////////////////////
385     __inline __range(0, SORT_HASH_TBL_SIZE-1) int GetSortHashValue(
386         __in LPCWSTR    pSortName,
387         __in DWORD      dwVersion)
388     {
389         int iHash = 12; // Seed hash value
390         int iMax;       // Number of characters to count (prevent problems with too-bad strings)
391
392         // Hash the string
393         if (pSortName)
394         {
395             for (iMax = 10; *pSortName != 0 && iMax != 0; pSortName++, iMax--)
396             {
397                 iHash <<= 1;
398                 iHash ^= ((*pSortName) & 0xdf);     // 0x20 will make cases be the same (and other wierd stuff too, but we don't care about that)
399             }
400         }
401
402         // Add the version hash
403         // (the middle 2 bytes are most interesting)
404         iHash ^= dwVersion >> 8;
405
406         // Mix up our bits and hash it with 128
407         _ASSERT(SORT_HASH_TBL_SIZE == 128);
408         return (iHash + (iHash >> 8)) & 0x7f;
409     }
410
411
412     ////////////////////////////////////////////////////////////////////////////
413     //
414     //  InsertSortHashNode
415     //
416     //  Inserts a sort hash node into the global sort hash tables.  It assumes
417     //  that all unused hash values in the table are pointing to NULL.  If
418     //  there is a collision, the new node will be added LAST in the list.
419     //  (Presuming that the most often used are also the first used)
420     //
421     //  We do an interlocked exchange and free the pointer if we can't add it.
422     //
423     //  Warning: We stick stuff in this list, but we never remove it, so it
424     //           get kind of big.  Removing entries would be difficult however
425     //           because it would require some sort of synchronization with the
426     //           reader functions (like GetLocaleInfo), or maybe an in-use flag
427     //           or spin count.
428     //
429     ////////////////////////////////////////////////////////////////////////////
430     PSORTHANDLE InsertSortHashNode(PSORTHANDLE pHashN)
431     {
432         __range(0, SORT_HASH_TBL_SIZE-1) UINT         index;
433         PSORTHANDLE  pSearch;
434         PSORTHANDLE* pNextToUpdate;
435
436         //
437         // Insert the hash node into the list (by name/version)
438         //
439 #ifdef _PREFAST_
440 #pragma warning(push)
441 #pragma warning(disable: 26037) // Prefast warning - Possible precondition violation due to failure to null terminate string -  GetSortHashValue only uses first 10 characters and sortName is null terminated
442 #endif // _PREFAST_
443         index = GetSortHashValue(pHashN->sortName, pHashN->dwNLSVersion);
444 #ifdef _PREFAST_
445 #pragma warning(pop)
446 #endif
447
448         // Get hash node
449         pSearch = g_pSortHash[index];
450
451         // Remember last pointer in case we need to add it
452         pNextToUpdate = &g_pSortHash[index];
453
454         // We'll be the last node when added
455         pHashN->pNext = NULL;
456
457         while(TRUE)
458         {
459             while (pSearch != NULL)
460             {
461                 // See if we already found a node.
462                 if ((pSearch->dwNLSVersion == pHashN->dwNLSVersion) &&
463                     NlsCompareInvariantNoCase( pSearch->sortName, pHashN->sortName,
464                                                LOCALE_NAME_MAX_LENGTH, TRUE) == 0)
465                 {
466                     // Its the same, which is unexpected, return the old one
467                     return pSearch;
468                 }
469
470                 pNextToUpdate = &pSearch->pNext;
471                 pSearch = pSearch->pNext;
472             }
473
474             // At end, try to add our node
475             pSearch = InterlockedCompareExchangeT(pNextToUpdate, pHashN, NULL);
476
477             // If pNextToUpdate isn't NULL then another process snuck in and updated the list
478             // while we were getting ready.
479             if (pSearch == NULL)
480             {
481                 // It was added, stop
482                 break;
483             }
484
485             // It wasn't added, pSearch now points to a new node that snuck in, so
486             // continue and try that one.  This should be really rare, even in a busy
487             // loop, so we don't try a real lock.  Either
488             // a) the snuck in node is the same as pHashN, and we'll return pSearch
489             //    in the first loop, or
490             // b) the snuck in node is new, in which case we'll try to readd.  Very worst
491             //    case we'd collide while someone added ALL of the other locales with our
492             //    hash, but eventually we'd hit case a.  (And there's only a couple hundred
493             //    tries, so this can't lock for long.)
494         }
495
496         // Return the same one we added
497         return pHashN;
498     }
499
500
501     ////////////////////////////////////////////////////////////////////////////
502     //
503     //  FindSortHashNode
504     //
505     //  Searches for the sort hash node for the given sort name & version.
506     //  The result is returned.  If none are found NULL is returned.
507     //
508     //  NOTE: Call GetSortNode() which calls this.
509     //
510     //  Defined as inline.
511     //
512     ////////////////////////////////////////////////////////////////////////////
513
514     __inline PSORTHANDLE FindSortHashNode(
515         __in LPCWSTR    pSortName,
516         __in DWORD      dwVersion)
517     {
518         PSORTHANDLE pHashN;
519         __range(0,SORT_HASH_TBL_SIZE-1) int         index;
520
521         // Get Index
522         index = GetSortHashValue(pSortName, dwVersion);
523
524         // Get hash node
525         pHashN = g_pSortHash[index];
526
527         // Look through the list to see if one matches name and user info
528         // We're sneaky here because we know our length of our hash name string is stored
529         // just before that string.
530         while ((pHashN != NULL) &&
531                ((dwVersion != pHashN->dwNLSVersion) ||
532                 (NlsCompareInvariantNoCase(pSortName, pHashN->sortName, LOCALE_NAME_MAX_LENGTH, TRUE) != 0)))
533         {
534             pHashN = pHashN->pNext;
535         }
536
537         return pHashN;
538     }
539
540
541     ////////////////////////////////////////////////////////////////////////////
542     //
543     //  MakeSortHashNode
544     //
545     //  Builds a sort hash node and sticks it in the hash table.
546     //
547     //  NOTE: Call GetSortNode() which calls this.
548     //
549     //  Defined as inline.
550     //
551     ////////////////////////////////////////////////////////////////////////////
552     PSORTHANDLE MakeSortHashNode(
553         __in LPCWSTR    pSortName,
554         __in DWORD      dwVersion)
555     {
556         NLSVERSIONINFO  sortVersion;
557
558         PSORTHANDLE     pSort = NULL;
559         PSORTHANDLE     pSortInHash;
560
561         // Valid locale, now we need to find out where to point this version at
562         SORTGETHANDLE pGetHandle = GetSortGetHandle(dwVersion);
563         if (pGetHandle == NULL) return NULL;
564
565         sortVersion.dwNLSVersionInfoSize = sizeof(NLSVERSIONINFO);
566         sortVersion.dwNLSVersion = dwVersion;
567         sortVersion.dwDefinedVersion = dwVersion;
568
569         pSort = pGetHandle(pSortName, &sortVersion, NULL);
570
571         // If still missing, fail
572         if (pSort == NULL)
573         {
574             // Invalid sort, fail
575             return NULL;
576         }
577
578         // Now we need to add it
579         pSortInHash = InsertSortHashNode(pSort);
580
581         // If we got a different one back then free the one we added
582         if (pSortInHash != pSort && pSortInHash)
583         {
584             // We got a different one from the hash (someone beat us to the cache)
585             // so use that and discard the new one.
586             DoSortCloseHandle(dwVersion, pSort);
587         }
588
589         return pSortInHash;
590     }
591
592
593     ////////////////////////////////////////////////////////////////////////////
594     //
595     //  GetSortNode
596     //
597     //  Get a sort hash node for the specified sort name & version
598     //
599     ////////////////////////////////////////////////////////////////////////////
600     PSORTHANDLE GetSortNode(
601         __in LPCWSTR    pSortName,
602         __in DWORD      dwVersion)
603     {
604         PSORTHANDLE pSortHashN = NULL;
605
606         // WARNING: We don't bother doing the null/default/system checks
607
608         // Didn't have an obvious one, look in the hash table
609         pSortHashN = FindSortHashNode(pSortName, dwVersion);
610
611         //
612         //  If the hash node does not exist, we may need to get make one
613         //
614         if (pSortHashN == NULL)
615         {
616             //
617             //  Hash node does NOT exist, try to make it
618
619        //
620             pSortHashN = MakeSortHashNode(pSortName, dwVersion);
621         }
622
623         //
624         //  If the hash node still does not exist, we may need to fallback to default
625         //  version
626         //
627         if (pSortHashN == NULL && dwVersion != SORT_VERSION_DEFAULT)
628         {
629             return GetSortNode(pSortName, SORT_VERSION_DEFAULT);
630         }
631
632         //
633         //  Return pointer to hash node
634         //  (null if we still don't have one)
635         //
636         return pSortHashN;
637     }
638
639     ////////////////////////////////////////////////////////////////////////////
640     //
641     //  SortNLSVersion
642     //  Check for the DWORD "CompatSortNLSVersion" CLR config option.
643     //
644     // .Net 4.0 introduces sorting changes that can affect the behavior of any of the methods
645     // in CompareInfo. To mitigate against compatibility problems Applications can enable the
646     // legacy CompareInfo behavior by using the 'SortNLSVersion' configuration option
647     //
648     // There are three ways to use the configuration option:
649     //
650     // 1) Config file (MyApp.exe.config)
651     //        <?xml version ="1.0"?>
652     //        <configuration>
653     //         <runtime>
654     //          <CompatSortNLSVersion enabled="4096"/><!--0x00001000 -->
655     //         </runtime>
656     //        </configuration>
657     // 2) Environment variable
658     //        set COMPLUS_CompatSortNLSVersion=4096
659     // 3) RegistryKey
660     //        [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework]
661     //        "CompatSortNLSVersion"=dword:00001000
662     //
663     ////////////////////////////////////////////////////////////////////////////
664     DWORD SortNLSVersion()
665     {
666 #ifdef FEATURE_CORECLR
667         return SORT_VERSION_DEFAULT;
668 #else
669         static bool sortNLSVersionConfigChecked = false;
670         static DWORD sortNLSVersion = SORT_VERSION_DEFAULT;
671
672         if(!sortNLSVersionConfigChecked)
673         {
674             BEGIN_SO_INTOLERANT_CODE_NO_THROW_CHECK_THREAD(return false);
675             sortNLSVersion = CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_CompatSortNLSVersion);
676             if(sortNLSVersion == 0)
677             {
678                 sortNLSVersion = SORT_VERSION_DEFAULT;
679             }
680             END_SO_INTOLERANT_CODE;
681
682             sortNLSVersionConfigChecked = true;
683         }
684         return sortNLSVersion;
685 #endif // !FEATURE_CORECLR
686     }
687
688     ////////////////////////////////////////////////////////////////////////////
689     //
690     //  VersionValue
691     //
692     //  Get the version from a version blob, resolving to the default version
693     //  if NULL
694     //
695     ////////////////////////////////////////////////////////////////////////////
696     __inline DWORD VersionValue(__in_opt const NLSVERSIONINFO * const lpVersionInformation)
697     {
698
699         //
700         //  If the caller passed null or zero we use the default version
701         //
702         if ((lpVersionInformation == NULL) ||
703             ((lpVersionInformation->dwNLSVersion == 0) &&
704              (lpVersionInformation->dwDefinedVersion ==0))
705              )
706         {
707             return SortNLSVersion();
708         }
709
710         // TODO: Will need to review this
711         if(((lpVersionInformation->dwNLSVersion == 0) &&
712             (lpVersionInformation->dwDefinedVersion != 0 )))
713         {
714             return lpVersionInformation->dwDefinedVersion;
715         }
716
717         return lpVersionInformation->dwNLSVersion;
718     }
719
720     ////////////////////////////////////////////////////////////////////////////
721     //
722     //  SortGetSortKey
723     //
724     //  Supposed to call the dll for the appropriate version.  If the default
725     //  version isn't available call the ordinal behavior (for minwin)
726     //
727     //  Just get the sort hash node and call the worker function
728     //
729     ////////////////////////////////////////////////////////////////////////////
730     __success(return != 0) int WINAPI SortGetSortKey(
731         __in LPCWSTR pLocaleName,
732         __in DWORD dwFlags,
733         __in_ecount(cchSrc) LPCWSTR pSrc,
734         __in int cchSrc,
735         __out_bcount_opt(cbDest) LPBYTE pDest,
736         __in int cbDest,
737         __in_opt CONST NLSVERSIONINFO *lpVersionInformation,
738         __in_opt LPVOID lpReserved,
739         __in_opt LPARAM lParam
740     )
741     {
742         PSORTHANDLE pSort = GetSortNode(pLocaleName, VersionValue(lpVersionInformation));
743         return SortDllGetSortKey(pSort, dwFlags, pSrc, cchSrc, pDest, cbDest, lpReserved, lParam);
744     }
745
746     // SortDllGetSortKey handles any modification to flags
747     // necessary before the actual call to the dll
748     __success(return != 0) int WINAPI SortDllGetSortKey(
749         __in PSORTHANDLE pSort,
750         __in DWORD dwFlags,
751         __in_ecount(cchSrc) LPCWSTR pSrc,
752         __in int cchSrc,
753         __out_bcount_opt(cbDest) LPBYTE pDest,
754         __in int cbDest,
755         __in_opt LPVOID lpReserved,
756         __in_opt LPARAM lParam )
757     {
758         if (pSort == NULL)
759         {
760             SetLastError(ERROR_INVALID_PARAMETER);
761             return 0;
762         }
763
764         //
765         // Note that GetSortKey'll have the opposite behavior for the
766         // linguistic casing flag (eg: use flag for bad behavior, linguistic
767         // by default)
768         dwFlags ^= NORM_LINGUISTIC_CASING;
769
770         return pSort->pSortGetSortKey(pSort, dwFlags, pSrc, cchSrc, pDest, cbDest, lpReserved, lParam);
771     }
772
773     __success(return != 0) int SortDllGetHashCode(
774         __in PSORTHANDLE pSort,
775         __in DWORD dwFlags,
776         __in_ecount(cchSrc) LPCWSTR pSrc,
777         __in int cchSrc,
778         __in_opt LPVOID lpReserved,
779         __in_opt LPARAM lParam )
780     {
781         if (pSort == NULL)
782         {
783             SetLastError(ERROR_INVALID_PARAMETER);
784             return 0;
785         }
786         const int SortDllGetHashCodeApiIntroducedVersion = 2;
787         if(pSort->dwSHVersion < SortDllGetHashCodeApiIntroducedVersion)
788         {
789             SetLastError(ERROR_NOT_SUPPORTED);
790             return 0;
791         }
792
793         //
794         // Note that GetSortKey'll have the opposite behavior for the
795         // linguistic casing flag (eg: use flag for bad behavior, linguistic
796         // by default)
797         dwFlags ^= NORM_LINGUISTIC_CASING;
798
799         return pSort->pSortGetHashCode(pSort, dwFlags, pSrc, cchSrc, lpReserved, lParam);
800     }
801
802
803     ////////////////////////////////////////////////////////////////////////////
804     //
805     //  SortChangeCase
806     //
807     //  Supposed to call the dll for the appropriate version.  If the default
808     //  version isn't available call the ordinal behavior (for minwin)
809     //
810     //  NOTE: The linguistic casing flags are backwards (ie: set the flag to
811     //        get the non-linguistic behavior.)  If we expose this then we'll
812     //        need to publish the no-linguistic flag.
813     //
814     //  Just get the sort hash node and call the worker function
815     //
816     ////////////////////////////////////////////////////////////////////////////
817     __success(return != 0) int WINAPI SortChangeCase(
818         __in LPCWSTR pLocaleName,
819         __in DWORD dwFlags,
820         __in_ecount(cchSrc) LPCWSTR pSrc,
821         __in int cchSrc,
822         __out_ecount_opt(cchDest) LPWSTR pDest,
823         __in int cchDest,
824         __in_opt CONST NLSVERSIONINFO * lpVersionInformation,
825         __in_opt LPVOID lpReserved,
826         __in_opt LPARAM lParam)
827     {
828         PSORTHANDLE pSort = GetSortNode(pLocaleName, VersionValue(lpVersionInformation));
829         return SortDllChangeCase(pSort, dwFlags, pSrc, cchSrc, pDest, cchDest, lpReserved, lParam);
830     }
831
832     // SortDllChangeCase handles any modification to flags
833     // necessary before the actual call to the dll
834     __success(return != 0) int WINAPI SortDllChangeCase(
835         __in PSORTHANDLE pSort,
836         __in DWORD dwFlags,
837         __in_ecount(cchSrc) LPCWSTR pSrc,
838         __in int cchSrc,
839         __out_ecount_opt(cchDest) LPWSTR pDest,
840         __in int cchDest,
841         __in_opt LPVOID lpReserved,
842         __in_opt LPARAM lParam)
843     {
844         if (pSort == NULL)
845         {
846             SetLastError(ERROR_INVALID_PARAMETER);
847             return 0;
848         }
849
850         // Note that Change Case'll have the opposite behavior for the
851         // linguistic casing flag (eg: use flag for bad behavior, linguistic
852         // by default)
853         dwFlags ^= LCMAP_LINGUISTIC_CASING;
854 #ifdef _PREFAST_
855 #pragma warning(push)
856 #pragma warning(disable: 26036) // prefast - Possible postcondition violation due to failure to null terminate string
857 #endif // _PREFAST_
858         return pSort->pSortChangeCase(pSort, dwFlags, pSrc, cchSrc, pDest, cchDest, lpReserved, lParam);
859 #ifdef _PREFAST_
860 #pragma warning(pop)
861 #endif
862     }
863
864     ////////////////////////////////////////////////////////////////////////////
865     //
866     //  SortCompareString
867     //
868     //  Supposed to call the dll for the appropriate version.  If the default
869     //  version isn't available call the ordinal behavior (for minwin)
870     //
871     //  Just get the sort hash node and call the worker function
872     //
873     ////////////////////////////////////////////////////////////////////////////
874     __success(return != 0) int WINAPI SortCompareString(
875         __in LPCWSTR lpLocaleName,
876         __in DWORD dwCmpFlags,
877         __in_ecount(cchCount1) LPCWSTR lpString1,
878         __in int cchCount1,
879         __in_ecount(cchCount2) LPCWSTR lpString2,
880         __in int cchCount2,
881         __in_opt CONST NLSVERSIONINFO * lpVersionInformation,
882         __in_opt LPVOID lpReserved,
883         __in_opt LPARAM lParam)
884     {
885         PSORTHANDLE pSort = GetSortNode(lpLocaleName, VersionValue(lpVersionInformation));
886         return SortDllCompareString(pSort, dwCmpFlags, lpString1, cchCount1, lpString2, cchCount2, lpReserved, lParam);
887
888     }
889
890     // SortDllCompareString handles any modification to flags
891     // necessary before the actual call to the dll
892     __success(return != 0) int WINAPI SortDllCompareString(
893         __in PSORTHANDLE pSort,
894         __in DWORD dwCmpFlags,
895         __in_ecount(cchCount1) LPCWSTR lpString1,
896         __in int cchCount1,
897         __in_ecount(cchCount2) LPCWSTR lpString2,
898         __in int cchCount2,
899         __in_opt LPVOID lpReserved,
900         __in_opt LPARAM lParam)
901     {
902         if (pSort == NULL)
903         {
904             SetLastError(ERROR_INVALID_PARAMETER);
905             return 0;
906         }
907
908         // Note that the dll will have the opposite behavior of CompareStringEx for the
909         // linguistic casing flag (eg: use flag for bad behavior, linguistic
910         // by default) because we want new public APIs to have the "right"
911         // behavior by default
912         dwCmpFlags ^= NORM_LINGUISTIC_CASING;
913
914         return pSort->pSortCompareString(pSort, dwCmpFlags, lpString1, cchCount1, lpString2, cchCount2, lpReserved, lParam);
915     }
916
917     ////////////////////////////////////////////////////////////////////////////
918     //
919     //  SortFindString
920     //
921     //  Finds lpStringValue within lpStringSource based on the rules given
922     //  in dwFindNLSStringFlags.
923     //
924     //  Supposed to call the dll for the appropriate version.  If the default
925     //  version isn't available call the ordinal behavior (for minwin)
926     //
927     //  Just get the sort hash node and call the worker function
928     //
929     ////////////////////////////////////////////////////////////////////////////
930     __success(return != 0) int WINAPI SortFindString(
931         __in LPCWSTR lpLocaleName,
932         __in DWORD dwFindNLSStringFlags,
933         __in_ecount(cchSource) LPCWSTR lpStringSource,
934         __in int cchSource,
935         __in_ecount(cchValue) LPCWSTR lpStringValue,
936         __in int cchValue,
937         __out_opt LPINT pcchFound,
938         __in_opt CONST NLSVERSIONINFO * lpVersionInformation,
939         __in_opt LPVOID lpReserved,
940         __in_opt LPARAM lParam)
941     {
942         PSORTHANDLE pSort = GetSortNode(lpLocaleName, VersionValue(lpVersionInformation));
943         return SortDllFindString(pSort, dwFindNLSStringFlags, lpStringSource, cchSource, lpStringValue, cchValue, pcchFound, lpReserved, lParam);
944     }
945
946     // SortDllFindString handles any modification to flags
947     // necessary before the actual call to the dll
948     __success(return != 0) int WINAPI SortDllFindString(
949         __in PSORTHANDLE pSort,
950         __in DWORD dwFindNLSStringFlags,
951         __in_ecount(cchSource) LPCWSTR lpStringSource,
952         __in int cchSource,
953         __in_ecount(cchValue) LPCWSTR lpStringValue,
954         __in int cchValue,
955         __out_opt LPINT pcchFound,
956         __in_opt LPVOID lpReserved,
957         __in_opt LPARAM lParam)
958     {
959         if (pSort == NULL)
960         {
961             SetLastError(ERROR_INVALID_PARAMETER);
962             return 0;
963         }
964
965         // Note that the dll will  have the opposite behavior of FindNlsString for the
966         // linguistic casing flag (eg: use flag for bad behavior, linguistic
967         // by default) because we want new public APIs to have the "right"
968         // behavior by default
969         dwFindNLSStringFlags ^= NORM_LINGUISTIC_CASING;
970
971         int cchFound; // we need to get the length even if the caller doesn't care about it (see below)
972         int result = pSort->pSortFindString(pSort, dwFindNLSStringFlags, lpStringSource, cchSource, lpStringValue, cchValue, &cchFound, lpReserved, lParam);
973         // When searching from end with an empty pattern (either empty string or all ignored characters)
974         // a match is found (result != -1)
975         //      Currently we get a result == 0 but we are hoping this will change
976         //      with Win7 to be the length of the source string (thus pointing past-the-end)
977         // and the length of the match (cchFound) will be 0
978         // For compatibility, we need to return the index of the last character (or 0 if the source is empty)
979         if((dwFindNLSStringFlags & FIND_FROMEND) &&
980             result != -1 &&
981             cchFound == 0 &&
982             cchSource != 0)
983         {
984             result = cchSource - 1;
985         }
986
987         // if the caller cares about the length, give it to them
988         if(pcchFound != NULL)
989         {
990             *pcchFound = cchFound;
991         }
992
993         return result;
994
995     }
996
997     ////////////////////////////////////////////////////////////////////////////
998     //
999     //  SortIsDefinedString
1000     //
1001     //  This routine looks for code points inside a string to see if they are
1002     //  defined within the NSL context. If lpVersionInformation is NULL, the
1003     //  version is the current version. Same thing the dwDefinedVersion is equal
1004     //  to zero.
1005     //
1006     //  Supposed to call the dll for the appropriate version.  If the default
1007     //  version isn't available call the ordinal behavior (for minwin)
1008     //
1009     //  Just get the sort hash node and call the worker function
1010     //
1011     ////////////////////////////////////////////////////////////////////////////
1012     BOOL WINAPI SortIsDefinedString(
1013         __in NLS_FUNCTION     Function,
1014         __in DWORD            dwFlags,
1015         __in CONST NLSVERSIONINFOEX * lpVersionInformation,
1016         __in_ecount(cchStr) LPCWSTR          lpString,
1017         __in INT              cchStr)
1018     {
1019         // Get an invariant sort node
1020         PSORTHANDLE pSort = GetSortNode(W(""), VersionValue((CONST NLSVERSIONINFO *)lpVersionInformation));
1021         return SortDllIsDefinedString(pSort, Function, dwFlags, lpString, cchStr);
1022     }
1023
1024     // SortDllIsDefinedString handles any modification to flags
1025     // necessary before the actual call to the dll
1026     BOOL WINAPI SortDllIsDefinedString(
1027         __in PSORTHANDLE      pSort,
1028         __in NLS_FUNCTION     Function,
1029         __in DWORD            dwFlags,
1030         __in_ecount(cchStr) LPCWSTR          lpString,
1031         __in INT              cchStr)
1032     {
1033         // Fail if we couldn't find one
1034         if (pSort == NULL)
1035         {
1036             SetLastError(ERROR_INVALID_PARAMETER);
1037             return 0;
1038         }
1039
1040         return pSort->pSortIsDefinedString(pSort, Function, dwFlags, lpString, cchStr);
1041     }
1042
1043     BOOL SortGetNLSVersion(__in PSORTHANDLE pSort,
1044                            __in NLS_FUNCTION Function,
1045                            __inout NLSVERSIONINFO * lpVersionInformation )
1046     {
1047         lpVersionInformation->dwNLSVersion = pSort->dwNLSVersion;
1048         lpVersionInformation->dwDefinedVersion = pSort->dwDefinedVersion;
1049
1050         return TRUE;
1051     }
1052
1053     // Wrapper for SortGetSortKey and SortChangeCase, which are both
1054     // smushed into LCMapStringEx
1055     __success(return != 0) int
1056         LCMapStringEx (__in LPCWSTR lpLocaleName,
1057                            __in DWORD dwMapFlags,
1058                            __in_ecount(cchSrc) LPCWSTR lpSrcStr,
1059                            __in int cchSrc,
1060                            __out_ecount_opt(cchDest) LPWSTR lpDestStr, // really this should be __out_awcount_opt(dwMapFlags & LCMAP_SORTKEY, cchDest)
1061                            __in int cchDest,
1062                            __in_opt CONST NLSVERSIONINFO * lpVersionInformation,
1063                            __in_opt LPVOID lpReserved,
1064                            __in_opt LPARAM lParam )
1065     {
1066         // Should be either sort key...
1067         if (dwMapFlags & LCMAP_SORTKEY)
1068         {
1069 #ifdef _PREFAST_
1070 #pragma warning(push)
1071 #pragma warning(disable: 26036) // Prefast - Possible precondition violation due to failure to null terminate string lpDestStr-interpreted differently depending on flag
1072 #endif // _PREFAST_
1073             return SortGetSortKey(lpLocaleName,
1074                                   dwMapFlags & ~(LCMAP_SORTKEY),    // Don't need sort key flag
1075                                   lpSrcStr,
1076                                   cchSrc,
1077                                   (LPBYTE)lpDestStr,        // Sort keys are bytes not WCHARs
1078                                   cchDest,                  // Sort keys are bytes not WCHARs
1079                                   lpVersionInformation,
1080                                   lpReserved,
1081                                   lParam);
1082 #ifdef _PREFAST_
1083 #pragma warning(pop)
1084 #endif
1085         }
1086
1087         //
1088         // Check for changing case conditions.  This may be combined with Chinese or Japanese
1089         // transliteration, but not with sort key nor ignore space/symbols
1090         //
1091         _ASSERT(dwMapFlags & (LCMAP_TITLECASE | LCMAP_UPPERCASE | LCMAP_LOWERCASE));
1092
1093         //
1094         // Call casing wrapper, which'll either call the correct version dll
1095         // or call ordinal behavior in the minwin case
1096         //
1097         return SortChangeCase(lpLocaleName,
1098                               dwMapFlags & ~(LCMAP_BYTEREV),
1099                               lpSrcStr,
1100                               cchSrc,
1101                               lpDestStr,
1102                               cchDest,
1103                               lpVersionInformation,
1104                               lpReserved,
1105                               lParam);
1106     }
1107
1108
1109     ////////////////////////////////////////////////////////////////////////////
1110     //
1111     //  IsAvailableVersion()
1112     //
1113     //  Get the SortGetHandle() function for the proper dll version.
1114     //
1115     ////////////////////////////////////////////////////////////////////////////
1116     BOOL IsAvailableVersion(__in_opt CONST NLSVERSIONINFO * pVersion)
1117     {
1118         return GetSortGetHandle(VersionValue(pVersion)) != NULL;
1119     }
1120
1121
1122     ////////////////////////////////////////////////////////////////////////////
1123     //
1124     //  GetSortHandle()
1125     //
1126     //  Get the SortHandle for the given locale and version
1127     //
1128     ////////////////////////////////////////////////////////////////////////////
1129     PSORTHANDLE GetSortHandle(__in LPCWSTR lpLocaleName, __in_opt CONST NLSVERSIONINFO * pVersion)
1130     {
1131         DWORD version = VersionValue(pVersion);
1132         if (GetSortGetHandle(version) == NULL)
1133         {
1134             return NULL;
1135         }
1136         return GetSortNode(lpLocaleName, version);
1137     }
1138
1139 }