You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
352 lines
12 KiB
352 lines
12 KiB
/*
|
|
* Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
|
|
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*/
|
|
|
|
/* We use APIs that access a so-called Windows "Environment Block",
|
|
* which looks like an array of jchars like this:
|
|
*
|
|
* FOO=BAR\u0000 ... GORP=QUUX\u0000\u0000
|
|
*
|
|
* This data structure has a number of peculiarities we must contend with:
|
|
* (see: http://windowssdk.msdn.microsoft.com/en-us/library/ms682009.aspx)
|
|
* - The NUL jchar separators, and a double NUL jchar terminator.
|
|
* It appears that the Windows implementation requires double NUL
|
|
* termination even if the environment is empty. We should always
|
|
* generate environments with double NUL termination, while accepting
|
|
* empty environments consisting of a single NUL.
|
|
* - on Windows9x, this is actually an array of 8-bit chars, not jchars,
|
|
* encoded in the system default encoding.
|
|
* - The block must be sorted by Unicode value, case-insensitively,
|
|
* as if folded to upper case.
|
|
* - There are magic environment variables maintained by Windows
|
|
* that start with a `=' (!) character. These are used for
|
|
* Windows drive current directory (e.g. "=C:=C:\WINNT") or the
|
|
* exit code of the last command (e.g. "=ExitCode=0000001").
|
|
*
|
|
* Since Java and non-9x Windows speak the same character set, and
|
|
* even the same encoding, we don't have to deal with unreliable
|
|
* conversion to byte streams. Just add a few NUL terminators.
|
|
*
|
|
* System.getenv(String) is case-insensitive, while System.getenv()
|
|
* returns a map that is case-sensitive, which is consistent with
|
|
* native Windows APIs.
|
|
*
|
|
* 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.io.*;
|
|
import java.util.*;
|
|
|
|
final class ProcessEnvironment extends HashMap<String,String>
|
|
{
|
|
|
|
private static final long serialVersionUID = -8017839552603542824L;
|
|
|
|
private static String validateName(String name) {
|
|
// An initial `=' indicates a magic Windows variable name -- OK
|
|
if (name.indexOf('=', 1) != -1 ||
|
|
name.indexOf('\u0000') != -1)
|
|
throw new IllegalArgumentException
|
|
("Invalid environment variable name: \"" + name + "\"");
|
|
return name;
|
|
}
|
|
|
|
private static String validateValue(String value) {
|
|
if (value.indexOf('\u0000') != -1)
|
|
throw new IllegalArgumentException
|
|
("Invalid environment variable value: \"" + value + "\"");
|
|
return value;
|
|
}
|
|
|
|
private static String nonNullString(Object o) {
|
|
if (o == null)
|
|
throw new NullPointerException();
|
|
return (String) o;
|
|
}
|
|
|
|
public String put(String key, String value) {
|
|
return super.put(validateName(key), validateValue(value));
|
|
}
|
|
|
|
public String get(Object key) {
|
|
return super.get(nonNullString(key));
|
|
}
|
|
|
|
public boolean containsKey(Object key) {
|
|
return super.containsKey(nonNullString(key));
|
|
}
|
|
|
|
public boolean containsValue(Object value) {
|
|
return super.containsValue(nonNullString(value));
|
|
}
|
|
|
|
public String remove(Object key) {
|
|
return super.remove(nonNullString(key));
|
|
}
|
|
|
|
private static class CheckedEntry
|
|
implements Map.Entry<String,String>
|
|
{
|
|
private final Map.Entry<String,String> e;
|
|
public CheckedEntry(Map.Entry<String,String> e) {this.e = e;}
|
|
public String getKey() { return e.getKey();}
|
|
public String getValue() { return e.getValue();}
|
|
public String setValue(String value) {
|
|
return e.setValue(validateValue(value));
|
|
}
|
|
public String toString() { return getKey() + "=" + getValue();}
|
|
public boolean equals(Object o) {return e.equals(o);}
|
|
public int hashCode() {return e.hashCode();}
|
|
}
|
|
|
|
private static class CheckedEntrySet
|
|
extends AbstractSet<Map.Entry<String,String>>
|
|
{
|
|
private final Set<Map.Entry<String,String>> s;
|
|
public CheckedEntrySet(Set<Map.Entry<String,String>> 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<String,String>> i = s.iterator();
|
|
public boolean hasNext() { return i.hasNext();}
|
|
public Map.Entry<String,String> next() {
|
|
return new CheckedEntry(i.next());
|
|
}
|
|
public void remove() { i.remove();}
|
|
};
|
|
}
|
|
private static Map.Entry<String,String> checkedEntry(Object o) {
|
|
@SuppressWarnings("unchecked")
|
|
Map.Entry<String,String> e = (Map.Entry<String,String>) o;
|
|
nonNullString(e.getKey());
|
|
nonNullString(e.getValue());
|
|
return e;
|
|
}
|
|
public boolean contains(Object o) {return s.contains(checkedEntry(o));}
|
|
public boolean remove(Object o) {return s.remove(checkedEntry(o));}
|
|
}
|
|
|
|
private static class CheckedValues extends AbstractCollection<String> {
|
|
private final Collection<String> c;
|
|
public CheckedValues(Collection<String> 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 c.iterator();}
|
|
public boolean contains(Object o) {return c.contains(nonNullString(o));}
|
|
public boolean remove(Object o) {return c.remove(nonNullString(o));}
|
|
}
|
|
|
|
private static class CheckedKeySet extends AbstractSet<String> {
|
|
private final Set<String> s;
|
|
public CheckedKeySet(Set<String> 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 s.iterator();}
|
|
public boolean contains(Object o) {return s.contains(nonNullString(o));}
|
|
public boolean remove(Object o) {return s.remove(nonNullString(o));}
|
|
}
|
|
|
|
public Set<String> keySet() {
|
|
return new CheckedKeySet(super.keySet());
|
|
}
|
|
|
|
public Collection<String> values() {
|
|
return new CheckedValues(super.values());
|
|
}
|
|
|
|
public Set<Map.Entry<String,String>> entrySet() {
|
|
return new CheckedEntrySet(super.entrySet());
|
|
}
|
|
|
|
|
|
private static final class NameComparator
|
|
implements Comparator<String> {
|
|
public int compare(String s1, String s2) {
|
|
// We can't use String.compareToIgnoreCase since it
|
|
// canonicalizes to lower case, while Windows
|
|
// canonicalizes to upper case! For example, "_" should
|
|
// sort *after* "Z", not before.
|
|
int n1 = s1.length();
|
|
int n2 = s2.length();
|
|
int min = Math.min(n1, n2);
|
|
for (int i = 0; i < min; i++) {
|
|
char c1 = s1.charAt(i);
|
|
char c2 = s2.charAt(i);
|
|
if (c1 != c2) {
|
|
c1 = Character.toUpperCase(c1);
|
|
c2 = Character.toUpperCase(c2);
|
|
if (c1 != c2)
|
|
// No overflow because of numeric promotion
|
|
return c1 - c2;
|
|
}
|
|
}
|
|
return n1 - n2;
|
|
}
|
|
}
|
|
|
|
private static final class EntryComparator
|
|
implements Comparator<Map.Entry<String,String>> {
|
|
public int compare(Map.Entry<String,String> e1,
|
|
Map.Entry<String,String> e2) {
|
|
return nameComparator.compare(e1.getKey(), e2.getKey());
|
|
}
|
|
}
|
|
|
|
// Allow `=' as first char in name, e.g. =C:=C:\DIR
|
|
static final int MIN_NAME_LENGTH = 1;
|
|
|
|
private static final NameComparator nameComparator;
|
|
private static final EntryComparator entryComparator;
|
|
private static final ProcessEnvironment theEnvironment;
|
|
private static final Map<String,String> theUnmodifiableEnvironment;
|
|
private static final Map<String,String> theCaseInsensitiveEnvironment;
|
|
|
|
static {
|
|
nameComparator = new NameComparator();
|
|
entryComparator = new EntryComparator();
|
|
theEnvironment = new ProcessEnvironment();
|
|
theUnmodifiableEnvironment
|
|
= Collections.unmodifiableMap(theEnvironment);
|
|
|
|
String envblock = environmentBlock();
|
|
int beg, end, eql;
|
|
for (beg = 0;
|
|
((end = envblock.indexOf('\u0000', beg )) != -1 &&
|
|
// An initial `=' indicates a magic Windows variable name -- OK
|
|
(eql = envblock.indexOf('=' , beg+1)) != -1);
|
|
beg = end + 1) {
|
|
// Ignore corrupted environment strings.
|
|
if (eql < end)
|
|
theEnvironment.put(envblock.substring(beg, eql),
|
|
envblock.substring(eql+1,end));
|
|
}
|
|
|
|
theCaseInsensitiveEnvironment = new TreeMap<>(nameComparator);
|
|
theCaseInsensitiveEnvironment.putAll(theEnvironment);
|
|
}
|
|
|
|
private ProcessEnvironment() {
|
|
super();
|
|
}
|
|
|
|
private ProcessEnvironment(int capacity) {
|
|
super(capacity);
|
|
}
|
|
|
|
// Only for use by System.getenv(String)
|
|
static String getenv(String name) {
|
|
// The original implementation used a native call to _wgetenv,
|
|
// but it turns out that _wgetenv is only consistent with
|
|
// GetEnvironmentStringsW (for non-ASCII) if `wmain' is used
|
|
// instead of `main', even in a process created using
|
|
// CREATE_UNICODE_ENVIRONMENT. Instead we perform the
|
|
// case-insensitive comparison ourselves. At least this
|
|
// guarantees that System.getenv().get(String) will be
|
|
// consistent with System.getenv(String).
|
|
return theCaseInsensitiveEnvironment.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 (Map<String,String>) theEnvironment.clone();
|
|
}
|
|
|
|
// Only for use by ProcessBuilder.environment(String[] envp)
|
|
static Map<String,String> emptyEnvironment(int capacity) {
|
|
return new ProcessEnvironment(capacity);
|
|
}
|
|
|
|
private static native String environmentBlock();
|
|
|
|
// Only for use by ProcessImpl.start()
|
|
String toEnvironmentBlock() {
|
|
// Sort Unicode-case-insensitively by name
|
|
List<Map.Entry<String,String>> list = new ArrayList<>(entrySet());
|
|
Collections.sort(list, entryComparator);
|
|
|
|
StringBuilder sb = new StringBuilder(size()*30);
|
|
int cmp = -1;
|
|
|
|
// Some versions of MSVCRT.DLL require SystemRoot to be set.
|
|
// So, we make sure that it is always set, even if not provided
|
|
// by the caller.
|
|
final String SYSTEMROOT = "SystemRoot";
|
|
|
|
for (Map.Entry<String,String> e : list) {
|
|
String key = e.getKey();
|
|
String value = e.getValue();
|
|
if (cmp < 0 && (cmp = nameComparator.compare(key, SYSTEMROOT)) > 0) {
|
|
// Not set, so add it here
|
|
addToEnvIfSet(sb, SYSTEMROOT);
|
|
}
|
|
addToEnv(sb, key, value);
|
|
}
|
|
if (cmp < 0) {
|
|
// Got to end of list and still not found
|
|
addToEnvIfSet(sb, SYSTEMROOT);
|
|
}
|
|
if (sb.length() == 0) {
|
|
// Environment was empty and SystemRoot not set in parent
|
|
sb.append('\u0000');
|
|
}
|
|
// Block is double NUL terminated
|
|
sb.append('\u0000');
|
|
return sb.toString();
|
|
}
|
|
|
|
// add the environment variable to the child, if it exists in parent
|
|
private static void addToEnvIfSet(StringBuilder sb, String name) {
|
|
String s = getenv(name);
|
|
if (s != null)
|
|
addToEnv(sb, name, s);
|
|
}
|
|
|
|
private static void addToEnv(StringBuilder sb, String name, String val) {
|
|
sb.append(name).append('=').append(val).append('\u0000');
|
|
}
|
|
|
|
static String toEnvironmentBlock(Map<String,String> map) {
|
|
return map == null ? null :
|
|
((ProcessEnvironment)map).toEnvironmentBlock();
|
|
}
|
|
}
|