001/* 002 * IzPack - Copyright 2001-2005 Julien Ponge, All Rights Reserved. 003 * 004 * http://www.izforge.com/izpack/ 005 * http://developer.berlios.de/projects/izpack/ 006 * 007 * Copyright 2002 Olexij Tkatchenko 008 * 009 * Licensed under the Apache License, Version 2.0 (the "License"); 010 * you may not use this file except in compliance with the License. 011 * You may obtain a copy of the License at 012 * 013 * http://www.apache.org/licenses/LICENSE-2.0 014 * 015 * Unless required by applicable law or agreed to in writing, software 016 * distributed under the License is distributed on an "AS IS" BASIS, 017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 018 * See the License for the specific language governing permissions and 019 * limitations under the License. 020 */ 021 022package com.izforge.izpack.util; 023 024import java.io.BufferedReader; 025import java.io.BufferedWriter; 026import java.io.File; 027import java.io.IOException; 028import java.io.InputStreamReader; 029import java.io.Reader; 030import java.io.StringWriter; 031import java.io.Writer; 032import java.util.ArrayList; 033import java.util.Collection; 034import java.util.Iterator; 035import java.util.List; 036 037import com.izforge.izpack.ExecutableFile; 038 039/** 040 * Executes a bunch of files. This class is intended to do a system dependent installation 041 * postprocessing. Executable file can be any file installed with current package. After execution 042 * the file can be optionally removed. Before execution on Unix systems execution flag will be set 043 * on processed file. 044 * 045 * @author Olexij Tkatchenko <ot@parcs.de> 046 */ 047public class FileExecutor 048{ 049 050 /** 051 * This is a grabber for stdout and stderr. It will be launched once at command execution end 052 * terminates if the apropriate stream runs out of data. 053 * 054 * @author Olexij Tkatchenko <ot@parcs.de> 055 */ 056 private static class MonitorInputStream implements Runnable 057 { 058 059 private BufferedReader reader; 060 061 private BufferedWriter writer; 062 063 private boolean shouldStop = false; 064 065 public MonitorInputStream(Reader in, Writer out) 066 { 067 reader = new BufferedReader(in); 068 writer = new BufferedWriter(out); 069 } 070 071 public void doStop() 072 { 073 shouldStop = true; 074 } 075 076 public void run() 077 { 078 try 079 { 080 String line; 081 while ((line = reader.readLine()) != null) 082 { 083 writer.write(line); 084 writer.newLine(); 085 writer.flush(); 086 if (shouldStop) return; 087 } 088 } 089 catch (IOException ioe) 090 { 091 ioe.printStackTrace(System.out); 092 } 093 } 094 } 095 096 private boolean stopThread(Thread t, MonitorInputStream m) 097 { 098 m.doStop(); 099 long softTimeout = 1000; 100 try 101 { 102 t.join(softTimeout); 103 } 104 catch (InterruptedException e) 105 {} 106 107 if (!t.isAlive()) return true; 108 109 t.interrupt(); 110 long hardTimeout = 1000; 111 try 112 { 113 t.join(hardTimeout); 114 } 115 catch (InterruptedException e) 116 {} 117 return !t.isAlive(); 118 } 119 120 /** 121 * Constructs a new executor. The executable files specified must have pretranslated paths 122 * (variables expanded and file separator characters converted if necessary). 123 * 124 * @param files the executable files to process 125 */ 126 public FileExecutor(Collection files) 127 { 128 this.files = files; 129 } 130 131 /** 132 * Constructs a new executor. 133 */ 134 public FileExecutor() 135 { 136 this.files = null; 137 } 138 139 /** 140 * Executed a system command and waits for completion. 141 * 142 * @param params system command as string array 143 * @param output contains output of the command index 0 = standard output index 1 = standard 144 * error 145 * @return exit status of process 146 */ 147 public int executeCommand(String[] params, String[] output) 148 { 149 StringBuffer retval = new StringBuffer(); 150 retval.append("executeCommand\n"); 151 if (params != null) 152 { 153 for (int i = 0; i < params.length; i++) 154 { 155 retval.append("\tparams: " + params[i]); 156 retval.append("\n"); 157 } 158 } 159 Process process = null; 160 MonitorInputStream outMonitor = null; 161 MonitorInputStream errMonitor = null; 162 Thread t1 = null; 163 Thread t2 = null; 164 int exitStatus = -1; 165 166 Debug.trace(retval); 167 168 try 169 { 170 // execute command 171 process = Runtime.getRuntime().exec(params); 172 173 boolean console = false;// TODO: impl from xml <execute 174 // in_console=true ...>, but works already 175 // if this flag is true 176 if (console) 177 { 178 Console c = new Console(process); 179 // save command output 180 output[0] = c.getOutputData(); 181 output[1] = c.getErrorData(); 182 exitStatus = process.exitValue(); 183 } 184 else 185 { 186 StringWriter outWriter = new StringWriter(); 187 StringWriter errWriter = new StringWriter(); 188 189 InputStreamReader or = new InputStreamReader(process.getInputStream()); 190 InputStreamReader er = new InputStreamReader(process.getErrorStream()); 191 outMonitor = new MonitorInputStream(or, outWriter); 192 errMonitor = new MonitorInputStream(er, errWriter); 193 t1 = new Thread(outMonitor); 194 t2 = new Thread(errMonitor); 195 t1.setDaemon(true); 196 t2.setDaemon(true); 197 t1.start(); 198 t2.start(); 199 200 // wait for command to complete 201 exitStatus = process.waitFor(); 202 t1.join(); 203 t2.join(); 204 205 // save command output 206 output[0] = outWriter.toString(); 207 Debug.trace("stdout:"); 208 Debug.trace(output[0]); 209 output[1] = errWriter.toString(); 210 Debug.trace("stderr:"); 211 Debug.trace(output[1]); 212 } 213 Debug.trace("exit status: " + Integer.toString(exitStatus)); 214 } 215 catch (InterruptedException e) 216 { 217 if (Debug.tracing()) e.printStackTrace(System.err); 218 stopThread(t1, outMonitor); 219 stopThread(t2, errMonitor); 220 output[0] = ""; 221 output[1] = e.getMessage() + "\n"; 222 process.destroy(); 223 } 224 catch (IOException e) 225 { 226 if (Debug.tracing()) e.printStackTrace(System.err); 227 output[0] = ""; 228 output[1] = e.getMessage() + "\n"; 229 } 230 return exitStatus; 231 } 232 233 /** 234 * Executes files specified at construction time. 235 * 236 * @param currentStage the stage of the installation 237 * @param handler The AbstractUIHandler to notify on errors. 238 * 239 * @return 0 on success, else the exit status of the last failed command 240 */ 241 public int executeFiles(int currentStage, AbstractUIHandler handler) 242 { 243 int exitStatus = 0; 244 String[] output = new String[2]; 245 // String permissions = (System.getProperty("user.name").equals("root")) 246 // ? "a+x" : "u+x"; 247 String permissions = "a+x"; 248 249 // loop through all executables 250 Iterator efileIterator = files.iterator(); 251 while (exitStatus == 0 && efileIterator.hasNext()) 252 { 253 ExecutableFile efile = (ExecutableFile) efileIterator.next(); 254 boolean deleteAfterwards = !efile.keepFile; 255 File file = new File(efile.path); 256 Debug.trace("handeling executable file " + efile); 257 258 // skip file if not for current OS (it might not have been installed 259 // at all) 260 if (!OsConstraint.oneMatchesCurrentSystem(efile.osList)) continue; 261 262 if (currentStage != ExecutableFile.UNINSTALL && OsVersion.IS_UNIX) 263 { 264 // fix executable permission for unix systems 265 Debug.trace("making file executable (setting executable flag)"); 266 String[] params = { "/bin/chmod", permissions, file.toString()}; 267 exitStatus = executeCommand(params, output); 268 if (exitStatus != 0) 269 { 270 handler.emitError("file execution error", "Error executing \n" + params[0] 271 + " " + params[1] + " " + params[2]); 272 continue; 273 } 274 } 275 276 // execute command in POSTINSTALL stage 277 if ((exitStatus == 0) 278 && ((currentStage == ExecutableFile.POSTINSTALL && efile.executionStage == ExecutableFile.POSTINSTALL) || (currentStage == ExecutableFile.UNINSTALL && efile.executionStage == ExecutableFile.UNINSTALL))) 279 { 280 List paramList = new ArrayList(); 281 if (ExecutableFile.BIN == efile.type) 282 paramList.add(file.toString()); 283 284 else if (ExecutableFile.JAR == efile.type && null == efile.mainClass) 285 { 286 paramList.add(System.getProperty("java.home") + "/bin/java"); 287 paramList.add("-jar"); 288 paramList.add(file.toString()); 289 } 290 else if (ExecutableFile.JAR == efile.type && null != efile.mainClass) 291 { 292 paramList.add(System.getProperty("java.home") + "/bin/java"); 293 paramList.add("-cp"); 294 paramList.add(file.toString()); 295 paramList.add(efile.mainClass); 296 } 297 298 if (null != efile.argList && !efile.argList.isEmpty()) 299 paramList.addAll(efile.argList); 300 301 String[] params = new String[paramList.size()]; 302 for (int i = 0; i < paramList.size(); i++) 303 params[i] = (String) paramList.get(i); 304 305 exitStatus = executeCommand(params, output); 306 307 // bring a dialog depending on return code and failure handling 308 if (exitStatus != 0) 309 { 310 deleteAfterwards = false; 311 String message = output[0] + "\n" + output[1]; 312 if (message.length() == 1) 313 message = "Failed to execute " + file.toString() + "."; 314 315 if (efile.onFailure == ExecutableFile.ABORT) 316 { 317 // CHECKME: let the user decide or abort anyway? 318 handler.emitError("file execution error", message); 319 } 320 else if (efile.onFailure == ExecutableFile.WARN) 321 { 322 // CHECKME: let the user decide or abort anyway? 323 handler.emitWarning("file execution error", message); 324 exitStatus = 0; 325 } 326 else 327 { 328 if (handler 329 .askQuestion(null, "Continue?", AbstractUIHandler.CHOICES_YES_NO) == AbstractUIHandler.ANSWER_YES) 330 exitStatus = 0; 331 } 332 333 } 334 335 } 336 337 // POSTINSTALL executables will be deleted 338 if (efile.executionStage == ExecutableFile.POSTINSTALL && deleteAfterwards) 339 { 340 if (file.canWrite()) file.delete(); 341 } 342 343 } 344 return exitStatus; 345 } 346 347 /** The files to execute. */ 348 private Collection files; 349}