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