/*******************************************************************************
* Copyright (c) 2013, 2015 Wind River Systems, Inc. and others. All rights reserved.
* This program and the accompanying materials are made available under the terms
* of the Eclipse Public License v1.0 which accompanies this distribution, and is
* available at http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Wind River Systems - initial API and implementation
*******************************************************************************/
package org.eclipse.tcf.te.runtime.utils;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.Platform;
import org.eclipse.osgi.service.environment.Constants;
/**
* Environment handling utility methods.
*/
public class Env {
// Reference to the monitor to lock if determining the native environment
private final static Object ENV_GET_MONITOR = new Object();
// Reference to the native environment with the case of the variable names preserved
private static Map<String, String> nativeEnvironmentCasePreserved = null;
/**
* Returns the merged environment of the native environment and the passed
* in environment. Passed in variables will overwrite the native environment
* if the same variables are set there.
* <p>
* For use with terminals, the parameter <code>terminal</code> should be set to
* <code>true</code>. In this case, the method will assure that the <code>TERM</code>
* environment variable is always set to <code>ANSI</code> and is not overwritten
* by the passed in environment.
*
* @param envp The environment to set on top of the native environment or <code>null</code>.
* @param terminal <code>True</code> if used with an terminal, <code>false</code> otherwise.
*
* @return The merged environment.
*/
public static String[] getEnvironment(String[] envp, boolean terminal) {
// Get the cached native environment
Map<String, String> nativeEnv = getNativeEnvironmentCasePreserved();
// Make a copy of the native environment so it can be manipulated without changing
// the cached environment
Map<String, String> env = new LinkedHashMap<String, String>(nativeEnv);
// Set the TERM environment variable if in terminal mode
if (terminal) env.put("TERM", "xterm"); //$NON-NLS-1$ //$NON-NLS-2$
// On Windows, the environment variable names are not case-sensitive. However,
// we desire to preserve the original case. Build up a translation map between
// an all lowercase name and the original environment name
Map<String, String> k2n = null;
if (Host.isWindowsHost()) {
k2n = new HashMap<String, String>();
for (String name : env.keySet()) {
k2n.put(name.toLowerCase(), name);
}
}
// If a "local" environment is provided, merge it with the native
// environment.
if (envp != null) {
for (int i = 0; i < envp.length; i++) {
// The full provided variable in form "name=value"
String envpPart = envp[i];
// Split the variable
int eqIdx = envpPart.indexOf('=');
if (eqIdx < 1)
continue;
String name = envpPart.substring(0, eqIdx);
// Map the variable name to the real environment name (Windows only)
if (Host.isWindowsHost()) {
if (k2n.containsKey(name.toLowerCase())) {
String candidate = k2n.get(name.toLowerCase());
Assert.isNotNull(candidate);
name = candidate;
}
// Filter out environment variables with bad names
if ("".equals(name.trim()) || name.contains(":")) { //$NON-NLS-1$ //$NON-NLS-2$
continue;
}
}
// Get the variable value
String value = envpPart.substring(eqIdx+1);
// Don't overwrite the TERM variable if in terminal mode
if (terminal && "TERM".equals(name)) continue; //$NON-NLS-1$
// If a variable with the name does not exist, just append it
if (!env.containsKey(name) && !"<unset>".equals(value)) { //$NON-NLS-1$
env.put(name, value);
} else if (env.containsKey(name)) {
// If the value contains the special placeholder "<unset>", remove the variable from the environment
if ("<unset>".equals(value)) {//$NON-NLS-1$
env.remove(name);
} else {
// A variable with the name already exist, check if the value is different
String oldValue = env.get(name);
if (oldValue != null && !oldValue.equals(value) || oldValue == null && value != null) {
env.put(name, value);
}
}
}
}
}
// Convert into an array of strings
List<String> keys = new ArrayList<String>(env.keySet());
// On Windows hosts, sort the environment keys
if (Host.isWindowsHost()) Collections.sort(keys);
Iterator<String> iter = keys.iterator();
List<String> strings = new ArrayList<String>(env.size());
StringBuilder buffer = null;
while (iter.hasNext()) {
String key = iter.next();
buffer = new StringBuilder(key);
buffer.append('=').append(env.get(key));
strings.add(buffer.toString());
}
return strings.toArray(new String[strings.size()]);
}
/**
* Determine the native environment.
*
* @return The native environment, or an empty map.
*/
private static Map<String, String> getNativeEnvironmentCasePreserved() {
synchronized (ENV_GET_MONITOR) {
if (nativeEnvironmentCasePreserved == null) {
nativeEnvironmentCasePreserved = new LinkedHashMap<String, String>();
cacheNativeEnvironment(nativeEnvironmentCasePreserved);
}
return new LinkedHashMap<String, String>(nativeEnvironmentCasePreserved);
}
}
/**
* Query the native environment and store it to the specified cache.
*
* @param cache The environment cache. Must not be <code>null</code>.
*/
private static void cacheNativeEnvironment(Map<String, String> cache) {
Assert.isNotNull(cache);
try {
String nativeCommand = null;
if (Platform.getOS().equals(Constants.OS_WIN32)) {
nativeCommand = "cmd.exe /C set"; //$NON-NLS-1$
} else if (!Platform.getOS().equals(Constants.OS_UNKNOWN)) {
nativeCommand = "env"; //$NON-NLS-1$
}
if (nativeCommand == null) { return; }
Process process = Runtime.getRuntime().exec(nativeCommand);
// read process directly on other platforms
// we need to parse out matching '{' and '}' for function declarations in .bash environments
// pattern is [function name]=() { and we must find the '}' on its own line with no trailing ';'
InputStream stream = process.getInputStream();
InputStreamReader isreader = new InputStreamReader(stream);
BufferedReader reader = new BufferedReader(isreader);
try {
String line = reader.readLine();
while (line != null) {
String key = null;
String value = null;
int func = line.indexOf("=()"); //$NON-NLS-1$
if (func > 0) {
key = line.substring(0, func);
// scan until we find the closing '}' with no following chars
value = line.substring(func + 1);
do {
line = reader.readLine();
if (line == null)
break;
if (line.equals("}")) //$NON-NLS-1$
value += ';';
value += ' ' + line;
} while (!line.equals("}")); //$NON-NLS-1$
line = reader.readLine();
} else {
int separator = line.indexOf('=');
if (separator > 0) {
key = line.substring(0, separator);
value = line.substring(separator + 1);
StringBuilder bufValue = new StringBuilder(value);
line = reader.readLine();
if (line != null) {
// this line has a '=' read ahead to check next line for '=', might be broken on more
// than one line
separator = line.indexOf('=');
while (separator < 0) {
bufValue.append(line.trim());
line = reader.readLine();
if (line == null) {
// if next line read is the end of the file quit the loop
break;
}
separator = line.indexOf('=');
}
}
value = bufValue.toString();
}
}
if (key != null) {
cache.put(key, value);
} else {
line = reader.readLine();
}
}
} finally {
reader.close();
}
} catch (IOException e) {
// Native environment-fetching code failed.
// This can easily happen and is not useful to log.
}
}
}