001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions Copyright [year] [name of copyright owner]". 013 * 014 * Copyright 2006-2009 Sun Microsystems, Inc. 015 * Portions Copyright 2013-2016 ForgeRock AS. 016 */ 017package org.opends.server.tools; 018 019import java.io.*; 020 021import org.forgerock.i18n.LocalizableMessage; 022import org.opends.server.core.DirectoryServer; 023import org.opends.server.loggers.JDKLogging; 024import org.opends.server.types.NullOutputStream; 025 026import com.forgerock.opendj.cli.*; 027 028import static org.opends.messages.CoreMessages.*; 029import static org.opends.messages.ToolMessages.*; 030import static org.opends.server.util.StaticUtils.*; 031import static com.forgerock.opendj.cli.Utils.filterExitCode; 032import static com.forgerock.opendj.cli.CommonArguments.*; 033 034/** 035 * This program provides a simple tool that will wait for a specified file to be 036 * deleted before exiting. It can be used in the process of confirming that the 037 * server has completed its startup or shutdown process. 038 */ 039public class WaitForFileDelete extends ConsoleApplication 040{ 041 /** 042 * The fully-qualified name of this class. 043 */ 044 private static final String CLASS_NAME = 045 "org.opends.server.tools.WaitForFileDelete"; 046 047 048 049 /** 050 * The exit code value that will be used if the target file is deleted 051 * successfully. 052 */ 053 public static final int EXIT_CODE_SUCCESS = 0; 054 055 056 057 /** 058 * The exit code value that will be used if an internal error occurs within 059 * this program. 060 */ 061 public static final int EXIT_CODE_INTERNAL_ERROR = 1; 062 063 064 065 /** 066 * The exit code value that will be used if a timeout occurs while waiting for 067 * the file to be removed. 068 */ 069 public static final int EXIT_CODE_TIMEOUT = 2; 070 071 072 073 /** 074 * Constructor for the WaitForFileDelete object. 075 * 076 * @param out the print stream to use for standard output. 077 * @param err the print stream to use for standard error. 078 * @param in the input stream to use for standard input. 079 */ 080 public WaitForFileDelete(PrintStream out, PrintStream err, InputStream in) 081 { 082 super(out, err); 083 } 084 085 /** 086 * Processes the command-line arguments and initiates the process of waiting 087 * for the file to be removed. 088 * 089 * @param args The command-line arguments provided to this program. 090 */ 091 public static void main(String[] args) 092 { 093 int retCode = mainCLI(args, true, System.out, System.err, System.in); 094 095 System.exit(retCode); 096 } 097 098 /** 099 * Processes the command-line arguments and initiates the process of waiting 100 * for the file to be removed. 101 * 102 * @param args The command-line arguments provided to this 103 * program. 104 * @param initializeServer Indicates whether to initialize the server. 105 * @param outStream The output stream to use for standard output, or 106 * <CODE>null</CODE> if standard output is not 107 * needed. 108 * @param errStream The output stream to use for standard error, or 109 * <CODE>null</CODE> if standard error is not 110 * needed. 111 * @param inStream The input stream to use for standard input. 112 * @return The error code. 113 */ 114 115 public static int mainCLI(String[] args, boolean initializeServer, 116 OutputStream outStream, OutputStream errStream, InputStream inStream) 117 { 118 int exitCode; 119 PrintStream out = NullOutputStream.wrapOrNullStream(outStream); 120 PrintStream err = NullOutputStream.wrapOrNullStream(errStream); 121 JDKLogging.disableLogging(); 122 try 123 { 124 WaitForFileDelete wffd = new WaitForFileDelete(out, err, System.in); 125 exitCode = wffd.mainWait(args); 126 if (exitCode != EXIT_CODE_SUCCESS) 127 { 128 exitCode = filterExitCode(exitCode); 129 } 130 } 131 catch (Exception e) 132 { 133 e.printStackTrace(); 134 exitCode = EXIT_CODE_INTERNAL_ERROR; 135 } 136 return exitCode; 137 } 138 139 140 141 /** 142 * Processes the command-line arguments and then waits for the specified file 143 * to be removed. 144 * 145 * @param args The command-line arguments provided to this program. 146 * @param out The output stream to use for standard output, or 147 * <CODE>null</CODE> if standard output is not 148 * needed. 149 * @param err The output stream to use for standard error, or 150 * <CODE>null</CODE> if standard error is not 151 * needed. 152 * @param inStream The input stream to use for standard input. 153 * 154 * @return An integer value of zero if the file was deleted successfully, or 155 * some other value if a problem occurred. 156 */ 157 private int mainWait(String[] args) 158 { 159 // Create all of the command-line arguments for this program. 160 final BooleanArgument showUsage; 161 final IntegerArgument timeout; 162 final StringArgument logFilePath; 163 final StringArgument targetFilePath; 164 final StringArgument outputFilePath; 165 final BooleanArgument quietMode; 166 167 LocalizableMessage toolDescription = INFO_WAIT4DEL_TOOL_DESCRIPTION.get(); 168 ArgumentParser argParser = new ArgumentParser(CLASS_NAME, toolDescription, 169 false); 170 171 try 172 { 173 targetFilePath = 174 StringArgument.builder("targetFile") 175 .shortIdentifier('f') 176 .description(INFO_WAIT4DEL_DESCRIPTION_TARGET_FILE.get()) 177 .required() 178 .valuePlaceholder(INFO_PATH_PLACEHOLDER.get()) 179 .buildAndAddToParser(argParser); 180 logFilePath = 181 StringArgument.builder("logFile") 182 .shortIdentifier('l') 183 .description(INFO_WAIT4DEL_DESCRIPTION_LOG_FILE.get()) 184 .valuePlaceholder(INFO_PATH_PLACEHOLDER.get()) 185 .buildAndAddToParser(argParser); 186 outputFilePath = 187 StringArgument.builder("outputFile") 188 .shortIdentifier('o') 189 .description(INFO_WAIT4DEL_DESCRIPTION_OUTPUT_FILE.get()) 190 .valuePlaceholder(INFO_PATH_PLACEHOLDER.get()) 191 .buildAndAddToParser(argParser); 192 timeout = 193 IntegerArgument.builder("timeout") 194 .shortIdentifier('t') 195 .description(INFO_WAIT4DEL_DESCRIPTION_TIMEOUT.get()) 196 .required() 197 .lowerBound(0) 198 .defaultValue(DirectoryServer.DEFAULT_TIMEOUT) 199 .valuePlaceholder(INFO_SECONDS_PLACEHOLDER.get()) 200 .buildAndAddToParser(argParser); 201 202 203 // Not used in this class, but required by the start-ds script (see issue #3814) 204 BooleanArgument.builder("useLastKnownGoodConfig") 205 .shortIdentifier('L') 206 .description(INFO_DSCORE_DESCRIPTION_LASTKNOWNGOODCFG.get()) 207 .buildAndAddToParser(argParser); 208 209 // Not used in this class, but required by the start-ds script (see issue #3814) 210 quietMode = quietArgument(); 211 argParser.addArgument(quietMode); 212 213 showUsage = showUsageArgument(); 214 argParser.addArgument(showUsage); 215 argParser.setUsageArgument(showUsage); 216 } 217 catch (ArgumentException ae) 218 { 219 LocalizableMessage message = ERR_CANNOT_INITIALIZE_ARGS.get(ae.getMessage()); 220 println(message); 221 return EXIT_CODE_INTERNAL_ERROR; 222 } 223 224 225 // Parse the command-line arguments provided to the program. 226 try 227 { 228 argParser.parseArguments(args); 229 } 230 catch (ArgumentException ae) 231 { 232 argParser.displayMessageAndUsageReference(getErrStream(), ERR_ERROR_PARSING_ARGS.get(ae.getMessage())); 233 return EXIT_CODE_INTERNAL_ERROR; 234 } 235 236 237 // If we should just display usage or version information, 238 // then print it and exit. 239 if (argParser.usageOrVersionDisplayed()) 240 { 241 return EXIT_CODE_SUCCESS; 242 } 243 244 245 // Get the file to watch. If it doesn't exist now, then exit immediately. 246 File targetFile = new File(targetFilePath.getValue()); 247 if (! targetFile.exists()) 248 { 249 return EXIT_CODE_SUCCESS; 250 } 251 252 253 // If a log file was specified, then open it. 254 long logFileOffset = 0L; 255 RandomAccessFile logFile = null; 256 if (logFilePath.isPresent()) 257 { 258 try 259 { 260 File f = new File(logFilePath.getValue()); 261 if (f.exists()) 262 { 263 logFile = new RandomAccessFile(f, "r"); 264 logFileOffset = logFile.length(); 265 logFile.seek(logFileOffset); 266 } 267 } 268 catch (Exception e) 269 { 270 println(WARN_WAIT4DEL_CANNOT_OPEN_LOG_FILE.get(logFilePath.getValue(), e)); 271 logFile = null; 272 } 273 } 274 275 276 // If an output file was specified and we could open the log file, open it 277 // and append data to it. 278 RandomAccessFile outputFile = null; 279 long outputFileOffset = 0L; 280 if (logFile != null && outputFilePath.isPresent()) 281 { 282 try 283 { 284 File f = new File(outputFilePath.getValue()); 285 if (f.exists()) 286 { 287 outputFile = new RandomAccessFile(f, "rw"); 288 outputFileOffset = outputFile.length(); 289 outputFile.seek(outputFileOffset); 290 } 291 } 292 catch (Exception e) 293 { 294 println(WARN_WAIT4DEL_CANNOT_OPEN_OUTPUT_FILE.get(outputFilePath.getValue(), e)); 295 outputFile = null; 296 } 297 } 298 // Figure out when to stop waiting. 299 long stopWaitingTime; 300 try 301 { 302 long timeoutMillis = 1000L * Integer.parseInt(timeout.getValue()); 303 if (timeoutMillis > 0) 304 { 305 stopWaitingTime = System.currentTimeMillis() + timeoutMillis; 306 } 307 else 308 { 309 stopWaitingTime = Long.MAX_VALUE; 310 } 311 } 312 catch (Exception e) 313 { 314 // This shouldn't happen, but if it does then ignore it. 315 stopWaitingTime = System.currentTimeMillis() + 60000; 316 } 317 318 319 // Operate in a loop, printing out any applicable log messages and waiting 320 // for the target file to be removed. 321 byte[] logBuffer = new byte[8192]; 322 while (System.currentTimeMillis() < stopWaitingTime) 323 { 324 if (logFile != null) 325 { 326 try 327 { 328 while (logFile.length() > logFileOffset) 329 { 330 int bytesRead = logFile.read(logBuffer); 331 if (bytesRead > 0) 332 { 333 if (outputFile == null) 334 { 335 getOutputStream().write(logBuffer, 0, bytesRead); 336 getOutputStream().flush(); 337 } 338 else 339 { 340 // Write on the file. 341 // TODO 342 outputFile.write(logBuffer, 0, bytesRead); 343 344 } 345 logFileOffset += bytesRead; 346 } 347 } 348 } 349 catch (Exception e) 350 { 351 // We'll just ignore this. 352 } 353 } 354 355 356 if (! targetFile.exists()) 357 { 358 break; 359 } 360 else 361 { 362 try 363 { 364 Thread.sleep(10); 365 } catch (InterruptedException ie) {} 366 } 367 } 368 369 close(outputFile); 370 371 if (targetFile.exists()) 372 { 373 println(ERR_TIMEOUT_DURING_STARTUP.get( 374 Integer.parseInt(timeout.getValue()), 375 timeout.getLongIdentifier())); 376 return EXIT_CODE_TIMEOUT; 377 } 378 else 379 { 380 return EXIT_CODE_SUCCESS; 381 } 382 } 383 384 /** {@inheritDoc} */ 385 @Override 386 public boolean isAdvancedMode() 387 { 388 return false; 389 } 390 391 /** {@inheritDoc} */ 392 @Override 393 public boolean isInteractive() 394 { 395 return false; 396 } 397 398 /** {@inheritDoc} */ 399 @Override 400 public boolean isMenuDrivenMode() 401 { 402 return false; 403 } 404 405 /** {@inheritDoc} */ 406 @Override 407 public boolean isQuiet() 408 { 409 return false; 410 } 411 412 /** {@inheritDoc} */ 413 @Override 414 public boolean isScriptFriendly() 415 { 416 return false; 417 } 418 419 /** {@inheritDoc} */ 420 @Override 421 public boolean isVerbose() 422 { 423 return false; 424 } 425} 426