/* * 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 * 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. */ /* We use APIs that access the standard Unix environ array, which * is defined by UNIX98 to look like: * * char **environ; * * These are unsorted, case-sensitive, null-terminated arrays of bytes * of the form FOO=BAR\000 which are usually encoded in the user's * default encoding (file.encoding is an excellent choice for * encoding/decoding these). However, even though the user cannot * directly access the underlying byte representation, we take pains * to pass on the child the exact byte representation we inherit from * the parent process for any environment name or value not created by * Javaland. So we keep track of all the byte representations. * * Internally, we define the types Variable and Value that exhibit * String/byteArray duality. The internal representation of the * environment then looks like a Map. But we don't * expose this to the user -- we only provide a Map * view, although we could also provide a Map view. * * The non-private methods in this class are not for general use even * within this package. Instead, they are the system-dependent parts * of the system-independent method of the same name. Don't even * think of using this class unless your method's name appears below. * * @author Martin Buchholz * @since 1.5 */ package java.lang; 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 { 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, // unless {@link #refreshEnv} is invoke. byte[][] environ = environ(); 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) map.put(Variable.valueOf(environ[i-1]), Value.valueOf(environ[i])); return map; } /* 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) */ static String getenv(String name) { return theUnmodifiableEnvironment.get(name); } /* Only for use by System.getenv() */ static Map getenv() { return theUnmodifiableEnvironment; } /* Only for use by ProcessBuilder.environment() */ @SuppressWarnings("unchecked") static Map environment() { return new StringEnvironment ((Map)(getTheEnvironment().clone())); } /* Only for use by Runtime.exec(...String[]envp...) */ static Map emptyEnvironment(int capacity) { return new StringEnvironment(new HashMap<>(capacity)); } private static native byte[][] environ(); // This class is not instantiable. private ProcessEnvironment() {} // Check that name is suitable for insertion into Environment map private static void validateVariable(String name) { if (name.indexOf('=') != -1 || name.indexOf('\u0000') != -1) throw new IllegalArgumentException ("Invalid environment variable name: \"" + name + "\""); } // Check that value is suitable for insertion into Environment map private static void validateValue(String value) { if (value.indexOf('\u0000') != -1) throw new IllegalArgumentException ("Invalid environment variable value: \"" + value + "\""); } // A class hiding the byteArray-String duality of // text data on Unixoid operating systems. private abstract static class ExternalData { protected final String str; protected final byte[] bytes; protected ExternalData(String str, byte[] bytes) { this.str = str; this.bytes = bytes; } public byte[] getBytes() { return bytes; } public String toString() { return str; } public boolean equals(Object o) { return o instanceof ExternalData && arrayEquals(getBytes(), ((ExternalData) o).getBytes()); } public int hashCode() { return arrayHash(getBytes()); } } private static class Variable extends ExternalData implements Comparable { protected Variable(String str, byte[] bytes) { super(str, bytes); } public static Variable valueOfQueryOnly(Object str) { return valueOfQueryOnly((String) str); } public static Variable valueOfQueryOnly(String str) { return new Variable(str, str.getBytes()); } public static Variable valueOf(String str) { validateVariable(str); return valueOfQueryOnly(str); } public static Variable valueOf(byte[] bytes) { return new Variable(new String(bytes), bytes); } public int compareTo(Variable variable) { return arrayCompare(getBytes(), variable.getBytes()); } public boolean equals(Object o) { return o instanceof Variable && super.equals(o); } } private static class Value extends ExternalData implements Comparable { protected Value(String str, byte[] bytes) { super(str, bytes); } public static Value valueOfQueryOnly(Object str) { return valueOfQueryOnly((String) str); } public static Value valueOfQueryOnly(String str) { return new Value(str, str.getBytes()); } public static Value valueOf(String str) { validateValue(str); return valueOfQueryOnly(str); } public static Value valueOf(byte[] bytes) { return new Value(new String(bytes), bytes); } public int compareTo(Value value) { return arrayCompare(getBytes(), value.getBytes()); } public boolean equals(Object o) { return o instanceof Value && super.equals(o); } } // This implements the String map view the user sees. private static abstract class AbstractStringEnvironment extends AbstractMap { protected abstract Map getMap(); private static String toString(Value v) { return v == null ? null : v.toString(); } public int size() {return getMap().size();} public boolean isEmpty() {return getMap().isEmpty();} public void clear() { getMap().clear();} public boolean containsKey(Object key) { return getMap().containsKey(Variable.valueOfQueryOnly(key)); } public boolean containsValue(Object value) { return getMap().containsValue(Value.valueOfQueryOnly(value)); } public String get(Object key) { return toString(getMap().get(Variable.valueOfQueryOnly(key))); } public String put(String key, String value) { return toString(getMap().put(Variable.valueOf(key), Value.valueOf(value))); } public String remove(Object key) { return toString(getMap().remove(Variable.valueOfQueryOnly(key))); } public Set keySet() { return new StringKeySet(getMap().keySet()); } public Set> entrySet() { return new StringEntrySet(getMap().entrySet()); } public Collection values() { return new StringValues(getMap().values()); } // It is technically feasible to provide a byte-oriented view // as follows: // public Map asByteArrayMap() { // return new ByteArrayEnvironment(getMap()); // } // Convert to Unix style environ as a monolithic byte array // inspired by the Windows Environment Block, except we work // exclusively with bytes instead of chars, and we need only // 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; count += entry.getValue().getBytes().length; } byte[] block = new byte[count]; int i = 0; for (Map.Entry entry : m.entrySet()) { byte[] key = entry.getKey ().getBytes(); byte[] value = entry.getValue().getBytes(); System.arraycopy(key, 0, block, i, key.length); i+=key.length; block[i++] = (byte) '='; System.arraycopy(value, 0, block, i, value.length); i+=value.length + 1; // No need to write NUL byte explicitly //block[i++] = (byte) '\u0000'; } envc[0] = m.size(); return block; } } 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); } private static class StringEntry implements Map.Entry { private final Map.Entry e; public StringEntry(Map.Entry e) {this.e = e;} public String getKey() {return e.getKey().toString();} public String getValue() {return e.getValue().toString();} public String setValue(String newValue) { return e.setValue(Value.valueOf(newValue)).toString(); } public String toString() {return getKey() + "=" + getValue();} public boolean equals(Object o) { return o instanceof StringEntry && e.equals(((StringEntry)o).e); } public int hashCode() {return e.hashCode();} } private static class StringEntrySet extends AbstractSet> { private final Set> s; public StringEntrySet(Set> s) {this.s = s;} public int size() {return s.size();} public boolean isEmpty() {return s.isEmpty();} public void clear() { s.clear();} public Iterator> iterator() { return new Iterator>() { Iterator> i = s.iterator(); public boolean hasNext() {return i.hasNext();} public Map.Entry next() { return new StringEntry(i.next()); } public void remove() {i.remove();} }; } private static Map.Entry vvEntry(final Object o) { if (o instanceof StringEntry) return ((StringEntry)o).e; return new Map.Entry() { public Variable getKey() { return Variable.valueOfQueryOnly(((Map.Entry)o).getKey()); } public Value getValue() { return Value.valueOfQueryOnly(((Map.Entry)o).getValue()); } public Value setValue(Value value) { throw new UnsupportedOperationException(); } }; } public boolean contains(Object o) { return s.contains(vvEntry(o)); } public boolean remove(Object o) { return s.remove(vvEntry(o)); } public boolean equals(Object o) { return o instanceof StringEntrySet && s.equals(((StringEntrySet) o).s); } public int hashCode() {return s.hashCode();} } private static class StringValues extends AbstractCollection { private final Collection c; public StringValues(Collection c) {this.c = c;} public int size() {return c.size();} public boolean isEmpty() {return c.isEmpty();} public void clear() { c.clear();} public Iterator iterator() { return new Iterator() { Iterator i = c.iterator(); public boolean hasNext() {return i.hasNext();} public String next() {return i.next().toString();} public void remove() {i.remove();} }; } public boolean contains(Object o) { return c.contains(Value.valueOfQueryOnly(o)); } public boolean remove(Object o) { return c.remove(Value.valueOfQueryOnly(o)); } public boolean equals(Object o) { return o instanceof StringValues && c.equals(((StringValues)o).c); } public int hashCode() {return c.hashCode();} } private static class StringKeySet extends AbstractSet { private final Set s; public StringKeySet(Set s) {this.s = s;} public int size() {return s.size();} public boolean isEmpty() {return s.isEmpty();} public void clear() { s.clear();} public Iterator iterator() { return new Iterator() { Iterator i = s.iterator(); public boolean hasNext() {return i.hasNext();} public String next() {return i.next().toString();} public void remove() { i.remove();} }; } public boolean contains(Object o) { return s.contains(Variable.valueOfQueryOnly(o)); } public boolean remove(Object o) { return s.remove(Variable.valueOfQueryOnly(o)); } } // Replace with general purpose method someday private static int arrayCompare(byte[]x, byte[] y) { int min = x.length < y.length ? x.length : y.length; for (int i = 0; i < min; i++) if (x[i] != y[i]) return x[i] - y[i]; return x.length - y.length; } // Replace with general purpose method someday private static boolean arrayEquals(byte[] x, byte[] y) { if (x.length != y.length) return false; for (int i = 0; i < x.length; i++) if (x[i] != y[i]) return false; return true; } // Replace with general purpose method someday private static int arrayHash(byte[] x) { int hash = 0; for (int i = 0; i < x.length; i++) hash = 31 * hash + x[i]; return hash; } }