1 /* 2 * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 /* We use APIs that access a so-called Windows "Environment Block", 27 * which looks like an array of jchars like this: 28 * 29 * FOO=BAR\u0000 ... GORP=QUUX\u0000\u0000 30 * 31 * This data structure has a number of peculiarities we must contend with: 32 * (see: http://windowssdk.msdn.microsoft.com/en-us/library/ms682009.aspx) 33 * - The NUL jchar separators, and a double NUL jchar terminator. 34 * It appears that the Windows implementation requires double NUL 35 * termination even if the environment is empty. We should always 36 * generate environments with double NUL termination, while accepting 37 * empty environments consisting of a single NUL. 38 * - on Windows9x, this is actually an array of 8-bit chars, not jchars, 39 * encoded in the system default encoding. 40 * - The block must be sorted by Unicode value, case-insensitively, 41 * as if folded to upper case. 42 * - There are magic environment variables maintained by Windows 43 * that start with a `=' (!) character. These are used for 44 * Windows drive current directory (e.g. "=C:=C:\WINNT") or the 45 * exit code of the last command (e.g. "=ExitCode=0000001"). 46 * 47 * Since Java and non-9x Windows speak the same character set, and 48 * even the same encoding, we don't have to deal with unreliable 49 * conversion to byte streams. Just add a few NUL terminators. 50 * 51 * System.getenv(String) is case-insensitive, while System.getenv() 52 * returns a map that is case-sensitive, which is consistent with 53 * native Windows APIs. 54 * 55 * The non-private methods in this class are not for general use even 56 * within this package. Instead, they are the system-dependent parts 57 * of the system-independent method of the same name. Don't even 58 * think of using this class unless your method's name appears below. 59 * 60 * @author Martin Buchholz 61 * @since 1.5 62 */ 63 64 package java.lang; 65 66 import java.io.*; 67 import java.util.*; 68 69 final class ProcessEnvironment extends HashMap<String,String> 70 { 71 72 private static final long serialVersionUID = -8017839552603542824L; 73 74 private static String validateName(String name) { 75 // An initial `=' indicates a magic Windows variable name -- OK 76 if (name.indexOf('=', 1) != -1 || 77 name.indexOf('\u0000') != -1) 78 throw new IllegalArgumentException 79 ("Invalid environment variable name: \"" + name + "\""); 80 return name; 81 } 82 83 private static String validateValue(String value) { 84 if (value.indexOf('\u0000') != -1) 85 throw new IllegalArgumentException 86 ("Invalid environment variable value: \"" + value + "\""); 87 return value; 88 } 89 90 private static String nonNullString(Object o) { 91 if (o == null) 92 throw new NullPointerException(); 93 return (String) o; 94 } 95 96 public String put(String key, String value) { 97 return super.put(validateName(key), validateValue(value)); 98 } 99 100 public String get(Object key) { 101 return super.get(nonNullString(key)); 102 } 103 104 public boolean containsKey(Object key) { 105 return super.containsKey(nonNullString(key)); 106 } 107 108 public boolean containsValue(Object value) { 109 return super.containsValue(nonNullString(value)); 110 } 111 112 public String remove(Object key) { 113 return super.remove(nonNullString(key)); 114 } 115 116 private static class CheckedEntry 117 implements Map.Entry<String,String> 118 { 119 private final Map.Entry<String,String> e; 120 public CheckedEntry(Map.Entry<String,String> e) {this.e = e;} 121 public String getKey() { return e.getKey();} 122 public String getValue() { return e.getValue();} 123 public String setValue(String value) { 124 return e.setValue(validateValue(value)); 125 } 126 public String toString() { return getKey() + "=" + getValue();} 127 public boolean equals(Object o) {return e.equals(o);} 128 public int hashCode() {return e.hashCode();} 129 } 130 131 private static class CheckedEntrySet 132 extends AbstractSet<Map.Entry<String,String>> 133 { 134 private final Set<Map.Entry<String,String>> s; 135 public CheckedEntrySet(Set<Map.Entry<String,String>> s) {this.s = s;} 136 public int size() {return s.size();} 137 public boolean isEmpty() {return s.isEmpty();} 138 public void clear() { s.clear();} 139 public Iterator<Map.Entry<String,String>> iterator() { 140 return new Iterator<Map.Entry<String,String>>() { 141 Iterator<Map.Entry<String,String>> i = s.iterator(); 142 public boolean hasNext() { return i.hasNext();} 143 public Map.Entry<String,String> next() { 144 return new CheckedEntry(i.next()); 145 } 146 public void remove() { i.remove();} 147 }; 148 } 149 private static Map.Entry<String,String> checkedEntry(Object o) { 150 @SuppressWarnings("unchecked") 151 Map.Entry<String,String> e = (Map.Entry<String,String>) o; 152 nonNullString(e.getKey()); 153 nonNullString(e.getValue()); 154 return e; 155 } 156 public boolean contains(Object o) {return s.contains(checkedEntry(o));} 157 public boolean remove(Object o) {return s.remove(checkedEntry(o));} 158 } 159 160 private static class CheckedValues extends AbstractCollection<String> { 161 private final Collection<String> c; 162 public CheckedValues(Collection<String> c) {this.c = c;} 163 public int size() {return c.size();} 164 public boolean isEmpty() {return c.isEmpty();} 165 public void clear() { c.clear();} 166 public Iterator<String> iterator() {return c.iterator();} 167 public boolean contains(Object o) {return c.contains(nonNullString(o));} 168 public boolean remove(Object o) {return c.remove(nonNullString(o));} 169 } 170 171 private static class CheckedKeySet extends AbstractSet<String> { 172 private final Set<String> s; 173 public CheckedKeySet(Set<String> s) {this.s = s;} 174 public int size() {return s.size();} 175 public boolean isEmpty() {return s.isEmpty();} 176 public void clear() { s.clear();} 177 public Iterator<String> iterator() {return s.iterator();} 178 public boolean contains(Object o) {return s.contains(nonNullString(o));} 179 public boolean remove(Object o) {return s.remove(nonNullString(o));} 180 } 181 182 public Set<String> keySet() { 183 return new CheckedKeySet(super.keySet()); 184 } 185 186 public Collection<String> values() { 187 return new CheckedValues(super.values()); 188 } 189 190 public Set<Map.Entry<String,String>> entrySet() { 191 return new CheckedEntrySet(super.entrySet()); 192 } 193 194 195 private static final class NameComparator 196 implements Comparator<String> { 197 public int compare(String s1, String s2) { 198 // We can't use String.compareToIgnoreCase since it 199 // canonicalizes to lower case, while Windows 200 // canonicalizes to upper case! For example, "_" should 201 // sort *after* "Z", not before. 202 int n1 = s1.length(); 203 int n2 = s2.length(); 204 int min = Math.min(n1, n2); 205 for (int i = 0; i < min; i++) { 206 char c1 = s1.charAt(i); 207 char c2 = s2.charAt(i); 208 if (c1 != c2) { 209 c1 = Character.toUpperCase(c1); 210 c2 = Character.toUpperCase(c2); 211 if (c1 != c2) 212 // No overflow because of numeric promotion 213 return c1 - c2; 214 } 215 } 216 return n1 - n2; 217 } 218 } 219 220 private static final class EntryComparator 221 implements Comparator<Map.Entry<String,String>> { 222 public int compare(Map.Entry<String,String> e1, 223 Map.Entry<String,String> e2) { 224 return nameComparator.compare(e1.getKey(), e2.getKey()); 225 } 226 } 227 228 // Allow `=' as first char in name, e.g. =C:=C:\DIR 229 static final int MIN_NAME_LENGTH = 1; 230 231 private static final NameComparator nameComparator; 232 private static final EntryComparator entryComparator; 233 private static final ProcessEnvironment theEnvironment; 234 private static final Map<String,String> theUnmodifiableEnvironment; 235 private static final Map<String,String> theCaseInsensitiveEnvironment; 236 237 static { 238 nameComparator = new NameComparator(); 239 entryComparator = new EntryComparator(); 240 theEnvironment = new ProcessEnvironment(); 241 theUnmodifiableEnvironment 242 = Collections.unmodifiableMap(theEnvironment); 243 244 String envblock = environmentBlock(); 245 int beg, end, eql; 246 for (beg = 0; 247 ((end = envblock.indexOf('\u0000', beg )) != -1 && 248 // An initial `=' indicates a magic Windows variable name -- OK 249 (eql = envblock.indexOf('=' , beg+1)) != -1); 250 beg = end + 1) { 251 // Ignore corrupted environment strings. 252 if (eql < end) 253 theEnvironment.put(envblock.substring(beg, eql), 254 envblock.substring(eql+1,end)); 255 } 256 257 theCaseInsensitiveEnvironment = new TreeMap<>(nameComparator); 258 theCaseInsensitiveEnvironment.putAll(theEnvironment); 259 } 260 261 private ProcessEnvironment() { 262 super(); 263 } 264 265 private ProcessEnvironment(int capacity) { 266 super(capacity); 267 } 268 269 static void refreshEnv() { 270 // TODO 271 } 272 273 // Only for use by System.getenv(String) 274 static String getenv(String name) { 275 // The original implementation used a native call to _wgetenv, 276 // but it turns out that _wgetenv is only consistent with 277 // GetEnvironmentStringsW (for non-ASCII) if `wmain' is used 278 // instead of `main', even in a process created using 279 // CREATE_UNICODE_ENVIRONMENT. Instead we perform the 280 // case-insensitive comparison ourselves. At least this 281 // guarantees that System.getenv().get(String) will be 282 // consistent with System.getenv(String). 283 return theCaseInsensitiveEnvironment.get(name); 284 } 285 286 // Only for use by System.getenv() 287 static Map<String,String> getenv() { 288 return theUnmodifiableEnvironment; 289 } 290 291 // Only for use by ProcessBuilder.environment() 292 @SuppressWarnings("unchecked") 293 static Map<String,String> environment() { 294 return (Map<String,String>) theEnvironment.clone(); 295 } 296 297 // Only for use by ProcessBuilder.environment(String[] envp) 298 static Map<String,String> emptyEnvironment(int capacity) { 299 return new ProcessEnvironment(capacity); 300 } 301 302 private static native String environmentBlock(); 303 304 // Only for use by ProcessImpl.start() 305 String toEnvironmentBlock() { 306 // Sort Unicode-case-insensitively by name 307 List<Map.Entry<String,String>> list = new ArrayList<>(entrySet()); 308 Collections.sort(list, entryComparator); 309 310 StringBuilder sb = new StringBuilder(size()*30); 311 int cmp = -1; 312 313 // Some versions of MSVCRT.DLL require SystemRoot to be set. 314 // So, we make sure that it is always set, even if not provided 315 // by the caller. 316 final String SYSTEMROOT = "SystemRoot"; 317 318 for (Map.Entry<String,String> e : list) { 319 String key = e.getKey(); 320 String value = e.getValue(); 321 if (cmp < 0 && (cmp = nameComparator.compare(key, SYSTEMROOT)) > 0) { 322 // Not set, so add it here 323 addToEnvIfSet(sb, SYSTEMROOT); 324 } 325 addToEnv(sb, key, value); 326 } 327 if (cmp < 0) { 328 // Got to end of list and still not found 329 addToEnvIfSet(sb, SYSTEMROOT); 330 } 331 if (sb.length() == 0) { 332 // Environment was empty and SystemRoot not set in parent 333 sb.append('\u0000'); 334 } 335 // Block is double NUL terminated 336 sb.append('\u0000'); 337 return sb.toString(); 338 } 339 340 // add the environment variable to the child, if it exists in parent 341 private static void addToEnvIfSet(StringBuilder sb, String name) { 342 String s = getenv(name); 343 if (s != null) 344 addToEnv(sb, name, s); 345 } 346 347 private static void addToEnv(StringBuilder sb, String name, String val) { 348 sb.append(name).append('=').append(val).append('\u0000'); 349 } 350 351 static String toEnvironmentBlock(Map<String,String> map) { 352 return map == null ? null : 353 ((ProcessEnvironment)map).toEnvironmentBlock(); 354 } 355 }