--- old/make/test/JtregNative.gmk 2017-05-17 16:11:03.000000000 +0100 +++ new/make/test/JtregNative.gmk 2017-05-17 16:11:03.000000000 +0100 @@ -42,8 +42,13 @@ # Add more directories here when needed. BUILD_JDK_JTREG_NATIVE_SRC := \ $(JDK_TOPDIR)/test/native_sanity \ + $(JDK_TOPDIR)/test/java/lang/System \ # +ifeq ($(TOOLCHAIN_TYPE), solstudio) + BUILD_JDK_JTREG_LIBRARIES_LDFLAGS_librefreshEnv := -lc +endif + BUILD_JDK_JTREG_OUTPUT_DIR := $(BUILD_OUTPUT)/support/test/jdk/jtreg/native BUILD_JDK_JTREG_IMAGE_DIR := $(TEST_IMAGE_DIR)/jdk/jtreg --- old/src/java.base/share/classes/java/lang/System.java 2017-05-17 16:11:05.000000000 +0100 +++ new/src/java.base/share/classes/java/lang/System.java 2017-05-17 16:11:05.000000000 +0100 @@ -1001,6 +1001,27 @@ } /** + * Refreshes the current system environment variables. + * + * In the unlikely event that the system environment variables have been + * modified, by some out-of-band mechanism, this method will re-read the + * current system environment variables. Upon successful invocation of this + * method: 1) subsequent invocations of {@linkplain #getenv(String) + * getenv(String)} may return a different value, and 2) subsequent + * method invocations on the {@code Map} return by {@linkplain #getenv()} + * getenv()} may return different results. + * + * @apiNote + * The invocation of this method is only required if there is knowledge that + * an out-of-band mechanism has modified the system environment variables. + * + * @since 9 + */ + public static void refreshEnv() { + ProcessEnvironment.refreshEnv(); + } + + /** * {@code System.Logger} instances log messages that will be * routed to the underlying logging framework the {@link System.LoggerFinder * LoggerFinder} uses. --- old/src/java.base/unix/classes/java/lang/ProcessEnvironment.java 2017-05-17 16:11:07.000000000 +0100 +++ new/src/java.base/unix/classes/java/lang/ProcessEnvironment.java 2017-05-17 16:11:07.000000000 +0100 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2017, 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 @@ -54,30 +54,73 @@ package java.lang; -import java.io.*; -import java.util.*; - +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; +import java.util.AbstractCollection; +import java.util.AbstractMap; +import java.util.AbstractSet; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; final class ProcessEnvironment { - private static final HashMap theEnvironment; - private static final Map theUnmodifiableEnvironment; static final int MIN_NAME_LENGTH = 0; + private static final VarHandle THE_ENVIRONMENT; + // must only be accessed through the VarHandle + private static HashMap theEnvironment = readSystemEnv(); + static { + try { + MethodHandles.Lookup l = MethodHandles.lookup(); + THE_ENVIRONMENT = l.findStaticVarHandle(ProcessEnvironment.class, + "theEnvironment", + HashMap.class); + } catch (ReflectiveOperationException e) { + throw new Error(e); + } + } + + // The underlying map must be accessed through this getter. + private static HashMap getTheEnvironment() { + return (HashMap)THE_ENVIRONMENT.getAcquire(); + } + + // Used by refreshEnv to update the underlying env variables map. + private static void setTheEnvironment(Map newMap) { + THE_ENVIRONMENT.setRelease(newMap); + } + + private static final AbstractStringEnvironment theStringEnvironment = + new TheStringEnvironment(); + private static final Map theUnmodifiableEnvironment = + Collections.unmodifiableMap(theStringEnvironment); + + private static HashMap readSystemEnv() { // We cache the C environment. This means that subsequent calls - // to putenv/setenv from C will not be visible from Java code. + // to putenv/setenv from C will not be visible from Java code, + // unless {@link #refreshEnv} is invoke. byte[][] environ = environ(); - theEnvironment = new HashMap<>(environ.length/2 + 3); + HashMap map = new HashMap<>((4 * environ.length) / 3); // Read environment variables back to front, // so that earlier variables override later ones. for (int i = environ.length-1; i > 0; i-=2) - theEnvironment.put(Variable.valueOf(environ[i-1]), - Value.valueOf(environ[i])); + map.put(Variable.valueOf(environ[i-1]), + Value.valueOf(environ[i])); + return map; + } - theUnmodifiableEnvironment - = Collections.unmodifiableMap - (new StringEnvironment(theEnvironment)); + /* Only used System.refreshEnv() */ + static void refreshEnv() { + HashMap newMap = readSystemEnv(); + // only one thread can update at a time + synchronized (theStringEnvironment) { + setTheEnvironment(newMap); + } } /* Only for use by System.getenv(String) */ @@ -94,7 +137,7 @@ @SuppressWarnings("unchecked") static Map environment() { return new StringEnvironment - ((Map)(theEnvironment.clone())); + ((Map)(getTheEnvironment().clone())); } /* Only for use by Runtime.exec(...String[]envp...) */ @@ -218,47 +261,47 @@ } // This implements the String map view the user sees. - private static class StringEnvironment + private static abstract class AbstractStringEnvironment extends AbstractMap { - private Map m; + protected abstract Map getMap(); + private static String toString(Value v) { return v == null ? null : v.toString(); } - public StringEnvironment(Map m) {this.m = m;} - public int size() {return m.size();} - public boolean isEmpty() {return m.isEmpty();} - public void clear() { m.clear();} + public int size() {return getMap().size();} + public boolean isEmpty() {return getMap().isEmpty();} + public void clear() { getMap().clear();} public boolean containsKey(Object key) { - return m.containsKey(Variable.valueOfQueryOnly(key)); + return getMap().containsKey(Variable.valueOfQueryOnly(key)); } public boolean containsValue(Object value) { - return m.containsValue(Value.valueOfQueryOnly(value)); + return getMap().containsValue(Value.valueOfQueryOnly(value)); } public String get(Object key) { - return toString(m.get(Variable.valueOfQueryOnly(key))); + return toString(getMap().get(Variable.valueOfQueryOnly(key))); } public String put(String key, String value) { - return toString(m.put(Variable.valueOf(key), + return toString(getMap().put(Variable.valueOf(key), Value.valueOf(value))); } public String remove(Object key) { - return toString(m.remove(Variable.valueOfQueryOnly(key))); + return toString(getMap().remove(Variable.valueOfQueryOnly(key))); } public Set keySet() { - return new StringKeySet(m.keySet()); + return new StringKeySet(getMap().keySet()); } public Set> entrySet() { - return new StringEntrySet(m.entrySet()); + return new StringEntrySet(getMap().entrySet()); } public Collection values() { - return new StringValues(m.values()); + return new StringValues(getMap().values()); } // It is technically feasible to provide a byte-oriented view // as follows: // public Map asByteArrayMap() { - // return new ByteArrayEnvironment(m); + // return new ByteArrayEnvironment(getMap()); // } @@ -268,6 +311,7 @@ // one trailing NUL on Unix. // This keeps the JNI as simple and efficient as possible. public byte[] toEnvironmentBlock(int[]envc) { + Map m = getMap(); int count = m.size() * 2; // For added '=' and NUL for (Map.Entry entry : m.entrySet()) { count += entry.getKey().getBytes().length; @@ -293,6 +337,24 @@ } } + private static class TheStringEnvironment extends AbstractStringEnvironment { + @Override + protected Map getMap() { + return ProcessEnvironment.getTheEnvironment(); + } + } + + private static class StringEnvironment extends AbstractStringEnvironment { + private final Map m; + StringEnvironment(Map map) { + this.m = map; + } + @Override + protected Map getMap() { + return m; + } + } + static byte[] toEnvironmentBlock(Map map, int[]envc) { return map == null ? null : ((StringEnvironment)map).toEnvironmentBlock(envc); --- old/src/java.base/windows/classes/java/lang/ProcessEnvironment.java 2017-05-17 16:11:09.000000000 +0100 +++ new/src/java.base/windows/classes/java/lang/ProcessEnvironment.java 2017-05-17 16:11:09.000000000 +0100 @@ -266,6 +266,10 @@ super(capacity); } + static void refreshEnv() { + // TODO + } + // Only for use by System.getenv(String) static String getenv(String name) { // The original implementation used a native call to _wgetenv, --- /dev/null 2017-05-17 16:11:11.000000000 +0100 +++ new/test/java/lang/System/refreshEnv/RefreshEnv.java 2017-05-17 16:11:10.000000000 +0100 @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2017, 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. + * + * 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. + */ + +/* + * @test + * @bug 8765432 + * @summary Basic test for System.refreshEnv + * @run testng/othervm/native RefreshEnv + */ + +import java.util.Map; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; +import static org.testng.Assert.*; + +public class RefreshEnv { + static { + System.loadLibrary("refreshEnv"); + } + + @DataProvider + public Object[][] envVariables() throws Exception { + return new Object[][] { + new Object[] { "REFRESH_TEST_A", "LARRY" }, + new Object[] { "REFRESH_TEST_B", "moe" }, + new Object[] { "REFRESH_TEST_C", "cUrLy" }, + new Object[] { "XXZZYY", "AABBCC" }, + new Object[] { "NO_VALUE", "" } + }; + } + + @Test(dataProvider = "envVariables") + public void test(String name, String value) { + Map envs = System.getenv(); + + assertEquals(envs.get(name), null); + assertEquals(System.getenv(name), null); + + setenv(name, value); + System.refreshEnv(); + + assertEquals(envs.get(name), value); // previous map reference + assertEquals(System.getenv().get(name), value); + assertEquals(System.getenv(name), value); + + } + + /** Sets, natively, the given environment variable with the given value. */ + static native int setenv(String name, String value); + +} --- /dev/null 2017-05-17 16:11:12.000000000 +0100 +++ new/test/java/lang/System/refreshEnv/librefreshEnv.c 2017-05-17 16:11:12.000000000 +0100 @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2017, 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. + * + * 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 WINDOWS +#include +#else +#include +#endif + +#include + +void throwByName(JNIEnv *env, const char *name, const char *msg) + { + jclass cls = (*env)->FindClass(env, name); + + if (cls != 0) + (*env)->ThrowNew(env, cls, msg); + } + + +JNIEXPORT void JNICALL +Java_RefreshEnv_setenv(JNIEnv *env, jclass unused, jstring name, jstring value) +{ + const char* cname; + const char* cvalue; + + cname = (*env)->GetStringUTFChars(env, name, NULL); + if (cname == NULL) { + return; + } + + cvalue = (*env)->GetStringUTFChars(env, value, NULL); + if (cvalue == NULL) { + (*env)->ReleaseStringUTFChars(env, name, cname); + return; + } + +#ifdef WINDOWS + if (!SetEnvironmentVariable(cname, cvalue)) { +#else + if (setenv(cname, cvalue, 1) != 0) { +#endif + throwByName(env, "java/lang/Exception", "setenv failed"); + } + + (*env)->ReleaseStringUTFChars(env, name, cname); + (*env)->ReleaseStringUTFChars(env, name, cvalue); + + return; +}