diff --git a/sdk/src/org.graalvm.nativeimage/snapshot.sigtest b/sdk/src/org.graalvm.nativeimage/snapshot.sigtest index 95f3e0a62e8e..919f385e3be8 100644 --- a/sdk/src/org.graalvm.nativeimage/snapshot.sigtest +++ b/sdk/src/org.graalvm.nativeimage/snapshot.sigtest @@ -483,6 +483,7 @@ CLSS public abstract interface !annotation org.graalvm.nativeimage.c.function.CL anno 0 java.lang.annotation.Target(java.lang.annotation.ElementType[] value=[TYPE, METHOD]) intf java.lang.annotation.Annotation meth public abstract !hasdefault boolean requireStatic() +meth public abstract !hasdefault java.lang.String[] dependsOn() meth public abstract java.lang.String value() CLSS public abstract interface !annotation org.graalvm.nativeimage.c.function.CMacroInfo diff --git a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/c/function/CLibrary.java b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/c/function/CLibrary.java index 18c9da039553..de00d235a5cf 100644 --- a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/c/function/CLibrary.java +++ b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/c/function/CLibrary.java @@ -68,4 +68,11 @@ * @since 19.1.0 */ boolean requireStatic() default false; + + /** + * Specifies the name of the libraries this library depends on. + * + * @since 20.1.0 + */ + String[] dependsOn() default {}; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/LibCHelper.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/LibCHelper.java index bdc3ce3bee49..c91ae4d9ca5f 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/LibCHelper.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/LibCHelper.java @@ -27,11 +27,16 @@ import org.graalvm.nativeimage.c.function.CFunction; import org.graalvm.nativeimage.c.function.CFunction.Transition; import org.graalvm.nativeimage.c.function.CLibrary; +import org.graalvm.nativeimage.c.type.CCharPointer; import org.graalvm.nativeimage.c.type.CCharPointerPointer; -@CLibrary(value = "libchelper", requireStatic = true) +@CLibrary(value = "libchelper", requireStatic = true, dependsOn = "java") public class LibCHelper { @CFunction(transition = Transition.NO_TRANSITION) public static native CCharPointerPointer getEnviron(); + @CFunction(transition = Transition.TO_NATIVE) + // Checkstyle: stop + public static native CCharPointer SVM_FindJavaTZmd(CCharPointer tzMappings, int length); + // Checkstyle: start } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/TimeZoneSubstitutions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/TimeZoneSubstitutions.java index 1fca7379397a..1fadd645f47a 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/TimeZoneSubstitutions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/TimeZoneSubstitutions.java @@ -24,84 +24,165 @@ */ package com.oracle.svm.core.jdk; -import static java.util.stream.Collectors.toMap; - -import java.util.Arrays; -import java.util.Map; -import java.util.TimeZone; - -import org.graalvm.compiler.options.Option; -import org.graalvm.nativeimage.hosted.Feature; -import org.graalvm.nativeimage.ImageSingletons; - +import com.oracle.svm.core.LibCHelper; +import com.oracle.svm.core.OS; +import com.oracle.svm.core.annotate.Alias; import com.oracle.svm.core.annotate.AutomaticFeature; +import com.oracle.svm.core.annotate.RecomputeFieldValue; import com.oracle.svm.core.annotate.Substitute; +import com.oracle.svm.core.util.VMError; +import org.graalvm.collections.EconomicMap; +import org.graalvm.compiler.options.Option; +import org.graalvm.compiler.options.OptionKey; + import com.oracle.svm.core.annotate.TargetClass; import com.oracle.svm.core.option.HostedOptionKey; +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.PinnedObject; +import org.graalvm.nativeimage.c.type.CCharPointer; +import org.graalvm.nativeimage.c.type.CTypeConversion; +import org.graalvm.nativeimage.hosted.Feature; +import org.graalvm.word.WordFactory; -final class TimeZoneSupport { - - final Map zones; - TimeZone defaultZone; - - TimeZoneSupport(Map zones, TimeZone defaultZone) { - this.zones = zones; - this.defaultZone = defaultZone; - } - - public static TimeZoneSupport instance() { - return ImageSingletons.lookup(TimeZoneSupport.class); - } -} +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.TimeZone; +/** + * The following classes aim to provide full support for time zones for native-image. This + * substitution is necessary due to the reliance on JAVA_HOME in the JDK. + * + * In summary in the JDK time zone data is extracted from the underlying platform via native + * methods, later the JDK code processes that data to create time zone objects exposed via JAVA + * APIs. + * + * Luckily JAVA_HOME is only really necessary for the Windows operating system. Posix operating + * systems rely on system calls independent of JAVA_HOME (except for null checks done in the JNI + * function wrapping the native time zone JDK functions). Thus for Posix operating systems this + * implementation, simply, by-passes the null check(in native image JAVA_HOME is null) and calls + * into the original native functions by substituting the JNI method + * TimeZone.getSystemTimeZoneID(String). + * + * In Windows, the JRE contains a special file called tzmappings, (see + * time + * zones in the jre), representing the mapping between Windows and Java time zones. The + * tzmappings file is read and parsed in the native JDK code. Thus for windows, + * {@link TimeZoneFeature} reads such file and stores it in the image heap at build-time. At + * run-time the contents of the file are passed to a custom implementation of the JDK time zones + * logic. The only difference in the custom implementation is reading and parsing time zone mappings + * from a buffer as opposed to a file. + */ @TargetClass(java.util.TimeZone.class) +@SuppressWarnings("unused") final class Target_java_util_TimeZone { + @Alias @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset) private static TimeZone defaultTimeZone; + @Substitute - private static TimeZone getDefaultRef() { - return TimeZoneSupport.instance().defaultZone; + private static String getSystemTimeZoneID(String javaHome) { + CCharPointer tzMappingsPtr = WordFactory.nullPointer(); + int contentLen = 0; + PinnedObject pinnedContent = null; + try { + if (ImageSingletons.contains(TimeZoneSupport.class)) { + byte[] content = ImageSingletons.lookup(TimeZoneSupport.class).getTzMappingsContent(); + contentLen = content.length; + pinnedContent = PinnedObject.create(content); + tzMappingsPtr = pinnedContent.addressOfArrayElement(0); + } + CCharPointer tzId = LibCHelper.SVM_FindJavaTZmd(tzMappingsPtr, contentLen); + return CTypeConversion.toJavaString(tzId); + } finally { + if (pinnedContent != null) { + pinnedContent.close(); + } + } } +} - @Substitute - private static void setDefault(TimeZone zone) { - TimeZoneSupport.instance().defaultZone = zone; +/** + * Holds time zone mapping data. + */ +final class TimeZoneSupport { + final byte[] tzMappingsContent; + + TimeZoneSupport(final byte[] content) { + this.tzMappingsContent = content; } - @Substitute - public static TimeZone getTimeZone(String id) { - return TimeZoneSupport.instance().zones.getOrDefault(id, TimeZoneSupport.instance().zones.get("GMT")); + public byte[] getTzMappingsContent() { + return tzMappingsContent; } } +/** + * Reads time zone mappings data and stores in the image heap, if necessary. + */ @AutomaticFeature final class TimeZoneFeature implements Feature { static class Options { - private static final TimeZone defaultZone = TimeZone.getDefault(); - @Option(help = "When true, all time zones will be pre-initialized in the image.")// - public static final HostedOptionKey IncludeAllTimeZones = new HostedOptionKey<>(false); + public static final HostedOptionKey IncludeAllTimeZones = new HostedOptionKey(false) { + @Override + protected void onValueUpdate(EconomicMap, Object> values, Boolean oldValue, Boolean newValue) { + super.onValueUpdate(values, oldValue, newValue); + printWarning(); + } + }; @Option(help = "The time zones, in addition to the default zone of the host, that will be pre-initialized in the image.")// - public static final HostedOptionKey IncludeTimeZones = new HostedOptionKey<>(new String[]{"GMT", "UTC", defaultZone.getID()}); + public static final HostedOptionKey IncludeTimeZones = new HostedOptionKey("") { + @Override + protected void onValueUpdate(EconomicMap, Object> values, String oldValue, String newValue) { + super.onValueUpdate(values, oldValue, newValue); + printWarning(); + } + }; + + private static void printWarning() { + // Checkstyle: stop + System.err.println("-H:IncludeAllTimeZones and -H:IncludeTimeZones are now deprecated. Native-image includes all timezones" + + "by default."); + // Checkstyle: resume + } + } + + private static byte[] cleanCR(byte[] buffer) { + byte[] scratch = new byte[buffer.length]; + int copied = 0; + for (byte b : buffer) { + if (b == '\r') { + continue; + } + scratch[copied++] = b; + } + byte[] content = new byte[copied]; + System.arraycopy(scratch, 0, content, 0, copied); + return content; } @Override public void afterRegistration(AfterRegistrationAccess access) { - final String[] supportedZoneIDs; - if (Options.IncludeAllTimeZones.getValue()) { - supportedZoneIDs = TimeZone.getAvailableIDs(); - } else { - supportedZoneIDs = Options.IncludeTimeZones.getValue(); + + if (OS.getCurrent() != OS.WINDOWS) { + return; + } + + // read tzmappings on windows + Path tzMappingsPath = Paths.get(System.getProperty("java.home"), "lib", "tzmappings"); + try { + byte[] buffer = Files.readAllBytes(tzMappingsPath); + // tzmappings has windows line endings on windows?? + byte[] content = cleanCR(buffer); + ImageSingletons.add(TimeZoneSupport.class, new TimeZoneSupport(content)); + } catch (IOException e) { + VMError.shouldNotReachHere("Failed to read time zone mappings. The time zone mappings should be part" + + "of your JDK usually found: " + tzMappingsPath.toAbsolutePath(), e); } - Map supportedZones = Arrays.stream(supportedZoneIDs) - .map(TimeZone::getTimeZone) - .collect(toMap(TimeZone::getID, tz -> tz, (tz1, tz2) -> tz1)); - ImageSingletons.add(TimeZoneSupport.class, new TimeZoneSupport(supportedZones, Options.defaultZone)); } } -/** - * This whole file should be eventually removed: GR-11844. - */ -public class TimeZoneSubstitutions { +class TimeZoneSubstitutions { } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/NativeLibraries.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/NativeLibraries.java index 1fe588f117dd..db86ca35a058 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/NativeLibraries.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/NativeLibraries.java @@ -34,10 +34,14 @@ import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedHashSet; +import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -101,7 +105,7 @@ public final class NativeLibraries { private final LinkedHashSet annotated; private final List libraries; - private final List staticLibraries; + private final DependencyGraph dependencyGraph; private final LinkedHashSet libraryPaths; private final List errors; @@ -118,6 +122,103 @@ public final class NativeLibraries { */ private static final Path CUSTOM_LIBC_STATIC_DIST_PATH = Paths.get("svm", "static-libs"); + public static final class DependencyGraph { + + private static final class Dependency { + private final String name; + private final Set dependencies; + + Dependency(String name, Set dependencies) { + assert dependencies != null; + this.name = name; + this.dependencies = dependencies; + } + + public String getName() { + return name; + } + + public Set getDependencies() { + return dependencies; + } + + @Override + public String toString() { + String depString = dependencies.stream().map(Dependency::getName).collect(Collectors.joining()); + return "Dependency{" + + "name='" + name + '\'' + + ", dependencies=[" + depString + + "]}"; + } + } + + private final Map allDependencies; + + public DependencyGraph() { + allDependencies = new ConcurrentHashMap<>(); + } + + public void add(String library, String... dependencies) { + UserError.guarantee(library != null, "The library name must be not null and not empty"); + + Dependency libraryDependency = putWhenAbsent(library, new Dependency(library, new HashSet<>())); + Set collectedDependencies = libraryDependency.getDependencies(); + if (dependencies == null) { + return; + } + + for (String dependency : dependencies) { + collectedDependencies.add(putWhenAbsent( + dependency, new Dependency(dependency, new HashSet<>()))); + } + } + + public List sort() { + final Set discovered = new HashSet<>(); + final Set processed = new LinkedHashSet<>(); + + for (Dependency dep : allDependencies.values()) { + visit(dep, discovered, processed); + } + + LinkedList names = new LinkedList<>(); + processed.forEach(n -> names.push(n.getName())); + return names; + } + + private Dependency putWhenAbsent(String libName, Dependency dep) { + if (!allDependencies.containsKey(libName)) { + allDependencies.put(libName, dep); + } + return allDependencies.get(libName); + } + + private void visit(Dependency dep, Set discovered, Set processed) { + if (processed.contains(dep)) { + return; + } + if (discovered.contains(dep)) { + String message = String.format("While building list of static libraries dependencies a cycle was discovered for dependency: %s ", dep.getName()); + UserError.abort(message); + } + + discovered.add(dep); + dep.getDependencies().forEach(d -> visit(d, discovered, processed)); + processed.add(dep); + } + + @Override + public String toString() { + String depsStr = allDependencies.values() + .stream() + .map(Dependency::toString) + .collect(Collectors.joining("\n")); + return "DependencyGraph{\n" + + depsStr + + '}'; + } + } + public NativeLibraries(ConstantReflectionProvider constantReflection, MetaAccessProvider metaAccess, SnippetReflectionProvider snippetReflection, TargetDescription target, ClassInitializationSupport classInitializationSupport, Path tempDirectory, DebugContext debug) { this.metaAccess = metaAccess; @@ -152,7 +253,7 @@ public NativeLibraries(ConstantReflectionProvider constantReflection, MetaAccess * libraries that have cyclic dependencies. */ libraries = Collections.synchronizedList(new ArrayList<>()); - staticLibraries = Collections.synchronizedList(new ArrayList<>()); + dependencyGraph = new DependencyGraph(); libraryPaths = initCLibraryPath(); @@ -270,7 +371,15 @@ public void addAnnotated(CLibrary library) { } public void addLibrary(String library, boolean requireStatic) { - (requireStatic ? staticLibraries : libraries).add(library); + addLibrary(library, requireStatic, null); + } + + public void addLibrary(String library, boolean requireStatic, String[] dependencies) { + if (requireStatic) { + dependencyGraph.add(library, dependencies); + } else { + libraries.add(library); + } } public Collection getLibraries() { @@ -280,7 +389,9 @@ public Collection getLibraries() { public Collection getStaticLibraries() { Map allStaticLibs = getAllStaticLibs(); List staticLibs = new ArrayList<>(); - for (String staticLibraryName : staticLibraries) { + List sortedList = dependencyGraph.sort(); + + for (String staticLibraryName : sortedList) { Path libraryPath = getStaticLibraryPath(allStaticLibs, staticLibraryName); if (libraryPath == null) { continue; @@ -444,7 +555,7 @@ public boolean processAnnotated() { return false; } for (CLibrary lib : annotated) { - addLibrary(lib.value(), lib.requireStatic()); + addLibrary(lib.value(), lib.requireStatic(), lib.dependsOn()); } annotated.clear(); return true; diff --git a/substratevm/src/com.oracle.svm.native.jvm.posix/src/JvmFuncs.c b/substratevm/src/com.oracle.svm.native.jvm.posix/src/JvmFuncs.c index d2e78ee02034..4ad73b8be781 100644 --- a/substratevm/src/com.oracle.svm.native.jvm.posix/src/JvmFuncs.c +++ b/substratevm/src/com.oracle.svm.native.jvm.posix/src/JvmFuncs.c @@ -253,4 +253,16 @@ int jio_snprintf(char *str, size_t count, const char *fmt, ...) { va_end(args); return len; } + +int jio_fprintf(FILE *fp, const char *fmt, ...) +{ + int len; + + va_list args; + va_start(args, fmt); + len = jio_vfprintf(fp, fmt, args); + va_end(args); + + return len; +} #endif diff --git a/substratevm/src/com.oracle.svm.native.jvm.windows/src/JvmFuncs.c b/substratevm/src/com.oracle.svm.native.jvm.windows/src/JvmFuncs.c index a6349f727999..437b3cf1dd3b 100644 --- a/substratevm/src/com.oracle.svm.native.jvm.windows/src/JvmFuncs.c +++ b/substratevm/src/com.oracle.svm.native.jvm.windows/src/JvmFuncs.c @@ -269,6 +269,18 @@ int jio_snprintf(char *str, size_t count, const char *fmt, ...) { va_end(args); return len; } + +int jio_fprintf(FILE *fp, const char *fmt, ...) +{ + int len; + + va_list args; + va_start(args, fmt); + len = jio_vfprintf(fp, fmt, args); + va_end(args); + + return len; +} #endif /* diff --git a/substratevm/src/com.oracle.svm.native.libchelper/src/timeZone.c b/substratevm/src/com.oracle.svm.native.libchelper/src/timeZone.c new file mode 100644 index 000000000000..77b0591dc884 --- /dev/null +++ b/substratevm/src/com.oracle.svm.native.libchelper/src/timeZone.c @@ -0,0 +1,597 @@ +/* + * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + +#ifdef _WIN64 +/* + * The following functions are an identical copy of the functions in file TimeZone_md.c found at + * https://github.com/graalvm/labs-openjdk-11/blob/67ddc3bcadd985ea26997457aec6696f21caf154/src/java.base/windows/native/libjava/TimeZone_md.c + * With the exceptions of the commented functions this file has not been modified from its original. + */ +#include +#include +#include + +#define VALUE_UNKNOWN 0 +#define VALUE_KEY 1 +#define VALUE_MAPID 2 +#define VALUE_GMTOFFSET 3 + +#define MAX_ZONE_CHAR 256 +#define MAX_MAPID_LENGTH 32 + +#define NT_TZ_KEY "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones" +#define WIN_TZ_KEY "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Time Zones" +#define WIN_CURRENT_TZ_KEY "System\\CurrentControlSet\\Control\\TimeZoneInformation" + +typedef struct _TziValue { + LONG bias; + LONG stdBias; + LONG dstBias; + SYSTEMTIME stdDate; + SYSTEMTIME dstDate; +} TziValue; + +/* + * Registry key names + */ +static void *keyNames[] = { + (void *) L"StandardName", + (void *) "StandardName", + (void *) L"Std", + (void *) "Std" +}; + +/* + * Indices to keyNames[] + */ +#define STANDARD_NAME 0 +#define STD_NAME 2 + +/* + * Calls RegQueryValueEx() to get the value for the specified key. If + * the platform is NT, 2000 or XP, it calls the Unicode + * version. Otherwise, it calls the ANSI version and converts the + * value to Unicode. In this case, it assumes that the current ANSI + * Code Page is the same as the native platform code page (e.g., Code + * Page 932 for the Japanese Windows systems. + * + * `keyIndex' is an index value to the keyNames in Unicode + * (WCHAR). `keyIndex' + 1 points to its ANSI value. + * + * Returns the status value. ERROR_SUCCESS if succeeded, a + * non-ERROR_SUCCESS value otherwise. + */ +static LONG +getValueInRegistry(HKEY hKey, + int keyIndex, + LPDWORD typePtr, + LPBYTE buf, + LPDWORD bufLengthPtr) +{ + LONG ret; + DWORD bufLength = *bufLengthPtr; + char val[MAX_ZONE_CHAR]; + DWORD valSize; + int len; + + *typePtr = 0; + ret = RegQueryValueExW(hKey, (WCHAR *) keyNames[keyIndex], NULL, + typePtr, buf, bufLengthPtr); + if (ret == ERROR_SUCCESS && *typePtr == REG_SZ) { + return ret; + } + + valSize = sizeof(val); + ret = RegQueryValueExA(hKey, (char *) keyNames[keyIndex + 1], NULL, + typePtr, val, &valSize); + if (ret != ERROR_SUCCESS) { + return ret; + } + if (*typePtr != REG_SZ) { + return ERROR_BADKEY; + } + + len = MultiByteToWideChar(CP_ACP, MB_ERR_INVALID_CHARS, + (LPCSTR) val, -1, + (LPWSTR) buf, bufLength/sizeof(WCHAR)); + if (len <= 0) { + return ERROR_BADKEY; + } + return ERROR_SUCCESS; +} + +/* + * Produces custom name "GMT+hh:mm" from the given bias in buffer. + */ +static void customZoneName(LONG bias, char *buffer) { + LONG gmtOffset; + int sign; + + if (bias > 0) { + gmtOffset = bias; + sign = -1; + } else { + gmtOffset = -bias; + sign = 1; + } + if (gmtOffset != 0) { + sprintf(buffer, "GMT%c%02d:%02d", + ((sign >= 0) ? '+' : '-'), + gmtOffset / 60, + gmtOffset % 60); + } else { + strcpy(buffer, "GMT"); + } +} + +/* + * Gets the current time zone entry in the "Time Zones" registry. + */ +static int getWinTimeZone(char *winZoneName, char *winMapID) +{ + DYNAMIC_TIME_ZONE_INFORMATION dtzi; + DWORD timeType; + DWORD bufSize; + DWORD val; + HANDLE hKey = NULL; + LONG ret; + ULONG valueType; + + /* + * Get the dynamic time zone information so that time zone redirection + * can be supported. (see JDK-7044727) + */ + timeType = GetDynamicTimeZoneInformation(&dtzi); + if (timeType == TIME_ZONE_ID_INVALID) { + goto err; + } + + /* + * Make sure TimeZoneKeyName is available from the API call. If + * DynamicDaylightTime is disabled, return a custom time zone name + * based on the GMT offset. Otherwise, return the TimeZoneKeyName + * value. + */ + if (dtzi.TimeZoneKeyName[0] != 0) { + if (dtzi.DynamicDaylightTimeDisabled) { + customZoneName(dtzi.Bias, winZoneName); + return VALUE_GMTOFFSET; + } + wcstombs(winZoneName, dtzi.TimeZoneKeyName, MAX_ZONE_CHAR); + return VALUE_KEY; + } + + /* + * If TimeZoneKeyName is not available, check whether StandardName + * is available to fall back to the older API GetTimeZoneInformation. + * If not, directly read the value from registry keys. + */ + if (dtzi.StandardName[0] == 0) { + ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, WIN_CURRENT_TZ_KEY, 0, + KEY_READ, (PHKEY)&hKey); + if (ret != ERROR_SUCCESS) { + goto err; + } + + /* + * Determine if auto-daylight time adjustment is turned off. + */ + bufSize = sizeof(val); + ret = RegQueryValueExA(hKey, "DynamicDaylightTimeDisabled", NULL, + &valueType, (LPBYTE) &val, &bufSize); + if (ret != ERROR_SUCCESS) { + goto err; + } + /* + * Return a custom time zone name if auto-daylight time adjustment + * is disabled. + */ + if (val == 1) { + customZoneName(dtzi.Bias, winZoneName); + (void) RegCloseKey(hKey); + return VALUE_GMTOFFSET; + } + + bufSize = MAX_ZONE_CHAR; + ret = RegQueryValueExA(hKey, "TimeZoneKeyName", NULL, + &valueType, (LPBYTE) winZoneName, &bufSize); + if (ret != ERROR_SUCCESS) { + goto err; + } + (void) RegCloseKey(hKey); + return VALUE_KEY; + } else { + /* + * Fall back to GetTimeZoneInformation + */ + TIME_ZONE_INFORMATION tzi; + HANDLE hSubKey = NULL; + DWORD nSubKeys, i; + ULONG valueType; + TCHAR subKeyName[MAX_ZONE_CHAR]; + TCHAR szValue[MAX_ZONE_CHAR]; + WCHAR stdNameInReg[MAX_ZONE_CHAR]; + TziValue tempTzi; + WCHAR *stdNamePtr = tzi.StandardName; + DWORD valueSize; + int onlyMapID; + + timeType = GetTimeZoneInformation(&tzi); + if (timeType == TIME_ZONE_ID_INVALID) { + goto err; + } + + ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, WIN_CURRENT_TZ_KEY, 0, + KEY_READ, (PHKEY)&hKey); + if (ret == ERROR_SUCCESS) { + /* + * Determine if auto-daylight time adjustment is turned off. + */ + bufSize = sizeof(val); + ret = RegQueryValueExA(hKey, "DynamicDaylightTimeDisabled", NULL, + &valueType, (LPBYTE) &val, &bufSize); + if (ret == ERROR_SUCCESS) { + if (val == 1 && tzi.DaylightDate.wMonth != 0) { + (void) RegCloseKey(hKey); + customZoneName(tzi.Bias, winZoneName); + return VALUE_GMTOFFSET; + } + } + + /* + * Win32 problem: If the length of the standard time name is equal + * to (or probably longer than) 32 in the registry, + * GetTimeZoneInformation() on NT returns a null string as its + * standard time name. We need to work around this problem by + * getting the same information from the TimeZoneInformation + * registry. + */ + if (tzi.StandardName[0] == 0) { + bufSize = sizeof(stdNameInReg); + ret = getValueInRegistry(hKey, STANDARD_NAME, &valueType, + (LPBYTE) stdNameInReg, &bufSize); + if (ret != ERROR_SUCCESS) { + goto err; + } + stdNamePtr = stdNameInReg; + } + (void) RegCloseKey(hKey); + } + + /* + * Open the "Time Zones" registry. + */ + ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, NT_TZ_KEY, 0, KEY_READ, (PHKEY)&hKey); + if (ret != ERROR_SUCCESS) { + ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, WIN_TZ_KEY, 0, KEY_READ, (PHKEY)&hKey); + /* + * If both failed, then give up. + */ + if (ret != ERROR_SUCCESS) { + return VALUE_UNKNOWN; + } + } + + /* + * Get the number of subkeys of the "Time Zones" registry for + * enumeration. + */ + ret = RegQueryInfoKey(hKey, NULL, NULL, NULL, &nSubKeys, + NULL, NULL, NULL, NULL, NULL, NULL, NULL); + if (ret != ERROR_SUCCESS) { + goto err; + } + + /* + * Compare to the "Std" value of each subkey and find the entry that + * matches the current control panel setting. + */ + onlyMapID = 0; + for (i = 0; i < nSubKeys; ++i) { + DWORD size = sizeof(subKeyName); + ret = RegEnumKeyEx(hKey, i, subKeyName, &size, NULL, NULL, NULL, NULL); + if (ret != ERROR_SUCCESS) { + goto err; + } + ret = RegOpenKeyEx(hKey, subKeyName, 0, KEY_READ, (PHKEY)&hSubKey); + if (ret != ERROR_SUCCESS) { + goto err; + } + + size = sizeof(szValue); + ret = getValueInRegistry(hSubKey, STD_NAME, &valueType, + szValue, &size); + if (ret != ERROR_SUCCESS) { + /* + * NT 4.0 SP3 fails here since it doesn't have the "Std" + * entry in the Time Zones registry. + */ + RegCloseKey(hSubKey); + onlyMapID = 1; + ret = RegOpenKeyExW(hKey, stdNamePtr, 0, KEY_READ, (PHKEY)&hSubKey); + if (ret != ERROR_SUCCESS) { + goto err; + } + break; + } + + if (wcscmp((WCHAR *)szValue, stdNamePtr) == 0) { + /* + * Some localized Win32 platforms use a same name to + * different time zones. So, we can't rely only on the name + * here. We need to check GMT offsets and transition dates + * to make sure it's the registry of the current time + * zone. + */ + DWORD tziValueSize = sizeof(tempTzi); + ret = RegQueryValueEx(hSubKey, "TZI", NULL, &valueType, + (unsigned char *) &tempTzi, &tziValueSize); + if (ret == ERROR_SUCCESS) { + if ((tzi.Bias != tempTzi.bias) || + (memcmp((const void *) &tzi.StandardDate, + (const void *) &tempTzi.stdDate, + sizeof(SYSTEMTIME)) != 0)) { + goto out; + } + + if (tzi.DaylightBias != 0) { + if ((tzi.DaylightBias != tempTzi.dstBias) || + (memcmp((const void *) &tzi.DaylightDate, + (const void *) &tempTzi.dstDate, + sizeof(SYSTEMTIME)) != 0)) { + goto out; + } + } + } + + /* + * found matched record, terminate search + */ + strcpy(winZoneName, subKeyName); + break; + } + out: + (void) RegCloseKey(hSubKey); + } + + /* + * Get the "MapID" value of the registry to be able to eliminate + * duplicated key names later. + */ + valueSize = MAX_MAPID_LENGTH; + ret = RegQueryValueExA(hSubKey, "MapID", NULL, &valueType, winMapID, &valueSize); + (void) RegCloseKey(hSubKey); + (void) RegCloseKey(hKey); + + if (ret != ERROR_SUCCESS) { + /* + * Vista doesn't have mapID. VALUE_UNKNOWN should be returned + * only for Windows NT. + */ + if (onlyMapID == 1) { + return VALUE_UNKNOWN; + } + } + } + + return VALUE_KEY; + + err: + if (hKey != NULL) { + (void) RegCloseKey(hKey); + } + return VALUE_UNKNOWN; +} + +/* + * An implementation of fgets on a buffer as opposed to a file object. + * + */ +static int SVM_readBufferUntilNewLine(char *dst, int num, int offset, char *source, int source_len) { + int read = 0; + while(read < num - 1) { + if (offset + read == source_len) { + break; + } + dst[read] = source[offset + read]; + if (dst[read] == '\n') { + read++; + break; + } + read++; + } + dst[read+1] = '\0'; + return read; +} + +/* + * Index values for the mapping table. + */ +#define TZ_WIN_NAME 0 +#define TZ_MAPID 1 +#define TZ_REGION 2 +#define TZ_JAVA_NAME 3 + +#define TZ_NITEMS 4 /* number of items (fields) */ + +/* + * Looks up the mapping table (tzmappings) and returns a Java time + * zone ID (e.g., "America/Los_Angeles") if found. Otherwise, NULL is + * returned. + * + * value_type is one of the following values: + * VALUE_KEY for exact key matching + * VALUE_MAPID for MapID (this is + * required for the old Windows, such as NT 4.0 SP3). + * + * The following function differs from the original by accepting a buffer and reading + * tzmappings data from it. The original function opens and reads such data from a file. + */ +static char *SVM_matchJavaTZ(const char *tzmappings, int value_type, char *tzName, + char *mapID, int tzmappingsLen) +{ + int line; + int IDmatched = 0; + FILE *fp; + char *javaTZName = NULL; + char *items[TZ_NITEMS]; + char *mapFileName; + char lineBuffer[MAX_ZONE_CHAR * 4]; + int noMapID = *mapID == '\0'; /* no mapID on Vista and later */ + int offset = 0; + const char* errorMessage = "unknown error"; + int currLocation; + int readChars; + + line = 0; + currLocation = 0; + readChars = 0; + // Reads from buffer and not from file + while((readChars = SVM_readBufferUntilNewLine(lineBuffer, sizeof(lineBuffer), currLocation, tzmappings, tzmappingsLen)) != 0) { + char *start, *idx, *endp; + int itemIndex = 0; + + currLocation += readChars; + line++; + start = idx = lineBuffer; + endp = &lineBuffer[sizeof(lineBuffer)]; + + /* + * Ignore comment and blank lines. + */ + if (*idx == '#' || *idx == '\n') { + continue; + } + + for (itemIndex = 0; itemIndex < TZ_NITEMS; itemIndex++) { + items[itemIndex] = start; + while (*idx && *idx != ':') { + if (++idx >= endp) { + errorMessage = "premature end of line"; + offset = (int)(idx - lineBuffer); + goto illegal_format; + } + } + if (*idx == '\0') { + errorMessage = "illegal null character found"; + offset = (int)(idx - lineBuffer); + goto illegal_format; + } + *idx++ = '\0'; + start = idx; + } + + if (*idx != '\n') { + errorMessage = "illegal non-newline character found"; + offset = (int)(idx - lineBuffer); + goto illegal_format; + } + + if (noMapID || strcmp(mapID, items[TZ_MAPID]) == 0) { + /* + * When there's no mapID, we need to scan items until the + * exact match is found or the end of data is detected. + */ + if (!noMapID) { + IDmatched = 1; + } + if (strcmp(items[TZ_WIN_NAME], tzName) == 0) { + /* + * Found the time zone in the mapping table. + */ + javaTZName = _strdup(items[TZ_JAVA_NAME]); + break; + } + } else { + if (IDmatched == 1) { + /* + * No need to look up the mapping table further. + */ + break; + } + } + } + + return javaTZName; + + illegal_format: + jio_fprintf(stderr, "Illegal format in tzmappings file: %s at line %d, offset %d.\n", + errorMessage, line, offset); + return NULL; +} + +extern char* getGMTOffsetID(); + +/* + * Detects the platform time zone which maps to a Java time zone ID. + * + * The following function only differs from the original by calling a custom parsing + * function called "SVM_matchJavaTZ" that accepts a data buffer. + * + * This function expects its argument to contain the whole tzmappings data and length as an arguments. + * The original function expected a char array whose content was the path to JAVA_HOME + */ +char *SVM_FindJavaTZmd(const char *tzmappings, int length) +{ + char winZoneName[MAX_ZONE_CHAR]; + char winMapID[MAX_MAPID_LENGTH]; + char *std_timezone = NULL; + int result; + + winMapID[0] = 0; + result = getWinTimeZone(winZoneName, winMapID); + + if (result != VALUE_UNKNOWN) { + if (result == VALUE_GMTOFFSET) { + std_timezone = _strdup(winZoneName); + } else { + std_timezone = SVM_matchJavaTZ(tzmappings, result, + winZoneName, winMapID, length); + if (std_timezone == NULL) { + std_timezone = getGMTOffsetID(); + } + } + } + return std_timezone; +} +#else +extern char* findJavaTZ_md(const char *); + +char *SVM_FindJavaTZmd(const char *tzmappings, int length) { + + /* + * For POSIX operating systems the original function + * does not need the JAVA_HOME nor tzmappings. Except + * for AIX (which is currently not supported in native image) + * + * We can safely call the original JDK function with java home set to + * NULL. Note the JNI wrapper of the below function, checks JAVA_HOME + * is not NULL and returns NULL if it is, stoppings us from directly + * calling this function from java without some type of substitution. + */ + return findJavaTZ_md((void *) 0); +} +#endif // _WIN64