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 the standard Unix environ array, which
27 * is defined by UNIX98 to look like:
28 *
29 *    char **environ;
30 *
31 * These are unsorted, case-sensitive, null-terminated arrays of bytes
32 * of the form FOO=BAR\000 which are usually encoded in the user's
33 * default encoding (file.encoding is an excellent choice for
34 * encoding/decoding these).  However, even though the user cannot
35 * directly access the underlying byte representation, we take pains
36 * to pass on the child the exact byte representation we inherit from
37 * the parent process for any environment name or value not created by
38 * Javaland.  So we keep track of all the byte representations.
39 *
40 * Internally, we define the types Variable and Value that exhibit
41 * String/byteArray duality.  The internal representation of the
42 * environment then looks like a Map<Variable,Value>.  But we don't
43 * expose this to the user -- we only provide a Map<String,String>
44 * view, although we could also provide a Map<byte[],byte[]> view.
45 *
46 * The non-private methods in this class are not for general use even
47 * within this package.  Instead, they are the system-dependent parts
48 * of the system-independent method of the same name.  Don't even
49 * think of using this class unless your method's name appears below.
50 *
51 * @author  Martin Buchholz
52 * @since   1.5
53 */
54
55package java.lang;
56
57import java.io.*;
58import java.util.*;
59
60
61final class ProcessEnvironment
62{
63    private static final HashMap<Variable,Value> theEnvironment;
64    private static final Map<String,String> theUnmodifiableEnvironment;
65    static final int MIN_NAME_LENGTH = 0;
66
67    static {
68        // We cache the C environment.  This means that subsequent calls
69        // to putenv/setenv from C will not be visible from Java code.
70        byte[][] environ = environ();
71        theEnvironment = new HashMap<>(environ.length/2 + 3);
72        // Read environment variables back to front,
73        // so that earlier variables override later ones.
74        for (int i = environ.length-1; i > 0; i-=2)
75            theEnvironment.put(Variable.valueOf(environ[i-1]),
76                               Value.valueOf(environ[i]));
77
78        theUnmodifiableEnvironment
79            = Collections.unmodifiableMap
80            (new StringEnvironment(theEnvironment));
81    }
82
83    /* Only for use by System.getenv(String) */
84    static String getenv(String name) {
85        return theUnmodifiableEnvironment.get(name);
86    }
87
88    /* Only for use by System.getenv() */
89    static Map<String,String> getenv() {
90        return theUnmodifiableEnvironment;
91    }
92
93    /* Only for use by ProcessBuilder.environment() */
94    @SuppressWarnings("unchecked")
95    static Map<String,String> environment() {
96        return new StringEnvironment
97            ((Map<Variable,Value>)(theEnvironment.clone()));
98    }
99
100    /* Only for use by Runtime.exec(...String[]envp...) */
101    static Map<String,String> emptyEnvironment(int capacity) {
102        return new StringEnvironment(new HashMap<Variable,Value>(capacity));
103    }
104
105    private static native byte[][] environ();
106
107    // This class is not instantiable.
108    private ProcessEnvironment() {}
109
110    // Check that name is suitable for insertion into Environment map
111    private static void validateVariable(String name) {
112        if (name.indexOf('=')      != -1 ||
113            name.indexOf('\u0000') != -1)
114            throw new IllegalArgumentException
115                ("Invalid environment variable name: \"" + name + "\"");
116    }
117
118    // Check that value is suitable for insertion into Environment map
119    private static void validateValue(String value) {
120        if (value.indexOf('\u0000') != -1)
121            throw new IllegalArgumentException
122                ("Invalid environment variable value: \"" + value + "\"");
123    }
124
125    // A class hiding the byteArray-String duality of
126    // text data on Unixoid operating systems.
127    private static abstract class ExternalData {
128        protected final String str;
129        protected final byte[] bytes;
130
131        protected ExternalData(String str, byte[] bytes) {
132            this.str = str;
133            this.bytes = bytes;
134        }
135
136        public byte[] getBytes() {
137            return bytes;
138        }
139
140        public String toString() {
141            return str;
142        }
143
144        public boolean equals(Object o) {
145            return o instanceof ExternalData
146                && arrayEquals(getBytes(), ((ExternalData) o).getBytes());
147        }
148
149        public int hashCode() {
150            return arrayHash(getBytes());
151        }
152    }
153
154    private static class Variable
155        extends ExternalData implements Comparable<Variable>
156    {
157        protected Variable(String str, byte[] bytes) {
158            super(str, bytes);
159        }
160
161        public static Variable valueOfQueryOnly(Object str) {
162            return valueOfQueryOnly((String) str);
163        }
164
165        public static Variable valueOfQueryOnly(String str) {
166            return new Variable(str, str.getBytes());
167        }
168
169        public static Variable valueOf(String str) {
170            validateVariable(str);
171            return valueOfQueryOnly(str);
172        }
173
174        public static Variable valueOf(byte[] bytes) {
175            return new Variable(new String(bytes), bytes);
176        }
177
178        public int compareTo(Variable variable) {
179            return arrayCompare(getBytes(), variable.getBytes());
180        }
181
182        public boolean equals(Object o) {
183            return o instanceof Variable && super.equals(o);
184        }
185    }
186
187    private static class Value
188        extends ExternalData implements Comparable<Value>
189    {
190        protected Value(String str, byte[] bytes) {
191            super(str, bytes);
192        }
193
194        public static Value valueOfQueryOnly(Object str) {
195            return valueOfQueryOnly((String) str);
196        }
197
198        public static Value valueOfQueryOnly(String str) {
199            return new Value(str, str.getBytes());
200        }
201
202        public static Value valueOf(String str) {
203            validateValue(str);
204            return valueOfQueryOnly(str);
205        }
206
207        public static Value valueOf(byte[] bytes) {
208            return new Value(new String(bytes), bytes);
209        }
210
211        public int compareTo(Value value) {
212            return arrayCompare(getBytes(), value.getBytes());
213        }
214
215        public boolean equals(Object o) {
216            return o instanceof Value && super.equals(o);
217        }
218    }
219
220    // This implements the String map view the user sees.
221    private static class StringEnvironment
222        extends AbstractMap<String,String>
223    {
224        private Map<Variable,Value> m;
225        private static String toString(Value v) {
226            return v == null ? null : v.toString();
227        }
228        public StringEnvironment(Map<Variable,Value> m) {this.m = m;}
229        public int size()        {return m.size();}
230        public boolean isEmpty() {return m.isEmpty();}
231        public void clear()      {       m.clear();}
232        public boolean containsKey(Object key) {
233            return m.containsKey(Variable.valueOfQueryOnly(key));
234        }
235        public boolean containsValue(Object value) {
236            return m.containsValue(Value.valueOfQueryOnly(value));
237        }
238        public String get(Object key) {
239            return toString(m.get(Variable.valueOfQueryOnly(key)));
240        }
241        public String put(String key, String value) {
242            return toString(m.put(Variable.valueOf(key),
243                                  Value.valueOf(value)));
244        }
245        public String remove(Object key) {
246            return toString(m.remove(Variable.valueOfQueryOnly(key)));
247        }
248        public Set<String> keySet() {
249            return new StringKeySet(m.keySet());
250        }
251        public Set<Map.Entry<String,String>> entrySet() {
252            return new StringEntrySet(m.entrySet());
253        }
254        public Collection<String> values() {
255            return new StringValues(m.values());
256        }
257
258        // It is technically feasible to provide a byte-oriented view
259        // as follows:
260        //      public Map<byte[],byte[]> asByteArrayMap() {
261        //          return new ByteArrayEnvironment(m);
262        //      }
263
264
265        // Convert to Unix style environ as a monolithic byte array
266        // inspired by the Windows Environment Block, except we work
267        // exclusively with bytes instead of chars, and we need only
268        // one trailing NUL on Unix.
269        // This keeps the JNI as simple and efficient as possible.
270        public byte[] toEnvironmentBlock(int[]envc) {
271            int count = m.size() * 2; // For added '=' and NUL
272            for (Map.Entry<Variable,Value> entry : m.entrySet()) {
273                count += entry.getKey().getBytes().length;
274                count += entry.getValue().getBytes().length;
275            }
276
277            byte[] block = new byte[count];
278
279            int i = 0;
280            for (Map.Entry<Variable,Value> entry : m.entrySet()) {
281                byte[] key   = entry.getKey  ().getBytes();
282                byte[] value = entry.getValue().getBytes();
283                System.arraycopy(key, 0, block, i, key.length);
284                i+=key.length;
285                block[i++] = (byte) '=';
286                System.arraycopy(value, 0, block, i, value.length);
287                i+=value.length + 1;
288                // No need to write NUL byte explicitly
289                //block[i++] = (byte) '\u0000';
290            }
291            envc[0] = m.size();
292            return block;
293        }
294    }
295
296    static byte[] toEnvironmentBlock(Map<String,String> map, int[]envc) {
297        return map == null ? null :
298            ((StringEnvironment)map).toEnvironmentBlock(envc);
299    }
300
301
302    private static class StringEntry
303        implements Map.Entry<String,String>
304    {
305        private final Map.Entry<Variable,Value> e;
306        public StringEntry(Map.Entry<Variable,Value> e) {this.e = e;}
307        public String getKey()   {return e.getKey().toString();}
308        public String getValue() {return e.getValue().toString();}
309        public String setValue(String newValue) {
310            return e.setValue(Value.valueOf(newValue)).toString();
311        }
312        public String toString() {return getKey() + "=" + getValue();}
313        public boolean equals(Object o) {
314            return o instanceof StringEntry
315                && e.equals(((StringEntry)o).e);
316        }
317        public int hashCode()    {return e.hashCode();}
318    }
319
320    private static class StringEntrySet
321        extends AbstractSet<Map.Entry<String,String>>
322    {
323        private final Set<Map.Entry<Variable,Value>> s;
324        public StringEntrySet(Set<Map.Entry<Variable,Value>> s) {this.s = s;}
325        public int size()        {return s.size();}
326        public boolean isEmpty() {return s.isEmpty();}
327        public void clear()      {       s.clear();}
328        public Iterator<Map.Entry<String,String>> iterator() {
329            return new Iterator<Map.Entry<String,String>>() {
330                Iterator<Map.Entry<Variable,Value>> i = s.iterator();
331                public boolean hasNext() {return i.hasNext();}
332                public Map.Entry<String,String> next() {
333                    return new StringEntry(i.next());
334                }
335                public void remove() {i.remove();}
336            };
337        }
338        private static Map.Entry<Variable,Value> vvEntry(final Object o) {
339            if (o instanceof StringEntry)
340                return ((StringEntry)o).e;
341            return new Map.Entry<Variable,Value>() {
342                public Variable getKey() {
343                    return Variable.valueOfQueryOnly(((Map.Entry)o).getKey());
344                }
345                public Value getValue() {
346                    return Value.valueOfQueryOnly(((Map.Entry)o).getValue());
347                }
348                public Value setValue(Value value) {
349                    throw new UnsupportedOperationException();
350                }
351            };
352        }
353        public boolean contains(Object o) { return s.contains(vvEntry(o)); }
354        public boolean remove(Object o)   { return s.remove(vvEntry(o)); }
355        public boolean equals(Object o) {
356            return o instanceof StringEntrySet
357                && s.equals(((StringEntrySet) o).s);
358        }
359        public int hashCode() {return s.hashCode();}
360    }
361
362    private static class StringValues
363          extends AbstractCollection<String>
364    {
365        private final Collection<Value> c;
366        public StringValues(Collection<Value> c) {this.c = c;}
367        public int size()        {return c.size();}
368        public boolean isEmpty() {return c.isEmpty();}
369        public void clear()      {       c.clear();}
370        public Iterator<String> iterator() {
371            return new Iterator<String>() {
372                Iterator<Value> i = c.iterator();
373                public boolean hasNext() {return i.hasNext();}
374                public String next()     {return i.next().toString();}
375                public void remove()     {i.remove();}
376            };
377        }
378        public boolean contains(Object o) {
379            return c.contains(Value.valueOfQueryOnly(o));
380        }
381        public boolean remove(Object o) {
382            return c.remove(Value.valueOfQueryOnly(o));
383        }
384        public boolean equals(Object o) {
385            return o instanceof StringValues
386                && c.equals(((StringValues)o).c);
387        }
388        public int hashCode() {return c.hashCode();}
389    }
390
391    private static class StringKeySet extends AbstractSet<String> {
392        private final Set<Variable> s;
393        public StringKeySet(Set<Variable> s) {this.s = s;}
394        public int size()        {return s.size();}
395        public boolean isEmpty() {return s.isEmpty();}
396        public void clear()      {       s.clear();}
397        public Iterator<String> iterator() {
398            return new Iterator<String>() {
399                Iterator<Variable> i = s.iterator();
400                public boolean hasNext() {return i.hasNext();}
401                public String next()     {return i.next().toString();}
402                public void remove()     {       i.remove();}
403            };
404        }
405        public boolean contains(Object o) {
406            return s.contains(Variable.valueOfQueryOnly(o));
407        }
408        public boolean remove(Object o) {
409            return s.remove(Variable.valueOfQueryOnly(o));
410        }
411    }
412
413    // Replace with general purpose method someday
414    private static int arrayCompare(byte[]x, byte[] y) {
415        int min = x.length < y.length ? x.length : y.length;
416        for (int i = 0; i < min; i++)
417            if (x[i] != y[i])
418                return x[i] - y[i];
419        return x.length - y.length;
420    }
421
422    // Replace with general purpose method someday
423    private static boolean arrayEquals(byte[] x, byte[] y) {
424        if (x.length != y.length)
425            return false;
426        for (int i = 0; i < x.length; i++)
427            if (x[i] != y[i])
428                return false;
429        return true;
430    }
431
432    // Replace with general purpose method someday
433    private static int arrayHash(byte[] x) {
434        int hash = 0;
435        for (int i = 0; i < x.length; i++)
436            hash = 31 * hash + x[i];
437        return hash;
438    }
439
440}
441