001/*
002 * Copyright (c) 2004-2010 The Regents of the University of California.
003 * All rights reserved.
004 *
005 * '$Author: kulkarni $'
006 * '$Date: 2010-10-05 21:46:36 +0000 (Tue, 05 Oct 2010) $'
007 * '$Revision: 26020 $'
008 *
009 * Permission is hereby granted, without written agreement and without
010 * license or royalty fees, to use, copy, modify, and distribute this
011 * software and its documentation for any purpose, provided that the above
012 * copyright notice and the following two paragraphs appear in all copies
013 * of this software.
014 *
015 * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
016 * FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
017 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
018 * THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
019 * SUCH DAMAGE.
020 *
021 * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
022 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
023 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
024 * PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
025 * CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
026 * ENHANCEMENTS, OR MODIFICATIONS.
027 *
028 */
029
030package org.kepler.ssh;
031
032import java.io.ByteArrayOutputStream;
033import java.io.File;
034import java.util.Collection;
035import java.util.Iterator;
036
037import org.apache.commons.logging.Log;
038import org.apache.commons.logging.LogFactory;
039import org.kepler.util.FilenameFilter_RegularPattern;
040
041/*
042 * This class provides functionality to execute command and copy files to
043 * and from a remote host.The remote host can be accessed either using SSH
044 * or GSISSH. This class is extended by SshExec and GsiSshExec.
045 */
046public abstract class RemoteExec implements ExecInterface {
047        public abstract boolean openConnection() throws SshException;
048
049        public abstract void closeConnection();
050
051        public abstract boolean getForcedCleanUp();
052
053        public abstract int getTimeout();
054
055        protected abstract int _copyTo(File lfile, String targetPath, boolean recursive)
056        throws SshException ;
057        public abstract int copyFrom(String rfile, File localPath, boolean recursive)
058        throws SshException;
059        private static final Log log = LogFactory
060                        .getLog(RemoteExec.class.getName());
061        private static final boolean isDebugging = log.isDebugEnabled();
062
063        /*
064         * Kill a remote process or its group. ProcessID should be given as a
065         * string. Returns nothing: it succeeds if it succeeds, otherwise just give
066         * it up here.
067         */
068        protected void kill(String pid, boolean group) {
069
070                if (pid == null || pid.trim().length() == 0)
071                        return;
072                String command;
073                if (group)
074                        command = new String("kill -9 -" + pid);
075                else
076                        command = new String("kill -9 " + pid);
077
078                ByteArrayOutputStream streamOut = new ByteArrayOutputStream();
079                ByteArrayOutputStream streamErr = new ByteArrayOutputStream();
080                int exitCode = 0;
081
082                // backup and set timeout and forcedCleanUp
083                int timeout_save = getTimeout();
084                boolean forcedCleanUp_save = getForcedCleanUp();
085                setTimeout(60, false, false); // give a minute for clean-up as max. We
086                                                                                // must not hang
087                // here
088                setForcedCleanUp(false); // would be stupid to kill the kill process
089
090                try {
091                        exitCode = executeCmd(command, streamOut, streamErr);
092                } catch (ExecTimeoutException ex) {
093                        log.error("Remote process killing (" + command + ") timeout");
094                        exitCode = 0;
095                } catch (ExecException ex) {
096                        exitCode = -1;
097                }
098
099                if (exitCode != 0 /* OK */&& exitCode != 1 /* NOEXISTS */) {
100                        log.warn("Remote process killing (" + command + ") failed:\n"
101                                        + streamErr);
102                }
103
104                // restore original timeout and forcedCleanUp values
105                setTimeout(timeout_save);
106                setForcedCleanUp(forcedCleanUp_save);
107
108        }
109
110        /**
111         * Create directory on the remote site with "mkdir" or "mkdir -p" command.
112         * It should be relative to the user's home dir, or an absolute path. If
113         * parentflag is true, the -p flag is used in the command so that an
114         * existing directory will not throw an error.
115         *
116         * @return true if succeeded throws ExecException (instance of SshExecption
117         *         or ExecTimeoutException)
118         */
119        public boolean createDir(String dir, boolean parentflag)
120                        throws ExecException {
121
122                if (dir == null || dir.trim().length() == 0)
123                        throw new SshException("Directory name not given");
124                String command;
125                if (parentflag)
126                        command = new String("mkdir -p " + dir);
127                else
128                        command = new String("mkdir " + dir);
129
130                ByteArrayOutputStream streamOut = new ByteArrayOutputStream();
131                ByteArrayOutputStream streamErr = new ByteArrayOutputStream();
132
133                int exitCode = executeCmd(command, streamOut, streamErr);
134
135                if (exitCode != 0) {
136                        throw new SshException("Remote directory creation (" + command
137                                        + ") failed:\n" + streamErr);
138                }
139                return true;
140        }
141
142        /**
143         * Delete file or directory on the remote site with "rm -rf" command! BE
144         * CAREFUL It should be relative to the user's home dir, or an absolute path
145         * For safety, * and ? is allowed in filename string only if explicitely
146         * asked with allowFileMask = true If you want to delete a directory,
147         * recursive should be set to true
148         *
149         * @return true if succeeded throws SshException
150         */
151        public boolean deleteFile(String fname, boolean recursive,
152                        boolean allowFileMask) throws ExecException {
153
154                if (fname == null || fname.trim().length() == 0)
155                        throw new SshException("File name not given");
156
157                String command;
158                if (recursive)
159                        command = new String("rm -rf " + fname);
160                else
161                        command = new String("rm -f " + fname);
162
163                // some error checking to avoid malicious removals
164                if (!allowFileMask) {
165                        if (fname.indexOf('*') != -1 || fname.indexOf('?') != -1)
166                                throw new SshException(
167                                                "File name contains file mask, but this was not allowed: "
168                                                                + fname);
169                }
170
171                String temp = fname;
172                if (temp.length() > 1 && temp.endsWith(File.separator)) {
173                        temp = temp.substring(0, temp.length() - 1);
174                        if (isDebugging)
175                                log.debug("  %  " + fname + " -> " + temp);
176                }
177
178                if (temp.equals(".") || temp.equals("..") || temp.equals("/"))
179                        throw new SshException(
180                                        "Directories like . .. / are not allowed to be removed: "
181                                                        + fname);
182
183                if (temp.equals("*") || temp.equals("./*") || temp.equals("../*")
184                                || temp.equals("/*"))
185                        throw new SshException(
186                                        "All files in directories like . .. / are not allowed to be removed: "
187                                                        + fname);
188
189                // end of error checking
190
191                if (isDebugging)
192                        log.debug("deleteDir command: " + command);
193
194                ByteArrayOutputStream streamOut = new ByteArrayOutputStream();
195                ByteArrayOutputStream streamErr = new ByteArrayOutputStream();
196
197                int exitCode = executeCmd(command, streamOut, streamErr);
198
199                switch (exitCode) {
200                case 0:
201                        break;
202                case -1:
203                        /* Possible bug!!! */
204                        /*
205                         * Current knowledge: we may receive -1 (for unknown reason) but the
206                         * file removal command actually succeeded. So here we ignore this
207                         * until someone hits this as a bug.
208                         */
209                        log
210                                        .warn("deleteFile(): -1 received as exit code from execution "
211                                                        + "but the removal probably succeeded. Check stdout and stderr:\n"
212                                                        + streamOut + " " + streamErr);
213                        break;
214                default:
215                        throw new SshException("Remote file removal failed: " + command
216                                        + "\nexit code: " + exitCode + "\nstdout:\n" + streamOut
217                                        + "\nstderr:\n" + streamErr + "\n-----------");
218                }
219                return true;
220        }
221
222        /**
223         * Copy local files to a remote directory Input: files is a Collection of
224         * files of type File, targetPath is either a directory in case of several
225         * files, or it is either a dir or filename in case of one single local file
226         * recursive: true if you want traverse directories
227         *
228         * @return number of files copied successfully SshException is thrown in
229         *         case of error.
230         */
231        @SuppressWarnings("unchecked")
232        public int copyTo(Collection files, String targetPath, boolean recursive)
233                        throws SshException {
234
235                int numberOfCopiedFiles = 0;
236
237                Iterator fileIt = files.iterator();
238                while (fileIt.hasNext()) {
239                        File lfile = (File) fileIt.next();
240                        numberOfCopiedFiles += copyTo(lfile, targetPath, recursive);
241                }
242                return numberOfCopiedFiles;
243        }
244
245        /**
246         * Copy a local file to a remote directory/path Input: file of type File
247         * (which can be a directory). The file name can be wildcarded too (but not
248         * the path elements!). targetPath is either a directory or filename
249         *
250         * @return number of files copied successfully (i.e either returns true or
251         *         an exception is raised)
252         */
253        public int copyTo(File lfile, String targetPath, boolean recursive)
254                        throws SshException {
255
256                File[] files = null;
257                
258                // if the file is wildcarded, we need the list of files
259                // Anand : Changed getName() to getPath()
260                //getName fails in case of *.txt - indexOf returns '0'
261                String name = lfile.getPath();
262                
263                if (name.indexOf("*") > 0 || name.indexOf("?") > 0) {
264                        
265                        name = lfile.getName();
266                        String pattern = name.replaceAll("\\.", "\\\\.").replaceAll("\\*",
267                                        ".*").replaceAll("\\?", ".");
268                        FilenameFilter_RegularPattern filter = new FilenameFilter_RegularPattern(
269                                        pattern);
270                        String dirname = lfile.getParent();
271                        if (dirname == null || dirname == "")
272                                dirname = ".";
273                        File dir = new File(dirname);
274                        files = dir.listFiles(filter);
275
276//Anand: Debug: Print the files being copied
277                        System.out.println("**** In " + this.getClass().getName() + " copyTo function *****");
278                        System.out.println("List of files in directory : " + dir.toString());
279                        for (int i = 0; i < files.length; i++)
280                                System.out.println(files[i].toString());
281//Anand: Debug end
282                        
283                } else { // no wildcards
284                        files = new File[1];
285                        files[0] = lfile;
286                }
287
288                int numberOfCopiedFiles = 0;
289                for (int i = 0; i < files.length; i++)
290                        numberOfCopiedFiles += _copyTo(files[i], targetPath, recursive);
291
292                return numberOfCopiedFiles;
293        }
294
295        /**
296         * Copy remote files from a remote directory to a local path Input: 'files'
297         * is a Collection of files of type String (! not like at copyTo !),
298         * 'targetPath' is either empty string (or null) in case the 'files' contain
299         * full paths to the individual files, or it should be a remote dir, and in
300         * this case each file name in 'files' will be extended with the remote dir
301         * name before copy. 'localPath' should be a directory name in case of
302         * several files. It can be a filename in case of a single file to be
303         * copied. This is a convenience method for copyFrom on several remote
304         * files. recursive: true if you want traverse directories
305         *
306         * @return number of files copied successfully SshException is thrown in
307         *         case of error.
308         */
309        public int copyFrom(String targetPath, Collection files, File localPath,
310                        boolean recursive) throws SshException {
311
312                int numberOfCopiedFiles = 0;
313
314                String rdir;
315                if (targetPath == null || targetPath.trim().equals("")) {
316                        rdir = "";
317                } else {
318                        rdir = targetPath;
319                        if (!rdir.endsWith("/"))
320                                rdir = rdir + "/";
321                }
322
323                Iterator fileIt = files.iterator();
324                while (fileIt.hasNext()) {
325                        String rfile = (String) fileIt.next();
326                        numberOfCopiedFiles += copyFrom(rdir + rfile, localPath, recursive);
327                }
328                return numberOfCopiedFiles;
329        }
330
331}