From 13c1f008c0d8beca934ebfd347dc8354f4c9db05 Mon Sep 17 00:00:00 2001 From: Alexandre Julliard Date: Thu, 25 Jun 2020 15:28:26 +0200 Subject: [PATCH] ntdll: Move the timezone detection code to the Unix library. Signed-off-by: Alexandre Julliard --- dlls/ntdll/tests/time.c | 36 ++- dlls/ntdll/time.c | 453 +-------------------------------- dlls/ntdll/unix/system.c | 398 +++++++++++++++++++++++++++++ dlls/ntdll/unix/unix_private.h | 6 + include/winternl.h | 1 + 5 files changed, 440 insertions(+), 454 deletions(-) diff --git a/dlls/ntdll/tests/time.c b/dlls/ntdll/tests/time.c index 99bddd4fecd..d756a8c839c 100644 --- a/dlls/ntdll/tests/time.c +++ b/dlls/ntdll/tests/time.c @@ -29,6 +29,8 @@ static VOID (WINAPI *pRtlTimeToTimeFields)( const LARGE_INTEGER *liTime, PTIME_FIELDS TimeFields) ; static VOID (WINAPI *pRtlTimeFieldsToTime)( PTIME_FIELDS TimeFields, PLARGE_INTEGER Time) ; static NTSTATUS (WINAPI *pNtQueryPerformanceCounter)( LARGE_INTEGER *counter, LARGE_INTEGER *frequency ); +static NTSTATUS (WINAPI *pNtQuerySystemInformation)( SYSTEM_INFORMATION_CLASS class, + void *info, ULONG size, ULONG *ret_size ); static NTSTATUS (WINAPI *pRtlQueryTimeZoneInformation)( RTL_TIME_ZONE_INFORMATION *); static NTSTATUS (WINAPI *pRtlQueryDynamicTimeZoneInformation)( RTL_DYNAMIC_TIME_ZONE_INFORMATION *); static BOOL (WINAPI *pRtlQueryUnbiasedInterruptTime)( ULONGLONG *time ); @@ -122,18 +124,19 @@ static void test_NtQueryPerformanceCounter(void) static void test_RtlQueryTimeZoneInformation(void) { - RTL_DYNAMIC_TIME_ZONE_INFORMATION tzinfo; + RTL_DYNAMIC_TIME_ZONE_INFORMATION tzinfo, tzinfo2; NTSTATUS status; + ULONG len; /* test RtlQueryTimeZoneInformation returns an indirect string, e.g. @tzres.dll,-32 (Vista or later) */ if (!pRtlQueryTimeZoneInformation || !pRtlQueryDynamicTimeZoneInformation) { - win_skip("Time zone name tests requires Vista or later\n"); + win_skip("Time zone name tests require Vista or later\n"); return; } - memset(&tzinfo, 0, sizeof(tzinfo)); + memset(&tzinfo, 0xcc, sizeof(tzinfo)); status = pRtlQueryDynamicTimeZoneInformation(&tzinfo); ok(status == STATUS_SUCCESS, "RtlQueryDynamicTimeZoneInformation failed, got %08x\n", status); @@ -144,7 +147,13 @@ static void test_RtlQueryTimeZoneInformation(void) "daylight time zone name isn't an indirect string, got %s\n", wine_dbgstr_w(tzinfo.DaylightName)); - memset(&tzinfo, 0, sizeof(tzinfo)); + memset(&tzinfo2, 0xcc, sizeof(tzinfo2)); + status = pNtQuerySystemInformation( SystemDynamicTimeZoneInformation, &tzinfo2, sizeof(tzinfo2), &len ); + ok( !status, "NtQuerySystemInformation failed %x\n", status ); + ok( len == sizeof(tzinfo2), "wrong len %u\n", len ); + ok( !memcmp( &tzinfo, &tzinfo2, sizeof(tzinfo2) ), "tz data is different\n" ); + + memset(&tzinfo, 0xcc, sizeof(tzinfo)); status = pRtlQueryTimeZoneInformation((RTL_TIME_ZONE_INFORMATION *)&tzinfo); ok(status == STATUS_SUCCESS, "RtlQueryTimeZoneInformation failed, got %08x\n", status); @@ -154,6 +163,24 @@ static void test_RtlQueryTimeZoneInformation(void) ok(tzinfo.DaylightName[0] == '@', "daylight time zone name isn't an indirect string, got %s\n", wine_dbgstr_w(tzinfo.DaylightName)); + + memset(&tzinfo, 0xcc, sizeof(tzinfo)); + status = pRtlQueryTimeZoneInformation((RTL_TIME_ZONE_INFORMATION *)&tzinfo); + ok(status == STATUS_SUCCESS, + "RtlQueryTimeZoneInformation failed, got %08x\n", status); + ok(tzinfo.StandardName[0] == '@', + "standard time zone name isn't an indirect string, got %s\n", + wine_dbgstr_w(tzinfo.StandardName)); + ok(tzinfo.DaylightName[0] == '@', + "daylight time zone name isn't an indirect string, got %s\n", + wine_dbgstr_w(tzinfo.DaylightName)); + + memset(&tzinfo2, 0xcc, sizeof(tzinfo2)); + status = pNtQuerySystemInformation( SystemTimeZoneInformation, &tzinfo2, + sizeof(RTL_TIME_ZONE_INFORMATION), &len ); + ok( !status, "NtQuerySystemInformation failed %x\n", status ); + ok( len == sizeof(RTL_TIME_ZONE_INFORMATION), "wrong len %u\n", len ); + ok( !memcmp( &tzinfo, &tzinfo2, sizeof(RTL_TIME_ZONE_INFORMATION) ), "tz data is different\n" ); } static ULONGLONG read_ksystem_time(volatile KSYSTEM_TIME *time) @@ -234,6 +261,7 @@ START_TEST(time) pRtlTimeToTimeFields = (void *)GetProcAddress(mod,"RtlTimeToTimeFields"); pRtlTimeFieldsToTime = (void *)GetProcAddress(mod,"RtlTimeFieldsToTime"); pNtQueryPerformanceCounter = (void *)GetProcAddress(mod, "NtQueryPerformanceCounter"); + pNtQuerySystemInformation = (void *)GetProcAddress(mod, "NtQuerySystemInformation"); pRtlQueryTimeZoneInformation = (void *)GetProcAddress(mod, "RtlQueryTimeZoneInformation"); pRtlQueryDynamicTimeZoneInformation = diff --git a/dlls/ntdll/time.c b/dlls/ntdll/time.c index 96b8ceab3bc..3174a39c2ca 100644 --- a/dlls/ntdll/time.c +++ b/dlls/ntdll/time.c @@ -51,23 +51,6 @@ WINE_DEFAULT_DEBUG_CHANNEL(ntdll); -#define SHORT_TZ_NAME_MAX 8 -struct tz_name_map { - WCHAR key_name[128]; - char short_name[SHORT_TZ_NAME_MAX]; -}; - -static int init_tz_info(RTL_DYNAMIC_TIME_ZONE_INFORMATION *tzi); - -static RTL_CRITICAL_SECTION TIME_tz_section; -static RTL_CRITICAL_SECTION_DEBUG critsect_debug = -{ - 0, 0, &TIME_tz_section, - { &critsect_debug.ProcessLocksList, &critsect_debug.ProcessLocksList }, - 0, 0, { (DWORD_PTR)(__FILE__ ": TIME_tz_section") } -}; -static RTL_CRITICAL_SECTION TIME_tz_section = { &critsect_debug, -1, 0, 0, 0, 0 }; - #define TICKSPERSEC 10000000 #define TICKSPERMSEC 10000 #define SECSPERDAY 86400 @@ -486,430 +469,6 @@ ULONG WINAPI DECLSPEC_HOTPATCH NtGetTickCount(void) return user_shared_data->u.TickCount.LowPart; } -/* calculate the mday of dst change date, so that for instance Sun 5 Oct 2007 - * (last Sunday in October of 2007) becomes Sun Oct 28 2007 - * - * Note: year, day and month must be in unix format. - */ -static int weekday_to_mday(int year, int day, int mon, int day_of_week) -{ - struct tm date; - time_t tmp; - int wday, mday; - - /* find first day in the month matching week day of the date */ - memset(&date, 0, sizeof(date)); - date.tm_year = year; - date.tm_mon = mon; - date.tm_mday = -1; - date.tm_wday = -1; - do - { - date.tm_mday++; - tmp = mktime(&date); - } while (date.tm_wday != day_of_week || date.tm_mon != mon); - - mday = date.tm_mday; - - /* find number of week days in the month matching week day of the date */ - wday = 1; /* 1 - 1st, ...., 5 - last */ - while (wday < day) - { - struct tm *tm; - - date.tm_mday += 7; - tmp = mktime(&date); - tm = localtime(&tmp); - if (tm->tm_mon != mon) - break; - mday = tm->tm_mday; - wday++; - } - - return mday; -} - -static BOOL match_tz_date(const RTL_SYSTEM_TIME *st, const RTL_SYSTEM_TIME *reg_st) -{ - WORD wDay; - - if (st->wMonth != reg_st->wMonth) return FALSE; - - if (!st->wMonth) return TRUE; /* no transition dates */ - - wDay = reg_st->wDay; - if (!reg_st->wYear) /* date in a day-of-week format */ - wDay = weekday_to_mday(st->wYear - 1900, reg_st->wDay, reg_st->wMonth - 1, reg_st->wDayOfWeek); - - if (st->wDay != wDay || - st->wHour != reg_st->wHour || - st->wMinute != reg_st->wMinute || - st->wSecond != reg_st->wSecond || - st->wMilliseconds != reg_st->wMilliseconds) return FALSE; - - return TRUE; -} - -static BOOL match_tz_info(const RTL_DYNAMIC_TIME_ZONE_INFORMATION *tzi, const RTL_DYNAMIC_TIME_ZONE_INFORMATION *reg_tzi) -{ - if (tzi->Bias == reg_tzi->Bias && - match_tz_date(&tzi->StandardDate, ®_tzi->StandardDate) && - match_tz_date(&tzi->DaylightDate, ®_tzi->DaylightDate)) - return TRUE; - - return FALSE; -} - -static int compare_tz_key(const void *a, const void *b) -{ - const struct tz_name_map *map_a, *map_b; - map_a = (const struct tz_name_map *)a; - map_b = (const struct tz_name_map *)b; - return wcscmp(map_a->key_name, map_b->key_name); -} - -static BOOL match_tz_name(const char* tz_name, - const RTL_DYNAMIC_TIME_ZONE_INFORMATION *reg_tzi) -{ - static const struct tz_name_map mapping[] = { - { {'K','o','r','e','a',' ','S','t','a','n','d','a','r','d',' ','T','i', - 'm','e',0 }, - "KST" }, - { {'T','o','k','y','o',' ','S','t','a','n','d','a','r','d',' ','T','i', - 'm','e',0 }, - "JST" }, - { {'Y','a','k','u','t','s','k',' ','S','t','a','n','d','a','r','d',' ', - 'T','i','m','e',0 }, - "+09" }, /* YAKST was used until tzdata 2016f */ - }; - struct tz_name_map *match, key; - - if (reg_tzi->DaylightDate.wMonth) - return TRUE; - - wcscpy(key.key_name, reg_tzi->TimeZoneKeyName); - match = bsearch(&key, mapping, ARRAY_SIZE(mapping), sizeof(mapping[0]), compare_tz_key); - if (!match) - return TRUE; - - return !strcmp(match->short_name, tz_name); -} - -static BOOL reg_query_value(HKEY hkey, LPCWSTR name, DWORD type, void *data, DWORD count) -{ - UNICODE_STRING nameW; - char buf[256]; - KEY_VALUE_PARTIAL_INFORMATION *info = (KEY_VALUE_PARTIAL_INFORMATION *)buf; - - if (count > sizeof(buf) - sizeof(KEY_VALUE_PARTIAL_INFORMATION)) - return FALSE; - - 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; -} - -static void find_reg_tz_info(RTL_DYNAMIC_TIME_ZONE_INFORMATION *tzi, const char* tz_name, int year) -{ - static const WCHAR Time_ZonesW[] = { '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 Dynamic_DstW[] = { 'D','y','n','a','m','i','c',' ','D','S','T',0 }; - static const WCHAR fmtW[] = { '%','d',0 }; - HANDLE hkey; - ULONG idx; - OBJECT_ATTRIBUTES attr, attrDynamic; - UNICODE_STRING nameW, nameDynamicW; - WCHAR buf[128], yearW[16]; - - NTDLL_swprintf(yearW, fmtW, year); - - attrDynamic.Length = sizeof(attrDynamic); - attrDynamic.RootDirectory = 0; /* will be replaced later */ - attrDynamic.ObjectName = &nameDynamicW; - attrDynamic.Attributes = 0; - attrDynamic.SecurityDescriptor = NULL; - attrDynamic.SecurityQualityOfService = NULL; - RtlInitUnicodeString(&nameDynamicW, Dynamic_DstW); - - attr.Length = sizeof(attr); - attr.RootDirectory = 0; - attr.ObjectName = &nameW; - attr.Attributes = 0; - attr.SecurityDescriptor = NULL; - attr.SecurityQualityOfService = NULL; - RtlInitUnicodeString(&nameW, Time_ZonesW); - if (NtOpenKey(&hkey, KEY_READ, &attr)) - { - WARN("Unable to open the time zones key\n"); - return; - } - - idx = 0; - nameW.Buffer = buf; - nameW.Length = sizeof(buf); - nameW.MaximumLength = sizeof(buf); - - while (!RtlpNtEnumerateSubKey(hkey, &nameW, idx++)) - { - 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 }; - static const WCHAR tziW[] = { 'T','Z','I',0 }; - RTL_DYNAMIC_TIME_ZONE_INFORMATION reg_tzi; - HANDLE hSubkey, hSubkeyDynamicDST; - BOOL is_dynamic = FALSE; - - struct tz_reg_data - { - LONG bias; - LONG std_bias; - LONG dlt_bias; - RTL_SYSTEM_TIME std_date; - RTL_SYSTEM_TIME dlt_date; - } tz_data; - - attr.Length = sizeof(attr); - attr.RootDirectory = hkey; - attr.ObjectName = &nameW; - attr.Attributes = 0; - attr.SecurityDescriptor = NULL; - attr.SecurityQualityOfService = NULL; - if (NtOpenKey(&hSubkey, KEY_READ, &attr)) - { - WARN("Unable to open subkey %s\n", debugstr_wn(nameW.Buffer, nameW.Length/sizeof(WCHAR))); - continue; - } - -#define get_value(hkey, name, type, data, len) \ - if (!reg_query_value(hkey, name, type, data, len)) \ - { \ - WARN("can't read data from %s\n", debugstr_w(name)); \ - NtClose(hkey); \ - continue; \ - } - - memset(®_tzi, 0, sizeof(reg_tzi)); - - if (!reg_query_value(hSubkey, mui_stdW, REG_SZ, reg_tzi.StandardName, sizeof(reg_tzi.StandardName))) - get_value(hSubkey, stdW, REG_SZ, reg_tzi.StandardName, sizeof(reg_tzi.StandardName)); - if (!reg_query_value(hSubkey, mui_dltW, REG_SZ, reg_tzi.DaylightName, sizeof(reg_tzi.DaylightName))) - get_value(hSubkey, dltW, REG_SZ, reg_tzi.DaylightName, sizeof(reg_tzi.DaylightName)); - memcpy(reg_tzi.TimeZoneKeyName, nameW.Buffer, nameW.Length); - reg_tzi.TimeZoneKeyName[nameW.Length/sizeof(WCHAR)] = 0; - - /* Check for Dynamic DST entry first */ - attrDynamic.RootDirectory = hSubkey; - if (!NtOpenKey(&hSubkeyDynamicDST, KEY_READ, &attrDynamic)) - { - is_dynamic = reg_query_value(hSubkeyDynamicDST, yearW, REG_BINARY, &tz_data, sizeof(tz_data)); - NtClose(hSubkeyDynamicDST); - } - - if (!is_dynamic) - get_value(hSubkey, tziW, REG_BINARY, &tz_data, sizeof(tz_data)); - -#undef get_value - - reg_tzi.Bias = tz_data.bias; - reg_tzi.StandardBias = tz_data.std_bias; - reg_tzi.DaylightBias = tz_data.dlt_bias; - reg_tzi.StandardDate = tz_data.std_date; - reg_tzi.DaylightDate = tz_data.dlt_date; - - TRACE("%s: bias %d\n", debugstr_wn(nameW.Buffer, nameW.Length/sizeof(WCHAR)), reg_tzi.Bias); - TRACE("std (d/m/y): %u/%02u/%04u day of week %u %u:%02u:%02u.%03u bias %d\n", - reg_tzi.StandardDate.wDay, reg_tzi.StandardDate.wMonth, - reg_tzi.StandardDate.wYear, reg_tzi.StandardDate.wDayOfWeek, - reg_tzi.StandardDate.wHour, reg_tzi.StandardDate.wMinute, - reg_tzi.StandardDate.wSecond, reg_tzi.StandardDate.wMilliseconds, - reg_tzi.StandardBias); - TRACE("dst (d/m/y): %u/%02u/%04u day of week %u %u:%02u:%02u.%03u bias %d\n", - reg_tzi.DaylightDate.wDay, reg_tzi.DaylightDate.wMonth, - reg_tzi.DaylightDate.wYear, reg_tzi.DaylightDate.wDayOfWeek, - reg_tzi.DaylightDate.wHour, reg_tzi.DaylightDate.wMinute, - reg_tzi.DaylightDate.wSecond, reg_tzi.DaylightDate.wMilliseconds, - reg_tzi.DaylightBias); - - NtClose(hSubkey); - - if (match_tz_info(tzi, ®_tzi) - && match_tz_name(tz_name, ®_tzi)) - { - *tzi = reg_tzi; - NtClose(hkey); - return; - } - - /* reset len */ - nameW.Length = sizeof(buf); - nameW.MaximumLength = sizeof(buf); - } - - NtClose(hkey); - - if (idx == 1) return; /* registry info not initialized yet */ - - FIXME("Can't find matching timezone information in the registry for " - "%s, bias %d, std (d/m/y): %u/%02u/%04u, dlt (d/m/y): %u/%02u/%04u\n", - tz_name, tzi->Bias, - tzi->StandardDate.wDay, tzi->StandardDate.wMonth, tzi->StandardDate.wYear, - tzi->DaylightDate.wDay, tzi->DaylightDate.wMonth, tzi->DaylightDate.wYear); -} - -static time_t find_dst_change(unsigned long min, unsigned long max, int *is_dst) -{ - time_t start; - struct tm *tm; - - start = min; - tm = localtime(&start); - *is_dst = !tm->tm_isdst; - TRACE("starting date isdst %d, %s", !*is_dst, ctime(&start)); - - while (min <= max) - { - time_t pos = (min + max) / 2; - tm = localtime(&pos); - - if (tm->tm_isdst != *is_dst) - min = pos + 1; - else - max = pos - 1; - } - return min; -} - -static int init_tz_info(RTL_DYNAMIC_TIME_ZONE_INFORMATION *tzi) -{ - static RTL_DYNAMIC_TIME_ZONE_INFORMATION cached_tzi; - static int current_year = -1, current_bias = 65535; - struct tm *tm; - char tz_name[SHORT_TZ_NAME_MAX]; - time_t year_start, year_end, tmp, dlt = 0, std = 0; - int is_dst, current_is_dst, bias; - - RtlEnterCriticalSection( &TIME_tz_section ); - - year_start = time(NULL); - tm = gmtime(&year_start); - bias = (LONG)(mktime(tm) - year_start) / 60; - - tm = localtime(&year_start); - current_is_dst = tm->tm_isdst; - if (current_year == tm->tm_year && current_bias == bias) - { - *tzi = cached_tzi; - RtlLeaveCriticalSection( &TIME_tz_section ); - return current_is_dst; - } - - memset(tzi, 0, sizeof(*tzi)); - if (!strftime(tz_name, sizeof(tz_name), "%Z", tm)) { - /* not enough room or another error */ - tz_name[0] = '\0'; - } - - TRACE("tz data will be valid through year %d, bias %d\n", tm->tm_year + 1900, bias); - current_year = tm->tm_year; - current_bias = bias; - - tzi->Bias = bias; - - tm->tm_isdst = 0; - tm->tm_mday = 1; - tm->tm_mon = tm->tm_hour = tm->tm_min = tm->tm_sec = tm->tm_wday = tm->tm_yday = 0; - year_start = mktime(tm); - TRACE("year_start: %s", ctime(&year_start)); - - tm->tm_mday = tm->tm_wday = tm->tm_yday = 0; - tm->tm_mon = 12; - tm->tm_hour = 23; - tm->tm_min = tm->tm_sec = 59; - year_end = mktime(tm); - TRACE("year_end: %s", ctime(&year_end)); - - tmp = find_dst_change(year_start, year_end, &is_dst); - if (is_dst) - dlt = tmp; - else - std = tmp; - - tmp = find_dst_change(tmp, year_end, &is_dst); - if (is_dst) - dlt = tmp; - else - std = tmp; - - TRACE("std: %s", ctime(&std)); - TRACE("dlt: %s", ctime(&dlt)); - - if (dlt == std || !dlt || !std) - TRACE("there is no daylight saving rules in this time zone\n"); - else - { - tmp = dlt - tzi->Bias * 60; - tm = gmtime(&tmp); - TRACE("dlt gmtime: %s", asctime(tm)); - - tzi->DaylightBias = -60; - tzi->DaylightDate.wYear = tm->tm_year + 1900; - tzi->DaylightDate.wMonth = tm->tm_mon + 1; - tzi->DaylightDate.wDayOfWeek = tm->tm_wday; - tzi->DaylightDate.wDay = tm->tm_mday; - tzi->DaylightDate.wHour = tm->tm_hour; - tzi->DaylightDate.wMinute = tm->tm_min; - tzi->DaylightDate.wSecond = tm->tm_sec; - tzi->DaylightDate.wMilliseconds = 0; - - TRACE("daylight (d/m/y): %u/%02u/%04u day of week %u %u:%02u:%02u.%03u bias %d\n", - tzi->DaylightDate.wDay, tzi->DaylightDate.wMonth, - tzi->DaylightDate.wYear, tzi->DaylightDate.wDayOfWeek, - tzi->DaylightDate.wHour, tzi->DaylightDate.wMinute, - tzi->DaylightDate.wSecond, tzi->DaylightDate.wMilliseconds, - tzi->DaylightBias); - - tmp = std - tzi->Bias * 60 - tzi->DaylightBias * 60; - tm = gmtime(&tmp); - TRACE("std gmtime: %s", asctime(tm)); - - tzi->StandardBias = 0; - tzi->StandardDate.wYear = tm->tm_year + 1900; - tzi->StandardDate.wMonth = tm->tm_mon + 1; - tzi->StandardDate.wDayOfWeek = tm->tm_wday; - tzi->StandardDate.wDay = tm->tm_mday; - tzi->StandardDate.wHour = tm->tm_hour; - tzi->StandardDate.wMinute = tm->tm_min; - tzi->StandardDate.wSecond = tm->tm_sec; - tzi->StandardDate.wMilliseconds = 0; - - TRACE("standard (d/m/y): %u/%02u/%04u day of week %u %u:%02u:%02u.%03u bias %d\n", - tzi->StandardDate.wDay, tzi->StandardDate.wMonth, - tzi->StandardDate.wYear, tzi->StandardDate.wDayOfWeek, - tzi->StandardDate.wHour, tzi->StandardDate.wMinute, - tzi->StandardDate.wSecond, tzi->StandardDate.wMilliseconds, - tzi->StandardBias); - } - - find_reg_tz_info(tzi, tz_name, current_year + 1900); - cached_tzi = *tzi; - - RtlLeaveCriticalSection( &TIME_tz_section ); - - return current_is_dst; -} - /*********************************************************************** * RtlQueryTimeZoneInformation [NTDLL.@] * @@ -924,11 +483,7 @@ static int init_tz_info(RTL_DYNAMIC_TIME_ZONE_INFORMATION *tzi) */ NTSTATUS WINAPI RtlQueryTimeZoneInformation(RTL_TIME_ZONE_INFORMATION *ret) { - RTL_DYNAMIC_TIME_ZONE_INFORMATION tzinfo; - - init_tz_info( &tzinfo ); - memcpy( ret, &tzinfo, sizeof(*ret) ); - return STATUS_SUCCESS; + return NtQuerySystemInformation( SystemTimeZoneInformation, ret, sizeof(*ret), NULL ); } /*********************************************************************** @@ -943,11 +498,9 @@ NTSTATUS WINAPI RtlQueryTimeZoneInformation(RTL_TIME_ZONE_INFORMATION *ret) * Success: STATUS_SUCCESS. * Failure: An NTSTATUS error code indicating the problem. */ -NTSTATUS WINAPI RtlQueryDynamicTimeZoneInformation(RTL_DYNAMIC_TIME_ZONE_INFORMATION *tzinfo) +NTSTATUS WINAPI RtlQueryDynamicTimeZoneInformation(RTL_DYNAMIC_TIME_ZONE_INFORMATION *ret) { - init_tz_info( tzinfo ); - - return STATUS_SUCCESS; + return NtQuerySystemInformation( SystemDynamicTimeZoneInformation, ret, sizeof(*ret), NULL ); } /*********************************************************************** diff --git a/dlls/ntdll/unix/system.c b/dlls/ntdll/unix/system.c index 1d81e8a04f9..5e2e763445c 100644 --- a/dlls/ntdll/unix/system.c +++ b/dlls/ntdll/unix/system.c @@ -1631,6 +1631,374 @@ static void get_performance_info( SYSTEM_PERFORMANCE_INFORMATION *info ) } +/* calculate the mday of dst change date, so that for instance Sun 5 Oct 2007 + * (last Sunday in October of 2007) becomes Sun Oct 28 2007 + * + * Note: year, day and month must be in unix format. + */ +static int weekday_to_mday(int year, int day, int mon, int day_of_week) +{ + struct tm date; + time_t tmp; + int wday, mday; + + /* find first day in the month matching week day of the date */ + memset(&date, 0, sizeof(date)); + date.tm_year = year; + date.tm_mon = mon; + date.tm_mday = -1; + date.tm_wday = -1; + do + { + date.tm_mday++; + tmp = mktime(&date); + } while (date.tm_wday != day_of_week || date.tm_mon != mon); + + mday = date.tm_mday; + + /* find number of week days in the month matching week day of the date */ + wday = 1; /* 1 - 1st, ...., 5 - last */ + while (wday < day) + { + struct tm *tm; + + date.tm_mday += 7; + tmp = mktime(&date); + tm = localtime(&tmp); + if (tm->tm_mon != mon) + break; + mday = tm->tm_mday; + wday++; + } + + return mday; +} + +static BOOL match_tz_date( const RTL_SYSTEM_TIME *st, const RTL_SYSTEM_TIME *reg_st ) +{ + WORD wDay; + + if (st->wMonth != reg_st->wMonth) return FALSE; + if (!st->wMonth) return TRUE; /* no transition dates */ + wDay = reg_st->wDay; + if (!reg_st->wYear) /* date in a day-of-week format */ + wDay = weekday_to_mday(st->wYear - 1900, reg_st->wDay, reg_st->wMonth - 1, reg_st->wDayOfWeek); + + return (st->wDay == wDay && + st->wHour == reg_st->wHour && + st->wMinute == reg_st->wMinute && + st->wSecond == reg_st->wSecond && + st->wMilliseconds == reg_st->wMilliseconds); +} + +static BOOL match_tz_info( const RTL_DYNAMIC_TIME_ZONE_INFORMATION *tzi, + const RTL_DYNAMIC_TIME_ZONE_INFORMATION *reg_tzi ) +{ + return (tzi->Bias == reg_tzi->Bias && + match_tz_date(&tzi->StandardDate, ®_tzi->StandardDate) && + match_tz_date(&tzi->DaylightDate, ®_tzi->DaylightDate)); +} + +static BOOL match_tz_name( const char *tz_name, const RTL_DYNAMIC_TIME_ZONE_INFORMATION *reg_tzi ) +{ + static const struct { WCHAR key_name[32]; const char *short_name; } mapping[] = + { + { {'K','o','r','e','a',' ','S','t','a','n','d','a','r','d',' ','T','i','m','e',0 }, + "KST" }, + { {'T','o','k','y','o',' ','S','t','a','n','d','a','r','d',' ','T','i','m','e',0 }, + "JST" }, + { {'Y','a','k','u','t','s','k',' ','S','t','a','n','d','a','r','d',' ','T','i','m','e',0 }, + "+09" }, /* YAKST was used until tzdata 2016f */ + }; + unsigned int i; + + if (reg_tzi->DaylightDate.wMonth) return TRUE; + for (i = 0; i < ARRAY_SIZE(mapping); i++) + { + if (!wcscmp( mapping[i].key_name, reg_tzi->TimeZoneKeyName )) + return !strcmp( mapping[i].short_name, tz_name ); + } + return TRUE; +} + +static BOOL reg_query_value( HKEY key, LPCWSTR name, DWORD type, void *data, DWORD count ) +{ + char buf[256]; + UNICODE_STRING nameW; + KEY_VALUE_PARTIAL_INFORMATION *info = (KEY_VALUE_PARTIAL_INFORMATION *)buf; + + if (count > sizeof(buf) - sizeof(KEY_VALUE_PARTIAL_INFORMATION)) return FALSE; + + RtlInitUnicodeString( &nameW, name ); + if (NtQueryValueKey( key, &nameW, KeyValuePartialInformation, buf, sizeof(buf), &count )) + return FALSE; + + if (info->Type != type) return FALSE; + memcpy( data, info->Data, info->DataLength ); + return TRUE; +} + +static void find_reg_tz_info(RTL_DYNAMIC_TIME_ZONE_INFORMATION *tzi, const char* tz_name, int year) +{ + 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 }; + static const WCHAR tziW[] = { 'T','Z','I',0 }; + static const WCHAR Time_ZonesW[] = { '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 Dynamic_DstW[] = { 'D','y','n','a','m','i','c',' ','D','S','T',0 }; + RTL_DYNAMIC_TIME_ZONE_INFORMATION reg_tzi; + HANDLE key, subkey, subkey_dyn = 0; + ULONG idx, len; + OBJECT_ATTRIBUTES attr; + UNICODE_STRING nameW, nameDynamicW; + WCHAR yearW[16]; + char buffer[128]; + KEY_BASIC_INFORMATION *info = (KEY_BASIC_INFORMATION *)buffer; + + sprintf( buffer, "%u", year ); + ascii_to_unicode( yearW, buffer, strlen(buffer) + 1 ); + + RtlInitUnicodeString( &nameW, Time_ZonesW ); + RtlInitUnicodeString( &nameDynamicW, Dynamic_DstW ); + + InitializeObjectAttributes( &attr, &nameW, 0, 0, NULL ); + if (NtOpenKey( &key, KEY_READ, &attr )) return; + + idx = 0; + while (!NtEnumerateKey( key, idx++, KeyBasicInformation, buffer, sizeof(buffer), &len )) + { + struct tz_reg_data + { + LONG bias; + LONG std_bias; + LONG dlt_bias; + RTL_SYSTEM_TIME std_date; + RTL_SYSTEM_TIME dlt_date; + } tz_data; + BOOL is_dynamic = FALSE; + + nameW.Buffer = info->Name; + nameW.Length = info->NameLength; + InitializeObjectAttributes( &attr, &nameW, 0, key, NULL ); + if (NtOpenKey( &subkey, KEY_READ, &attr )) continue; + + memset( ®_tzi, 0, sizeof(reg_tzi) ); + memcpy(reg_tzi.TimeZoneKeyName, nameW.Buffer, nameW.Length); + reg_tzi.TimeZoneKeyName[nameW.Length/sizeof(WCHAR)] = 0; + + if (!reg_query_value(subkey, mui_stdW, REG_SZ, reg_tzi.StandardName, sizeof(reg_tzi.StandardName)) && + !reg_query_value(subkey, stdW, REG_SZ, reg_tzi.StandardName, sizeof(reg_tzi.StandardName))) + goto next; + + if (!reg_query_value(subkey, mui_dltW, REG_SZ, reg_tzi.DaylightName, sizeof(reg_tzi.DaylightName)) && + !reg_query_value(subkey, dltW, REG_SZ, reg_tzi.DaylightName, sizeof(reg_tzi.DaylightName))) + goto next; + + /* Check for Dynamic DST entry first */ + InitializeObjectAttributes( &attr, &nameDynamicW, 0, subkey, NULL ); + if (!NtOpenKey( &subkey_dyn, KEY_READ, &attr )) + { + is_dynamic = reg_query_value( subkey_dyn, yearW, REG_BINARY, &tz_data, sizeof(tz_data) ); + NtClose( subkey_dyn ); + } + if (!is_dynamic && !reg_query_value( subkey, tziW, REG_BINARY, &tz_data, sizeof(tz_data) )) + goto next; + + reg_tzi.Bias = tz_data.bias; + reg_tzi.StandardBias = tz_data.std_bias; + reg_tzi.DaylightBias = tz_data.dlt_bias; + reg_tzi.StandardDate = tz_data.std_date; + reg_tzi.DaylightDate = tz_data.dlt_date; + + TRACE("%s: bias %d\n", debugstr_us(&nameW), reg_tzi.Bias); + TRACE("std (d/m/y): %u/%02u/%04u day of week %u %u:%02u:%02u.%03u bias %d\n", + reg_tzi.StandardDate.wDay, reg_tzi.StandardDate.wMonth, + reg_tzi.StandardDate.wYear, reg_tzi.StandardDate.wDayOfWeek, + reg_tzi.StandardDate.wHour, reg_tzi.StandardDate.wMinute, + reg_tzi.StandardDate.wSecond, reg_tzi.StandardDate.wMilliseconds, + reg_tzi.StandardBias); + TRACE("dst (d/m/y): %u/%02u/%04u day of week %u %u:%02u:%02u.%03u bias %d\n", + reg_tzi.DaylightDate.wDay, reg_tzi.DaylightDate.wMonth, + reg_tzi.DaylightDate.wYear, reg_tzi.DaylightDate.wDayOfWeek, + reg_tzi.DaylightDate.wHour, reg_tzi.DaylightDate.wMinute, + reg_tzi.DaylightDate.wSecond, reg_tzi.DaylightDate.wMilliseconds, + reg_tzi.DaylightBias); + + if (match_tz_info( tzi, ®_tzi ) && match_tz_name( tz_name, ®_tzi )) + { + *tzi = reg_tzi; + NtClose( subkey ); + NtClose( key ); + return; + } + next: + NtClose( subkey ); + } + NtClose( key ); + + if (idx == 1) return; /* registry info not initialized yet */ + + FIXME("Can't find matching timezone information in the registry for " + "%s, bias %d, std (d/m/y): %u/%02u/%04u, dlt (d/m/y): %u/%02u/%04u\n", + tz_name, tzi->Bias, + tzi->StandardDate.wDay, tzi->StandardDate.wMonth, tzi->StandardDate.wYear, + tzi->DaylightDate.wDay, tzi->DaylightDate.wMonth, tzi->DaylightDate.wYear); +} + +static time_t find_dst_change(unsigned long min, unsigned long max, int *is_dst) +{ + time_t start; + struct tm *tm; + + start = min; + tm = localtime(&start); + *is_dst = !tm->tm_isdst; + TRACE("starting date isdst %d, %s", !*is_dst, ctime(&start)); + + while (min <= max) + { + time_t pos = (min + max) / 2; + tm = localtime(&pos); + + if (tm->tm_isdst != *is_dst) + min = pos + 1; + else + max = pos - 1; + } + return min; +} + +static RTL_CRITICAL_SECTION TIME_tz_section; +static RTL_CRITICAL_SECTION_DEBUG critsect_debug = +{ + 0, 0, &TIME_tz_section, + { &critsect_debug.ProcessLocksList, &critsect_debug.ProcessLocksList }, + 0, 0, { (DWORD_PTR)(__FILE__ ": TIME_tz_section") } +}; +static RTL_CRITICAL_SECTION TIME_tz_section = { &critsect_debug, -1, 0, 0, 0, 0 }; + +static void get_timezone_info( RTL_DYNAMIC_TIME_ZONE_INFORMATION *tzi ) +{ + static RTL_DYNAMIC_TIME_ZONE_INFORMATION cached_tzi; + static int current_year = -1, current_bias = 65535; + struct tm *tm; + char tz_name[16]; + time_t year_start, year_end, tmp, dlt = 0, std = 0; + int is_dst, bias; + + RtlEnterCriticalSection( &TIME_tz_section ); + + year_start = time(NULL); + tm = gmtime(&year_start); + bias = (LONG)(mktime(tm) - year_start) / 60; + + tm = localtime(&year_start); + if (current_year == tm->tm_year && current_bias == bias) + { + *tzi = cached_tzi; + RtlLeaveCriticalSection( &TIME_tz_section ); + return; + } + + memset(tzi, 0, sizeof(*tzi)); + if (!strftime(tz_name, sizeof(tz_name), "%Z", tm)) { + /* not enough room or another error */ + tz_name[0] = '\0'; + } + + TRACE("tz data will be valid through year %d, bias %d\n", tm->tm_year + 1900, bias); + current_year = tm->tm_year; + current_bias = bias; + + tzi->Bias = bias; + + tm->tm_isdst = 0; + tm->tm_mday = 1; + tm->tm_mon = tm->tm_hour = tm->tm_min = tm->tm_sec = tm->tm_wday = tm->tm_yday = 0; + year_start = mktime(tm); + TRACE("year_start: %s", ctime(&year_start)); + + tm->tm_mday = tm->tm_wday = tm->tm_yday = 0; + tm->tm_mon = 12; + tm->tm_hour = 23; + tm->tm_min = tm->tm_sec = 59; + year_end = mktime(tm); + TRACE("year_end: %s", ctime(&year_end)); + + tmp = find_dst_change(year_start, year_end, &is_dst); + if (is_dst) + dlt = tmp; + else + std = tmp; + + tmp = find_dst_change(tmp, year_end, &is_dst); + if (is_dst) + dlt = tmp; + else + std = tmp; + + TRACE("std: %s", ctime(&std)); + TRACE("dlt: %s", ctime(&dlt)); + + if (dlt == std || !dlt || !std) + TRACE("there is no daylight saving rules in this time zone\n"); + else + { + tmp = dlt - tzi->Bias * 60; + tm = gmtime(&tmp); + TRACE("dlt gmtime: %s", asctime(tm)); + + tzi->DaylightBias = -60; + tzi->DaylightDate.wYear = tm->tm_year + 1900; + tzi->DaylightDate.wMonth = tm->tm_mon + 1; + tzi->DaylightDate.wDayOfWeek = tm->tm_wday; + tzi->DaylightDate.wDay = tm->tm_mday; + tzi->DaylightDate.wHour = tm->tm_hour; + tzi->DaylightDate.wMinute = tm->tm_min; + tzi->DaylightDate.wSecond = tm->tm_sec; + tzi->DaylightDate.wMilliseconds = 0; + + TRACE("daylight (d/m/y): %u/%02u/%04u day of week %u %u:%02u:%02u.%03u bias %d\n", + tzi->DaylightDate.wDay, tzi->DaylightDate.wMonth, + tzi->DaylightDate.wYear, tzi->DaylightDate.wDayOfWeek, + tzi->DaylightDate.wHour, tzi->DaylightDate.wMinute, + tzi->DaylightDate.wSecond, tzi->DaylightDate.wMilliseconds, + tzi->DaylightBias); + + tmp = std - tzi->Bias * 60 - tzi->DaylightBias * 60; + tm = gmtime(&tmp); + TRACE("std gmtime: %s", asctime(tm)); + + tzi->StandardBias = 0; + tzi->StandardDate.wYear = tm->tm_year + 1900; + tzi->StandardDate.wMonth = tm->tm_mon + 1; + tzi->StandardDate.wDayOfWeek = tm->tm_wday; + tzi->StandardDate.wDay = tm->tm_mday; + tzi->StandardDate.wHour = tm->tm_hour; + tzi->StandardDate.wMinute = tm->tm_min; + tzi->StandardDate.wSecond = tm->tm_sec; + tzi->StandardDate.wMilliseconds = 0; + + TRACE("standard (d/m/y): %u/%02u/%04u day of week %u %u:%02u:%02u.%03u bias %d\n", + tzi->StandardDate.wDay, tzi->StandardDate.wMonth, + tzi->StandardDate.wYear, tzi->StandardDate.wDayOfWeek, + tzi->StandardDate.wHour, tzi->StandardDate.wMinute, + tzi->StandardDate.wSecond, tzi->StandardDate.wMilliseconds, + tzi->StandardBias); + } + + find_reg_tz_info(tzi, tz_name, current_year + 1900); + cached_tzi = *tzi; + RtlLeaveCriticalSection( &TIME_tz_section ); +} + + /****************************************************************************** * NtQuerySystemInformation (NTDLL.@) */ @@ -2080,6 +2448,21 @@ NTSTATUS WINAPI NtQuerySystemInformation( SYSTEM_INFORMATION_CLASS class, break; } + case SystemTimeZoneInformation: + { + RTL_DYNAMIC_TIME_ZONE_INFORMATION tz; + + get_timezone_info( &tz ); + len = sizeof(RTL_TIME_ZONE_INFORMATION); + if (size >= len) + { + if (!info) ret = STATUS_ACCESS_VIOLATION; + else memcpy( info, &tz, len); + } + else ret = STATUS_INFO_LENGTH_MISMATCH; + break; + } + case SystemLogicalProcessorInformation: { SYSTEM_LOGICAL_PROCESSOR_INFORMATION *buf; @@ -2153,6 +2536,21 @@ NTSTATUS WINAPI NtQuerySystemInformation( SYSTEM_INFORMATION_CLASS class, break; } + case SystemDynamicTimeZoneInformation: + { + RTL_DYNAMIC_TIME_ZONE_INFORMATION tz; + + get_timezone_info( &tz ); + len = sizeof(tz); + if (size >= len) + { + if (!info) ret = STATUS_ACCESS_VIOLATION; + else memcpy( info, &tz, len); + } + else ret = STATUS_INFO_LENGTH_MISMATCH; + break; + } + default: FIXME( "(0x%08x,%p,0x%08x,%p) stub\n", class, info, size, ret_size ); diff --git a/dlls/ntdll/unix/unix_private.h b/dlls/ntdll/unix/unix_private.h index 5b7bd4aa273..1f06c91eff7 100644 --- a/dlls/ntdll/unix/unix_private.h +++ b/dlls/ntdll/unix/unix_private.h @@ -244,6 +244,12 @@ static inline const char *debugstr_us( const UNICODE_STRING *us ) return debugstr_wn( us->Buffer, us->Length / sizeof(WCHAR) ); } +/* convert from straight ASCII to Unicode without depending on the current codepage */ +static inline void ascii_to_unicode( WCHAR *dst, const char *src, size_t len ) +{ + while (len--) *dst++ = (unsigned char)*src++; +} + static inline size_t ntdll_wcslen( const WCHAR *str ) { const WCHAR *s = str; diff --git a/include/winternl.h b/include/winternl.h index ae41af85b3f..9a70a2014f1 100644 --- a/include/winternl.h +++ b/include/winternl.h @@ -986,6 +986,7 @@ typedef enum _SYSTEM_INFORMATION_CLASS { SystemSuperfetchInformation = 79, SystemMemoryListInformation = 80, SystemFileCacheInformationEx = 81, + SystemDynamicTimeZoneInformation = 102, SystemLogicalProcessorInformationEx = 107, SystemInformationClassMax } SYSTEM_INFORMATION_CLASS, *PSYSTEM_INFORMATION_CLASS;