com.synopsys.arc.jenkinsci.plugins.cygwinprocesskiller.util.CygwinKillHelper.java Source code

Java tutorial

Introduction

Here is the source code for com.synopsys.arc.jenkinsci.plugins.cygwinprocesskiller.util.CygwinKillHelper.java

Source

/*
 * The MIT License
 *
 * Copyright 2013 Oleg Nenashev, Synopsys Inc.
 *
 * 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 com.synopsys.arc.jenkinsci.plugins.cygwinprocesskiller.util;

import com.synopsys.arc.jenkinsci.plugins.cygwinprocesskiller.CygwinKillerInstallation;
import com.synopsys.arc.jenkinsci.plugins.cygwinprocesskiller.CygwinProcessKillerPlugin;
import com.synopsys.arc.jenkinsci.plugins.cygwinprocesskiller.Messages;
import hudson.EnvVars;
import hudson.FilePath;
import hudson.Launcher.ProcStarter;
import hudson.Proc;
import hudson.model.Node;
import hudson.model.TaskListener;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.TimeUnit;
import org.apache.commons.io.output.ByteArrayOutputStream;

/**
 * Class provides basic Cygwin operations.
 * This class is designed to be launched on the master only.
 * @author Oleg Nenashev
 */
//TODO: Logging
public class CygwinKillHelper {
    private final TaskListener log;
    private final Node node;
    private final CygwinKillerInstallation tool;
    private final int processPID;

    // On-demand variables 
    private FilePath tmpDir;
    private FilePath substitutedHome;

    private final CygwinProcessKillerPlugin plugin = CygwinProcessKillerPlugin.Instance();
    private static final String CYGWIN_START_PREFIX = "CYGWIN_";
    private static final String CYGWIN_BINARY_PATH = "\\bin\\";
    private static final int WAIT_TIMEOUT_SEC = 500;

    public CygwinKillHelper(TaskListener log, Node node, CygwinKillerInstallation tool, int processPID) {
        this.log = log;
        this.node = node;
        this.tool = tool;
        this.processPID = processPID;
        this.substitutedHome = this.tmpDir = null; // will be retrieved on-demand
    }

    /**
     * Checks that Cygwin is available on the host.
     */
    public boolean isCygwin() throws InterruptedException {
        OutputStream str = new ByteArrayOutputStream();
        try { // Catch tool installation exceptions
            execCommand("uname", str, str, "-a");
        } catch (IOException ex) {
            logError(Messages.Message_CygwinCheckFailed() + ex.getMessage());
            return false;
        }
        return str.toString().startsWith(CYGWIN_START_PREFIX);
    }

    private FilePath getTmpDir() throws IOException, InterruptedException {
        if (tmpDir == null) {
            tmpDir = findTmpDir(node);
        }
        return tmpDir;
    }

    /**
     * Executes script on the target host.
     * @param script Script to be executed
     * @param out Output stream, which returns both stderr and stdout
     * @param args Script arguments
     * @return return code of the script
     * @throws IOException
     * @throws InterruptedException 
     */
    public int execScript(String script, OutputStream out, String... args)
            throws IOException, InterruptedException {
        // Prepare a temp file
        FilePath tmpFile = getTmpDir().createTempFile("cygwin_process_killer_", ".sh");
        tmpFile.write(script, null);

        String[] cmd = new String[1 + args.length];
        cmd[0] = tmpFile.getRemote();
        System.arraycopy(args, 0, cmd, 1, args.length);

        return execCommand("bash", out, out, cmd);
    }

    /**
     * Executes command with specified arguments.
     * @param command Command to be executed
     * @param stdout Output stream for STDOUT
     * @param stderr Output stream for STDERR
     * @param args Arguments to be passed
     * @return exit code of the triggered command
     * @throws IOException 
     * @throws InterruptedException Execution has been interrupted
     */
    public int execCommand(String command, OutputStream stdout, OutputStream stderr, String... args)
            throws IOException, InterruptedException {
        String[] cmd = new String[1 + args.length];
        cmd[0] = getCygwinBinaryCommand(command);
        System.arraycopy(args, 0, cmd, 1, args.length);

        ProcStarter starter = node.createLauncher(log).launch().cmds(cmd).envs(constructVariables()).stdout(stdout)
                .stderr(stderr).pwd(getTmpDir());
        Proc proc = starter.start();
        int resultCode = proc.joinWithTimeout(WAIT_TIMEOUT_SEC, TimeUnit.SECONDS, log);
        starter.readStdout();
        return resultCode;
    }

    /**
     * Terminates process by PID, which has been provided in the constructor.
     */
    public boolean kill() throws IOException, InterruptedException {
        OutputStream str = new ByteArrayOutputStream();
        int res = execScript(plugin.getKillScript(), str, Integer.toString(processPID));

        if (res != 0) {
            logError("CygwinKiller cannot kill the process tree (parent pid=" + processPID + ")");
        }
        return res != 0;
    }

    private String getCygwinBinaryCommand(String commandName) throws IOException, InterruptedException {
        return tool != null ? getSubstitutedHome().getRemote() + CYGWIN_BINARY_PATH + commandName + ".exe"
                : commandName + ".exe";
    }

    private static FilePath findTmpDir(Node node) throws IOException, InterruptedException {
        if (node == null) {
            throw new IllegalArgumentException("Must pass non-null node");
        }

        FilePath root = node.getRootPath();
        if (root == null) {
            throw new IllegalArgumentException("Node " + node.getDisplayName() + " seems to be offline");
        }

        FilePath tmpDir = root.child("cygwin_process_killer").child("tmp");
        if (!tmpDir.exists()) {
            tmpDir.mkdirs();
        }
        return tmpDir;
    }

    private Map<String, String> constructVariables() throws IOException, InterruptedException {
        Map<String, String> envVars = new TreeMap<String, String>();
        if (tool != null) {
            FilePath homePath = getSubstitutedHome();
            String overridenPaths = homePath.child("bin").getRemote() + File.pathSeparator
                    + homePath.child("lib").getRemote();
            envVars.put("PATH", overridenPaths);
            envVars.put("CYGWIN_HOME", homePath.getRemote());
        }
        return envVars;
    }

    private FilePath getSubstitutedHome() throws IOException, InterruptedException {
        if (substitutedHome == null && tool != null) {
            try {
                substitutedHome = getCygwinHome(null);
            } catch (CygwinKillerException ex) {
                String msg = Messages.Message_InstallationFailed() + ex.getMessage();
                logError(msg);
                throw new IOException(msg, ex);
            }
        }
        return substitutedHome;
    }

    private void logError(String message) {
        log.error("[" + CygwinProcessKillerPlugin.PLUGIN_NAME + "] - " + message);
    }

    private FilePath getCygwinHome(EnvVars additionalVars)
            throws CygwinKillerException, IOException, InterruptedException {
        String home = tool.forNode(node, log).getHome();
        if (additionalVars != null && additionalVars.size() != 0) {
            home = additionalVars.expand(home);
        }

        // Get and check
        //TODO: check for cygwinHome.isDirectory() and existense
        File cygwinHome = new File(home);
        /*if (!cygwinHome.exists()) {
        throw new CygwinKillerException("Cygwin home directory "+cygwinHome+" does not exist");
        } 
        if (!cygwinHome.isAbsolute()) {
        throw new CygwinKillerException("Cygwin home should be an absolute path to a directory");
        } */
        return new FilePath(cygwinHome);
    }
}