Signed-off-by: Daniel Lehman dlehman25@gmail.com --- v3: - fix subject line v2: - return MUI strings (borrows from kernel32:TIME_GetSpecificTimeZoneInfo) - current test bots with languages are on Vista, which doesn't support EnumDynamicTimeZoneInformation tested manually with Spanish locale on Windows 10 1809 --- dlls/advapi32/advapi32.spec | 2 +- .../api-ms-win-core-timezone-l1-1-0.spec | 2 +- dlls/kernelbase/kernelbase.spec | 2 +- dlls/kernelbase/main.c | 134 ++++++++++++ dlls/kernelbase/tests/Makefile.in | 3 +- dlls/kernelbase/tests/time.c | 200 ++++++++++++++++++ include/timezoneapi.h | 32 +++ 7 files changed, 371 insertions(+), 4 deletions(-) create mode 100644 dlls/kernelbase/tests/time.c create mode 100644 include/timezoneapi.h
diff --git a/dlls/advapi32/advapi32.spec b/dlls/advapi32/advapi32.spec index 91d6750658..ee9aba4057 100644 --- a/dlls/advapi32/advapi32.spec +++ b/dlls/advapi32/advapi32.spec @@ -272,7 +272,7 @@ # @ stub EncryptionDisable @ stdcall EnumDependentServicesA(long long ptr long ptr ptr) @ stdcall EnumDependentServicesW(long long ptr long ptr ptr) -# @ stub EnumDynamicTimeZoneInformation +@ stdcall EnumDynamicTimeZoneInformation(long ptr) kernelbase.EnumDynamicTimeZoneInformation @ stub EnumServiceGroupA @ stub EnumServiceGroupW @ stdcall EnumServicesStatusA (long long long ptr long ptr ptr ptr) diff --git a/dlls/api-ms-win-core-timezone-l1-1-0/api-ms-win-core-timezone-l1-1-0.spec b/dlls/api-ms-win-core-timezone-l1-1-0/api-ms-win-core-timezone-l1-1-0.spec index 3e38d70596..0f42eda5d8 100644 --- a/dlls/api-ms-win-core-timezone-l1-1-0/api-ms-win-core-timezone-l1-1-0.spec +++ b/dlls/api-ms-win-core-timezone-l1-1-0/api-ms-win-core-timezone-l1-1-0.spec @@ -1,4 +1,4 @@ -@ stub EnumDynamicTimeZoneInformation +@ stdcall EnumDynamicTimeZoneInformation(long ptr) kernelbase.EnumDynamicTimeZoneInformation @ stdcall FileTimeToSystemTime(ptr ptr) kernel32.FileTimeToSystemTime @ stdcall GetDynamicTimeZoneInformation(ptr) kernel32.GetDynamicTimeZoneInformation @ stdcall GetDynamicTimeZoneInformationEffectiveYears(ptr ptr ptr) kernel32.GetDynamicTimeZoneInformationEffectiveYears diff --git a/dlls/kernelbase/kernelbase.spec b/dlls/kernelbase/kernelbase.spec index 46163150c1..c366153971 100644 --- a/dlls/kernelbase/kernelbase.spec +++ b/dlls/kernelbase/kernelbase.spec @@ -297,7 +297,7 @@ @ stdcall EnumDateFormatsExW(ptr long long) kernel32.EnumDateFormatsExW @ stdcall EnumDateFormatsW(ptr long long) kernel32.EnumDateFormatsW # @ stub EnumDeviceDrivers -# @ stub EnumDynamicTimeZoneInformation +@ stdcall EnumDynamicTimeZoneInformation(long ptr) @ stdcall EnumLanguageGroupLocalesW(ptr long long ptr) kernel32.EnumLanguageGroupLocalesW # @ stub EnumPageFilesA # @ stub EnumPageFilesW diff --git a/dlls/kernelbase/main.c b/dlls/kernelbase/main.c index 3fce23657b..231a817a8e 100644 --- a/dlls/kernelbase/main.c +++ b/dlls/kernelbase/main.c @@ -165,3 +165,137 @@ HRESULT WINAPI QISearch(void *base, const QITAB *table, REFIID riid, void **obj) *obj = NULL; return E_NOINTERFACE; } + +static BOOL reg_load_mui_string(HKEY hkey, LPCWSTR value, LPWSTR buffer, DWORD size) +{ + static const WCHAR advapi32W[] = {'a','d','v','a','p','i','3','2','.','d','l','l',0}; + static DWORD (WINAPI *pRegLoadMUIStringW)(HKEY, LPCWSTR, LPWSTR, DWORD, DWORD *, + DWORD, LPCWSTR); + static WCHAR sysdir[MAX_PATH]; + static HMODULE hDll; + + if (!hDll) + { + hDll = LoadLibraryExW(advapi32W, NULL, LOAD_LIBRARY_SEARCH_SYSTEM32); + if (hDll) + { + pRegLoadMUIStringW = (void *)GetProcAddress(hDll, "RegLoadMUIStringW"); + GetSystemDirectoryW(sysdir, ARRAY_SIZE(sysdir)); + } + } + + if (pRegLoadMUIStringW && + !pRegLoadMUIStringW(hkey, value, buffer, size, NULL, 0, sysdir)) + return TRUE; + + return FALSE; +} + +static BOOL reg_query_value(HKEY hkey, const WCHAR *name, DWORD type, void *data, DWORD count) +{ + char buf[256]; + UNICODE_STRING nameW; + KEY_VALUE_PARTIAL_INFORMATION *info = (KEY_VALUE_PARTIAL_INFORMATION *)buf; + + RtlInitUnicodeString( &nameW, name ); + if (NtQueryValueKey( hkey, &nameW, KeyValuePartialInformation, buf, sizeof(buf), &count )) + return FALSE; + + if (info->Type != type) + return FALSE; + + memcpy( data, info->Data, info->DataLength ); + return TRUE; +} + +/****************************************************************************** + * EnumDynamicTimeZoneInformation (KERNELBASE.@) + */ +DWORD WINAPI EnumDynamicTimeZoneInformation(const DWORD index, + DYNAMIC_TIME_ZONE_INFORMATION *dtzi) +{ + static const WCHAR Time_ZonesW[] = { '\','R','E','G','I','S','T','R','Y','\', + 'M','a','c','h','i','n','e','\', + 'S','o','f','t','w','a','r','e','\', + 'M','i','c','r','o','s','o','f','t','\', + 'W','i','n','d','o','w','s',' ','N','T','\', + 'C','u','r','r','e','n','t','V','e','r','s','i','o','n','\', + 'T','i','m','e',' ','Z','o','n','e','s',0 }; + static const WCHAR tziW[] = { 'T','Z','I',0 }; + static const WCHAR stdW[] = { 'S','t','d',0 }; + static const WCHAR dltW[] = { 'D','l','t',0 }; + static const WCHAR mui_stdW[] = { 'M','U','I','_','S','t','d',0 }; + static const WCHAR mui_dltW[] = { 'M','U','I','_','D','l','t',0 }; + char node_buf[sizeof(KEY_NODE_INFORMATION) + ARRAY_SIZE(dtzi->TimeZoneKeyName)]; + KEY_NODE_INFORMATION *info = (KEY_NODE_INFORMATION *)node_buf; + HANDLE time_zones_key, sub_key; + OBJECT_ATTRIBUTES attr; + UNICODE_STRING nameW; + DWORD size; + NTSTATUS status; + struct tz_reg_data + { + LONG bias; + LONG std_bias; + LONG dlt_bias; + SYSTEMTIME std_date; + SYSTEMTIME dlt_date; + } tz_data; + + if (!dtzi) + return ERROR_INVALID_PARAMETER; + + attr.Length = sizeof(attr); + attr.RootDirectory = 0; + attr.ObjectName = &nameW; + attr.Attributes = 0; + attr.SecurityDescriptor = NULL; + attr.SecurityQualityOfService = NULL; + RtlInitUnicodeString( &nameW, Time_ZonesW ); + status = NtOpenKeyEx( &time_zones_key, KEY_READ, &attr, KEY_ENUMERATE_SUB_KEYS ); + if (status) return RtlNtStatusToDosError( status ); + + status = NtEnumerateKey( time_zones_key, index, KeyNodeInformation, + node_buf, sizeof(node_buf), &size ); + if (status) + { + NtClose( time_zones_key ); + return RtlNtStatusToDosError( status ); + } + + attr.RootDirectory = time_zones_key; + info->Name[info->NameLength / sizeof(WCHAR)] = 0; + RtlInitUnicodeString( &nameW, info->Name ); + status = NtOpenKeyEx( &sub_key, KEY_READ, &attr, KEY_QUERY_VALUE ); + if (status) + { + NtClose( time_zones_key ); + return RtlNtStatusToDosError( status ); + } + + if (!reg_load_mui_string( sub_key, mui_stdW, dtzi->StandardName, sizeof(dtzi->StandardName) ) && + !reg_query_value( sub_key, stdW, REG_SZ, dtzi->StandardName, sizeof(dtzi->StandardName) )) + goto done; + + if (!reg_load_mui_string( sub_key, mui_dltW, dtzi->DaylightName, sizeof(dtzi->DaylightName) ) && + !reg_query_value( sub_key, dltW, REG_SZ, dtzi->DaylightName, sizeof(dtzi->DaylightName) )) + goto done; + + if (!reg_query_value( sub_key, tziW, REG_BINARY, &tz_data, sizeof(tz_data) )) + goto done; + + dtzi->Bias = tz_data.bias; + dtzi->StandardBias = tz_data.std_bias; + dtzi->DaylightBias = tz_data.dlt_bias; + memcpy( &dtzi->StandardDate, &tz_data.std_date, sizeof(tz_data.std_date) ); + memcpy( &dtzi->DaylightDate, &tz_data.dlt_date, sizeof(tz_data.dlt_date) ); + lstrcpyW( dtzi->TimeZoneKeyName, info->Name ); + dtzi->DynamicDaylightTimeDisabled = FALSE; + + SetLastError( ERROR_SUCCESS ); + +done: + NtClose( sub_key ); + NtClose( time_zones_key ); + return ERROR_SUCCESS; +} diff --git a/dlls/kernelbase/tests/Makefile.in b/dlls/kernelbase/tests/Makefile.in index 22e4a17a58..2006f5275b 100644 --- a/dlls/kernelbase/tests/Makefile.in +++ b/dlls/kernelbase/tests/Makefile.in @@ -2,4 +2,5 @@ TESTDLL = kernelbase.dll
C_SRCS = \ path.c \ - sync.c + sync.c \ + time.c diff --git a/dlls/kernelbase/tests/time.c b/dlls/kernelbase/tests/time.c new file mode 100644 index 0000000000..8a1f8702f6 --- /dev/null +++ b/dlls/kernelbase/tests/time.c @@ -0,0 +1,200 @@ +/* + * Time tests + * + * Copyright 2019 Daniel Lehman + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include <stdarg.h> +#include <windef.h> +#include <winbase.h> +#include <winreg.h> +#include <stdlib.h> +#include <stdio.h> +#include <winerror.h> +#include <winuser.h> +#include <winternl.h> + +#include "wine/test.h" + +static DWORD (WINAPI *pRegGetValueW)(HKEY,LPCWSTR,LPCWSTR,DWORD,DWORD*,void*,DWORD*); +static DWORD (WINAPI *pRegLoadMUIStringW)(HKEY,LPCWSTR,LPWSTR,DWORD,DWORD*,DWORD,LPCWSTR); + +static DWORD (WINAPI *pEnumDynamicTimeZoneInformation)(const DWORD, + DYNAMIC_TIME_ZONE_INFORMATION*); + +static const char *dbgstr_SYSTEMTIME(const SYSTEMTIME *st) +{ + static int index; + static char buf[2][64]; + + index %= ARRAY_SIZE(buf); + sprintf(buf[index], "%02d-%02d-%04d %02d:%02d:%02d.%03d", + st->wMonth, st->wDay, st->wYear, + st->wHour, st->wMinute, st->wSecond, st->wMilliseconds); + return buf[index++]; +} + +static void test_EnumDynamicTimeZoneInformation(void) +{ + LSTATUS status; + HKEY key, subkey; + WCHAR name[32]; + WCHAR keyname[128]; + WCHAR sysdir[MAX_PATH]; + DWORD index, ret, gle, size; + DYNAMIC_TIME_ZONE_INFORMATION bogus_dtzi, dtzi; + static const WCHAR stdW[] = {'S','t','d',0}; + static const WCHAR dltW[] = {'D','l','t',0}; + static const WCHAR tziW[] = {'T','Z','I',0}; + static const WCHAR mui_stdW[] = {'M','U','I','_','S','t','d',0}; + static const WCHAR mui_dltW[] = {'M','U','I','_','D','l','t',0}; + struct tz_reg_data + { + LONG bias; + LONG std_bias; + LONG dlt_bias; + SYSTEMTIME std_date; + SYSTEMTIME dlt_date; + } tz_data; + + if (!pEnumDynamicTimeZoneInformation) + { + win_skip("EnumDynamicTimeZoneInformation is not supported.\n"); + return; + } + + if (pRegLoadMUIStringW) + GetSystemDirectoryW(sysdir, ARRAY_SIZE(sysdir)); + + SetLastError(0xdeadbeef); + ret = pEnumDynamicTimeZoneInformation(0, NULL); + gle = GetLastError(); + ok(gle == 0xdeadbeef, "got 0x%x\n", gle); + ok(ret == ERROR_INVALID_PARAMETER, "got %d\n", ret); + + memset(&bogus_dtzi, 0xcc, sizeof(bogus_dtzi)); + memset(&dtzi, 0xcc, sizeof(dtzi)); + SetLastError(0xdeadbeef); + ret = pEnumDynamicTimeZoneInformation(-1, &dtzi); + gle = GetLastError(); + ok(gle == 0xdeadbeef, "got 0x%x\n", gle); + ok(ret == ERROR_NO_MORE_ITEMS, "got %d\n", ret); + ok(!memcmp(&dtzi, &bogus_dtzi, sizeof(dtzi)), "mismatch\n"); + + status = RegOpenKeyExA(HKEY_LOCAL_MACHINE, + "Software\Microsoft\Windows NT\CurrentVersion\Time Zones", 0, + KEY_ENUMERATE_SUB_KEYS|KEY_QUERY_VALUE, &key); + ok(status == ERROR_SUCCESS, "got %d\n", status); + index = 0; + while (!(status = RegEnumKeyW(key, index, keyname, sizeof(keyname)))) + { + subkey = NULL; + status = RegOpenKeyExW(key, keyname, 0, KEY_QUERY_VALUE, &subkey); + ok(status == ERROR_SUCCESS, "got %d\n", status); + + memset(&dtzi, 0xcc, sizeof(dtzi)); + SetLastError(0xdeadbeef); + ret = pEnumDynamicTimeZoneInformation(index, &dtzi); + gle = GetLastError(); + /* recently added time zones may not have MUI strings */ + ok(gle == ERROR_SUCCESS || + gle == ERROR_RESOURCE_TYPE_NOT_FOUND /* Win10 1809 32-bit */ || + gle == ERROR_MUI_FILE_NOT_FOUND /* Win10 1809 64-bit */, + "got 0x%x\n", gle); + ok(ret == ERROR_SUCCESS, "got %d\n", ret); + ok(!lstrcmpW(dtzi.TimeZoneKeyName, keyname), "expected %s, got %s\n", + wine_dbgstr_w(keyname), wine_dbgstr_w(dtzi.TimeZoneKeyName)); + + if (gle == ERROR_SUCCESS) + { + size = sizeof(name); + memset(name, 0, sizeof(name)); + if (pRegLoadMUIStringW) + status = pRegLoadMUIStringW(subkey, mui_stdW, name, size, &size, 0, sysdir); + else + status = pRegGetValueW(subkey, NULL, stdW, RRF_RT_REG_SZ, NULL, name, &size); + ok(status == ERROR_SUCCESS, "status %d name %s\n", status, wine_dbgstr_w(name)); + ok(!memcmp(&dtzi.StandardName, name, size), + "expected %s, got %s\n", wine_dbgstr_w(name), wine_dbgstr_w(dtzi.StandardName)); + + size = sizeof(name); + memset(name, 0, sizeof(name)); + if (pRegLoadMUIStringW) + status = pRegLoadMUIStringW(subkey, mui_dltW, name, size, &size, 0, sysdir); + else + status = pRegGetValueW(subkey, NULL, dltW, RRF_RT_REG_SZ, NULL, name, &size); + ok(status == ERROR_SUCCESS, "status %d name %s\n", status, wine_dbgstr_w(name)); + ok(!memcmp(&dtzi.DaylightName, name, size), + "expected %s, got %s\n", wine_dbgstr_w(name), wine_dbgstr_w(dtzi.DaylightName)); + } + else + { + ok(!dtzi.StandardName[0], "expected empty StandardName\n"); + ok(!dtzi.DaylightName[0], "expected empty DaylightName\n"); + } + + ok(!dtzi.DynamicDaylightTimeDisabled, "got %d\n", dtzi.DynamicDaylightTimeDisabled); + + size = sizeof(tz_data); + status = pRegGetValueW(key, keyname, tziW, RRF_RT_REG_BINARY, NULL, &tz_data, &size); + ok(status == ERROR_SUCCESS, "got %d\n", status); + + ok(dtzi.Bias == tz_data.bias, "expected %d, got %d\n", + tz_data.bias, dtzi.Bias); + ok(dtzi.StandardBias == tz_data.std_bias, "expected %d, got %d\n", + tz_data.std_bias, dtzi.StandardBias); + ok(dtzi.DaylightBias == tz_data.dlt_bias, "expected %d, got %d\n", + tz_data.dlt_bias, dtzi.DaylightBias); + + ok(!memcmp(&dtzi.StandardDate, &tz_data.std_date, sizeof(dtzi.StandardDate)), + "expected %s, got %s\n", + dbgstr_SYSTEMTIME(&tz_data.std_date), dbgstr_SYSTEMTIME(&dtzi.StandardDate)); + + ok(!memcmp(&dtzi.DaylightDate, &tz_data.dlt_date, sizeof(dtzi.DaylightDate)), + "expected %s, got %s\n", + dbgstr_SYSTEMTIME(&tz_data.dlt_date), dbgstr_SYSTEMTIME(&dtzi.DaylightDate)); + + RegCloseKey(subkey); + index++; + } + ok(status == ERROR_NO_MORE_ITEMS, "got %d\n", status); + + memset(&dtzi, 0xcc, sizeof(dtzi)); + SetLastError(0xdeadbeef); + ret = pEnumDynamicTimeZoneInformation(index, &dtzi); + gle = GetLastError(); + ok(gle == 0xdeadbeef, "got 0x%x\n", gle); + ok(ret == ERROR_NO_MORE_ITEMS, "got %d\n", ret); + ok(!memcmp(&dtzi, &bogus_dtzi, sizeof(dtzi)), "mismatch\n"); + + RegCloseKey(key); +} + +START_TEST(time) +{ + HMODULE hmod; + + hmod = LoadLibraryA("advapi32.dll"); + pRegLoadMUIStringW = (void *)GetProcAddress(hmod, "RegLoadMUIStringW"); + pRegGetValueW = (void *)GetProcAddress(hmod, "RegGetValueW"); + + hmod = LoadLibraryA("kernelbase.dll"); + pEnumDynamicTimeZoneInformation = + (void *)GetProcAddress(hmod, "EnumDynamicTimeZoneInformation"); + + test_EnumDynamicTimeZoneInformation(); +} diff --git a/include/timezoneapi.h b/include/timezoneapi.h new file mode 100644 index 0000000000..0bba897718 --- /dev/null +++ b/include/timezoneapi.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2019 Daniel Lehman + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#ifndef _APISETTIMEZONE_ +#define _APISETTIMEZONE_ + +#ifdef __cplusplus +extern "C" { +#endif + +WINBASEAPI DWORD WINAPI EnumDynamicTimeZoneInformation(const DWORD, DYNAMIC_TIME_ZONE_INFORMATION *); + +#ifdef __cplusplus +} +#endif + +#endif /* _APISETTIMEZONE_ */
I think it's better to have this in advapi32 for now to avoid explicit library load and forwards pointing to each other. Once we'll start to move things out of advapi32 one extra function won't make a difference.