]> git.argeo.org Git - gpl/argeo-slc.git/blobdiff - runtime/org.argeo.slc.core/src/main/java/org/argeo/slc/core/execution/tasks/SystemCall.java
Add sudo and chroot support.
[gpl/argeo-slc.git] / runtime / org.argeo.slc.core / src / main / java / org / argeo / slc / core / execution / tasks / SystemCall.java
index 5046abb0a3bd2f47d178488ab777fe5dbd356829..6d916462c7ad44817ae3a45e620b4751140ac8cf 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2010 Mathieu Baudier <mbaudier@argeo.org>
+ * 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<Object> 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<SystemCallOutputListener> 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<String, String> environmentVariablesToUse = null;
-               if (environmentVariables.size() > 0) {
-                       environmentVariablesToUse = new HashMap<String, String>();
-                       if (mergeEnvironmentVariables)
-                               environmentVariablesToUse.putAll(System.getenv());
+               environmentVariablesToUse = new HashMap<String, String>();
+               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<String, String> 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 {
                }
 
        }
-
 }