package org.eclipse.dltk.core.internal.rse; import java.io.BufferedOutputStream; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Writer; import java.net.URI; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Random; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.Status; import org.eclipse.dltk.compiler.util.Util; import org.eclipse.dltk.core.DLTKCore; import org.eclipse.dltk.core.environment.IDeployment; import org.eclipse.dltk.core.environment.IEnvironment; import org.eclipse.dltk.core.environment.IExecutionEnvironment; import org.eclipse.dltk.core.environment.IExecutionLogger; import org.eclipse.dltk.core.internal.rse.perfomance.RSEPerfomanceStatistics; import org.eclipse.dltk.internal.launching.execution.EFSDeployment; import org.eclipse.dltk.utils.TextUtils; import org.eclipse.osgi.util.NLS; import org.eclipse.rse.core.model.IHost; import org.eclipse.rse.core.subsystems.ISubSystem; import org.eclipse.rse.services.clientserver.messages.SystemMessageException; import org.eclipse.rse.services.files.IFileService; import org.eclipse.rse.services.shells.IHostShell; import org.eclipse.rse.services.shells.IShellService; import org.eclipse.rse.subsystems.files.core.servicesubsystem.IFileServiceSubSystem; import org.eclipse.rse.subsystems.shells.core.subsystems.servicesubsystem.IShellServiceSubSystem; public class RSEExecEnvironment implements IExecutionEnvironment { private static final String EXEC_BIN_SH = "exec /bin/sh "; //$NON-NLS-1$ private static final String TOKEN_PREFIX = "DLTK_INITIAL_PREFIX_EXECUTION_STRING:"; //$NON-NLS-1$ private final RSEEnvironment environment; private static int counter = -1; private static final Map<IHost, Map<String, String>> hostToEnvironment = new HashMap<>(); public RSEExecEnvironment(RSEEnvironment env) { this.environment = env; } @Override public IDeployment createDeployment() { if (RSEPerfomanceStatistics.PERFOMANCE_TRACING) { RSEPerfomanceStatistics .inc(RSEPerfomanceStatistics.DEPLOYMENTS_CREATED); } if (!getEnvironment().connect()) { return null; } String tmpDir = getTempDir(); if (tmpDir != null) { String rootPath = tmpDir + environment.getSeparator() + getTempName("dltk", ".tmp"); //$NON-NLS-1$ //$NON-NLS-2$ URI rootUri = createRemoteURI(environment.getHost(), rootPath); try { return new EFSDeployment(environment, rootUri); } catch (CoreException e) { if (DLTKCore.DEBUG) { e.printStackTrace(); } } } return null; } private URI createRemoteURI(IHost host, String rootPath) { return RSEEnvironment.getURIFor(host, rootPath); } @SuppressWarnings("unchecked") private <SUBSYSTEM extends ISubSystem> SUBSYSTEM getSubSystem(IHost host, Class<SUBSYSTEM> clazz) { ISubSystem[] subsys = host.getSubSystems(); for (int i = 0; i < subsys.length; i++) { if (clazz.isInstance(subsys[i])) return (SUBSYSTEM) subsys[i]; } return null; } private String getTempName(String prefix, String suffix) { if (counter == -1) { counter = new Random().nextInt() & 0xffff; } counter++; return prefix + Integer.toString(counter) + suffix; } private String getTempDir() { final IHost host = environment.getHost(); final IShellServiceSubSystem system = getSubSystem(host, IShellServiceSubSystem.class); if (system == null) { DLTKRSEPlugin.logWarning(NLS.bind( Messages.RSEExecEnvironment_hostNotFound, host.getName())); return null; } try { system.connect(new NullProgressMonitor(), false); final String tmp = system.getConnectorService().getTempDirectory(); if (tmp != null && tmp.length() != 0) { return tmp; } else { return "/tmp"; //$NON-NLS-1$ } } catch (Exception e) { if (DLTKCore.DEBUG) { e.printStackTrace(); } } return null; } @Override public Process exec(String[] cmdLine, IPath workingDir, String[] environment) throws CoreException { return exec(cmdLine, workingDir, environment, null); } @Override public Process exec(String[] cmdLine, IPath workingDir, String[] environment, IExecutionLogger logger) throws CoreException { if (RSEPerfomanceStatistics.PERFOMANCE_TRACING) { RSEPerfomanceStatistics .inc(RSEPerfomanceStatistics.EXECUTION_COUNT); } final long start = RSEPerfomanceStatistics.PERFOMANCE_TRACING ? System .currentTimeMillis() : 0; final IHost host = this.environment.getHost(); // obtain IFileService final IFileServiceSubSystem fileService = getSubSystem(host, IFileServiceSubSystem.class); if (fileService == null) { throw new CoreException(newStatus( RSEStatusConstants.NO_FILE_SERVICE, NLS.bind( Messages.RSEExecEnvironment_NoFileServicerError, host.getAliasName()), null)); } if (!getEnvironment().connect()) { throw new CoreException(newStatus( RSEStatusConstants.NO_FILE_SERVICE, NLS.bind( Messages.RSEExecEnvironment_NotConnected, host .getAliasName()), null)); } // remote path for launcher file final String tmpLauncherDir = getTempDir(); final String tmpLauncher = "dltk-" + fileService.getUserId() + System.currentTimeMillis() + ".sh"; //$NON-NLS-1$ //$NON-NLS-2$ final String tmpLauncherPath = tmpLauncherDir + fileService.getSeparatorChar() + tmpLauncher; // build commands final List<String> commands = new ArrayList<>(); if (workingDir != null) { final String p = this.environment.convertPathToString(workingDir); commands.add("cd " + p); //$NON-NLS-1$ } else { commands.add("cd /"); //$NON-NLS-1$ } /* * Sometimes environment variables aren't set by the runCommand() call, * so use export. */ if (environment != null) { // TODO Skip environment variables which are already in shell? for (int i = 0; i < environment.length; i++) { final String env = environment[i]; if (isSafeEnvironmentVariable(extractName(env))) { commands.add(buildExportCommand(env)); } } } final String token = TOKEN_PREFIX + System.currentTimeMillis(); final String echoCmd = "echo \"" + token + "\""; //$NON-NLS-1$ //$NON-NLS-2$ commands.add(echoCmd); commands.add(buildCommand(cmdLine)); commands.add(echoCmd); commands.add("rm -f " + tmpLauncherPath); //$NON-NLS-1$ if (logger != null) { logger.logLine("launcher=" + tmpLauncherDir + '/' + tmpLauncher); //$NON-NLS-1$ for (String command : commands) { logger.logLine("launcher:" + command); //$NON-NLS-1$ } logger.logLine("launcher:END"); //$NON-NLS-1$ } // save launcher to the remote location try { try (final OutputStream os = fileService.getFileService().getOutputStream(tmpLauncherDir, tmpLauncher, IFileService.TEXT_MODE, new NullProgressMonitor())) { try (final Writer writer = new OutputStreamWriter(new BufferedOutputStream(os, 4096), fileService.getRemoteEncoding())) { for (String command : commands) { writer.write(command); writer.write('\n'); } writer.flush(); } } } catch (Exception e) { final String msg = NLS.bind(Messages.RSEExecEnvironment_LauncherUploadError, host.getAliasName(), e.getMessage()); throw new CoreException(newStatus(RSEStatusConstants.LAUNCHER_UPLOAD_ERROR, msg, e)); } // execute uploaded launcher in remote shell final IShellServiceSubSystem shell = getSubSystem(host, IShellServiceSubSystem.class); if (shell == null) { throw new CoreException(newStatus( RSEStatusConstants.NO_SHELL_SERVICE, NLS.bind( Messages.RSEExecEnvironment_NoShellService, host .getAliasName()), null)); } try { shell.connect(new NullProgressMonitor(), false); } catch (Exception e) { throw new CoreException(newStatus(RSEStatusConstants.CONNECT_ERROR, NLS.bind(Messages.RSEExecEnvironment_ErrorConnecting, host .getAliasName(), e.getMessage()), e)); } if (!shell.isConnected()) { throw new CoreException(newStatus( RSEStatusConstants.NOT_CONNECTED_ERROR, NLS.bind( Messages.RSEExecEnvironment_NotConnected, host .getAliasName()), null)); } // TODO try to use "exec" channel instead of "shell" one. final IShellService shellService = shell.getShellService(); final String command = EXEC_BIN_SH + tmpLauncherPath; final IHostShell hostShell; try { hostShell = shellService.runCommand(Util.EMPTY_STRING, command, environment, new NullProgressMonitor()); } catch (SystemMessageException e) { throw new CoreException(newStatus( RSEStatusConstants.COMMAND_RUN_ERROR, NLS.bind( Messages.RSEExecEnvironment_ErrorRunningCommand, host.getAliasName(), e.getMessage()), e)); } // wrap shell as java.lang.Process and return try { return new MyHostShellProcessAdapter(hostShell, token, logger); } catch (Exception e) { hostShell.writeToShell(MyHostShellProcessAdapter.CTRL_C); hostShell.exit(); throw new CoreException(newStatus( RSEStatusConstants.INTERNAL_ERROR, NLS.bind( Messages.RSEExecEnvironment_ProcessCreateError, e .getMessage()), e)); } finally { if (RSEPerfomanceStatistics.PERFOMANCE_TRACING) { RSEPerfomanceStatistics.inc( RSEPerfomanceStatistics.EXECUTION_TIME, System .currentTimeMillis() - start); } } } private static final List<String> UNSAFE_ENV_VARS = Arrays .asList(TextUtils .split( "GROUPS;BASH_ARGC;BASH_ARGV;BASH_SOURCE;BASH_LINENO;BASH_VERSINFO;EUID;PPID;SHELLOPTS;UID", ';')); //$NON-NLS-1$ /** * @param envVarName * @return */ @Override public boolean isSafeEnvironmentVariable(String envVarName) { return !UNSAFE_ENV_VARS.contains(envVarName); } private static String buildExportCommand(String envEntry) { return toShellArguments(envEntry) + ";export " + extractName(envEntry); //$NON-NLS-1$ } private static Status newStatus(int code, String msg, final Throwable exception) { return new Status(IStatus.ERROR, DLTKRSEPlugin.PLUGIN_ID, code, msg, exception); } /** * @param environmentEntry * @return */ private static String extractName(String environmentEntry) { final int pos = environmentEntry.indexOf('='); return pos > 0 ? environmentEntry.substring(0, pos) : environmentEntry; } private static int scanMissingQuote(String cmd) { int quote1 = -1; int quote2 = -1; for (int i = 0; i < cmd.length(); i++) { final char ch = cmd.charAt(i); if (ch == '"' && quote1 == -1) { quote2 = quote2 == -1 ? i : -1; } else if (ch == '\'' && quote2 == -1) { quote1 = quote1 == -1 ? i : -1; } } if (quote1 != -1 || quote2 != -1) { if (quote1 == -1) { return quote2; } else if (quote2 == -1) { return quote1; } else { return Math.min(quote1, quote2); } } return -1; } private static String toShellArguments(String cmd) { final int quote = scanMissingQuote(cmd); if (quote != -1) { return toShellArguments(cmd.substring(0, quote)) + "\\" //$NON-NLS-1$ + cmd.charAt(quote) + toShellArguments(cmd.substring(quote + 1)); } boolean quote1 = false; boolean quote2 = false; final StringBuilder sb = new StringBuilder(cmd.length() * 2); for (int i = 0; i < cmd.length(); i++) { final char ch = cmd.charAt(i); if (ch == '"' && !quote1) { quote2 = !quote2; } else if (ch == '\'' && !quote2) { quote1 = !quote1; } if (ch == ' ' && !quote1 && !quote2) { sb.append('\\'); } sb.append(ch); } return sb.toString(); } // public static void main(String[] args) { // System.out.println(toShellArguments("A='")); // System.out.println(toShellArguments("B1='\"")); // System.out.println(toShellArguments("B1=\"'")); // System.out.println(toShellArguments("C=\" \"")); // System.out.println(toShellArguments("D=\" \"'\" \"")); // } private String buildCommand(String[] cmdLine) { StringBuffer cmd = new StringBuffer(); for (int i = 0; i < cmdLine.length; i++) { if (i != 0) { cmd.append(" "); //$NON-NLS-1$ } cmd.append(cmdLine[i]); } return cmd.toString(); } @Override public Map<String, String> getEnvironmentVariables(boolean realyNeed) { if (!getEnvironment().connect()) { return null; } if (!realyNeed) { return new HashMap<>(); } final long start = System.currentTimeMillis(); synchronized (hostToEnvironment) { final Map<String, String> result = hostToEnvironment .get(environment.getHost()); if (result != null) { return new HashMap<>(result); } } final Map<String, String> result = new HashMap<>(); try { Process process = exec(new String[] { "set" }, Path.EMPTY, null); //$NON-NLS-1$ if (process != null) { final BufferedReader input = new BufferedReader( new InputStreamReader(process.getInputStream())); Thread t = new Thread(NLS.bind( Messages.RSEExecEnvironment_fetchEnvVars, environment .getHost().getName())) { @Override public void run() { try { while (true) { String line = input.readLine(); if (line == null) { break; } line = line.trim(); int pos = line.indexOf("="); //$NON-NLS-1$ if (pos != -1) { String varName = line.substring(0, pos); String varValue = line.substring(pos + 1); if (isValid(varName, varValue)) { result.put(varName, varValue); } } } } catch (IOException e) { if (DLTKCore.DEBUG) DLTKRSEPlugin.log(e); } } private boolean isValid(String varName, String varValue) { return !"".equals(varName) && !"_".equals(varName); //$NON-NLS-1$ //$NON-NLS-2$ } }; t.start(); try { t.join(25000);// No more than 25 seconds } catch (InterruptedException e) { DLTKRSEPlugin.log(e); } process.destroy(); } } catch (CoreException e) { DLTKRSEPlugin.log(e); } if (!result.isEmpty()) { synchronized (hostToEnvironment) { hostToEnvironment.put(environment.getHost(), Collections .unmodifiableMap(result)); } } if (RSEPerfomanceStatistics.PERFOMANCE_TRACING) { final long end = System.currentTimeMillis(); RSEPerfomanceStatistics .inc(RSEPerfomanceStatistics.ENVIRONMENT_RECEIVE_COUNT); RSEPerfomanceStatistics.inc( RSEPerfomanceStatistics.ENVIRONMENT_RECEIVE_TIME, (end - start)); } return result; } @Override public IEnvironment getEnvironment() { return environment; } @Override public boolean isValidExecutableAndEquals(String possibleName, IPath path) { if (environment.getHost().getSystemType().isWindows()) { possibleName = possibleName.toLowerCase(); String fName = path.removeFileExtension().toString().toLowerCase(); String ext = path.getFileExtension(); if (possibleName.equals(fName) && ("exe".equalsIgnoreCase(ext) || "bat".equalsIgnoreCase(ext))) { //$NON-NLS-1$ //$NON-NLS-2$ return true; } } else { String fName = path.lastSegment(); if (fName.equals(possibleName)) { return true; } } return false; } }