0N/A/*
3909N/A * Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved.
0N/A * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0N/A *
0N/A * This code is free software; you can redistribute it and/or modify it
0N/A * under the terms of the GNU General Public License version 2 only, as
2362N/A * published by the Free Software Foundation. Oracle designates this
0N/A * particular file as subject to the "Classpath" exception as provided
2362N/A * by Oracle in the LICENSE file that accompanied this code.
0N/A *
0N/A * This code is distributed in the hope that it will be useful, but WITHOUT
0N/A * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0N/A * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0N/A * version 2 for more details (a copy is included in the LICENSE file that
0N/A * accompanied this code).
0N/A *
0N/A * You should have received a copy of the GNU General Public License version
0N/A * 2 along with this work; if not, write to the Free Software Foundation,
0N/A * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0N/A *
2362N/A * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
2362N/A * or visit www.oracle.com if you need additional information or have any
2362N/A * questions.
0N/A */
0N/A
0N/A/* We use APIs that access a so-called Windows "Environment Block",
0N/A * which looks like an array of jchars like this:
0N/A *
0N/A * FOO=BAR\u0000 ... GORP=QUUX\u0000\u0000
0N/A *
0N/A * This data structure has a number of peculiarities we must contend with:
0N/A * (see: http://windowssdk.msdn.microsoft.com/en-us/library/ms682009.aspx)
0N/A * - The NUL jchar separators, and a double NUL jchar terminator.
0N/A * It appears that the Windows implementation requires double NUL
0N/A * termination even if the environment is empty. We should always
0N/A * generate environments with double NUL termination, while accepting
0N/A * empty environments consisting of a single NUL.
0N/A * - on Windows9x, this is actually an array of 8-bit chars, not jchars,
0N/A * encoded in the system default encoding.
0N/A * - The block must be sorted by Unicode value, case-insensitively,
0N/A * as if folded to upper case.
0N/A * - There are magic environment variables maintained by Windows
0N/A * that start with a `=' (!) character. These are used for
0N/A * Windows drive current directory (e.g. "=C:=C:\WINNT") or the
0N/A * exit code of the last command (e.g. "=ExitCode=0000001").
0N/A *
0N/A * Since Java and non-9x Windows speak the same character set, and
0N/A * even the same encoding, we don't have to deal with unreliable
0N/A * conversion to byte streams. Just add a few NUL terminators.
0N/A *
0N/A * System.getenv(String) is case-insensitive, while System.getenv()
0N/A * returns a map that is case-sensitive, which is consistent with
0N/A * native Windows APIs.
0N/A *
0N/A * The non-private methods in this class are not for general use even
0N/A * within this package. Instead, they are the system-dependent parts
0N/A * of the system-independent method of the same name. Don't even
0N/A * think of using this class unless your method's name appears below.
0N/A *
0N/A * @author Martin Buchholz
0N/A * @since 1.5
0N/A */
0N/A
0N/Apackage java.lang;
0N/A
0N/Aimport java.io.*;
0N/Aimport java.util.*;
0N/A
0N/Afinal class ProcessEnvironment extends HashMap<String,String>
0N/A{
0N/A private static String validateName(String name) {
0N/A // An initial `=' indicates a magic Windows variable name -- OK
0N/A if (name.indexOf('=', 1) != -1 ||
0N/A name.indexOf('\u0000') != -1)
0N/A throw new IllegalArgumentException
0N/A ("Invalid environment variable name: \"" + name + "\"");
0N/A return name;
0N/A }
0N/A
0N/A private static String validateValue(String value) {
0N/A if (value.indexOf('\u0000') != -1)
0N/A throw new IllegalArgumentException
0N/A ("Invalid environment variable value: \"" + value + "\"");
0N/A return value;
0N/A }
0N/A
0N/A private static String nonNullString(Object o) {
0N/A if (o == null)
0N/A throw new NullPointerException();
0N/A return (String) o;
0N/A }
0N/A
0N/A public String put(String key, String value) {
0N/A return super.put(validateName(key), validateValue(value));
0N/A }
0N/A
0N/A public String get(Object key) {
0N/A return super.get(nonNullString(key));
0N/A }
0N/A
0N/A public boolean containsKey(Object key) {
0N/A return super.containsKey(nonNullString(key));
0N/A }
0N/A
0N/A public boolean containsValue(Object value) {
0N/A return super.containsValue(nonNullString(value));
0N/A }
0N/A
0N/A public String remove(Object key) {
0N/A return super.remove(nonNullString(key));
0N/A }
0N/A
0N/A private static class CheckedEntry
0N/A implements Map.Entry<String,String>
0N/A {
0N/A private final Map.Entry<String,String> e;
0N/A public CheckedEntry(Map.Entry<String,String> e) {this.e = e;}
0N/A public String getKey() { return e.getKey();}
0N/A public String getValue() { return e.getValue();}
0N/A public String setValue(String value) {
0N/A return e.setValue(validateValue(value));
0N/A }
0N/A public String toString() { return getKey() + "=" + getValue();}
0N/A public boolean equals(Object o) {return e.equals(o);}
0N/A public int hashCode() {return e.hashCode();}
0N/A }
0N/A
0N/A private static class CheckedEntrySet
0N/A extends AbstractSet<Map.Entry<String,String>>
0N/A {
0N/A private final Set<Map.Entry<String,String>> s;
0N/A public CheckedEntrySet(Set<Map.Entry<String,String>> s) {this.s = s;}
0N/A public int size() {return s.size();}
0N/A public boolean isEmpty() {return s.isEmpty();}
0N/A public void clear() { s.clear();}
0N/A public Iterator<Map.Entry<String,String>> iterator() {
0N/A return new Iterator<Map.Entry<String,String>>() {
0N/A Iterator<Map.Entry<String,String>> i = s.iterator();
0N/A public boolean hasNext() { return i.hasNext();}
0N/A public Map.Entry<String,String> next() {
0N/A return new CheckedEntry(i.next());
0N/A }
0N/A public void remove() { i.remove();}
0N/A };
0N/A }
4103N/A private static Map.Entry<String,String> checkedEntry(Object o) {
0N/A Map.Entry<String,String> e = (Map.Entry<String,String>) o;
0N/A nonNullString(e.getKey());
0N/A nonNullString(e.getValue());
0N/A return e;
0N/A }
0N/A public boolean contains(Object o) {return s.contains(checkedEntry(o));}
0N/A public boolean remove(Object o) {return s.remove(checkedEntry(o));}
0N/A }
0N/A
0N/A private static class CheckedValues extends AbstractCollection<String> {
0N/A private final Collection<String> c;
0N/A public CheckedValues(Collection<String> c) {this.c = c;}
0N/A public int size() {return c.size();}
0N/A public boolean isEmpty() {return c.isEmpty();}
0N/A public void clear() { c.clear();}
0N/A public Iterator<String> iterator() {return c.iterator();}
0N/A public boolean contains(Object o) {return c.contains(nonNullString(o));}
0N/A public boolean remove(Object o) {return c.remove(nonNullString(o));}
0N/A }
0N/A
0N/A private static class CheckedKeySet extends AbstractSet<String> {
0N/A private final Set<String> s;
0N/A public CheckedKeySet(Set<String> s) {this.s = s;}
0N/A public int size() {return s.size();}
0N/A public boolean isEmpty() {return s.isEmpty();}
0N/A public void clear() { s.clear();}
0N/A public Iterator<String> iterator() {return s.iterator();}
0N/A public boolean contains(Object o) {return s.contains(nonNullString(o));}
0N/A public boolean remove(Object o) {return s.remove(nonNullString(o));}
0N/A }
0N/A
0N/A public Set<String> keySet() {
0N/A return new CheckedKeySet(super.keySet());
0N/A }
0N/A
0N/A public Collection<String> values() {
0N/A return new CheckedValues(super.values());
0N/A }
0N/A
0N/A public Set<Map.Entry<String,String>> entrySet() {
0N/A return new CheckedEntrySet(super.entrySet());
0N/A }
0N/A
0N/A
0N/A private static final class NameComparator
0N/A implements Comparator<String> {
0N/A public int compare(String s1, String s2) {
0N/A // We can't use String.compareToIgnoreCase since it
0N/A // canonicalizes to lower case, while Windows
0N/A // canonicalizes to upper case! For example, "_" should
0N/A // sort *after* "Z", not before.
0N/A int n1 = s1.length();
0N/A int n2 = s2.length();
0N/A int min = Math.min(n1, n2);
0N/A for (int i = 0; i < min; i++) {
0N/A char c1 = s1.charAt(i);
0N/A char c2 = s2.charAt(i);
0N/A if (c1 != c2) {
0N/A c1 = Character.toUpperCase(c1);
0N/A c2 = Character.toUpperCase(c2);
0N/A if (c1 != c2)
0N/A // No overflow because of numeric promotion
0N/A return c1 - c2;
0N/A }
0N/A }
0N/A return n1 - n2;
0N/A }
0N/A }
0N/A
0N/A private static final class EntryComparator
0N/A implements Comparator<Map.Entry<String,String>> {
0N/A public int compare(Map.Entry<String,String> e1,
0N/A Map.Entry<String,String> e2) {
0N/A return nameComparator.compare(e1.getKey(), e2.getKey());
0N/A }
0N/A }
0N/A
0N/A // Allow `=' as first char in name, e.g. =C:=C:\DIR
0N/A static final int MIN_NAME_LENGTH = 1;
0N/A
0N/A private static final NameComparator nameComparator;
0N/A private static final EntryComparator entryComparator;
0N/A private static final ProcessEnvironment theEnvironment;
0N/A private static final Map<String,String> theUnmodifiableEnvironment;
0N/A private static final Map<String,String> theCaseInsensitiveEnvironment;
0N/A
0N/A static {
0N/A nameComparator = new NameComparator();
0N/A entryComparator = new EntryComparator();
0N/A theEnvironment = new ProcessEnvironment();
0N/A theUnmodifiableEnvironment
0N/A = Collections.unmodifiableMap(theEnvironment);
0N/A
0N/A String envblock = environmentBlock();
0N/A int beg, end, eql;
0N/A for (beg = 0;
0N/A ((end = envblock.indexOf('\u0000', beg )) != -1 &&
0N/A // An initial `=' indicates a magic Windows variable name -- OK
0N/A (eql = envblock.indexOf('=' , beg+1)) != -1);
0N/A beg = end + 1) {
0N/A // Ignore corrupted environment strings.
0N/A if (eql < end)
0N/A theEnvironment.put(envblock.substring(beg, eql),
0N/A envblock.substring(eql+1,end));
0N/A }
0N/A
3323N/A theCaseInsensitiveEnvironment = new TreeMap<>(nameComparator);
0N/A theCaseInsensitiveEnvironment.putAll(theEnvironment);
0N/A }
0N/A
0N/A private ProcessEnvironment() {
0N/A super();
0N/A }
0N/A
0N/A private ProcessEnvironment(int capacity) {
0N/A super(capacity);
0N/A }
0N/A
0N/A // Only for use by System.getenv(String)
0N/A static String getenv(String name) {
0N/A // The original implementation used a native call to _wgetenv,
0N/A // but it turns out that _wgetenv is only consistent with
0N/A // GetEnvironmentStringsW (for non-ASCII) if `wmain' is used
0N/A // instead of `main', even in a process created using
0N/A // CREATE_UNICODE_ENVIRONMENT. Instead we perform the
0N/A // case-insensitive comparison ourselves. At least this
0N/A // guarantees that System.getenv().get(String) will be
0N/A // consistent with System.getenv(String).
0N/A return theCaseInsensitiveEnvironment.get(name);
0N/A }
0N/A
0N/A // Only for use by System.getenv()
0N/A static Map<String,String> getenv() {
0N/A return theUnmodifiableEnvironment;
0N/A }
0N/A
0N/A // Only for use by ProcessBuilder.environment()
0N/A static Map<String,String> environment() {
0N/A return (Map<String,String>) theEnvironment.clone();
0N/A }
0N/A
4103N/A // Only for use by ProcessBuilder.environment(String[] envp)
0N/A static Map<String,String> emptyEnvironment(int capacity) {
0N/A return new ProcessEnvironment(capacity);
0N/A }
0N/A
0N/A private static native String environmentBlock();
0N/A
0N/A // Only for use by ProcessImpl.start()
0N/A String toEnvironmentBlock() {
0N/A // Sort Unicode-case-insensitively by name
3323N/A List<Map.Entry<String,String>> list = new ArrayList<>(entrySet());
0N/A Collections.sort(list, entryComparator);
0N/A
0N/A StringBuilder sb = new StringBuilder(size()*30);
4103N/A int cmp = -1;
4103N/A
4103N/A // Some versions of MSVCRT.DLL require SystemRoot to be set.
4103N/A // So, we make sure that it is always set, even if not provided
4103N/A // by the caller.
4103N/A final String SYSTEMROOT = "SystemRoot";
4103N/A
4103N/A for (Map.Entry<String,String> e : list) {
4103N/A String key = e.getKey();
4103N/A String value = e.getValue();
4103N/A if (cmp < 0 && (cmp = nameComparator.compare(key, SYSTEMROOT)) > 0) {
4103N/A // Not set, so add it here
4103N/A addToEnvIfSet(sb, SYSTEMROOT);
4103N/A }
4103N/A addToEnv(sb, key, value);
4103N/A }
4103N/A if (cmp < 0) {
4103N/A // Got to end of list and still not found
4103N/A addToEnvIfSet(sb, SYSTEMROOT);
4103N/A }
4103N/A if (sb.length() == 0) {
4103N/A // Environment was empty and SystemRoot not set in parent
0N/A sb.append('\u0000');
4103N/A }
4103N/A // Block is double NUL terminated
0N/A sb.append('\u0000');
0N/A return sb.toString();
0N/A }
0N/A
4103N/A // add the environment variable to the child, if it exists in parent
4103N/A private static void addToEnvIfSet(StringBuilder sb, String name) {
4103N/A String s = getenv(name);
4103N/A if (s != null)
4103N/A addToEnv(sb, name, s);
4103N/A }
4103N/A
4103N/A private static void addToEnv(StringBuilder sb, String name, String val) {
4103N/A sb.append(name).append('=').append(val).append('\u0000');
4103N/A }
4103N/A
0N/A static String toEnvironmentBlock(Map<String,String> map) {
0N/A return map == null ? null :
0N/A ((ProcessEnvironment)map).toEnvironmentBlock();
0N/A }
0N/A}