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