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