Package hudson

Source Code of hudson.Launcher$LocalLauncher

/*
* The MIT License
*
* Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Stephen Connolly
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package hudson;

import hudson.Proc.LocalProc;
import hudson.Proc.RemoteProc;
import hudson.model.Computer;
import hudson.model.Hudson;
import hudson.model.TaskListener;
import hudson.model.Node;
import hudson.remoting.Callable;
import hudson.remoting.Channel;
import hudson.remoting.Pipe;
import hudson.remoting.RemoteInputStream;
import hudson.remoting.RemoteOutputStream;
import hudson.remoting.VirtualChannel;
import hudson.util.StreamCopyThread;
import hudson.util.ArgumentListBuilder;
import hudson.util.ProcessTree;
import org.apache.commons.io.input.NullInputStream;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.Map;
import java.util.List;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;

import static org.apache.commons.io.output.NullOutputStream.NULL_OUTPUT_STREAM;

/**
* Starts a process.
*
* <p>
* This hides the difference between running programs locally vs remotely.
*
*
* <h2>'env' parameter</h2>
* <p>
* To allow important environment variables to be copied over to the remote machine,
* the 'env' parameter shouldn't contain default inherited environment variables
* (which often contains machine-specific information, like PATH, TIMEZONE, etc.)
*
* <p>
* {@link Launcher} is responsible for inheriting environment variables.
*
*
* @author Kohsuke Kawaguchi, Winston Prakash (bug fixes)
*
* @see FilePath#createLauncher(TaskListener)
*/
public abstract class Launcher {

    protected final TaskListener listener;

    protected final VirtualChannel channel;

    public Launcher(TaskListener listener, VirtualChannel channel) {
        this.listener = listener;
        this.channel = channel;
    }

    /**
     * Constructor for a decorator.
     */
    protected Launcher(Launcher launcher) {
        this(launcher.listener, launcher.channel);
    }

    /**
     * Gets the channel that can be used to run a program remotely.
     *
     * @return
     *      null if the target node is not configured to support this.
     *      this is a transitional measure.
     *      Note that a launcher for the master is always non-null.
     */
    public VirtualChannel getChannel() {
        return channel;
    }

    /**
     * Gets the {@link TaskListener} that this launcher uses to
     * report the commands that it's executing.
     */
    public TaskListener getListener() {
        return listener;
    }

    /**
     * If this {@link Launcher} is encapsulating an execution on a specific {@link Computer},
     * return it.
     *
     * <p>
     * Because of the way internal Hudson abstractions are set up (that is, {@link Launcher} only
     * needs a {@link VirtualChannel} to do its job and isn't really required that the channel
     * comes from an existing {@link Computer}), this method may not always the right {@link Computer} instance.
     *
     * @return
     *      null if this launcher is not created from a {@link Computer} object.
     * @deprecated since 2008-11-16.
     *      See the javadoc for why this is inherently unreliable. If you are trying to
     *      figure out the current {@link Computer} from within a build, use
     *      {@link Computer#currentComputer()} 
     */
    public Computer getComputer() {
        for( Computer c : Hudson.getInstance().getComputers() )
            if(c.getChannel()==channel)
                return c;
        return null;
    }

    /**
     * Builder pattern for configuring a process to launch.
     * @since 1.311
     */
    public final class ProcStarter {
        protected List<String> commands;
        protected boolean[] masks;
        protected FilePath pwd;
        protected OutputStream stdout = NULL_OUTPUT_STREAM, stderr;
        protected InputStream stdin = new NullInputStream(0);
        protected String[] envs;

        public ProcStarter cmds(String... args) {
            return cmds(Arrays.asList(args));
        }

        public ProcStarter cmds(File program, String... args) {
            commands = new ArrayList<String>(args.length+1);
            commands.add(program.getPath());
            commands.addAll(Arrays.asList(args));
            return this;
        }

        public ProcStarter cmds(List<String> args) {
            commands = new ArrayList<String>(args);
            return this;
        }

        public ProcStarter cmds(ArgumentListBuilder args) {
            commands = args.toList();
            masks = args.toMaskArray();
            return this;
        }

        public List<String> cmds() {
            return commands;
        }

        public ProcStarter masks(boolean... masks) {
            this.masks = masks;
            return this;
        }

        public boolean[] masks() {
            return masks;
        }

        public ProcStarter pwd(FilePath workDir) {
            this.pwd = workDir;
            return this;
        }

        public ProcStarter pwd(File workDir) {
            return pwd(new FilePath(workDir));
        }

        public ProcStarter pwd(String workDir) {
            return pwd(new File(workDir));
        }

        public FilePath pwd() {
            return pwd;
        }

        public ProcStarter stdout(OutputStream out) {
            this.stdout = out;
            return this;
        }

        /**
         * Sends the stdout to the given {@link TaskListener}.
         */
        public ProcStarter stdout(TaskListener out) {
            return stdout(out.getLogger());
        }

        public OutputStream stdout() {
            return stdout;
        }

        /**
         * Controls where the stderr of the process goes.
         * By default, it's bundled into stdout.
         */
        public ProcStarter stderr(OutputStream err) {
            this.stderr =  err;
            return this;
        }

        public OutputStream stderr() {
            return stderr;
        }

        /**
         * Controls where the stdin of the process comes from.
         * By default, <tt>/dev/null</tt>.
         */
        public ProcStarter stdin(InputStream in) {
            this.stdin = in;
            return this;
        }

        public InputStream stdin() {
            return stdin;
        }

        /**
         * Sets the environment variable overrides.
         *
         * <p>
         * In adition to what the current process
         * is inherited (if this is going to be launched from a slave agent, that
         * becomes the "current" process), these variables will be also set.
         */
        public ProcStarter envs(Map<String, String> overrides) {
            return envs(Util.mapToEnv(overrides));
        }

        /**
         * @param overrides
         *      List of "VAR=VALUE". See {@link #envs(Map)} for the semantics.
         */
        public ProcStarter envs(String... overrides) {
            this.envs = overrides;
            return this;
        }

        public String[] envs() {
            return envs;
        }

        /**
         * Starts the new process as configured.
         */
        public Proc start() throws IOException {
            return launch(this);
        }

        /**
         * Starts the process and waits for its completion.
         */
        public int join() throws IOException, InterruptedException {
            return start().join();
        }

        /**
         * Copies a {@link ProcStarter}.
         */
        public ProcStarter copy() {
            return new ProcStarter().cmds(commands).pwd(pwd).masks(masks).stdin(stdin).stdout(stdout).stderr(stderr).envs(envs);
        }
    }

    /**
     * Launches a process by using a {@linkplain ProcStarter builder-pattern} to configure
     * the parameters.
     */
    public final ProcStarter launch() {
        return new ProcStarter();
    }

    /**
     * @deprecated as of 1.311
     *      Use {@link #launch()} and its associated builder pattern
     */
    public final Proc launch(String cmd, Map<String,String> env, OutputStream out, FilePath workDir) throws IOException {
        return launch(cmd,Util.mapToEnv(env),out,workDir);
    }

    /**
     * @deprecated as of 1.311
     *      Use {@link #launch()} and its associated builder pattern
     */
    public final Proc launch(String[] cmd, Map<String, String> env, OutputStream out, FilePath workDir) throws IOException {
        return launch(cmd, Util.mapToEnv(env), out, workDir);
    }

    /**
     * @deprecated as of 1.311
     *      Use {@link #launch()} and its associated builder pattern
     */
    public final Proc launch(String[] cmd, Map<String, String> env, InputStream in, OutputStream out) throws IOException {
        return launch(cmd, Util.mapToEnv(env), in, out);
    }

    /**
     * Launch a command with optional censoring of arguments from the listener (Note: <strong>The censored portions will
     * remain visible through /proc, pargs, process explorer, etc. i.e. people logged in on the same machine</strong>
     * This version of the launch command just ensures that it is not visible from a build log which is exposed via the
     * web)
     *
     * @param cmd     The command and all it's arguments.
     * @param mask    Which of the command and arguments should be masked from the listener
     * @param env     Environment variable overrides.
     * @param out     stdout and stderr of the process will be sent to this stream. the stream won't be closed.
     * @param workDir null if the working directory could be anything.
     * @return The process of the command.
     * @throws IOException When there are IO problems.
     *
     * @deprecated as of 1.311
     *      Use {@link #launch()} and its associated builder pattern
     */
    public final Proc launch(String[] cmd, boolean[] mask, Map<String, String> env, OutputStream out, FilePath workDir) throws IOException {
        return launch(cmd, mask, Util.mapToEnv(env), out, workDir);
    }

    /**
     * Launch a command with optional censoring of arguments from the listener (Note: <strong>The censored portions will
     * remain visible through /proc, pargs, process explorer, etc. i.e. people logged in on the same machine</strong>
     * This version of the launch command just ensures that it is not visible from a build log which is exposed via the
     * web)
     *
     * @param cmd     The command and all it's arguments.
     * @param mask    Which of the command and arguments should be masked from the listener
     * @param env     Environment variable overrides.
     * @param in      null if there's no input.
     * @param out     stdout and stderr of the process will be sent to this stream. the stream won't be closed.
     * @return The process of the command.
     * @throws IOException When there are IO problems.
     *
     * @deprecated as of 1.311
     *      Use {@link #launch()} and its associated builder pattern
     */
    public final Proc launch(String[] cmd, boolean[] mask, Map<String, String> env, InputStream in, OutputStream out) throws IOException {
        return launch(cmd, mask, Util.mapToEnv(env), in, out);
    }

    /**
     * @deprecated as of 1.311
     *      Use {@link #launch()} and its associated builder pattern
     */
    public final Proc launch(String cmd,String[] env,OutputStream out, FilePath workDir) throws IOException {
        return launch(Util.tokenize(cmd),env,out,workDir);
    }

    /**
     * @deprecated as of 1.311
     *      Use {@link #launch()} and its associated builder pattern
     */
    public final Proc launch(String[] cmd, String[] env, OutputStream out, FilePath workDir) throws IOException {
        return launch(cmd, env, null, out, workDir);
    }

    /**
     * @deprecated as of 1.311
     *      Use {@link #launch()} and its associated builder pattern
     */
    public final Proc launch(String[] cmd, String[] env, InputStream in, OutputStream out) throws IOException {
        return launch(cmd, env, in, out, null);
    }

    /**
     * Launch a command with optional censoring of arguments from the listener (Note: <strong>The censored portions will
     * remain visible through /proc, pargs, process explorer, etc. i.e. people logged in on the same machine</strong>
     * This version of the launch command just ensures that it is not visible from a build log which is exposed via the
     * web)
     *
     * @param cmd     The command and all it's arguments.
     * @param mask    Which of the command and arguments should be masked from the listener
     * @param env     Environment variable overrides.
     * @param out     stdout and stderr of the process will be sent to this stream. the stream won't be closed.
     * @param workDir null if the working directory could be anything.
     * @return The process of the command.
     * @throws IOException When there are IO problems.
     *
     * @deprecated as of 1.311
     *      Use {@link #launch()} and its associated builder pattern
     */
    public final Proc launch(String[] cmd, boolean[] mask, String[] env, OutputStream out, FilePath workDir) throws IOException {
        return launch(cmd, mask, env, null, out, workDir);
    }

    /**
     * Launch a command with optional censoring of arguments from the listener (Note: <strong>The censored portions will
     * remain visible through /proc, pargs, process explorer, etc. i.e. people logged in on the same machine</strong>
     * This version of the launch command just ensures that it is not visible from a build log which is exposed via the
     * web)
     *
     * @param cmd     The command and all it's arguments.
     * @param mask    Which of the command and arguments should be masked from the listener
     * @param env     Environment variable overrides.
     * @param in      null if there's no input.
     * @param out     stdout and stderr of the process will be sent to this stream. the stream won't be closed.
     * @return The process of the command.
     * @throws IOException When there are IO problems.
     *
     * @deprecated as of 1.311
     *      Use {@link #launch()} and its associated builder pattern
     */
    public final Proc launch(String[] cmd, boolean[] mask, String[] env, InputStream in, OutputStream out) throws IOException {
        return launch(cmd, mask, env, in, out, null);
    }

    /**
     * @param env
     *      Environment variable overrides.
     * @param in
     *      null if there's no input.
     * @param workDir
     *      null if the working directory could be anything.
     * @param out
     *      stdout and stderr of the process will be sent to this stream.
     *      the stream won't be closed.
     *
     * @deprecated as of 1.311
     *      Use {@link #launch()} and its associated builder pattern
     */
    public Proc launch(String[] cmd, String[] env, InputStream in, OutputStream out, FilePath workDir) throws IOException {
        return launch(launch().cmds(cmd).envs(env).stdin(in).stdout(out).pwd(workDir));
    }

    /**
     * Launch a command with optional censoring of arguments from the listener (Note: <strong>The censored portions will
     * remain visible through /proc, pargs, process explorer, etc. i.e. people logged in on the same machine</strong>
     * This version of the launch command just ensures that it is not visible from a build log which is exposed via the
     * web)
     *
     * @param cmd     The command and all it's arguments.
     * @param mask    Which of the command and arguments should be masked from the listener
     * @param env     Environment variable overrides.
     * @param in      null if there's no input.
     * @param out     stdout and stderr of the process will be sent to this stream. the stream won't be closed.
     * @param workDir null if the working directory could be anything.
     * @return The process of the command.
     * @throws IOException When there are IO problems.
     *
     * @deprecated as of 1.311
     *      Use {@link #launch()} and its associated builder pattern
     */
    public Proc launch(String[] cmd, boolean[] mask, String[] env, InputStream in, OutputStream out, FilePath workDir) throws IOException {
        return launch(launch().cmds(cmd).masks(mask).envs(env).stdin(in).stdout(out).pwd(workDir));
    }

    /**
     * Primarily invoked from {@link ProcStarter#start()} to start a process with a specific launcher.
     */
    public abstract Proc launch(ProcStarter starter) throws IOException;

    /**
     * Launches a specified process and connects its input/output to a {@link Channel}, then
     * return it.
     *
     * <p>
     * When the returned channel is terminated, the process will be killed.
     *
     * @param out
     *      Where the stderr from the launched process will be sent.
     * @param workDir
     *      The working directory of the new process, or null to inherit
     *      from the current process
     * @param envVars
     *      Environment variable overrides. In addition to what the current process
     *      is inherited (if this is going to be launched from a slave agent, that
     *      becomes the "current" process), these variables will be also set.
     */
    public abstract Channel launchChannel(String[] cmd, OutputStream out, FilePath workDir, Map<String,String> envVars) throws IOException, InterruptedException;

    /**
     * Returns true if this {@link Launcher} is going to launch on Unix.
     */
    public boolean isUnix() {
        return File.pathSeparatorChar==':';
    }

    /**
     * Calls {@link ProcessTree#killAll(Map)} to kill processes.
     */
    public abstract void kill(Map<String,String> modelEnvVars) throws IOException, InterruptedException;

    /**
     * Prints out the command line to the listener so that users know what we are doing.
     */
    protected final void printCommandLine(String[] cmd, FilePath workDir) {
        StringBuilder buf = new StringBuilder();
        if (workDir != null) {
            buf.append('[');
            if(showFullPath)
                buf.append(workDir.getRemote());
            else
                buf.append(workDir.getRemote().replaceFirst("^.+[/\\\\]", ""));
            buf.append("] ");
        }
        buf.append('$');
        for (String c : cmd) {
            buf.append(' ');
            if(c.indexOf(' ')>=0) {
                if(c.indexOf('"')>=0)
                    buf.append('\'').append(c).append('\'');
                else
                    buf.append('"').append(c).append('"');
            } else
                buf.append(c);
        }
        listener.getLogger().println(buf.toString());
    }

    /**
     * Prints out the command line to the listener with some portions masked to prevent sensitive information from being
     * recorded on the listener.
     *
     * @param cmd     The commands
     * @param mask    An array of booleans which control whether a cmd element should be masked (<code>true</code>) or
     *                remain unmasked (<code>false</code>).
     * @param workDir The work dir.
     */
    protected final void maskedPrintCommandLine(List<String> cmd, boolean[] mask, FilePath workDir) {
        if(mask==null) {
            printCommandLine(cmd.toArray(new String[cmd.size()]),workDir);
            return;
        }
       
        assert mask.length == cmd.size();
        final String[] masked = new String[cmd.size()];
        for (int i = 0; i < cmd.size(); i++) {
            if (mask[i]) {
                masked[i] = "********";
            } else {
                masked[i] = cmd.get(i);
            }
        }
        printCommandLine(masked, workDir);
    }
    protected final void maskedPrintCommandLine(String[] cmd, boolean[] mask, FilePath workDir) {
        maskedPrintCommandLine(Arrays.asList(cmd),mask,workDir);
    }

    /**
     * Returns a decorated {@link Launcher} for the given node.
     */
    public final Launcher decorateFor(Node node) {
        Launcher l = this;
        for (LauncherDecorator d : LauncherDecorator.all())
            l = d.decorate(l,node);
        return l;
    }

    /**
     * Returns a decorated {@link Launcher} that puts the given set of arguments as a prefix to any commands
     * that it invokes.
     *
     * @since 1.299
     */
    public final Launcher decorateByPrefix(final String... prefix) {
        final Launcher outer = this;
        return new Launcher(outer) {
            @Override
            public Proc launch(ProcStarter starter) throws IOException {
                starter.commands.addAll(0,Arrays.asList(prefix));
                starter.masks = prefix(starter.masks);
                return outer.launch(starter);
            }

            @Override
            public Channel launchChannel(String[] cmd, OutputStream out, FilePath workDir, Map<String, String> envVars) throws IOException, InterruptedException {
                return outer.launchChannel(prefix(cmd),out,workDir,envVars);
            }

            @Override
            public void kill(Map<String, String> modelEnvVars) throws IOException, InterruptedException {
                outer.kill(modelEnvVars);
            }

            private String[] prefix(String[] args) {
                String[] newArgs = new String[args.length+prefix.length];
                System.arraycopy(prefix,0,newArgs,0,prefix.length);
                System.arraycopy(args,0,newArgs,prefix.length,args.length);
                return newArgs;
            }

            private boolean[] prefix(boolean[] args) {
                boolean[] newArgs = new boolean[args.length+prefix.length];
                System.arraycopy(args,0,newArgs,prefix.length,args.length);
                return newArgs;
            }
        };
    }

    /**
     * {@link Launcher} that launches process locally.
     */
    public static class LocalLauncher extends Launcher {
        public LocalLauncher(TaskListener listener) {
            this(listener,Hudson.MasterComputer.localChannel);
        }

        public LocalLauncher(TaskListener listener, VirtualChannel channel) {
            super(listener, channel);
        }

        @Override
        public Proc launch(ProcStarter ps) throws IOException {
            maskedPrintCommandLine(ps.commands, ps.masks, ps.pwd);

            EnvVars jobEnv = inherit(ps.envs);

            // replace variables in command line
            String[] jobCmd = new String[ps.commands.size()];
            for ( int idx = 0 ; idx < jobCmd.length; idx++ )
              jobCmd[idx] = jobEnv.expand(ps.commands.get(idx));

            return new LocalProc(jobCmd, Util.mapToEnv(jobEnv), ps.stdin, ps.stdout, ps.stderr, toFile(ps.pwd));
        }

        private File toFile(FilePath f) {
            return f==null ? null : new File(f.getRemote());
        }

        public Channel launchChannel(String[] cmd, OutputStream out, FilePath workDir, Map<String,String> envVars) throws IOException {
            printCommandLine(cmd, workDir);

            ProcessBuilder pb = new ProcessBuilder(cmd);
            pb.directory(toFile(workDir));
            if (envVars!=null) pb.environment().putAll(envVars);

            return launchChannel(out, pb);
        }

        @Override
        public void kill(Map<String, String> modelEnvVars) throws InterruptedException {
            ProcessTree.get().killAll(modelEnvVars);
        }

        /**
         * @param out
         *      Where the stderr from the launched process will be sent.
         */
        public Channel launchChannel(OutputStream out, ProcessBuilder pb) throws IOException {
            final EnvVars cookie = EnvVars.createCookie();
            pb.environment().putAll(cookie);

            final Process proc = pb.start();

            final Thread t2 = new StreamCopyThread(pb.command()+": stderr copier", proc.getErrorStream(), out);
            t2.start();

            return new Channel("locally launched channel on "+ pb.command(),
                Computer.threadPoolForRemoting, proc.getInputStream(), proc.getOutputStream(), out) {

                /**
                 * Kill the process when the channel is severed.
                 */
                @Override
                protected synchronized void terminate(IOException e) {
                    super.terminate(e);
                    ProcessTree pt = ProcessTree.get();
                    try {
                        pt.killAll(proc,cookie);
                    } catch (InterruptedException x) {
                        LOGGER.log(Level.INFO, "Interrupted", x);
                    }
                }

                @Override
                public synchronized void close() throws IOException {
                    super.close();
                    // wait for all the output from the process to be picked up
                    try {
                        t2.join();
                    } catch (InterruptedException e) {
                        // process the interrupt later
                        Thread.currentThread().interrupt();
                    }
                }
            };
        }
    }

    /**
     * Launches processes remotely by using the given channel.
     */
    public static class RemoteLauncher extends Launcher {
        private final boolean isUnix;

        public RemoteLauncher(TaskListener listener, VirtualChannel channel, boolean isUnix) {
            super(listener, channel);
            this.isUnix = isUnix;
        }

        public Proc launch(ProcStarter ps) throws IOException {
            final OutputStream out = ps.stdout == null ? null : new RemoteOutputStream(new CloseProofOutputStream(ps.stdout));
            final OutputStream err = ps.stderr==null ? null : new RemoteOutputStream(new CloseProofOutputStream(ps.stderr));
            final InputStream  in  = ps.stdin==null ? null : new RemoteInputStream(ps.stdin);
            final String workDir = ps.pwd==null ? null : ps.pwd.getRemote();

            return new RemoteProc(getChannel().callAsync(new RemoteLaunchCallable(ps.commands, ps.masks, ps.envs, in, out, err, workDir, listener)));
        }

        public Channel launchChannel(String[] cmd, OutputStream err, FilePath _workDir, Map<String,String> envOverrides) throws IOException, InterruptedException {
            printCommandLine(cmd, _workDir);

            Pipe out = Pipe.createRemoteToLocal();
            final String workDir = _workDir==null ? null : _workDir.getRemote();

            OutputStream os = getChannel().call(new RemoteChannelLaunchCallable(cmd, out, err, workDir, envOverrides));

            return new Channel("remotely launched channel on "+channel,
                Computer.threadPoolForRemoting, out.getIn(), new BufferedOutputStream(os));
        }

        @Override
        public boolean isUnix() {
            return isUnix;
        }

        @Override
        public void kill(final Map<String,String> modelEnvVars) throws IOException, InterruptedException {
            getChannel().call(new KillTask(modelEnvVars));
        }

        private static final class KillTask implements Callable<Void,RuntimeException> {
            private final Map<String, String> modelEnvVars;

            public KillTask(Map<String, String> modelEnvVars) {
                this.modelEnvVars = modelEnvVars;
            }

            public Void call() throws RuntimeException {
                try {
                    ProcessTree.get().killAll(modelEnvVars);
                } catch (InterruptedException e) {
                    // we are asked to terminate early by the caller, so no need to do anything
                }
                return null;
            }

            private static final long serialVersionUID = 1L;
        }
    }

    private static class RemoteLaunchCallable implements Callable<Integer,IOException> {
        private final List<String> cmd;
        private final boolean[] masks;
        private final String[] env;
        private final InputStream in;
        private final OutputStream out;
        private final OutputStream err;
        private final String workDir;
        private final TaskListener listener;

        RemoteLaunchCallable(List<String> cmd, boolean[] masks, String[] env, InputStream in, OutputStream out, OutputStream err, String workDir, TaskListener listener) {
            this.cmd = new ArrayList<String>(cmd);
            this.masks = masks;
            this.env = env;
            this.in = in;
            this.out = out;
            this.err = err;
            this.workDir = workDir;
            this.listener = listener;
        }

        public Integer call() throws IOException {
            Launcher.ProcStarter ps = new LocalLauncher(listener).launch();
            ps.cmds(cmd).masks(masks).envs(env).stdin(in).stdout(out).stderr(err);
            if(workDir!=null)   ps.pwd(workDir);

            Proc p = ps.start();
            try {
                return p.join();
            } catch (InterruptedException e) {
                return -1;
            } finally{
                try {
                    // Fix: http://issues.hudson-ci.org/browse/HUDSON-7809
                    // This call should not return immediately after the
                    // process is done. The pipe associated with the channel
                    // may be still transmitting data.
                    // Get the channel associated with this thread and flush
                    // its IO pipe
                    Channel.current().flushPipe();
                } catch (InterruptedException ex) {
                    Logger.getLogger(Launcher.class.getName()).log(Level.INFO, null, ex);
                }
            }
        }

        private static final long serialVersionUID = 1L;
    }

    private static class RemoteChannelLaunchCallable implements Callable<OutputStream,IOException> {
        private final String[] cmd;
        private final Pipe out;
        private final String workDir;
        private final OutputStream err;
        private final Map<String,String> envOverrides;

        public RemoteChannelLaunchCallable(String[] cmd, Pipe out, OutputStream err, String workDir, Map<String,String> envOverrides) {
            this.cmd = cmd;
            this.out = out;
            this.err = new RemoteOutputStream(err);
            this.workDir = workDir;
            this.envOverrides = envOverrides;
        }

        public OutputStream call() throws IOException {
            Process p = Runtime.getRuntime().exec(cmd,
                Util.mapToEnv(inherit(envOverrides)),
                workDir == null ? null : new File(workDir));

            List<String> cmdLines = Arrays.asList(cmd);
            new StreamCopyThread("stdin copier for remote agent on "+cmdLines,
                p.getInputStream(), out.getOut()).start();
            new StreamCopyThread("stderr copier for remote agent on "+cmdLines,
                p.getErrorStream(), err).start();

            // TODO: don't we need to join?

            return new RemoteOutputStream(p.getOutputStream());
        }

        private static final long serialVersionUID = 1L;
    }

    /**
     * Expands the list of environment variables by inheriting current env variables.
     */
    private static EnvVars inherit(String[] env) {
        // convert String[] to Map first
        EnvVars m = new EnvVars();
        if(env!=null) {
            for (String e : env) {
                int index = e.indexOf('=');
                m.put(e.substring(0,index), e.substring(index+1));
            }
        }
        // then do the inheritance
        return inherit(m);
    }

    /**
     * Expands the list of environment variables by inheriting current env variables.
     */
    private static EnvVars inherit(Map<String,String> overrides) {
        EnvVars m = new EnvVars(EnvVars.masterEnvVars);
        for (Map.Entry<String,String> o : overrides.entrySet())
            m.override(o.getKey(),m.expand(o.getValue()));
        return m;
    }

    /**
     * Debug option to display full current path instead of just the last token.
     */
    public static boolean showFullPath = false;

    private static final Logger LOGGER = Logger.getLogger(Launcher.class.getName());
}
TOP

Related Classes of hudson.Launcher$LocalLauncher

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.