| /* | |
| * Copyright (c) 2003, 2023, 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<Variable,Value>. But we don't | |
| * expose this to the user -- we only provide a Map<String,String> | |
| * view, although we could also provide a Map<byte[],byte[]> 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.util.*; | |
| import static java.lang.ProcessImpl.JNU_CHARSET; | |
| final class ProcessEnvironment | |
| { | |
| private static final HashMap<Variable,Value> theEnvironment; | |
| private static final Map<String,String> theUnmodifiableEnvironment; | |
| static final int MIN_NAME_LENGTH = 0; | |
| static { | |
| // We cache the C environment. This means that subsequent calls | |
| // to putenv/setenv from C will not be visible from Java code. | |
| byte[][] environ = environ(); | |
| theEnvironment = new HashMap<>(environ.length/2 + 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])); | |
| theUnmodifiableEnvironment | |
| = Collections.unmodifiableMap | |
| (new StringEnvironment(theEnvironment)); | |
| } | |
| /* Only for use by System.getenv(String) */ | |
| static String getenv(String name) { | |
| return theUnmodifiableEnvironment.get(name); | |
| } | |
| /* Only for use by System.getenv() */ | |
| static Map<String,String> getenv() { | |
| return theUnmodifiableEnvironment; | |
| } | |
| /* Only for use by ProcessBuilder.environment() */ | |
| @SuppressWarnings("unchecked") | |
| static Map<String,String> environment() { | |
| return new StringEnvironment | |
| ((Map<Variable,Value>)(theEnvironment.clone())); | |
| } | |
| /* Only for use by Runtime.exec(...String[]envp...) */ | |
| static Map<String,String> 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 | |
| && Arrays.equals(getBytes(), ((ExternalData) o).getBytes()); | |
| } | |
| public int hashCode() { | |
| return Arrays.hashCode(getBytes()); | |
| } | |
| } | |
| private static class Variable | |
| extends ExternalData implements Comparable<Variable> | |
| { | |
| 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(JNU_CHARSET)); | |
| } | |
| public static Variable valueOf(String str) { | |
| validateVariable(str); | |
| return valueOfQueryOnly(str); | |
| } | |
| public static Variable valueOf(byte[] bytes) { | |
| return new Variable(new String(bytes, JNU_CHARSET), bytes); | |
| } | |
| public int compareTo(Variable variable) { | |
| return Arrays.compare(getBytes(), variable.getBytes()); | |
| } | |
| public boolean equals(Object o) { | |
| return o instanceof Variable && super.equals(o); | |
| } | |
| } | |
| private static class Value | |
| extends ExternalData implements Comparable<Value> | |
| { | |
| 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(JNU_CHARSET)); | |
| } | |
| public static Value valueOf(String str) { | |
| validateValue(str); | |
| return valueOfQueryOnly(str); | |
| } | |
| public static Value valueOf(byte[] bytes) { | |
| return new Value(new String(bytes, JNU_CHARSET), bytes); | |
| } | |
| public int compareTo(Value value) { | |
| return Arrays.compare(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 class StringEnvironment | |
| extends AbstractMap<String,String> | |
| { | |
| private Map<Variable,Value> m; | |
| private static String toString(Value v) { | |
| return v == null ? null : v.toString(); | |
| } | |
| public StringEnvironment(Map<Variable,Value> m) {this.m = m;} | |
| public int size() {return m.size();} | |
| public boolean isEmpty() {return m.isEmpty();} | |
| public void clear() { m.clear();} | |
| public boolean containsKey(Object key) { | |
| return m.containsKey(Variable.valueOfQueryOnly(key)); | |
| } | |
| public boolean containsValue(Object value) { | |
| return m.containsValue(Value.valueOfQueryOnly(value)); | |
| } | |
| public String get(Object key) { | |
| return toString(m.get(Variable.valueOfQueryOnly(key))); | |
| } | |
| public String put(String key, String value) { | |
| return toString(m.put(Variable.valueOf(key), | |
| Value.valueOf(value))); | |
| } | |
| public String remove(Object key) { | |
| return toString(m.remove(Variable.valueOfQueryOnly(key))); | |
| } | |
| public Set<String> keySet() { | |
| return new StringKeySet(m.keySet()); | |
| } | |
| public Set<Map.Entry<String,String>> entrySet() { | |
| return new StringEntrySet(m.entrySet()); | |
| } | |
| public Collection<String> values() { | |
| return new StringValues(m.values()); | |
| } | |
| // It is technically feasible to provide a byte-oriented view | |
| // as follows: | |
| // public Map<byte[],byte[]> asByteArrayMap() { | |
| // return new ByteArrayEnvironment(m); | |
| // } | |
| // 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) { | |
| int count = m.size() * 2; // For added '=' and NUL | |
| for (Map.Entry<Variable,Value> entry : m.entrySet()) { | |
| count += entry.getKey().getBytes().length; | |
| count += entry.getValue().getBytes().length; | |
| } | |
| byte[] block = new byte[count]; | |
| int i = 0; | |
| for (Map.Entry<Variable,Value> 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; | |
| } | |
| } | |
| static byte[] toEnvironmentBlock(Map<String,String> map, int[]envc) { | |
| return map == null ? null : | |
| ((StringEnvironment)map).toEnvironmentBlock(envc); | |
| } | |
| private static class StringEntry | |
| implements Map.Entry<String,String> | |
| { | |
| private final Map.Entry<Variable,Value> e; | |
| public StringEntry(Map.Entry<Variable,Value> 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<Map.Entry<String,String>> | |
| { | |
| private final Set<Map.Entry<Variable,Value>> s; | |
| public StringEntrySet(Set<Map.Entry<Variable,Value>> s) {this.s = s;} | |
| public int size() {return s.size();} | |
| public boolean isEmpty() {return s.isEmpty();} | |
| public void clear() { s.clear();} | |
| public Iterator<Map.Entry<String,String>> iterator() { | |
| return new Iterator<Map.Entry<String,String>>() { | |
| Iterator<Map.Entry<Variable,Value>> i = s.iterator(); | |
| public boolean hasNext() {return i.hasNext();} | |
| public Map.Entry<String,String> next() { | |
| return new StringEntry(i.next()); | |
| } | |
| public void remove() {i.remove();} | |
| }; | |
| } | |
| private static Map.Entry<Variable,Value> vvEntry(final Object o) { | |
| if (o instanceof StringEntry) | |
| return ((StringEntry)o).e; | |
| return new Map.Entry<Variable,Value>() { | |
| 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<String> | |
| { | |
| private final Collection<Value> c; | |
| public StringValues(Collection<Value> c) {this.c = c;} | |
| public int size() {return c.size();} | |
| public boolean isEmpty() {return c.isEmpty();} | |
| public void clear() { c.clear();} | |
| public Iterator<String> iterator() { | |
| return new Iterator<String>() { | |
| Iterator<Value> 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<String> { | |
| private final Set<Variable> s; | |
| public StringKeySet(Set<Variable> s) {this.s = s;} | |
| public int size() {return s.size();} | |
| public boolean isEmpty() {return s.isEmpty();} | |
| public void clear() { s.clear();} | |
| public Iterator<String> iterator() { | |
| return new Iterator<String>() { | |
| Iterator<Variable> 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)); | |
| } | |
| } | |
| } |