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