org.kepler.ssh.LocalExec.java Source code

Java tutorial

Introduction

Here is the source code for org.kepler.ssh.LocalExec.java

Source

/*
 * Copyright (c) 2004-2010 The Regents of the University of California.
 * All rights reserved.
 *
 * '$Author: barseghian $'
 * '$Date: 2012-10-30 15:37:51 -0700 (Tue, 30 Oct 2012) $' 
 * '$Revision: 30990 $'
 * 
 * Permission is hereby granted, without written agreement and without
 * license or royalty fees, to use, copy, modify, and distribute this
 * software and its documentation for any purpose, provided that the above
 * copyright notice and the following two paragraphs appear in all copies
 * of this software.
 *
 * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
 * FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
 * THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
 * PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
 * CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
 * ENHANCEMENTS, OR MODIFICATIONS.
 *
 */

package org.kepler.ssh;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.nio.channels.FileChannel;
import java.util.Collection;
import java.util.Iterator;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.kepler.util.FilenameFilter_RegularPattern;

import ptolemy.util.StringUtilities;

/**
 * Local command execution. This class implements the ExecInterface to provide
 * the same functionality for local operations as what Ssh does for remote ones.
 * Thus, other classes can hide the difference between an ssh execution and
 * local execution.
 */
public class LocalExec implements ExecInterface {

    private static final Log log = LogFactory.getLog(LocalExec.class.getName());
    private static final boolean isDebugging = log.isDebugEnabled();

    private static int nInstances = 0; // to catch the very first instantiation

    // timeout variables
    private int timeout = 0; // timeout in seconds
    private boolean timeoutRestartOnStdout = false; // restart timer if stdout
    // has data
    private boolean timeoutRestartOnStderr = false; // restart timer if stderr
    // has data

    // public final static int timeoutErrorCode = -32767;

    public LocalExec() {
        _commandCount = getSystemProps();
        nInstances++;

        /*
         * On local host we have no session opening/closing, so this is the
         * place to generate a SESSION_OPENED event, but only once (and there
         * will be no SESSION_CLOSED event)
         */
        if (nInstances == 1) {
            SshEventRegistry.instance.notifyListeners(new SshEvent(SshEvent.SESSION_OPENED, "local"));
        }

    }

    /**
     * addIdentity, useless for local exec 
     */
    public void addIdentity(String identity) {
        // do nothing 
    }

    /**
     * port forwarding not working on local exec
     */
    public void setPortForwardingL(String spec) throws ExecException {
        // do nothing
    }

    /**
     * port forwarding not working on local exec
     */
    public void setPortForwardingR(String spec) throws ExecException {
        // do nothing
    }

    public boolean openConnection() throws ExecException {
        return true;
    }

    public void closeConnection() {
        // do nothing
    }

    /**
     * Specify if killing of external processes (i.e. clean-up) after error or
     * timeout is required. Not implemented for local execution.
     */
    public void setForcedCleanUp(boolean foo) {
    }

    /**
     * Set timeout for the operations. Timeout should be given in seconds. If
     * 'stdout' is set to true, the timer is restarted whenever there is data on
     * stdout. If 'stderr' is set to true, the timer is restarted whenever there
     * is data on stderr. executeCmd will throw an ExecException, an instance of
     * ExecTimeoutException if the timeout limit is reached. 'seconds' = 0 means
     * no timeout at all.
     */
    public void setTimeout(int seconds, boolean stdout, boolean stderr) {
        timeout = seconds;
        timeoutRestartOnStdout = stdout;
        timeoutRestartOnStderr = stderr;
    }

    public void setTimeout(int seconds) {
        timeout = seconds;
    }

    /**
     * Execute a command on the local machine 'command' is the full command with
     * arguments to be executed return: the exit code of the command additional
     * effects: streamOut is continuously written during execution the stdout of
     * the command. Similarly, streamErr is continuously written during exec the
     * stderr. It forwards all Exceptions that arise during java exec.
     */
    public int executeCmd(String command, OutputStream streamOut, OutputStream streamErr) throws ExecException {
        return executeCmd(command, streamOut, streamErr, null);
    }

    public int executeCmd(String command, OutputStream streamOut, OutputStream streamErr, String thirdPartyTarget)
            throws ExecException {
        _commandArr[_commandCount] = command;

        Runtime rt = Runtime.getRuntime();
        Process proc;

        // get the pwd/passphrase to the third party (and perform authentication
        // if not yet done)
        String pwd = SshSession.getPwdToThirdParty(thirdPartyTarget);

        try {
            proc = rt.exec(_commandArr);
        } catch (Exception ex) {
            //ex.printStackTrace();
            throw new ExecException("Cannot execute cmd ** : " + _commandArr[_commandCount] + ex);
        }

        // System.out.println("%%% Process started");

        // the streams from the process: stdout and stderr
        BufferedReader out_in = new BufferedReader(new InputStreamReader(proc.getInputStream())); // stdout
        BufferedReader err_in = new BufferedReader(new InputStreamReader(proc.getErrorStream())); // stderr

        // the streams towards the caller: stdout and stderr
        BufferedWriter out_out = new BufferedWriter(new OutputStreamWriter(streamOut));
        BufferedWriter err_out = new BufferedWriter(new OutputStreamWriter(streamErr));

        BufferedWriter proc_in = new BufferedWriter(new OutputStreamWriter(proc.getOutputStream())); // stdin

        String line; // Temp for each line of output.
        int exitVal = -32766;
        boolean readOut = true;
        boolean readErr = true;
        boolean finished = false;
        boolean checkForPwd = (pwd != null);
        char c[] = new char[256];
        int charsRead;

        // variables for the timeout checking
        long start = System.currentTimeMillis();
        long current = 0;
        long maxtime = timeout * 1000L;

        while (!finished) { // will stop when the process terminates or after
            // timeout
            // check the status of the process
            try {
                exitVal = proc.exitValue();
                finished = true; // process terminated so exit this loop after
                                 // reading the buffers
            } catch (IllegalThreadStateException ex) {
                // process not yet terminated so we go further
            }

            // read stdout
            if (readOut) {
                try {
                    while (out_in.ready()) {
                        charsRead = out_in.read(c, 0, 256);
                        out_out.write(c, 0, charsRead);

                        // System.out.println("%%% "+ new String(c, 0,
                        // charsRead));
                        /*
                         * try { proc_in.write("Anyadat\n", 0, 8); // send the
                         * password proc_in.flush(); } catch (Exception ex) {
                         * System.out.println("### "+ex);
                         * 
                         * }
                         */
                        if (checkForPwd && containsPasswordRequest(c, 0, charsRead)) {

                            // System.out.println("%%% Found password request");

                            out_out.flush(); // so you may see the request on
                                             // stdout already
                            proc_in.write(pwd + "\n", 0, pwd.length() + 1); // send
                            // the
                            // password
                            proc_in.flush();
                            log.info("Sent password to third party.");
                            checkForPwd = false; // even if it's wrong, do not
                                                 // do it again
                        }
                        if (timeoutRestartOnStdout)
                            start = System.currentTimeMillis(); // restart
                        // timeout timer
                    }
                } catch (IOException ioe) {
                    log.error("<IOException> when reading the stdout: " + ioe + "</IOException>");
                    readOut = false;
                }
            }

            // read stderr
            if (readErr) {
                try {
                    while (err_in.ready()) {
                        charsRead = err_in.read(c, 0, 256);
                        err_out.write(c, 0, charsRead);
                        System.out.println("### " + new String(c, 0, charsRead));
                        if (checkForPwd && containsPasswordRequest(c, 0, charsRead)) {

                            System.out.println("### Found password request");

                            out_out.flush(); // so you may see the request on
                                             // stdout already
                            proc_in.write(pwd + "\n", 0, pwd.length() + 1); // send
                            // the
                            // password
                            proc_in.flush();
                            log.info("Sent password to third party.");
                            checkForPwd = false; // even if it's wrong, do not
                                                 // do it again
                        }
                        if (timeoutRestartOnStderr)
                            start = System.currentTimeMillis(); // restart
                        // timeout timer
                    }
                } catch (IOException ioe) {
                    log.error("<IOException> when reading the stderr: " + ioe + "</IOException>");
                    readErr = false;
                }
            }

            // sleep a bit to not overload the system
            if (!finished)
                try {
                    java.lang.Thread.sleep(100);
                } catch (InterruptedException ex) {
                }

            // check timeout
            current = System.currentTimeMillis();
            if (timeout > 0 && maxtime < current - start) {
                log.error("Timeout: " + timeout + "s elapsed for command " + command);
                proc.destroy();
                throw new ExecTimeoutException(command);
                // exitVal = timeoutErrorCode;
                // finished = true;
            }

        }

        try {
            // flush to caller
            out_out.flush();
            err_out.flush();
            // close streams from/to child process
            out_in.close();
            err_in.close();
            proc_in.close();
        } catch (IOException ex) {
            log.error("Could not flush output streams: " + ex);
        }

        // System.out.println("ExitValue: " + exitVal);
        return exitVal;

    }

    /**
     * Look for one of the strings password/passphrase/passcode in the char[]
     * array. Return true if found any. Case insensitive search.
     */
    private boolean containsPasswordRequest(char[] buf, int startPos, int endPos) {
        // look for strings password/passphrase/passcode
        int i = startPos;
        while (i + 7 < endPos) {
            if (Character.toLowerCase(buf[i]) == 'p' && Character.toLowerCase(buf[i + 1]) == 'a'
                    && Character.toLowerCase(buf[i + 2]) == 's' && Character.toLowerCase(buf[i + 3]) == 's') {

                // found "pass", look further for word/code/phrase
                if (Character.toLowerCase(buf[i + 4]) == 'w' && Character.toLowerCase(buf[i + 5]) == 'o'
                        && Character.toLowerCase(buf[i + 6]) == 'r' && Character.toLowerCase(buf[i + 7]) == 'd') {
                    log.info("PWDSearch: found request for password.");
                    return true;
                } else if (Character.toLowerCase(buf[i + 4]) == 'c' && Character.toLowerCase(buf[i + 5]) == 'o'
                        && Character.toLowerCase(buf[i + 6]) == 'd' && Character.toLowerCase(buf[i + 7]) == 'e') {
                    log.info("PWDSearch: found request for passcode.");
                    return true;
                } else if ((i + 9 < endPos) && (Character.toLowerCase(buf[i + 4]) == 'p'
                        && Character.toLowerCase(buf[i + 5]) == 'h' && Character.toLowerCase(buf[i + 6]) == 'r'
                        && Character.toLowerCase(buf[i + 7]) == 'a' && Character.toLowerCase(buf[i + 8]) == 's'
                        && Character.toLowerCase(buf[i + 9]) == 'e')) {
                    log.info("PWDSearch: found request for passphrase.");
                    return true;
                }
            }
            i = i + 1;
        }
        return false;
    }

    /**
     * Create a directory given as String parameter. It calls File.mkdir() or
     * File.mkdirs() depending on the parentflag. Returns true iff directory is
     * created. False is not returned but an exception otherwise. It catches
     * SecurityException (from File.mkdir() or File.mkdirs()) and rethrows it as
     * ExecException.
     * 
     * The method works equivalently with SshExec.createDir(). That is, if the
     * directory exists and parentflag is set, true is returned; if parentflag
     * is not set, an exception is thrown.
     */
    public boolean createDir(String dir, boolean parentflag) throws ExecException {

        if (dir == null || dir.trim().length() == 0) {
            log.error("Directory name not given");
            return false;
        }

        boolean b = false;
        try {
            File d = new File(dir);

            // error check: a file with this name exists and is not a directory
            if (d.exists() && !d.isDirectory()) {
                throw new ExecException("File " + dir + " exists but is not a directory.");
            }

            // error check: directory exists
            if (d.isDirectory())
                if (parentflag) // parentflag is set: we are done and return
                    // success
                    return true;
                else
                    // parentflag is not set: we should return error
                    throw new ExecException("Directory " + dir + " already exists.");

            // call mkdir or mkdirs
            if (parentflag)
                b = d.mkdirs();
            else
                b = d.mkdir();

            if (!b) {
                throw new ExecException("Directory " + dir + " has NOT been created for unknown reasons");
            }
        } catch (SecurityException ex) {
            throw new ExecException("Security error: " + ex);
        }

        return b;
    }

    /**
     * To be implemented. Delete files or directories! BE CAREFUL It should be
     * relative to the current dir, or an absolute path For safety, * and ? is
     * allowed in filename string only if explicitely asked with allowFileMask =
     * true If you want to delete a directory, recursive should be set to true
     * 
     * @return true if succeeded throws ExecException
     */
    public boolean deleteFile(String fname, boolean recursive, boolean allowFileMask) throws ExecException {

        if (fname == null || fname.trim().length() == 0)
            throw new ExecException("File name not given");

        // some error checking to avoid malicious removals
        if (!allowFileMask) {
            if (fname.indexOf('*') != -1 || fname.indexOf('?') != -1)
                throw new ExecException("File name contains file mask, but this was not allowed: " + fname);
        }

        if (fname.equals("*") || fname.equals("./*") || fname.equals("../*") || fname.equals("/*"))
            throw new ExecException("All files in directories like . .. / are not allowed to be removed: " + fname);

        String temp = fname;
        if (temp.length() > 1 && temp.endsWith(File.separator)) {
            temp = temp.substring(0, temp.length() - 1);
            if (isDebugging)
                log.debug("  %  " + fname + " -> " + temp);
        }

        if (temp.equals(".") || temp.equals("..") || temp.equals("/") || temp.equals(File.separator))
            throw new ExecException("Directories like . .. / are not allowed to be removed: " + fname);

        // end of error checking

        // to be implemented...
        LocalDelete ld = new LocalDelete();
        return ld.deleteFiles(fname, recursive);

    }

    /**
     * Copy local files to a local/remote directory Input: files is a Collection
     * of files of type File, targetPath is either a directory in case of
     * several files, or it is either a dir or filename in case of one single
     * local file recursive: true if you want traverse directories
     * 
     * @return number of files copied successfully
     */
    public int copyTo(Collection files, String targetPath, boolean recursive) throws ExecException {

        int numberOfCopiedFiles = 0;

        Iterator fileIt = files.iterator();
        while (fileIt.hasNext()) {
            File lfile = (File) fileIt.next();
            numberOfCopiedFiles += copyTo(lfile, targetPath, recursive);
        }
        return numberOfCopiedFiles;
    }

    /**
     * Copy a local file to a local directory/path Input: file of type File
     * (which can be a directory). The file name can be wildcarded too (but not
     * the path elements!). targetPath is either a directory or filename
     * 
     * @return number of files copied successfully (i.e either returns true or
     *         an exception is raised)
     */
    public int copyTo(File lfile, String targetPath, boolean recursive) throws ExecException {

        File[] files = null;
        // if the file is wildcarded, we need the list of files
        // Anand : Changed getName() to getPath()
        //getName fails in case of *.txt - indexOf returns '0'
        String name = lfile.getPath();

        if (name.indexOf("*") > 0 || name.indexOf("?") > 0) {
            String pattern = name.replaceAll("\\.", "\\\\.").replaceAll("\\*", ".*").replaceAll("\\?", ".");

            FilenameFilter_RegularPattern filter = new FilenameFilter_RegularPattern(pattern);
            String dirname = lfile.getParent();
            if (dirname == null || dirname == "")
                dirname = ".";
            File dir = new File(dirname);
            files = dir.listFiles(filter);

        } else { // no wildcards
            files = new File[1];
            files[0] = lfile;
        }

        int numberOfCopiedFiles = 0;
        for (int i = 0; i < files.length; i++)
            numberOfCopiedFiles += _copyTo(files[i], targetPath, recursive);

        return numberOfCopiedFiles;
    }

    /**
     * Copy _one_ local file to a local directory/path Input: file of type File
     * (which can be a directory) Input must not have wildcards. targetPath is
     * either a directory or filename
     * 
     * @return number of files copied successfully (i.e either returns true or
     *         an exception is raised)
     */
    private int _copyTo(File lfile, String targetPath, boolean recursive) throws ExecException {

        if (!lfile.exists()) {
            throw new ExecException("File does not exist: " + lfile);
        }

        // check: recursive traversal of directories enabled?
        if (lfile.isDirectory()) {
            if (!recursive)
                throw new SshException("File " + lfile + " is a directory. Set recursive copy!");
        }

        int numberOfCopiedFiles = 0;

        try {
            // recursive handling of directories
            if (lfile.isDirectory()) {
                numberOfCopiedFiles = copyDir(lfile, new File(targetPath));
            } else {
                // copy one file
                File target = new File(targetPath);
                if (target.exists() && target.isDirectory())
                    target = new File(targetPath, lfile.getName());
                copyFile(lfile, target);
                numberOfCopiedFiles++;
            }
        } catch (IOException ex) {
            log.error(ex);
            throw new ExecException("Cannot copy " + lfile + " to " + targetPath + ":\n" + ex);
        }

        return numberOfCopiedFiles;
    }

    /**
     * Copy files from a directory to a local path Input: 'files' is a
     * Collection of files of type String (! not like at copyTo !), 'sourcePath'
     * is either empty string (or null) in case the 'files' contain full paths
     * to the individual files, or it should be a remote dir, and in this case
     * each file name in 'files' will be extended with the remote dir name
     * before copy. 'localPath' should be a directory name in case of several
     * files. It can be a filename in case of a single file to be copied. This
     * is a convenience method for copyFrom on several remote files. recursive:
     * true if you want traverse directories
     * 
     * @return number of files copied successfully
     */
    public int copyFrom(String sourcePath, Collection files, File localPath, boolean recursive)
            throws ExecException {

        int numberOfCopiedFiles = 0;

        String sdir;
        if (sourcePath == null || sourcePath.trim().equals("")) {
            sdir = "";
        } else {
            sdir = sourcePath;
            if (!sdir.endsWith(File.separator))
                sdir = sdir + File.separator;
        }

        Iterator fileIt = files.iterator();
        while (fileIt.hasNext()) {
            String sfile = (String) fileIt.next();
            numberOfCopiedFiles += copyFrom(sdir + sfile, localPath, recursive);
        }
        return numberOfCopiedFiles;
    }

    /**
     * Copy a local file into a local file Input: 'sfile' of type String (can be
     * a directory or filename) 'localPath' is either a directory or filename
     * Only if 'recursive' is set, will directories copied recursively.
     * 
     * @return number of files copied successfully (i.e either returns true or
     *         an exception is raised) Note: on local filesystem, this method
     *         does the same as copyTo
     */
    public int copyFrom(String sourcePath, File localPath, boolean recursive) throws ExecException {

        return copyTo(new File(sourcePath), localPath.getAbsolutePath(), recursive);

    }

    /*
     * 
     * Private methods
     */

    /**
     * Copies src directory to dst. It assumes that src exists and is a
     * directory. If the dst directory does not exist, it is created. If it
     * exists, a subdirectory with the name of src is created. Thus, this method
     * works the same way as 'cp' and 'scp' and org.kepler.ssh.SshExec
     */
    private int copyDir(File src, File dst) throws IOException {

        int numberOfCopiedFiles = 0;

        if (dst.exists()) {
            // create a subdirectory withing dst (to be compliant with 'cp' and
            // 'scp')
            dst = new File(dst, src.getName());
        }
        dst.mkdir();

        numberOfCopiedFiles++; // the directory counts one

        File[] files = src.listFiles();
        for (int i = 0; i < files.length; i++) {
            // if (isDebugging) log.debug(" %     " + files[i]);
            if (files[i].isDirectory()) {
                numberOfCopiedFiles += copyDir(files[i], new File(dst, files[i].getName()));
            } else {
                copyFile(files[i], new File(dst, files[i].getName()));
                numberOfCopiedFiles++;
            }
        }
        return numberOfCopiedFiles;
    }

    /**
     * Copies src file to dst file. If the dst file does not exist, it is
     * created
     */
    private void copyFile(File src, File dst) throws IOException {
        // see if source and destination are the same
        if (src.equals(dst)) {
            // do not copy
            return;
        }

        //System.out.println("copying " + src + " to " + dst);

        FileChannel srcChannel = new FileInputStream(src).getChannel();
        FileChannel dstChannel = new FileOutputStream(dst).getChannel();
        dstChannel.transferFrom(srcChannel, 0, srcChannel.size());
        srcChannel.close();
        dstChannel.close();

        /* hacking for non-windows */
        // set the permission of the target file the same as the source file
        if (_commandArr[0] == "/bin/sh") {

            String osName = StringUtilities.getProperty("os.name");
            if (osName.startsWith("Mac OS X")) {

                // chmod --reference does not exist on mac, so do the best
                // we can using the java file api
                // WARNING: this relies on the umask to set the group, world
                // permissions.
                dst.setExecutable(src.canExecute());
                dst.setWritable(src.canWrite());

            } else {

                String cmd = "chmod --reference=" + src.getAbsolutePath() + " " + dst.getAbsolutePath();
                try {
                    ByteArrayOutputStream streamOut = new ByteArrayOutputStream();
                    ByteArrayOutputStream streamErr = new ByteArrayOutputStream();
                    executeCmd(cmd, streamOut, streamErr);
                } catch (ExecException e) {
                    log.warn("Tried to set the target file permissions the same as "
                            + "the source but the command failed: " + cmd + "\n" + e);
                }
            }
        }
    }

    //TODO: Anand: this function does not handle case of windows 7.
    //It considers windows 7 to be in last else block, hence added
    //and extra condition for contains(windows).
    private int getSystemProps() {
        // Get OS name
        String osName = System.getProperty("os.name");
        //System.out.println("<OS>" + osName + "</OS>");
        if (osName.equals("Windows 95")) {
            _commandArr[0] = "command.com";
            _commandArr[1] = "/C";
            _charsToSkip = 6;
            return 2;
        } else if (osName.equals("Windows NT") || osName.equals("Windows XP") || osName.equals("Windows 2000")
                || osName.toLowerCase().contains("windows")) {
            _commandArr[0] = "cmd.exe";
            _commandArr[1] = "/C";
            _charsToSkip = 6;
            return 2;
        } else {
            _commandArr[0] = "/bin/sh";
            _commandArr[1] = "-c";
            _charsToSkip = 5;
            return 2;
        }
    } // end-of-getSystemProps

    // ////////////////////////////////////////////////////////////////////
    // // private variables ////

    // The combined command to execute.
    private int _commandCount;
    private String _commandStr = "";
    private String _commandArr[] = new String[3];
    private int _charsToSkip = 6;

} // end-of-class-CondorJob