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-2008 Sun Microsystems, Inc.
015 * Portions Copyright 2012-2016 ForgeRock AS.
016 */
017package org.opends.server.tools;
018
019import static com.forgerock.opendj.cli.ArgumentConstants.*;
020import static com.forgerock.opendj.cli.CommonArguments.*;
021import static com.forgerock.opendj.cli.Utils.*;
022
023import static org.opends.messages.ToolMessages.*;
024import static org.opends.server.util.StaticUtils.*;
025
026import java.io.OutputStream;
027import java.io.PrintStream;
028import java.util.ArrayList;
029import java.util.List;
030
031import org.forgerock.i18n.LocalizableMessage;
032import org.forgerock.opendj.ldap.DN;
033import org.forgerock.opendj.server.config.server.BackendCfg;
034import org.opends.server.api.Backend;
035import org.opends.server.api.Backend.BackendOperation;
036import org.opends.server.backends.VerifyConfig;
037import org.opends.server.core.DirectoryServer;
038import org.opends.server.core.DirectoryServer.DirectoryServerVersionHandler;
039import org.opends.server.core.LockFileManager;
040import org.opends.server.loggers.JDKLogging;
041import org.opends.server.types.InitializationException;
042import org.opends.server.types.NullOutputStream;
043import org.opends.server.util.BuildVersion;
044
045import com.forgerock.opendj.cli.ArgumentException;
046import com.forgerock.opendj.cli.ArgumentParser;
047import com.forgerock.opendj.cli.BooleanArgument;
048import com.forgerock.opendj.cli.StringArgument;
049
050/**
051 * This program provides a utility to verify the contents of the indexes
052 * of a Directory Server backend.  This will be a process that is
053 * intended to run separate from Directory Server and not internally within the
054 * server process (e.g., via the tasks interface).
055 */
056public class VerifyIndex
057{
058
059  /**
060   * Processes the command-line arguments and invokes the verify process.
061   *
062   * @param  args  The command-line arguments provided to this program.
063   */
064  public static void main(String[] args)
065  {
066    int retCode = mainVerifyIndex(args, true, System.err);
067    if(retCode != 0)
068    {
069      System.exit(filterExitCode(retCode));
070    }
071  }
072
073  /**
074   * Processes the command-line arguments and invokes the verify process.
075   *
076   * @param  args              The command-line arguments provided to this
077   *                           program.
078   * @param  initializeServer  Indicates whether to initialize the server.
079   * @param  errStream         The output stream to use for standard error, or
080   *                           {@code null} if standard error is not needed.
081   * @return The error code.
082   */
083  public static int mainVerifyIndex(String[] args, boolean initializeServer,
084                                    OutputStream errStream)
085  {
086    PrintStream err = NullOutputStream.wrapOrNullStream(errStream);
087    JDKLogging.enableConsoleLoggingForOpenDJTool();
088
089    // Define the command-line arguments that may be used with this program.
090    StringArgument  configFile              = null;
091    StringArgument  baseDNString            = null;
092    StringArgument  indexList               = null;
093    BooleanArgument cleanMode               = null;
094    BooleanArgument countErrors             = null;
095    BooleanArgument displayUsage            = null;
096
097
098    // Create the command-line argument parser for use with this program.
099    LocalizableMessage toolDescription = INFO_VERIFYINDEX_TOOL_DESCRIPTION.get();
100    ArgumentParser argParser =
101         new ArgumentParser("org.opends.server.tools.VerifyIndex",
102                            toolDescription, false);
103    argParser.setShortToolDescription(REF_SHORT_DESC_VERIFY_INDEX.get());
104    argParser.setVersionHandler(new DirectoryServerVersionHandler());
105
106    // Initialize all the command-line argument types and register them with the parser.
107    try
108    {
109      configFile =
110              StringArgument.builder("configFile")
111                      .shortIdentifier('f')
112                      .description(INFO_DESCRIPTION_CONFIG_FILE.get())
113                      .hidden()
114                      .required()
115                      .valuePlaceholder(INFO_CONFIGFILE_PLACEHOLDER.get())
116                      .buildAndAddToParser(argParser);
117      baseDNString =
118              StringArgument.builder(OPTION_LONG_BASEDN)
119                      .shortIdentifier(OPTION_SHORT_BASEDN)
120                      .description(INFO_VERIFYINDEX_DESCRIPTION_BASE_DN.get())
121                      .required()
122                      .valuePlaceholder(INFO_BASEDN_PLACEHOLDER.get())
123                      .buildAndAddToParser(argParser);
124      indexList =
125              StringArgument.builder("index")
126                      .shortIdentifier('i')
127                      .description(INFO_VERIFYINDEX_DESCRIPTION_INDEX_NAME.get())
128                      .multiValued()
129                      .valuePlaceholder(INFO_INDEX_PLACEHOLDER.get())
130                      .buildAndAddToParser(argParser);
131      cleanMode =
132              BooleanArgument.builder("clean")
133                      .shortIdentifier('c')
134                      .description(INFO_VERIFYINDEX_DESCRIPTION_VERIFY_CLEAN.get())
135                      .buildAndAddToParser(argParser);
136      countErrors =
137              BooleanArgument.builder("countErrors")
138                      .description(INFO_VERIFYINDEX_DESCRIPTION_COUNT_ERRORS.get())
139                      .buildAndAddToParser(argParser);
140
141      displayUsage = showUsageArgument();
142      argParser.addArgument(displayUsage);
143      argParser.setUsageArgument(displayUsage);
144    }
145    catch (ArgumentException ae)
146    {
147      printWrappedText(err, ERR_CANNOT_INITIALIZE_ARGS.get(ae.getMessage()));
148      return 1;
149    }
150
151
152    // Parse the command-line arguments provided to this program.
153    try
154    {
155      argParser.parseArguments(args);
156    }
157    catch (ArgumentException ae)
158    {
159      argParser.displayMessageAndUsageReference(err, ERR_ERROR_PARSING_ARGS.get(ae.getMessage()));
160      return 1;
161    }
162
163
164    // If we should just display usage or version information,
165    // then print it and exit.
166    if (argParser.usageOrVersionDisplayed())
167    {
168      return 0;
169    }
170
171    if (cleanMode.isPresent() && indexList.getValues().size() != 1)
172    {
173      argParser.displayMessageAndUsageReference(err, ERR_VERIFYINDEX_VERIFY_CLEAN_REQUIRES_SINGLE_INDEX.get());
174      return 1;
175    }
176
177    // Checks the version - if upgrade required, the tool is unusable
178    try
179    {
180      BuildVersion.checkVersionMismatch();
181    }
182    catch (InitializationException e)
183    {
184      printWrappedText(err, e.getMessage());
185      return 1;
186    }
187
188    if (initializeServer)
189    {
190      try
191      {
192        new DirectoryServer.InitializationBuilder(configFile.getValue())
193            .requireCryptoServices()
194            .initialize();
195      }
196      catch (InitializationException ie)
197      {
198        printWrappedText(err, ERR_CANNOT_INITIALIZE_SERVER_COMPONENTS.get(ie.getLocalizedMessage()));
199        return 1;
200      }
201    }
202
203    // Decode the base DN provided by the user.
204    DN verifyBaseDN ;
205    try
206    {
207      verifyBaseDN = DN.valueOf(baseDNString.getValue());
208    }
209    catch (Exception e)
210    {
211      printWrappedText(err, ERR_CANNOT_DECODE_BASE_DN.get(baseDNString.getValue(), getExceptionMessage(e)));
212      return 1;
213    }
214
215
216    // Get information about the backends defined in the server.  Iterate
217    // through them, finding the one backend to be verified.
218    List<Backend<?>> backendList = new ArrayList<>();
219    List<BackendCfg> entryList = new ArrayList<>();
220    List<List<DN>> dnList = new ArrayList<>();
221    BackendToolUtils.getBackends(backendList, entryList, dnList);
222
223    Backend<?> backend = null;
224    int numBackends = backendList.size();
225    for (int i=0; i < numBackends; i++)
226    {
227      Backend<?> b = backendList.get(i);
228      List<DN>    baseDNs = dnList.get(i);
229
230      if (baseDNs.contains(verifyBaseDN))
231      {
232        if (backend != null)
233        {
234          printWrappedText(err, ERR_MULTIPLE_BACKENDS_FOR_BASE.get(baseDNString.getValue()));
235          return 1;
236        }
237        backend = b;
238      }
239    }
240
241    if (backend == null)
242    {
243      printWrappedText(err, ERR_NO_BACKENDS_FOR_BASE.get(baseDNString.getValue()));
244      return 1;
245    }
246
247    if (!backend.supports(BackendOperation.INDEXING))
248    {
249      printWrappedText(err, ERR_BACKEND_NO_INDEXING_SUPPORT.get());
250      return 1;
251    }
252
253    // Initialize the verify configuration.
254    VerifyConfig verifyConfig = new VerifyConfig();
255    verifyConfig.setBaseDN(verifyBaseDN);
256    if (cleanMode.isPresent())
257    {
258      for (String s : indexList.getValues())
259      {
260        verifyConfig.addCleanIndex(s);
261      }
262    }
263    else
264    {
265      for (String s : indexList.getValues())
266      {
267        verifyConfig.addCompleteIndex(s);
268      }
269    }
270
271
272    // Acquire a shared lock for the backend.
273    try
274    {
275      String lockFile = LockFileManager.getBackendLockFileName(backend);
276      StringBuilder failureReason = new StringBuilder();
277      if (! LockFileManager.acquireSharedLock(lockFile, failureReason))
278      {
279        printWrappedText(err, ERR_VERIFYINDEX_CANNOT_LOCK_BACKEND.get(backend.getBackendID(), failureReason));
280        return 1;
281      }
282    }
283    catch (Exception e)
284    {
285      printWrappedText(err, ERR_VERIFYINDEX_CANNOT_LOCK_BACKEND.get(backend.getBackendID(), getExceptionMessage(e)));
286      return 1;
287    }
288
289
290    try
291    {
292      // Launch the verify process.
293      final long errorCount = backend.verifyBackend(verifyConfig);
294      if (countErrors.isPresent())
295      {
296        if (errorCount > Integer.MAX_VALUE)
297        {
298          return Integer.MAX_VALUE;
299        }
300        return (int) errorCount;
301      }
302      return 0;
303    }
304    catch (InitializationException e)
305    {
306      printWrappedText(err, ERR_VERIFYINDEX_ERROR_DURING_VERIFY.get(e.getMessage()));
307      return 1;
308    }
309    catch (Exception e)
310    {
311      printWrappedText(err, ERR_VERIFYINDEX_ERROR_DURING_VERIFY.get(stackTraceToSingleLineString(e)));
312      return 1;
313    }
314    finally
315    {
316      // Release the shared lock on the backend.
317      try
318      {
319        String lockFile = LockFileManager.getBackendLockFileName(backend);
320        StringBuilder failureReason = new StringBuilder();
321        if (! LockFileManager.releaseLock(lockFile, failureReason))
322        {
323          printWrappedText(err, WARN_VERIFYINDEX_CANNOT_UNLOCK_BACKEND.get(backend.getBackendID(), failureReason));
324        }
325      }
326      catch (Exception e)
327      {
328        printWrappedText(err,
329            WARN_VERIFYINDEX_CANNOT_UNLOCK_BACKEND.get(backend.getBackendID(), getExceptionMessage(e)));
330      }
331    }
332  }
333}