reactos/ntoskrnl/ex/locale.c

720 lines
21 KiB
C
Raw Normal View History

/*
* PROJECT: ReactOS Kernel
* LICENSE: GPL - See COPYING in the top level directory
* FILE: ntoskrnl/ex/locale.c
* PURPOSE: Locale (Language) Support for the Executive
* PROGRAMMERS: Alex Ionescu (alex.ionescu@reactos.org)
* Eric Kohl
* Thomas Weidenmueller (w3seek@reactos.org
*/
/* INCLUDES ******************************************************************/
#include <ntoskrnl.h>
#define NDEBUG
#include <debug.h>
/* GLOBALS *******************************************************************/
/* System IDs: EN_US */
LCID PsDefaultSystemLocaleId = 0x00000409;
LANGID PsInstallUILanguageId = LANGIDFROMLCID(0x00000409);
/* UI/Thread IDs: Same as system */
LCID PsDefaultThreadLocaleId = 0x00000409;
LANGID PsDefaultUILanguageId = LANGIDFROMLCID(0x00000409);
/* DEFINES *******************************************************************/
#define BOGUS_LOCALE_ID 0xFFFF0000
/* PRIVATE FUNCTIONS *********************************************************/
/**
* @brief
* Validates the registry data of a NLS locale.
*
* @param[in] LocaleData
* A pointer to partial information that contains
* the NLS locale data.
*
* @return
* Returns TRUE if the following conditions are met,
* otherwise FALSE is returned.
*/
static
__inline
BOOLEAN
ExpValidateNlsLocaleData(
_In_ PKEY_VALUE_PARTIAL_INFORMATION LocaleData)
{
PWCHAR Data;
/* Is this a null-terminated string type? */
if (LocaleData->Type != REG_SZ)
{
return FALSE;
}
/* Does it have a consistent length? */
if (LocaleData->DataLength < sizeof(WCHAR))
{
return FALSE;
}
/* Is the locale set and null-terminated? */
Data = (PWSTR)LocaleData->Data;
if (Data[0] != L'1' || Data[1] != UNICODE_NULL)
{
return FALSE;
}
/* All of the conditions above are met */
return TRUE;
}
/**
* @brief
* Validates a NLS locale. Whether a locale is valid
* or not depends on the following conditions:
*
* - The locale must exist in the Locale key, otherwise
* in the Alternate Sorts key;
*
* - The locale must exist in the Language Groups key, and
* the queried value must be readable;
*
* - The locale registry data must be of REG_SIZE type,
* has a consistent length and the locale belongs to
* a supported language group that is set.
*
* @param[in] LocaleId
* A locale identifier that corresponds to a specific
* locale to be validated.
*
* @return
* Returns STATUS_SUCCESS if the function has successfully
* validated the locale and it is valid. STATUS_OBJECT_NAME_NOT_FOUND
* is returned if the following locale does not exist on the system.
* A failure NTSTATUS code is returned otherwise.
*/
static
NTSTATUS
ExpValidateNlsLocaleId(
_In_ LCID LocaleId)
{
NTSTATUS Status;
HANDLE NlsLocaleKey = NULL, AltSortKey = NULL, LangGroupKey = NULL;
OBJECT_ATTRIBUTES NlsLocalKeyAttrs, AltSortKeyAttrs, LangGroupKeyAttrs;
PKEY_VALUE_PARTIAL_INFORMATION BufferKey;
WCHAR ValueBuffer[20], LocaleIdBuffer[20];
ULONG ReturnedLength;
UNICODE_STRING LocaleIdString;
static UNICODE_STRING NlsLocaleKeyPath = RTL_CONSTANT_STRING(L"\\Registry\\Machine\\SYSTEM\\CurrentControlSet\\Control\\Nls\\Locale");
static UNICODE_STRING AltSortKeyPath = RTL_CONSTANT_STRING(L"\\Registry\\Machine\\SYSTEM\\CurrentControlSet\\Control\\Nls\\Locale\\Alternate Sorts");
static UNICODE_STRING LangGroupPath = RTL_CONSTANT_STRING(L"\\Registry\\Machine\\SYSTEM\\CurrentControlSet\\Control\\Nls\\Language Groups");
/* Initialize the registry path attributes */
InitializeObjectAttributes(&NlsLocalKeyAttrs,
&NlsLocaleKeyPath,
OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
NULL,
NULL);
InitializeObjectAttributes(&AltSortKeyAttrs,
&AltSortKeyPath,
OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
NULL,
NULL);
InitializeObjectAttributes(&LangGroupKeyAttrs,
&LangGroupPath,
OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
NULL,
NULL);
/* Copy the locale ID into a buffer */
swprintf(LocaleIdBuffer,
L"%08lx",
(ULONG)LocaleId);
/* And build the LCID string */
RtlInitUnicodeString(&LocaleIdString, LocaleIdBuffer);
/* Open the NLS locale key */
Status = ZwOpenKey(&NlsLocaleKey,
KEY_QUERY_VALUE,
&NlsLocalKeyAttrs);
if (!NT_SUCCESS(Status))
{
DPRINT1("Failed to open %wZ (Status 0x%lx)\n", NlsLocaleKeyPath, Status);
return Status;
}
/* Open the NLS alternate sort locales key */
Status = ZwOpenKey(&AltSortKey,
KEY_QUERY_VALUE,
&AltSortKeyAttrs);
if (!NT_SUCCESS(Status))
{
DPRINT1("Failed to open %wZ (Status 0x%lx)\n", AltSortKeyPath, Status);
goto Quit;
}
/* Open the NLS language groups key */
Status = ZwOpenKey(&LangGroupKey,
KEY_QUERY_VALUE,
&LangGroupKeyAttrs);
if (!NT_SUCCESS(Status))
{
DPRINT1("Failed to open %wZ (Status 0x%lx)\n", LangGroupPath, Status);
goto Quit;
}
/* Check if the captured locale ID exists in the list of other locales */
BufferKey = (PKEY_VALUE_PARTIAL_INFORMATION)ValueBuffer;
Status = ZwQueryValueKey(NlsLocaleKey,
&LocaleIdString,
KeyValuePartialInformation,
BufferKey,
sizeof(ValueBuffer),
&ReturnedLength);
if (!NT_SUCCESS(Status))
{
/* We failed, retry by looking at the alternate sorts locales */
Status = ZwQueryValueKey(AltSortKey,
&LocaleIdString,
KeyValuePartialInformation,
BufferKey,
sizeof(ValueBuffer),
&ReturnedLength);
if (!NT_SUCCESS(Status))
{
DPRINT1("Failed to query value from Alternate Sorts key (Status 0x%lx)\n", Status);
goto Quit;
}
}
/* Ensure the queried locale is of the right key type with a sane length */
if (BufferKey->Type != REG_SZ ||
BufferKey->DataLength < sizeof(WCHAR))
{
DPRINT1("The queried locale is of bad value type or length (Type %lu, DataLength %lu)\n",
BufferKey->Type, BufferKey->DataLength);
Status = STATUS_OBJECT_NAME_NOT_FOUND;
goto Quit;
}
/* We got what we need, now query the locale from the language groups */
RtlInitUnicodeString(&LocaleIdString, (PWSTR)BufferKey->Data);
Status = ZwQueryValueKey(LangGroupKey,
&LocaleIdString,
KeyValuePartialInformation,
BufferKey,
sizeof(ValueBuffer),
&ReturnedLength);
if (!NT_SUCCESS(Status))
{
DPRINT1("Failed to query value from Language Groups key (Status 0x%lx)\n", Status);
goto Quit;
}
/*
* We have queried the locale with its data. However we are not finished here yet,
* because the locale data could be malformed or the locale itself was not set
* so ensure all of these conditions are met.
*/
if (!ExpValidateNlsLocaleData(BufferKey))
{
DPRINT1("The locale data is not valid!\n");
Status = STATUS_OBJECT_NAME_NOT_FOUND;
}
Quit:
if (LangGroupKey != NULL)
{
ZwClose(LangGroupKey);
}
if (AltSortKey != NULL)
{
ZwClose(AltSortKey);
}
if (NlsLocaleKey != NULL)
{
ZwClose(NlsLocaleKey);
}
return Status;
}
NTSTATUS
NTAPI
ExpGetCurrentUserUILanguage(IN PCWSTR MuiName,
OUT LANGID* LanguageId)
{
UCHAR ValueBuffer[256];
PKEY_VALUE_PARTIAL_INFORMATION ValueInfo;
OBJECT_ATTRIBUTES ObjectAttributes;
UNICODE_STRING KeyName =
RTL_CONSTANT_STRING(L"Control Panel\\Desktop");
UNICODE_STRING ValueName;
UNICODE_STRING ValueString;
ULONG ValueLength;
ULONG Value;
HANDLE UserKey;
HANDLE KeyHandle;
NTSTATUS Status;
PAGED_CODE();
/* Setup the key name */
RtlInitUnicodeString(&ValueName, MuiName);
/* Open the use key */
Status = RtlOpenCurrentUser(KEY_READ, &UserKey);
if (!NT_SUCCESS(Status)) return Status;
/* Initialize the attributes and open the key */
InitializeObjectAttributes(&ObjectAttributes,
&KeyName,
OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
UserKey,
NULL);
Status = ZwOpenKey(&KeyHandle, KEY_QUERY_VALUE,&ObjectAttributes);
if (NT_SUCCESS(Status))
{
/* Set buffer and query the current value */
ValueInfo = (PKEY_VALUE_PARTIAL_INFORMATION)ValueBuffer;
Status = ZwQueryValueKey(KeyHandle,
&ValueName,
KeyValuePartialInformation,
ValueBuffer,
sizeof(ValueBuffer),
&ValueLength);
if (NT_SUCCESS(Status))
{
/* Success, is the value the right type? */
if (ValueInfo->Type == REG_SZ)
{
/* It is. Initialize the data and convert it */
RtlInitUnicodeString(&ValueString, (PWSTR)ValueInfo->Data);
Status = RtlUnicodeStringToInteger(&ValueString, 16, &Value);
if (NT_SUCCESS(Status))
{
/* Return the language */
*LanguageId = (USHORT)Value;
}
}
else
{
/* Fail */
Status = STATUS_UNSUCCESSFUL;
}
}
/* Close the key */
ZwClose(KeyHandle);
}
/* Close the user key and return */
ZwClose(UserKey);
return Status;
}
NTSTATUS
NTAPI
ExpSetCurrentUserUILanguage(IN PCWSTR MuiName,
IN LANGID LanguageId)
{
OBJECT_ATTRIBUTES ObjectAttributes;
UNICODE_STRING KeyName = RTL_CONSTANT_STRING(L"Control Panel\\Desktop");
UNICODE_STRING ValueName;
WCHAR ValueBuffer[8];
ULONG ValueLength;
HANDLE UserHandle;
HANDLE KeyHandle;
NTSTATUS Status;
PAGED_CODE();
/* Check that the passed language ID is not bogus */
if (LanguageId & BOGUS_LOCALE_ID)
{
return STATUS_INVALID_PARAMETER;
}
/* Setup the key name */
RtlInitUnicodeString(&ValueName, MuiName);
/* Open the use key */
Status = RtlOpenCurrentUser(KEY_WRITE, &UserHandle);
if (!NT_SUCCESS(Status)) return Status;
/* Initialize the attributes */
InitializeObjectAttributes(&ObjectAttributes,
&KeyName,
OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
UserHandle,
NULL);
/* Validate the language ID */
Status = ExpValidateNlsLocaleId(MAKELCID(LanguageId, SORT_DEFAULT));
if (NT_SUCCESS(Status))
{
/* Open the key */
Status = ZwOpenKey(&KeyHandle, KEY_SET_VALUE, &ObjectAttributes);
if (NT_SUCCESS(Status))
{
/* Setup the value name */
ValueLength = swprintf(ValueBuffer,
L"%04lX",
(ULONG)LanguageId);
/* Set the length for the call and set the value */
ValueLength = (ValueLength + 1) * sizeof(WCHAR);
Status = ZwSetValueKey(KeyHandle,
&ValueName,
0,
REG_SZ,
ValueBuffer,
ValueLength);
/* Close the handle for this key */
ZwClose(KeyHandle);
}
}
/* Close the user key and return status */
ZwClose(UserHandle);
return Status;
}
/* PUBLIC FUNCTIONS **********************************************************/
NTSTATUS
NTAPI
NtQueryDefaultLocale(IN BOOLEAN UserProfile,
OUT PLCID DefaultLocaleId)
{
NTSTATUS Status = STATUS_SUCCESS;
PAGED_CODE();
/* Enter SEH for probing */
_SEH2_TRY
{
/* Check if we came from user mode */
if (KeGetPreviousMode() != KernelMode)
{
/* Probe the language ID */
ProbeForWriteLangId(DefaultLocaleId);
}
/* Check if we have a user profile */
if (UserProfile)
{
/* Return session wide thread locale */
*DefaultLocaleId = MmGetSessionLocaleId();
}
else
{
/* Return system locale */
*DefaultLocaleId = PsDefaultSystemLocaleId;
}
}
_SEH2_EXCEPT(ExSystemExceptionFilter())
{
/* Get exception code */
Status = _SEH2_GetExceptionCode();
}
_SEH2_END;
/* Return status */
return Status;
}
NTSTATUS
NTAPI
NtSetDefaultLocale(IN BOOLEAN UserProfile,
IN LCID DefaultLocaleId)
{
OBJECT_ATTRIBUTES ObjectAttributes;
UNICODE_STRING KeyName;
UNICODE_STRING ValueName;
UNICODE_STRING LocaleString;
HANDLE KeyHandle = NULL;
ULONG ValueLength;
WCHAR ValueBuffer[20];
HANDLE UserKey = NULL;
NTSTATUS Status;
UCHAR KeyValueBuffer[256];
PKEY_VALUE_PARTIAL_INFORMATION KeyValueInformation;
PAGED_CODE();
/* Check that the passed locale ID is not bogus */
if (DefaultLocaleId & BOGUS_LOCALE_ID)
{
return STATUS_INVALID_PARAMETER;
}
/* Check if we have a profile */
if (UserProfile)
{
/* Open the user's key */
Status = RtlOpenCurrentUser(KEY_WRITE, &UserKey);
if (!NT_SUCCESS(Status)) return Status;
/* Initialize the registry location */
RtlInitUnicodeString(&KeyName, L"Control Panel\\International");
RtlInitUnicodeString(&ValueName, L"Locale");
}
else
{
/* Initialize the system registry location */
RtlInitUnicodeString(&KeyName,
L"\\Registry\\Machine\\System\\CurrentControlSet"
L"\\Control\\Nls\\Language");
RtlInitUnicodeString(&ValueName, L"Default");
}
/* Initialize the object attributes */
InitializeObjectAttributes(&ObjectAttributes,
&KeyName,
OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
UserKey,
NULL);
/* Check if we don't have a default locale yet */
if (!DefaultLocaleId)
{
/* Open the key for reading */
Status = ZwOpenKey(&KeyHandle, KEY_QUERY_VALUE, &ObjectAttributes);
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
/* Query the key value */
KeyValueInformation = (PKEY_VALUE_PARTIAL_INFORMATION)KeyValueBuffer;
Status = ZwQueryValueKey(KeyHandle,
&ValueName,
KeyValuePartialInformation,
KeyValueInformation,
sizeof(KeyValueBuffer),
&ValueLength);
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
/* Check if this is a REG_DWORD */
if ((KeyValueInformation->Type == REG_DWORD) &&
(KeyValueInformation->DataLength == sizeof(ULONG)))
{
/* It contains the LCID as a DWORD */
DefaultLocaleId = *((ULONG*)KeyValueInformation->Data);
}
/* Otherwise check for a REG_SZ */
else if (KeyValueInformation->Type == REG_SZ)
{
/* Initialize a unicode string from the value data */
LocaleString.Buffer = (PWCHAR)KeyValueInformation->Data;
LocaleString.Length = (USHORT)KeyValueInformation->DataLength;
LocaleString.MaximumLength = LocaleString.Length;
/* Convert the hex string to a number */
RtlUnicodeStringToInteger(&LocaleString, 16, &DefaultLocaleId);
}
else
{
Status = STATUS_UNSUCCESSFUL;
}
}
else
{
/* We have a locale, validate it */
Status = ExpValidateNlsLocaleId(DefaultLocaleId);
if (NT_SUCCESS(Status))
{
/* Open the key now */
Status = ZwOpenKey(&KeyHandle, KEY_SET_VALUE, &ObjectAttributes);
if (NT_SUCCESS(Status))
{
/* Check if we had a profile */
if (UserProfile)
{
/* Fill in the buffer */
ValueLength = swprintf(ValueBuffer,
L"%08lx",
(ULONG)DefaultLocaleId);
}
else
{
/* Fill in the buffer */
ValueLength = swprintf(ValueBuffer,
L"%04lx",
(ULONG)DefaultLocaleId & 0xFFFF);
}
/* Set the length for the registry call */
ValueLength = (ValueLength + 1) * sizeof(WCHAR);
/* Now write the actual value */
Status = ZwSetValueKey(KeyHandle,
&ValueName,
0,
REG_SZ,
ValueBuffer,
ValueLength);
}
}
}
Cleanup:
/* Close the locale key */
if (KeyHandle)
{
ObCloseHandle(KeyHandle, KernelMode);
}
/* Close the user key */
if (UserKey)
{
ObCloseHandle(UserKey, KernelMode);
}
/* Check for success */
if (NT_SUCCESS(Status))
{
/* Check if it was for a user */
if (UserProfile)
{
/* Set the session wide thread locale */
MmSetSessionLocaleId(DefaultLocaleId);
}
else
{
/* Set system locale */
PsDefaultSystemLocaleId = DefaultLocaleId;
}
}
/* Return status */
return Status;
}
/*
* @implemented
*/
NTSTATUS
NTAPI
NtQueryInstallUILanguage(OUT LANGID* LanguageId)
{
NTSTATUS Status = STATUS_SUCCESS;
PAGED_CODE();
/* Enter SEH for probing */
_SEH2_TRY
{
/* Check if we came from user mode */
if (KeGetPreviousMode() != KernelMode)
{
/* Probe the Language ID */
ProbeForWriteLangId(LanguageId);
}
/* Return it */
*LanguageId = PsInstallUILanguageId;
}
_SEH2_EXCEPT(ExSystemExceptionFilter())
{
/* Get exception code */
Status = _SEH2_GetExceptionCode();
}
_SEH2_END;
/* Return status */
return Status;
}
/*
* @implemented
*/
NTSTATUS
NTAPI
NtQueryDefaultUILanguage(OUT LANGID* LanguageId)
{
NTSTATUS Status;
LANGID SafeLanguageId;
PAGED_CODE();
/* Call the executive helper routine */
Status = ExpGetCurrentUserUILanguage(L"MultiUILanguageId", &SafeLanguageId);
/* Enter SEH for probing */
_SEH2_TRY
{
/* Check if we came from user mode */
if (KeGetPreviousMode() != KernelMode)
{
/* Probe the Language ID */
ProbeForWriteLangId(LanguageId);
}
if (NT_SUCCESS(Status))
{
/* Success, return the language */
*LanguageId = SafeLanguageId;
}
else
{
/* Failed, use fallback value */
// NOTE: Windows doesn't use PsDefaultUILanguageId.
*LanguageId = PsInstallUILanguageId;
}
}
_SEH2_EXCEPT(ExSystemExceptionFilter())
{
/* Return exception code */
_SEH2_YIELD(return _SEH2_GetExceptionCode());
}
_SEH2_END;
/* Return success */
return STATUS_SUCCESS;
}
/*
* @implemented
*/
NTSTATUS
NTAPI
NtSetDefaultUILanguage(IN LANGID LanguageId)
{
NTSTATUS Status;
PAGED_CODE();
/* Check if the caller specified a language id */
if (LanguageId)
{
/* Set the pending MUI language id */
Status = ExpSetCurrentUserUILanguage(L"MUILanguagePending", LanguageId);
}
else
{
/* Otherwise get the pending MUI language id */
Status = ExpGetCurrentUserUILanguage(L"MUILanguagePending", &LanguageId);
if (!NT_SUCCESS(Status))
{
return Status;
}
/* And apply it as actual */
Status = ExpSetCurrentUserUILanguage(L"MultiUILanguageId", LanguageId);
}
return Status;
}
/* EOF */