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}