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 static org.opends.messages.ToolMessages.*;
020import static org.opends.server.config.ConfigConstants.*;
021import static org.opends.server.util.ServerConstants.*;
022import static org.opends.server.util.StaticUtils.*;
023
024import static com.forgerock.opendj.cli.ArgumentConstants.*;
025import static com.forgerock.opendj.cli.Utils.*;
026import static com.forgerock.opendj.cli.CommonArguments.*;
027
028import java.io.File;
029import java.io.OutputStream;
030import java.io.PrintStream;
031import java.text.SimpleDateFormat;
032import java.util.ArrayList;
033import java.util.Date;
034import java.util.HashMap;
035import java.util.HashSet;
036import java.util.List;
037import java.util.TimeZone;
038
039import org.forgerock.i18n.slf4j.LocalizedLogger;
040import org.forgerock.opendj.config.server.ConfigException;
041import org.forgerock.util.Utils;
042import org.opends.server.admin.std.server.BackendCfg;
043import org.opends.server.api.Backend;
044import org.opends.server.api.Backend.BackendOperation;
045import org.opends.server.core.CoreConfigManager;
046import org.opends.server.core.DirectoryServer;
047import org.opends.server.core.LockFileManager;
048import org.opends.server.extensions.ConfigFileHandler;
049import org.opends.server.loggers.DebugLogger;
050import org.opends.server.loggers.ErrorLogPublisher;
051import org.opends.server.loggers.ErrorLogger;
052import org.opends.server.loggers.JDKLogging;
053import org.opends.server.loggers.TextErrorLogPublisher;
054import org.opends.server.loggers.TextWriter;
055import org.opends.server.protocols.ldap.LDAPAttribute;
056import org.opends.server.tasks.BackupTask;
057import org.opends.server.tools.tasks.TaskTool;
058import org.opends.server.types.BackupConfig;
059import org.opends.server.types.BackupDirectory;
060import org.forgerock.opendj.ldap.DN;
061import org.opends.server.types.DirectoryException;
062import org.opends.server.types.InitializationException;
063import org.opends.server.types.NullOutputStream;
064import org.opends.server.types.RawAttribute;
065import org.opends.server.util.cli.LDAPConnectionArgumentParser;
066
067import com.forgerock.opendj.cli.Argument;
068import com.forgerock.opendj.cli.ArgumentException;
069import com.forgerock.opendj.cli.BooleanArgument;
070import com.forgerock.opendj.cli.ClientException;
071import com.forgerock.opendj.cli.StringArgument;
072
073/**
074 * This program provides a utility that may be used to back up a Directory
075 * Server backend in a binary form that may be quickly archived and restored.
076 * The format of the backup may vary based on the backend type and does not need
077 * to be something that can be handled by any other backend type.  This will be
078 * a process that is intended to run separate from Directory Server and not
079 * internally within the server process (e.g., via the tasks interface).
080 */
081public class BackUpDB extends TaskTool
082{
083
084  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
085
086  /**
087   * The main method for BackUpDB tool.
088   *
089   * @param  args  The command-line arguments provided to this program.
090   */
091  public static void main(String[] args)
092  {
093    int retCode = mainBackUpDB(args, true, System.out, System.err);
094
095    if(retCode != 0)
096    {
097      System.exit(filterExitCode(retCode));
098    }
099  }
100
101  /**
102   * Processes the command-line arguments and invokes the backup process.
103   *
104   * @param  args  The command-line arguments provided to this program.
105   *
106   * @return The error code.
107   */
108  public static int mainBackUpDB(String[] args)
109  {
110    return mainBackUpDB(args, true, System.out, System.err);
111  }
112
113  /**
114   * Processes the command-line arguments and invokes the backup process.
115   *
116   * @param  args              The command-line arguments provided to this
117   *                           program.
118   * @param  initializeServer  Indicates whether to initialize the server.
119   * @param  outStream         The output stream to use for standard output, or
120   *                           {@code null} if standard output is not needed.
121   * @param  errStream         The output stream to use for standard error, or
122   *                           {@code null} if standard error is not needed.
123   *
124   * @return The error code.
125   */
126  public static int mainBackUpDB(String[] args, boolean initializeServer,
127                                 OutputStream outStream, OutputStream errStream)
128  {
129    BackUpDB tool = new BackUpDB();
130    return tool.process(args, initializeServer, outStream, errStream);
131  }
132
133  /** Define the command-line arguments that may be used with this program. */
134  private BooleanArgument backUpAll;
135  private BooleanArgument compress;
136  private BooleanArgument encrypt;
137  private BooleanArgument hash;
138  private BooleanArgument incremental;
139  private BooleanArgument signHash;
140  private StringArgument  backendID;
141  private StringArgument  backupIDString;
142  private StringArgument  configClass;
143  private StringArgument  configFile;
144  private StringArgument  backupDirectory;
145  private StringArgument  incrementalBaseID;
146
147  private int process(String[] args, boolean initializeServer,
148                      OutputStream outStream, OutputStream errStream)
149  {
150    PrintStream out = NullOutputStream.wrapOrNullStream(outStream);
151    PrintStream err = NullOutputStream.wrapOrNullStream(errStream);
152    JDKLogging.disableLogging();
153
154    // Create the command-line argument parser for use with this program.
155    LDAPConnectionArgumentParser argParser =
156            createArgParser("org.opends.server.tools.BackUpDB",
157                            INFO_BACKUPDB_TOOL_DESCRIPTION.get());
158    argParser.setShortToolDescription(REF_SHORT_DESC_BACKUP.get());
159
160
161    // Initialize all the command-line argument types and register them with the
162    // parser.
163    try
164    {
165      configClass =
166              StringArgument.builder(OPTION_LONG_CONFIG_CLASS)
167                      .shortIdentifier(OPTION_SHORT_CONFIG_CLASS)
168                      .description(INFO_DESCRIPTION_CONFIG_CLASS.get())
169                      .hidden()
170                      .required()
171                      .defaultValue(ConfigFileHandler.class.getName())
172                      .valuePlaceholder(INFO_CONFIGCLASS_PLACEHOLDER.get())
173                      .buildAndAddToParser(argParser);
174      configFile =
175              StringArgument.builder("configFile")
176                      .shortIdentifier('f')
177                      .description(INFO_DESCRIPTION_CONFIG_FILE.get())
178                      .hidden()
179                      .required()
180                      .valuePlaceholder(INFO_CONFIGFILE_PLACEHOLDER.get())
181                      .buildAndAddToParser(argParser);
182      backendID =
183              StringArgument.builder("backendID")
184                      .shortIdentifier('n')
185                      .description(INFO_BACKUPDB_DESCRIPTION_BACKEND_ID.get())
186                      .multiValued()
187                      .valuePlaceholder(INFO_BACKENDNAME_PLACEHOLDER.get())
188                      .buildAndAddToParser(argParser);
189      backUpAll =
190              BooleanArgument.builder("backUpAll")
191                      .shortIdentifier('a')
192                      .description(INFO_BACKUPDB_DESCRIPTION_BACKUP_ALL.get())
193                      .buildAndAddToParser(argParser);
194      backupIDString =
195              StringArgument.builder("backupID")
196                      .shortIdentifier('I')
197                      .description(INFO_BACKUPDB_DESCRIPTION_BACKUP_ID.get())
198                      .valuePlaceholder(INFO_BACKUPID_PLACEHOLDER.get())
199                      .buildAndAddToParser(argParser);
200      backupDirectory =
201              StringArgument.builder("backupDirectory")
202                      .shortIdentifier('d')
203                      .description(INFO_BACKUPDB_DESCRIPTION_BACKUP_DIR.get())
204                      .required()
205                      .valuePlaceholder(INFO_BACKUPDIR_PLACEHOLDER.get())
206                      .buildAndAddToParser(argParser);
207      incremental =
208              BooleanArgument.builder("incremental")
209                      .shortIdentifier('i')
210                      .description(INFO_BACKUPDB_DESCRIPTION_INCREMENTAL.get())
211                      .buildAndAddToParser(argParser);
212      incrementalBaseID =
213              StringArgument.builder("incrementalBaseID")
214                      .shortIdentifier('B')
215                      .description(INFO_BACKUPDB_DESCRIPTION_INCREMENTAL_BASE_ID.get())
216                      .valuePlaceholder(INFO_BACKUPID_PLACEHOLDER.get())
217                      .buildAndAddToParser(argParser);
218      compress =
219              BooleanArgument.builder(OPTION_LONG_COMPRESS)
220                      .shortIdentifier(OPTION_SHORT_COMPRESS)
221                      .description(INFO_BACKUPDB_DESCRIPTION_COMPRESS.get())
222                      .buildAndAddToParser(argParser);
223      encrypt =
224              BooleanArgument.builder("encrypt")
225                      .shortIdentifier('y')
226                      .description(INFO_BACKUPDB_DESCRIPTION_ENCRYPT.get())
227                      .buildAndAddToParser(argParser);
228      hash =
229              BooleanArgument.builder("hash")
230                      .shortIdentifier('A')
231                      .description(INFO_BACKUPDB_DESCRIPTION_HASH.get())
232                      .buildAndAddToParser(argParser);
233      signHash =
234              BooleanArgument.builder("signHash")
235                      .shortIdentifier('s')
236                      .description(INFO_BACKUPDB_DESCRIPTION_SIGN_HASH.get())
237                      .buildAndAddToParser(argParser);
238
239      final BooleanArgument displayUsage = showUsageArgument();
240      argParser.addArgument(displayUsage);
241      argParser.setUsageArgument(displayUsage);
242    }
243    catch (ArgumentException ae)
244    {
245      printWrappedText(err, ERR_CANNOT_INITIALIZE_ARGS.get(ae.getMessage()));
246      return 1;
247    }
248
249    // Init the default values so that they can appear also on the usage.
250    argParser.getArguments().initArgumentsWithConfiguration(argParser);
251
252    // Parse the command-line arguments provided to this program.
253    try
254    {
255      argParser.parseArguments(args);
256      validateTaskArgs();
257    }
258    catch (ArgumentException ae)
259    {
260      argParser.displayMessageAndUsageReference(err, ERR_ERROR_PARSING_ARGS.get(ae.getMessage()));
261      return 1;
262    }
263    catch (ClientException ce)
264    {
265      // No need to display the usage since the problem comes with a provided value.
266      printWrappedText(err, ce.getMessageObject());
267      return 1;
268    }
269
270
271    // If we should just display usage or version information,
272    // then print it and exit.
273    if (argParser.usageOrVersionDisplayed())
274    {
275      return 0;
276    }
277
278    // Make sure that either the backUpAll argument was provided or at least one
279    // backend ID was given.  They are mutually exclusive.
280    if (backUpAll.isPresent())
281    {
282      if (backendID.isPresent())
283      {
284        argParser.displayMessageAndUsageReference(err, ERR_BACKUPDB_CANNOT_MIX_BACKUP_ALL_AND_BACKEND_ID.get(
285            backUpAll.getLongIdentifier(), backendID.getLongIdentifier()));
286        return 1;
287      }
288    }
289    else if (! backendID.isPresent())
290    {
291      argParser.displayMessageAndUsageReference(err, ERR_BACKUPDB_NEED_BACKUP_ALL_OR_BACKEND_ID.get(
292          backUpAll.getLongIdentifier(), backendID.getLongIdentifier()));
293      return 1;
294    }
295    else
296    {
297      // Check that the backendID has not been expressed twice.
298      HashSet<String> backendIDLowerCase = new HashSet<>();
299      HashSet<String> repeatedBackendIds = new HashSet<>();
300      for (String id : backendID.getValues())
301      {
302        String lId = id.toLowerCase();
303        if (!backendIDLowerCase.add(lId))
304        {
305          repeatedBackendIds.add(lId);
306        }
307      }
308      if (!repeatedBackendIds.isEmpty())
309      {
310        argParser.displayMessageAndUsageReference(err,
311            ERR_BACKUPDB_REPEATED_BACKEND_ID.get(Utils.joinAsString(", ", repeatedBackendIds)));
312        return 1;
313      }
314    }
315
316    // If the incremental base ID was specified, then make sure it is an
317    // incremental backup.
318    if (incrementalBaseID.isPresent() && ! incremental.isPresent())
319    {
320      argParser.displayMessageAndUsageReference(err, ERR_BACKUPDB_INCREMENTAL_BASE_REQUIRES_INCREMENTAL.get(
321              incrementalBaseID.getLongIdentifier(), incremental.getLongIdentifier()));
322      return 1;
323    }
324
325    // Encryption or signing requires the ADS backend be available for
326    // CryptoManager access to secret key entries. If no connection arguments
327    //  are present, infer an offline backup.
328    if ((encrypt.isPresent() || signHash.isPresent())
329            && ! argParser.connectionArgumentsPresent()) {
330      argParser.displayMessageAndUsageReference(err, ERR_BACKUPDB_ENCRYPT_OR_SIGN_REQUIRES_ONLINE.get(
331          encrypt.getLongIdentifier(), signHash.getLongIdentifier()));
332      return 1;
333    }
334
335    // If the signHash option was provided, then make sure that the hash option
336    // was given.
337    if (signHash.isPresent() && !hash.isPresent())
338    {
339      argParser.displayMessageAndUsageReference(err,
340          ERR_BACKUPDB_SIGN_REQUIRES_HASH.get(signHash.getLongIdentifier(), hash.getLongIdentifier()));
341      return 1;
342    }
343
344
345    // Checks the version - if upgrade required, the tool is unusable
346    try
347    {
348      checkVersion();
349    }
350    catch (InitializationException e)
351    {
352      printWrappedText(err, e.getMessage());
353      return 1;
354    }
355
356    return process(argParser, initializeServer, out, err);
357
358  }
359
360  /** {@inheritDoc} */
361  @Override
362  public void addTaskAttributes(List<RawAttribute> attributes)
363  {
364    addIfHasValue(attributes, ATTR_TASK_BACKUP_ALL, backUpAll);
365    addIfHasValue(attributes, ATTR_TASK_BACKUP_COMPRESS, compress);
366    addIfHasValue(attributes, ATTR_TASK_BACKUP_ENCRYPT, encrypt);
367    addIfHasValue(attributes, ATTR_TASK_BACKUP_HASH, hash);
368    addIfHasValue(attributes, ATTR_TASK_BACKUP_INCREMENTAL, incremental);
369    addIfHasValue(attributes, ATTR_TASK_BACKUP_SIGN_HASH, signHash);
370
371    List<String> backendIDs = backendID.getValues();
372    if (backendIDs != null && !backendIDs.isEmpty()) {
373      attributes.add(
374              new LDAPAttribute(ATTR_TASK_BACKUP_BACKEND_ID, backendIDs));
375    }
376
377    addIfHasValue(attributes, ATTR_BACKUP_ID, backupIDString);
378    addIfHasValue(attributes, ATTR_BACKUP_DIRECTORY_PATH, backupDirectory);
379    addIfHasValue(attributes, ATTR_TASK_BACKUP_INCREMENTAL_BASE_ID, incrementalBaseID);
380  }
381
382  private void addIfHasValue(List<RawAttribute> attributes, String attrName, Argument arg)
383  {
384    if (hasValueDifferentThanDefaultValue(arg)) {
385      attributes.add(new LDAPAttribute(attrName, arg.getValue()));
386    }
387  }
388
389  private boolean hasValueDifferentThanDefaultValue(Argument arg)
390  {
391    return arg.getValue() != null
392        && !arg.getValue().equals(arg.getDefaultValue());
393  }
394
395  /** {@inheritDoc} */
396  @Override
397  public String getTaskObjectclass() {
398    return "ds-task-backup";
399  }
400
401  /** {@inheritDoc} */
402  @Override
403  public Class<?> getTaskClass() {
404    return BackupTask.class;
405  }
406
407  /** {@inheritDoc} */
408  @Override
409  protected int processLocal(boolean initializeServer,
410                           PrintStream out,
411                           PrintStream err) {
412
413    // Make sure that the backup directory exists.  If not, then create it.
414    File backupDirFile = new File(backupDirectory.getValue());
415    if (! backupDirFile.exists())
416    {
417      try
418      {
419        backupDirFile.mkdirs();
420      }
421      catch (Exception e)
422      {
423        printWrappedText(
424                err, ERR_BACKUPDB_CANNOT_CREATE_BACKUP_DIR.get(backupDirectory.getValue(), getExceptionMessage(e)));
425        return 1;
426      }
427    }
428
429    // If no backup ID was provided, then create one with the current timestamp.
430    String backupID;
431    if (backupIDString.isPresent())
432    {
433      backupID = backupIDString.getValue();
434    }
435    else
436    {
437      SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT_GMT_TIME);
438      dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
439      backupID = dateFormat.format(new Date());
440    }
441
442    // If the incremental base ID was specified, then make sure it is an
443    // incremental backup.
444    String incrementalBase;
445    if (incrementalBaseID.isPresent())
446    {
447      incrementalBase = incrementalBaseID.getValue();
448    }
449    else
450    {
451      incrementalBase = null;
452    }
453
454    // Perform the initial bootstrap of the Directory Server and process the
455    // configuration.
456    DirectoryServer directoryServer = DirectoryServer.getInstance();
457    if (initializeServer)
458    {
459      try
460      {
461        DirectoryServer.bootstrapClient();
462        DirectoryServer.initializeJMX();
463      }
464      catch (Exception e)
465      {
466        printWrappedText(err, ERR_SERVER_BOOTSTRAP_ERROR.get(getExceptionMessage(e)));
467        return 1;
468      }
469
470      try
471      {
472        directoryServer.initializeConfiguration(configClass.getValue(),
473                                                configFile.getValue());
474      }
475      catch (InitializationException ie)
476      {
477        printWrappedText(err, ERR_CANNOT_LOAD_CONFIG.get(ie.getMessage()));
478        return 1;
479      }
480      catch (Exception e)
481      {
482        printWrappedText(err, ERR_CANNOT_LOAD_CONFIG.get(getExceptionMessage(e)));
483        return 1;
484      }
485
486
487
488      // Initialize the Directory Server schema elements.
489      try
490      {
491        directoryServer.initializeSchema();
492      }
493      catch (ConfigException | InitializationException e)
494      {
495        printWrappedText(err, ERR_CANNOT_LOAD_SCHEMA.get(e.getMessage()));
496        return 1;
497      }
498      catch (Exception e)
499      {
500        printWrappedText(err, ERR_CANNOT_LOAD_SCHEMA.get(getExceptionMessage(e)));
501        return 1;
502      }
503
504
505      // Initialize the Directory Server core configuration.
506      try
507      {
508        CoreConfigManager coreConfigManager = new CoreConfigManager(directoryServer.getServerContext());
509        coreConfigManager.initializeCoreConfig();
510      }
511      catch (ConfigException | InitializationException e)
512      {
513        printWrappedText(err, ERR_CANNOT_INITIALIZE_CORE_CONFIG.get(e.getMessage()));
514        return 1;
515      }
516      catch (Exception e)
517      {
518        printWrappedText(err, ERR_CANNOT_INITIALIZE_CORE_CONFIG.get(getExceptionMessage(e)));
519        return 1;
520      }
521
522
523      // Initialize the Directory Server crypto manager.
524      try
525      {
526        directoryServer.initializeCryptoManager();
527      }
528      catch (ConfigException | InitializationException e)
529      {
530        printWrappedText(err, ERR_CANNOT_INITIALIZE_CRYPTO_MANAGER.get(e.getMessage()));
531        return 1;
532      }
533      catch (Exception e)
534      {
535        printWrappedText(err, ERR_CANNOT_INITIALIZE_CRYPTO_MANAGER.get(getExceptionMessage(e)));
536        return 1;
537      }
538
539      try
540      {
541        ErrorLogPublisher errorLogPublisher =
542            TextErrorLogPublisher.getToolStartupTextErrorPublisher(
543            new TextWriter.STREAM(out));
544        ErrorLogger.getInstance().addLogPublisher(errorLogPublisher);
545        DebugLogger.getInstance().addPublisherIfRequired(new TextWriter.STREAM(out));
546      }
547      catch(Exception e)
548      {
549        err.println("Error installing the custom error logger: " +
550                    stackTraceToSingleLineString(e));
551      }
552    }
553
554
555    // Get information about the backends defined in the server, and determine
556    // whether we are backing up multiple backends or a single backend.
557    ArrayList<Backend>     backendList = new ArrayList<>();
558    ArrayList<BackendCfg>  entryList   = new ArrayList<>();
559    ArrayList<List<DN>>    dnList      = new ArrayList<>();
560    BackendToolUtils.getBackends(backendList, entryList, dnList);
561    int numBackends = backendList.size();
562
563    boolean multiple;
564    ArrayList<Backend<?>> backendsToArchive = new ArrayList<>(numBackends);
565    HashMap<String,BackendCfg> configEntries = new HashMap<>(numBackends);
566    if (backUpAll.isPresent())
567    {
568      for (int i=0; i < numBackends; i++)
569      {
570        Backend<?> b = backendList.get(i);
571        if (b.supports(BackendOperation.BACKUP))
572        {
573          backendsToArchive.add(b);
574          configEntries.put(b.getBackendID(), entryList.get(i));
575        }
576      }
577
578      // We'll proceed as if we're backing up multiple backends in this case
579      // even if there's just one.
580      multiple = true;
581    }
582    else
583    {
584      // Iterate through the set of backends and pick out those that were requested.
585      HashSet<String> requestedBackends = new HashSet<>(backendID.getValues());
586      for (int i=0; i < numBackends; i++)
587      {
588        Backend<?> b = backendList.get(i);
589        if (requestedBackends.contains(b.getBackendID()))
590        {
591          if (!b.supports(BackendOperation.BACKUP))
592          {
593            logger.warn(WARN_BACKUPDB_BACKUP_NOT_SUPPORTED, b.getBackendID());
594          }
595          else
596          {
597            backendsToArchive.add(b);
598            configEntries.put(b.getBackendID(), entryList.get(i));
599            requestedBackends.remove(b.getBackendID());
600          }
601        }
602      }
603
604      if (! requestedBackends.isEmpty())
605      {
606        for (String id : requestedBackends)
607        {
608          logger.error(ERR_BACKUPDB_NO_BACKENDS_FOR_ID, id);
609        }
610
611        return 1;
612      }
613
614
615      // See if there are multiple backends to archive.
616      multiple = backendsToArchive.size() > 1;
617    }
618
619
620    // If there are no backends to archive, then print an error and exit.
621    if (backendsToArchive.isEmpty())
622    {
623      logger.warn(WARN_BACKUPDB_NO_BACKENDS_TO_ARCHIVE);
624      return 1;
625    }
626
627
628    // Iterate through the backends to archive and back them up individually.
629    boolean errorsEncountered = false;
630    for (Backend<?> b : backendsToArchive)
631    {
632      // Acquire a shared lock for this backend.
633      try
634      {
635        String        lockFile      = LockFileManager.getBackendLockFileName(b);
636        StringBuilder failureReason = new StringBuilder();
637        if (! LockFileManager.acquireSharedLock(lockFile, failureReason))
638        {
639          logger.error(ERR_BACKUPDB_CANNOT_LOCK_BACKEND, b.getBackendID(), failureReason);
640          errorsEncountered = true;
641          continue;
642        }
643      }
644      catch (Exception e)
645      {
646        logger.error(ERR_BACKUPDB_CANNOT_LOCK_BACKEND, b.getBackendID(), getExceptionMessage(e));
647        errorsEncountered = true;
648        continue;
649      }
650
651
652      logger.info(NOTE_BACKUPDB_STARTING_BACKUP, b.getBackendID());
653
654
655      // Get the config entry for this backend.
656      BackendCfg configEntry = configEntries.get(b.getBackendID());
657
658
659      // Get the path to the directory to use for this backup.  If we will be
660      // backing up multiple backends (or if we are backing up all backends,
661      // even if there's only one of them), then create a subdirectory for each
662      // backend.
663      String backupDirPath;
664      if (multiple)
665      {
666        backupDirPath = backupDirectory.getValue() + File.separator +
667                        b.getBackendID();
668      }
669      else
670      {
671        backupDirPath = backupDirectory.getValue();
672      }
673
674
675      // If the directory doesn't exist, then create it.  If it does exist, then
676      // see if it has a backup descriptor file.
677      BackupDirectory backupDir;
678      backupDirFile = new File(backupDirPath);
679      if (backupDirFile.exists())
680      {
681        String descriptorPath = backupDirPath + File.separator +
682                                BACKUP_DIRECTORY_DESCRIPTOR_FILE;
683        File descriptorFile = new File(descriptorPath);
684        if (descriptorFile.exists())
685        {
686          try
687          {
688            backupDir =
689                 BackupDirectory.readBackupDirectoryDescriptor(backupDirPath);
690          }
691          catch (ConfigException ce)
692          {
693            logger.error(ERR_BACKUPDB_CANNOT_PARSE_BACKUP_DESCRIPTOR, descriptorPath, ce.getMessage());
694            errorsEncountered = true;
695
696            try
697            {
698              String lockFile = LockFileManager.getBackendLockFileName(b);
699              StringBuilder failureReason = new StringBuilder();
700              if (! LockFileManager.releaseLock(lockFile, failureReason))
701              {
702                logger.warn(WARN_BACKUPDB_CANNOT_UNLOCK_BACKEND, b.getBackendID(), failureReason);
703              }
704            }
705            catch (Exception e)
706            {
707              logger.warn(WARN_BACKUPDB_CANNOT_UNLOCK_BACKEND, b.getBackendID(), getExceptionMessage(e));
708            }
709
710            continue;
711          }
712          catch (Exception e)
713          {
714            logger.error(ERR_BACKUPDB_CANNOT_PARSE_BACKUP_DESCRIPTOR, descriptorPath, getExceptionMessage(e));
715            errorsEncountered = true;
716
717            try
718            {
719              String lockFile = LockFileManager.getBackendLockFileName(b);
720              StringBuilder failureReason = new StringBuilder();
721              if (! LockFileManager.releaseLock(lockFile, failureReason))
722              {
723                logger.warn(WARN_BACKUPDB_CANNOT_UNLOCK_BACKEND, b.getBackendID(), failureReason);
724              }
725            }
726            catch (Exception e2)
727            {
728              logger.warn(WARN_BACKUPDB_CANNOT_UNLOCK_BACKEND, b.getBackendID(), getExceptionMessage(e2));
729            }
730
731            continue;
732          }
733        }
734        else
735        {
736          backupDir = new BackupDirectory(backupDirPath, configEntry.dn());
737        }
738      }
739      else
740      {
741        try
742        {
743          backupDirFile.mkdirs();
744        }
745        catch (Exception e)
746        {
747          logger.error(ERR_BACKUPDB_CANNOT_CREATE_BACKUP_DIR, backupDirPath, getExceptionMessage(e));
748          errorsEncountered = true;
749
750          try
751          {
752            String lockFile = LockFileManager.getBackendLockFileName(b);
753            StringBuilder failureReason = new StringBuilder();
754            if (! LockFileManager.releaseLock(lockFile, failureReason))
755            {
756              logger.warn(WARN_BACKUPDB_CANNOT_UNLOCK_BACKEND, b.getBackendID(), failureReason);
757            }
758          }
759          catch (Exception e2)
760          {
761            logger.warn(WARN_BACKUPDB_CANNOT_UNLOCK_BACKEND, b.getBackendID(), getExceptionMessage(e2));
762          }
763
764          continue;
765        }
766
767        backupDir = new BackupDirectory(backupDirPath, configEntry.dn());
768      }
769
770
771      // Create a backup configuration and determine whether the requested
772      // backup can be performed using the selected backend.
773      BackupConfig backupConfig = new BackupConfig(backupDir, backupID,
774                                                   incremental.isPresent());
775      backupConfig.setCompressData(compress.isPresent());
776      backupConfig.setEncryptData(encrypt.isPresent());
777      backupConfig.setHashData(hash.isPresent());
778      backupConfig.setSignHash(signHash.isPresent());
779      backupConfig.setIncrementalBaseID(incrementalBase);
780
781      if (!b.supports(BackendOperation.BACKUP))
782      {
783        logger.error(ERR_BACKUPDB_CANNOT_BACKUP, b.getBackendID());
784        errorsEncountered = true;
785
786        try
787        {
788          String lockFile = LockFileManager.getBackendLockFileName(b);
789          StringBuilder failureReason = new StringBuilder();
790          if (! LockFileManager.releaseLock(lockFile, failureReason))
791          {
792            logger.warn(WARN_BACKUPDB_CANNOT_UNLOCK_BACKEND, b.getBackendID(), failureReason);
793          }
794        }
795        catch (Exception e2)
796        {
797          logger.warn(WARN_BACKUPDB_CANNOT_UNLOCK_BACKEND, b.getBackendID(), getExceptionMessage(e2));
798        }
799
800        continue;
801      }
802
803
804      // Perform the backup.
805      try
806      {
807        b.createBackup(backupConfig);
808      }
809      catch (DirectoryException de)
810      {
811        logger.error(ERR_BACKUPDB_ERROR_DURING_BACKUP, b.getBackendID(), de.getMessageObject());
812        errorsEncountered = true;
813
814        try
815        {
816          String lockFile = LockFileManager.getBackendLockFileName(b);
817          StringBuilder failureReason = new StringBuilder();
818          if (! LockFileManager.releaseLock(lockFile, failureReason))
819          {
820            logger.warn(WARN_BACKUPDB_CANNOT_UNLOCK_BACKEND, b.getBackendID(), failureReason);
821          }
822        }
823        catch (Exception e)
824        {
825          logger.warn(WARN_BACKUPDB_CANNOT_UNLOCK_BACKEND, b.getBackendID(), getExceptionMessage(e));
826        }
827
828        continue;
829      }
830      catch (Exception e)
831      {
832        logger.error(ERR_BACKUPDB_ERROR_DURING_BACKUP, b.getBackendID(), getExceptionMessage(e));
833        errorsEncountered = true;
834
835        try
836        {
837          String lockFile = LockFileManager.getBackendLockFileName(b);
838          StringBuilder failureReason = new StringBuilder();
839          if (! LockFileManager.releaseLock(lockFile, failureReason))
840          {
841            logger.warn(WARN_BACKUPDB_CANNOT_UNLOCK_BACKEND, b.getBackendID(), failureReason);
842          }
843        }
844        catch (Exception e2)
845        {
846          logger.warn(WARN_BACKUPDB_CANNOT_UNLOCK_BACKEND, b.getBackendID(), getExceptionMessage(e2));
847        }
848
849        continue;
850      }
851
852
853      // Release the shared lock for the backend.
854      try
855      {
856        String lockFile = LockFileManager.getBackendLockFileName(b);
857        StringBuilder failureReason = new StringBuilder();
858        if (! LockFileManager.releaseLock(lockFile, failureReason))
859        {
860          logger.warn(WARN_BACKUPDB_CANNOT_UNLOCK_BACKEND, b.getBackendID(), failureReason);
861          errorsEncountered = true;
862        }
863      }
864      catch (Exception e)
865      {
866        logger.warn(WARN_BACKUPDB_CANNOT_UNLOCK_BACKEND, b.getBackendID(), getExceptionMessage(e));
867        errorsEncountered = true;
868      }
869    }
870
871
872    // Print a final completed message, indicating whether there were any errors
873    // in the process.
874    int ret = 0;
875    if (errorsEncountered)
876    {
877      logger.info(NOTE_BACKUPDB_COMPLETED_WITH_ERRORS);
878      ret = 1;
879    }
880    else
881    {
882      logger.info(NOTE_BACKUPDB_COMPLETED_SUCCESSFULLY);
883    }
884    return ret;
885  }
886
887  /** {@inheritDoc} */
888  @Override
889  public String getTaskId() {
890    return backupIDString != null ? backupIDString.getValue() : null;
891  }
892}