X-Git-Url: http://git.argeo.org/?a=blobdiff_plain;f=runtime%2Forg.argeo.slc.core%2Fsrc%2Fmain%2Fjava%2Forg%2Fargeo%2Fslc%2Fcore%2Fexecution%2Ftasks%2FSystemCall.java;h=6d916462c7ad44817ae3a45e620b4751140ac8cf;hb=fff2cbcf8d151f58c1348f2b0ca3d3044b789dfa;hp=5046abb0a3bd2f47d178488ab777fe5dbd356829;hpb=e05000e1e8d950dfb2b25984b171105510c6ab38;p=gpl%2Fargeo-slc.git diff --git a/runtime/org.argeo.slc.core/src/main/java/org/argeo/slc/core/execution/tasks/SystemCall.java b/runtime/org.argeo.slc.core/src/main/java/org/argeo/slc/core/execution/tasks/SystemCall.java index 5046abb0a..6d916462c 100644 --- a/runtime/org.argeo.slc.core/src/main/java/org/argeo/slc/core/execution/tasks/SystemCall.java +++ b/runtime/org.argeo.slc.core/src/main/java/org/argeo/slc/core/execution/tasks/SystemCall.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010 Mathieu Baudier + * Copyright (C) 2007-2012 Argeo GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.argeo.slc.core.execution.tasks; import java.io.File; @@ -22,12 +21,17 @@ import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.io.PipedInputStream; +import java.io.PipedOutputStream; import java.io.Writer; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.UUID; + +import javax.security.auth.callback.CallbackHandler; import org.apache.commons.exec.CommandLine; import org.apache.commons.exec.DefaultExecutor; @@ -50,6 +54,8 @@ import org.argeo.slc.core.test.SimpleResultPart; import org.argeo.slc.test.TestResult; import org.argeo.slc.test.TestStatus; import org.springframework.core.io.Resource; +import org.springframework.security.Authentication; +import org.springframework.security.context.SecurityContextHolder; /** Execute an OS specific system call. */ public class SystemCall implements Runnable { @@ -62,6 +68,7 @@ public class SystemCall implements Runnable { private String cmd = null; private List command = null; + private Executor executor = new DefaultExecutor(); private Boolean synchronous = true; private String stdErrLogLevel = "ERROR"; @@ -69,7 +76,14 @@ public class SystemCall implements Runnable { private Resource stdOutFile = null; private Resource stdErrFile = null; + private Resource stdInFile = null; + /** + * If no {@link #stdInFile} provided, writing to this stream will write to + * the stdin of the process. + */ + private OutputStream stdInSink = null; + private Boolean redirectStdOut = false; private List outputListeners = Collections @@ -84,15 +98,28 @@ public class SystemCall implements Runnable { private Boolean exceptionOnFailed = true; private Boolean mergeEnvironmentVariables = true; + private Authentication authentication; + private String osConsole = null; private String generateScript = null; + /** 24 hours */ private Long watchdogTimeout = 24 * 60 * 60 * 1000l; private TestResult testResult; private ExecutionResources executionResources; + /** Sudo the command, as root if empty or as user if not. */ + private String sudo = null; + // TODO make it more secure and robust, test only once + private final String sudoPrompt = UUID.randomUUID().toString(); + private String askPassProgram = "/usr/libexec/openssh/ssh-askpass"; + private boolean firstLine = true; + private CallbackHandler callbackHandler; + /** Chroot to the this path (must not be empty) */ + private String chroot = null; + /** Empty constructor */ public SystemCall() { @@ -127,6 +154,8 @@ public class SystemCall implements Runnable { /** Executes the system call. */ public void run() { + authentication = SecurityContextHolder.getContext().getAuthentication(); + // Manage streams Writer stdOutWriter = null; OutputStream stdOutputStream = null; @@ -145,13 +174,17 @@ public class SystemCall implements Runnable { stdErrWriter = createWriter(stdOutFile, true); } - if (stdInFile != null) - try { + try { + if (stdInFile != null) stdInStream = stdInFile.getInputStream(); - } catch (IOException e2) { - throw new SlcException("Cannot open a stream for " + stdInFile, - e2); + else { + stdInStream = new PipedInputStream(); + stdInSink = new PipedOutputStream( + (PipedInputStream) stdInStream); } + } catch (IOException e2) { + throw new SlcException("Cannot open a stream for " + stdInFile, e2); + } if (log.isTraceEnabled()) { log.debug("os.name=" + System.getProperty("os.name")); @@ -161,24 +194,28 @@ public class SystemCall implements Runnable { // Execution directory File dir = new File(getExecDirToUse()); - if (!dir.exists()) - dir.mkdirs(); + // if (!dir.exists()) + // dir.mkdirs(); // Watchdog to check for lost processes - Executor executor = new DefaultExecutor(); - executor.setWatchdog(new ExecuteWatchdog(watchdogTimeout)); + Executor executorToUse; + if (executor != null) + executorToUse = executor; + else + executorToUse = new DefaultExecutor(); + executorToUse.setWatchdog(new ExecuteWatchdog(watchdogTimeout)); if (redirectStreams) { // Redirect standard streams - executor.setStreamHandler(createExecuteStreamHandler(stdOutWriter, - stdOutputStream, stdErrWriter, stdInStream)); + executorToUse.setStreamHandler(createExecuteStreamHandler( + stdOutWriter, stdOutputStream, stdErrWriter, stdInStream)); } else { // Dummy stream handler (otherwise pump is used) - executor.setStreamHandler(new DummyexecuteStreamHandler()); + executorToUse.setStreamHandler(new DummyexecuteStreamHandler()); } - executor.setProcessDestroyer(new ShutdownHookProcessDestroyer()); - executor.setWorkingDirectory(dir); + executorToUse.setProcessDestroyer(new ShutdownHookProcessDestroyer()); + executorToUse.setWorkingDirectory(dir); // Command line to use final CommandLine commandLine = createCommandLine(); @@ -188,12 +225,11 @@ public class SystemCall implements Runnable { // Env variables Map environmentVariablesToUse = null; - if (environmentVariables.size() > 0) { - environmentVariablesToUse = new HashMap(); - if (mergeEnvironmentVariables) - environmentVariablesToUse.putAll(System.getenv()); + environmentVariablesToUse = new HashMap(); + if (mergeEnvironmentVariables) + environmentVariablesToUse.putAll(System.getenv()); + if (environmentVariables.size() > 0) environmentVariablesToUse.putAll(environmentVariables); - } // Execute ExecuteResultHandler executeResultHandler = createExecuteResultHandler(commandLine); @@ -204,7 +240,7 @@ public class SystemCall implements Runnable { try { if (synchronous) try { - int exitValue = executor.execute(commandLine, + int exitValue = executorToUse.execute(commandLine, environmentVariablesToUse); executeResultHandler.onProcessComplete(exitValue); } catch (ExecuteException e1) { @@ -212,10 +248,12 @@ public class SystemCall implements Runnable { Thread.currentThread().interrupt(); return; } + // Sleep 1s in order to make sure error logs are flushed + Thread.sleep(1000); executeResultHandler.onProcessFailed(e1); } else - executor.execute(commandLine, environmentVariablesToUse, + executorToUse.execute(commandLine, environmentVariablesToUse, executeResultHandler); } catch (SlcException e) { throw e; @@ -226,6 +264,7 @@ public class SystemCall implements Runnable { IOUtils.closeQuietly(stdOutWriter); IOUtils.closeQuietly(stdErrWriter); IOUtils.closeQuietly(stdInStream); + IOUtils.closeQuietly(stdInSink); } } @@ -233,10 +272,16 @@ public class SystemCall implements Runnable { public synchronized String function() { final StringBuffer buf = new StringBuffer(""); SystemCallOutputListener tempOutputListener = new SystemCallOutputListener() { + private Long lineCount = 0l; + public void newLine(SystemCall systemCall, String line, Boolean isError) { - if (!isError) + if (!isError) { + if (lineCount != 0l) + buf.append('\n'); buf.append(line); + lineCount++; + } } }; addOutputListener(tempOutputListener); @@ -281,11 +326,40 @@ public class SystemCall implements Runnable { throw new SlcException( "Specify the command either as a line or as a list."); else if (cmdToUse != null) { + if (chroot != null && !chroot.trim().equals("")) + cmdToUse = "chroot \"" + chroot + "\" " + cmdToUse; + if (sudo != null) { + environmentVariables.put("SUDO_ASKPASS", askPassProgram); + if (!sudo.trim().equals("")) + cmdToUse = "sudo -p " + sudoPrompt + " -u " + sudo + " " + + cmdToUse; + else + cmdToUse = "sudo -p " + sudoPrompt + " " + cmdToUse; + } + + // GENERATE COMMAND LINE commandLine = CommandLine.parse(cmdToUse); } else if (commandToUse != null) { if (commandToUse.size() == 0) throw new SlcException("Command line is empty."); + if (chroot != null && sudo != null) { + commandToUse.add(0, "chroot"); + commandToUse.add(1, chroot); + } + + if (sudo != null) { + environmentVariables.put("SUDO_ASKPASS", askPassProgram); + commandToUse.add(0, "sudo"); + commandToUse.add(1, "-p"); + commandToUse.add(2, sudoPrompt); + if (!sudo.trim().equals("")) { + commandToUse.add(3, "-u"); + commandToUse.add(4, sudo); + } + } + + // GENERATE COMMAND LINE commandLine = new CommandLine(commandToUse.get(0).toString()); for (int i = 1; i < commandToUse.size(); i++) { @@ -328,24 +402,60 @@ public class SystemCall implements Runnable { final Writer stdErrWriter, final InputStream stdInStream) { // Log writers - - PumpStreamHandler pumpStreamHandler = new PumpStreamHandler( - stdOutputStream != null ? stdOutputStream - : new LogOutputStream() { - protected void processLine(String line, int level) { - if (line != null && !line.trim().equals("")) - logStdOut(line); - if (stdOutWriter != null) - appendLineToFile(stdOutWriter, line); - } - }, new LogOutputStream() { + OutputStream stdout = stdOutputStream != null ? stdOutputStream + : new LogOutputStream() { protected void processLine(String line, int level) { + // if (firstLine) { + // if (sudo != null && callbackHandler != null + // && line.startsWith(sudoPrompt)) { + // try { + // PasswordCallback pc = new PasswordCallback( + // "sudo password", false); + // Callback[] cbs = { pc }; + // callbackHandler.handle(cbs); + // char[] pwd = pc.getPassword(); + // char[] arr = Arrays.copyOf(pwd, + // pwd.length + 1); + // arr[arr.length - 1] = '\n'; + // IOUtils.write(arr, stdInSink); + // stdInSink.flush(); + // } catch (Exception e) { + // throw new SlcException( + // "Cannot retrieve sudo password", e); + // } + // } + // firstLine = false; + // } + if (line != null && !line.trim().equals("")) - logStdErr(line); - if (stdErrWriter != null) - appendLineToFile(stdErrWriter, line); + logStdOut(line); + + if (stdOutWriter != null) + appendLineToFile(stdOutWriter, line); } - }, stdInStream); + }; + + OutputStream stderr = new LogOutputStream() { + protected void processLine(String line, int level) { + if (line != null && !line.trim().equals("")) + logStdErr(line); + if (stdErrWriter != null) + appendLineToFile(stdErrWriter, line); + } + }; + + PumpStreamHandler pumpStreamHandler = new PumpStreamHandler(stdout, + stderr, stdInStream) { + + @Override + public void stop() { + // prevents the method to block when joining stdin + if (stdInSink != null) + IOUtils.closeQuietly(stdInSink); + + super.stop(); + } + }; return pumpStreamHandler; } @@ -367,6 +477,7 @@ public class SystemCall implements Runnable { } public void onProcessFailed(ExecuteException e) { + String msg = "System call '" + commandLine + "' failed."; if (testResult != null) { forwardPath(testResult); @@ -382,6 +493,7 @@ public class SystemCall implements Runnable { }; } + @Deprecated protected void forwardPath(TestResult testResult) { // TODO: allocate a TreeSPath } @@ -391,17 +503,10 @@ public class SystemCall implements Runnable { */ protected String getExecDirToUse() { try { - File dir = null; if (execDir != null) { - // Replace '/' by local file separator, for portability - execDir.replace('/', File.separatorChar); - dir = new File(execDir).getCanonicalFile(); + return execDir; } - - if (dir == null) - return System.getProperty("user.dir"); - else - return dir.getPath(); + return System.getProperty("user.dir"); } catch (Exception e) { throw new SlcException("Cannot find exec dir", e); } @@ -421,6 +526,12 @@ public class SystemCall implements Runnable { /** Log from the underlying streams. */ protected void log(String logLevel, String line) { + // TODO optimize + if (SecurityContextHolder.getContext().getAuthentication() == null) { + SecurityContextHolder.getContext() + .setAuthentication(authentication); + } + if ("ERROR".equals(logLevel)) log.error(line); else if ("WARN".equals(logLevel)) @@ -538,6 +649,10 @@ public class SystemCall implements Runnable { this.environmentVariables = environmentVariables; } + public Map getEnvironmentVariables() { + return environmentVariables; + } + public void setWatchdogTimeout(Long watchdogTimeout) { this.watchdogTimeout = watchdogTimeout; } @@ -603,6 +718,22 @@ public class SystemCall implements Runnable { this.outputListeners = outputListeners; } + public void setExecutor(Executor executor) { + this.executor = executor; + } + + public void setSudo(String sudo) { + this.sudo = sudo; + } + + public void setCallbackHandler(CallbackHandler callbackHandler) { + this.callbackHandler = callbackHandler; + } + + public void setChroot(String chroot) { + this.chroot = chroot; + } + private class DummyexecuteStreamHandler implements ExecuteStreamHandler { public void setProcessErrorStream(InputStream is) throws IOException { @@ -621,5 +752,4 @@ public class SystemCall implements Runnable { } } - }