From 3a2fb8fbf8b986016429073370d5fe41a23dc3c2 Mon Sep 17 00:00:00 2001
From: neil_a_wilson <neil_a_wilson@localhost>
Date: Sun, 02 Sep 2007 01:12:13 +0000
Subject: [PATCH] Update the server so that it has the ability to save a copy of its current configuration into a ".startok" file whenever it starts successfully.  The start-ds script and DirectoryEnvironmentConfig class have been updated to expose an option to try to start the server using this "last known good" configuration rather than the active config file.

---
 opendj-sdk/opends/src/admin/defn/org/opends/server/admin/std/GlobalConfiguration.xml                             |   26 ++
 opendj-sdk/opends/src/server/org/opends/server/core/DirectoryServer.java                                         |   71 ++++++-
 opendj-sdk/opends/src/server/org/opends/server/api/ConfigHandler.java                                            |   20 ++
 opendj-sdk/opends/src/messages/messages/core.properties                                                          |    3 
 opendj-sdk/opends/resource/config/config.ldif                                                                    |    1 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/ConfigFileHandlerTestCase.java |   14 +
 opendj-sdk/opends/src/server/org/opends/server/extensions/ConfigFileHandler.java                                 |  268 ++++++++++++++++++++++++++---
 opendj-sdk/opends/src/server/org/opends/server/util/ServerConstants.java                                         |   10 +
 opendj-sdk/opends/src/server/org/opends/server/core/CoreConfigManager.java                                       |    3 
 opendj-sdk/opends/src/messages/messages/config.properties                                                        |   17 +
 opendj-sdk/opends/resource/schema/02-config.ldif                                                                 |    7 
 opendj-sdk/opends/src/server/org/opends/server/types/DirectoryEnvironmentConfig.java                             |   71 +++++++
 12 files changed, 472 insertions(+), 39 deletions(-)

diff --git a/opendj-sdk/opends/resource/config/config.ldif b/opendj-sdk/opends/resource/config/config.ldif
index d20663e..0bd7b5c 100644
--- a/opendj-sdk/opends/resource/config/config.ldif
+++ b/opendj-sdk/opends/resource/config/config.ldif
@@ -48,6 +48,7 @@
 ds-cfg-default-password-policy: cn=Default Password Policy,cn=Password Policies,cn=config
 ds-cfg-return-bind-error-messages: false
 ds-cfg-idle-time-limit: 0 seconds
+ds-cfg-save-config-on-successful-startup: true
 ds-cfg-allowed-task: org.opends.server.tasks.AddSchemaFileTask
 ds-cfg-allowed-task: org.opends.server.tasks.BackupTask
 ds-cfg-allowed-task: org.opends.server.tasks.DisconnectClientTask
diff --git a/opendj-sdk/opends/resource/schema/02-config.ldif b/opendj-sdk/opends/resource/schema/02-config.ldif
index 1a90fba..9c9b6b9 100644
--- a/opendj-sdk/opends/resource/schema/02-config.ldif
+++ b/opendj-sdk/opends/resource/schema/02-config.ldif
@@ -1611,6 +1611,10 @@
 attributeTypes: ( 1.3.6.1.4.1.26027.1.1.478
   NAME 'ds-cfg-message-template-file' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
   X-ORIGIN 'OpenDS Directory Server' )
+attributeTypes: ( 1.3.6.1.4.1.26027.1.1.485
+  NAME 'ds-cfg-save-config-on-successful-startup'
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE
+  X-ORIGIN 'OpenDS Directory Server' )
 objectClasses: ( 1.3.6.1.4.1.26027.1.2.1
   NAME 'ds-cfg-access-control-handler' SUP top STRUCTURAL
   MUST ( cn $ ds-cfg-acl-handler-class $ ds-cfg-acl-handler-enabled )
@@ -1830,7 +1834,8 @@
   ds-cfg-reject-unauthenticated-requests  $
   ds-cfg-bind-with-dn-requires-password $ ds-cfg-lookthrough-limit $
   ds-cfg-smtp-server $ ds-cfg-allowed-task $ ds-cfg-disabled-privilege $
-  ds-cfg-return-bind-error-messages $ ds-cfg-idle-time-limit )
+  ds-cfg-return-bind-error-messages $ ds-cfg-idle-time-limit $
+  ds-cfg-save-config-on-successful-startup )
   X-ORIGIN 'OpenDS Directory Server' )
 objectClasses: ( 1.3.6.1.4.1.26027.1.2.41 NAME 'ds-cfg-root-dn' SUP top
   AUXILIARY MAY ds-cfg-alternate-bind-dn X-ORIGIN 'OpenDS Directory Server' )
diff --git a/opendj-sdk/opends/src/admin/defn/org/opends/server/admin/std/GlobalConfiguration.xml b/opendj-sdk/opends/src/admin/defn/org/opends/server/admin/std/GlobalConfiguration.xml
index 0949ecb..2561d3d 100644
--- a/opendj-sdk/opends/src/admin/defn/org/opends/server/admin/std/GlobalConfiguration.xml
+++ b/opendj-sdk/opends/src/admin/defn/org/opends/server/admin/std/GlobalConfiguration.xml
@@ -702,7 +702,7 @@
 
   <adm:property name="idle-time-limit" mandatory="false" multi-valued="false">
     <adm:synopsis>
-      Specifies the maximum lenght of time that a client connection may remain
+      Specifies the maximum length of time that a client connection may remain
       established since its last completed operation.  A value of "0 seconds"
       indicates that no idle time limit will be enforced.
     </adm:synopsis>
@@ -722,5 +722,29 @@
     </adm:profile>
   </adm:property>
 
+  <adm:property name="save-config-on-successful-startup" mandatory="false">
+    <adm:synopsis>
+      Indicates whether the Directory Server should save a copy of its
+      configuration whenever the startup process completes successfully.  This
+      can ensure that the server provides a "last known good" configuration,
+      which can be used as a reference (or copied into the active config) if the
+      server fails to start with the current "active" configuration.
+    </adm:synopsis>
+    <adm:default-behavior>
+      <adm:defined>
+        <adm:value>true</adm:value>
+      </adm:defined>
+    </adm:default-behavior>
+    <adm:syntax>
+      <adm:boolean />
+    </adm:syntax>
+    <adm:profile name="ldap">
+      <ldap:attribute>
+        <ldap:oid>1.3.6.1.4.1.26027.1.1.485</ldap:oid>
+        <ldap:name>ds-cfg-save-config-on-successful-startup</ldap:name>
+      </ldap:attribute>
+    </adm:profile>
+  </adm:property>
+
 </adm:managed-object>
 
diff --git a/opendj-sdk/opends/src/messages/messages/config.properties b/opendj-sdk/opends/src/messages/messages/config.properties
index 51b3003..cc0b3ab 100644
--- a/opendj-sdk/opends/src/messages/messages/config.properties
+++ b/opendj-sdk/opends/src/messages/messages/config.properties
@@ -2131,3 +2131,20 @@
  '%s' is invalid.  An SMTP server value must have an IP address or a \
  resolvable name, and it may optionally be followed by a colon and an integer \
  value between 1 and 65535 to specify the server port number
+SEVERE_ERR_STARTOK_CANNOT_OPEN_FOR_READING_698=An error occurred while \
+ attempting to open the current configuration file %s for reading in order to \
+ copy it to the ".startok" file:  %s
+SEVERE_ERR_STARTOK_CANNOT_OPEN_FOR_WRITING_699=An error occurred while \
+ attempting to open file %s in order to write the ".startok" configuration \
+ file:  %s
+SEVERE_ERR_STARTOK_CANNOT_WRITE_700=An error occurred while attempting to \
+ copy the current configuration from file %s into temporary file %s for use \
+ as the ".startok" configuration file:  %s
+SEVERE_ERR_STARTOK_CANNOT_RENAME_701=An error occurred while attempting to \
+ rename file %s to %s for use as the ".startok" configuration file:  %s
+NOTICE_CONFIG_FILE_USING_STARTOK_FILE_702=The Directory Server is starting \
+ using the last known good configuration file %s rather than the active \
+ configuration file %s
+SEVERE_WARN_CONFIG_FILE_NO_STARTOK_FILE_703=No last known good configuration \
+ file %s exists.  The server will attempt to start using the active \
+ configuration file %s
diff --git a/opendj-sdk/opends/src/messages/messages/core.properties b/opendj-sdk/opends/src/messages/messages/core.properties
index 23478ba..c65a87e 100644
--- a/opendj-sdk/opends/src/messages/messages/core.properties
+++ b/opendj-sdk/opends/src/messages/messages/core.properties
@@ -1652,3 +1652,6 @@
  store backend %s is not enabled
 SEVERE_ERR_CRYPTOMGR_ADS_TRUST_STORE_BACKEND_WRONG_CLASS_651=The backend %s \
  is not a trust store backend
+INFO_DSCORE_DESCRIPTION_LASTKNOWNGOODCFG_652=Attempt to start using the \
+ configuration that was in place at the last successful startup (if it is \
+ available) rather than using the current active configuration
diff --git a/opendj-sdk/opends/src/server/org/opends/server/api/ConfigHandler.java b/opendj-sdk/opends/src/server/org/opends/server/api/ConfigHandler.java
index 5c8701e..5f1a563 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/api/ConfigHandler.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/api/ConfigHandler.java
@@ -132,5 +132,25 @@
    */
   public abstract void writeUpdatedConfig()
          throws DirectoryException;
+
+
+
+  /**
+   * Indicates that the Directory Server has started successfully and
+   * that the configuration handler should save a copy of the current
+   * configuration for use as a "last known good" reference.  Note
+   * that this may not be possible with some kinds of configuration
+   * repositories, so it should be a best effort attempt.
+   * <BR><BR>
+   * This method should only be called by the Directory Server itself
+   * when the server has started successfully.  It should not be
+   * invoked by any other component at any other time.
+   */
+  @org.opends.server.types.PublicAPI(
+       stability=org.opends.server.types.StabilityLevel.VOLATILE,
+       mayInstantiate=false,
+       mayExtend=true,
+       mayInvoke=false)
+  public abstract void writeSuccessfulStartupConfig();
 }
 
diff --git a/opendj-sdk/opends/src/server/org/opends/server/core/CoreConfigManager.java b/opendj-sdk/opends/src/server/org/opends/server/core/CoreConfigManager.java
index 13960ac..a9bf894 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/core/CoreConfigManager.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/core/CoreConfigManager.java
@@ -337,6 +337,9 @@
          globalConfig.isReturnBindErrorMessages());
 
     DirectoryServer.setIdleTimeLimit(globalConfig.getIdleTimeLimit());
+
+    DirectoryServer.setSaveConfigOnSuccessfulStartup(
+         globalConfig.isSaveConfigOnSuccessfulStartup());
   }
 
 
diff --git a/opendj-sdk/opends/src/server/org/opends/server/core/DirectoryServer.java b/opendj-sdk/opends/src/server/org/opends/server/core/DirectoryServer.java
index 028bad1..c5efb40 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/core/DirectoryServer.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/core/DirectoryServer.java
@@ -359,6 +359,10 @@
   // been abandoned.
   private boolean notifyAbandonedOperations;
 
+  // Indicates whether to save a copy of the configuration on successful
+  // startup.
+  private boolean saveConfigOnSuccessfulStartup;
+
   // Indicates whether the server is currently in the process of shutting down.
   private boolean shuttingDown;
 
@@ -1428,6 +1432,14 @@
       }
 
 
+      // If we should write a copy of the config on successful startup, then do
+      // so now.
+      if (saveConfigOnSuccessfulStartup)
+      {
+        configHandler.writeSuccessfulStartupConfig();
+      }
+
+
       // Mark the current time as the start time and indicate that the server is
       // now running.
       startUpTime  = System.currentTimeMillis();
@@ -7727,6 +7739,37 @@
 
 
   /**
+   * Indicates whether the Directory Server should save a copy of its
+   * configuration whenever it is started successfully.
+   *
+   * @return  {@code true} if the server should save a copy of its configuration
+   *          whenever it is started successfully, or {@code false} if not.
+   */
+  public static boolean saveConfigOnSuccessfulStartup()
+  {
+    return directoryServer.saveConfigOnSuccessfulStartup;
+  }
+
+
+
+  /**
+   * Specifies whether the Directory Server should save a copy of its
+   * configuration whenever it is started successfully.
+   *
+   * @param  saveConfigOnSuccessfulStartup  Specifies whether the server should
+   *                                        save a copy of its configuration
+   *                                        whenever it is started successfully.
+   */
+  public static void setSaveConfigOnSuccessfulStartup(
+                          boolean saveConfigOnSuccessfulStartup)
+  {
+    directoryServer.saveConfigOnSuccessfulStartup =
+         saveConfigOnSuccessfulStartup;
+  }
+
+
+
+  /**
    * Registers the provided backup task listener with the Directory Server.
    *
    * @param  listener  The backup task listener to register with the Directory
@@ -8999,15 +9042,16 @@
   public static void main(String[] args)
   {
     // Define the arguments that may be provided to the server.
-    BooleanArgument checkStartability = null;
-    BooleanArgument quietMode         = null;
-    BooleanArgument windowsNetStart   = null;
-    BooleanArgument displayUsage      = null;
-    BooleanArgument fullVersion       = null;
-    BooleanArgument noDetach          = null;
-    BooleanArgument systemInfo        = null;
-    StringArgument  configClass       = null;
-    StringArgument  configFile        = null;
+    BooleanArgument checkStartability      = null;
+    BooleanArgument quietMode              = null;
+    BooleanArgument windowsNetStart        = null;
+    BooleanArgument displayUsage           = null;
+    BooleanArgument fullVersion            = null;
+    BooleanArgument noDetach               = null;
+    BooleanArgument systemInfo             = null;
+    BooleanArgument useLastKnownGoodConfig = null;
+    StringArgument  configClass            = null;
+    StringArgument  configFile             = null;
 
 
     // Create the command-line argument parser for use with this program.
@@ -9064,6 +9108,13 @@
       argParser.addArgument(systemInfo);
 
 
+      useLastKnownGoodConfig =
+           new BooleanArgument("lastknowngoodconfig", 'L',
+                               "useLastKnownGoodConfig",
+                               INFO_DSCORE_DESCRIPTION_LASTKNOWNGOODCFG.get());
+      argParser.addArgument(useLastKnownGoodConfig);
+
+
       noDetach = new BooleanArgument("nodetach", 'N', "nodetach",
                                      INFO_DSCORE_DESCRIPTION_NODETACH.get());
       argParser.addArgument(noDetach);
@@ -9389,6 +9440,8 @@
                                     configClass.getValue());
       environmentConfig.setProperty(PROPERTY_CONFIG_FILE,
                                     configFile.getValue());
+      environmentConfig.setProperty(PROPERTY_USE_LAST_KNOWN_GOOD_CONFIG,
+           String.valueOf(useLastKnownGoodConfig.isPresent()));
 
 
       startupErrorLogPublisher =
diff --git a/opendj-sdk/opends/src/server/org/opends/server/extensions/ConfigFileHandler.java b/opendj-sdk/opends/src/server/org/opends/server/extensions/ConfigFileHandler.java
index 54bfa16..228105f 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/extensions/ConfigFileHandler.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/extensions/ConfigFileHandler.java
@@ -25,7 +25,6 @@
  *      Portions Copyright 2006-2007 Sun Microsystems, Inc.
  */
 package org.opends.server.extensions;
-import org.opends.messages.Message;
 
 
 
@@ -60,6 +59,7 @@
 import javax.crypto.CipherOutputStream;
 import javax.crypto.Mac;
 
+import org.opends.messages.Message;
 import org.opends.server.api.AlertGenerator;
 import org.opends.server.api.ClientConnection;
 import org.opends.server.api.ConfigAddListener;
@@ -135,6 +135,9 @@
   // Indicates whether to maintain a configuration archive.
   private boolean maintainConfigArchive;
 
+  // Indicates whether to start using the last known good configuration.
+  private boolean useLastKnownGoodConfig;
+
   // A SHA-1 digest of the last known configuration.  This should only be
   // incorrect if the server configuration file has been manually edited with
   // the server online, which is a bad thing.
@@ -199,15 +202,41 @@
     configLock = new ReentrantLock();
 
 
-    // Make sure that the configuration file exists.
+    // Determine whether we should try to start using the last known good
+    // configuration.  If so, then only do so if such a file exists.  If it
+    // doesn't exist, then fall back on the active configuration file.
     this.configFile = configFile;
-    File f = new File(configFile);
+    DirectoryEnvironmentConfig envConfig =
+         DirectoryServer.getEnvironmentConfig();
+    useLastKnownGoodConfig = envConfig.useLastKnownGoodConfiguration();
+    File f = null;
+    if (useLastKnownGoodConfig)
+    {
+      f = new File(configFile + ".startok");
+      if (! f.exists())
+      {
+        logError(WARN_CONFIG_FILE_NO_STARTOK_FILE.get(f.getAbsolutePath(),
+                                                      configFile));
+        useLastKnownGoodConfig = false;
+        f = new File(configFile);
+      }
+      else
+      {
+        logError(NOTE_CONFIG_FILE_USING_STARTOK_FILE.get(f.getAbsolutePath(),
+                                                         configFile));
+      }
+    }
+    else
+    {
+      f = new File(configFile);
+    }
 
     try
     {
       if (! f.exists())
       {
-        Message message = ERR_CONFIG_FILE_DOES_NOT_EXIST.get(configFile);
+        Message message = ERR_CONFIG_FILE_DOES_NOT_EXIST.get(
+                               f.getAbsolutePath());
         throw new InitializationException(message);
       }
     }
@@ -228,7 +257,7 @@
       }
 
       Message message = ERR_CONFIG_FILE_CANNOT_VERIFY_EXISTENCE.get(
-          configFile, String.valueOf(e));
+                             f.getAbsolutePath(), String.valueOf(e));
       throw new InitializationException(message);
     }
 
@@ -236,11 +265,9 @@
     // Check to see if a configuration archive exists.  If not, then create one.
     // If so, then check whether the current configuration matches the last
     // configuration in the archive.  If it doesn't, then archive it.
-    DirectoryEnvironmentConfig envConfig =
-         DirectoryServer.getEnvironmentConfig();
     maintainConfigArchive = envConfig.maintainConfigArchive();
     maxConfigArchiveSize  = envConfig.getMaxConfigArchiveSize();
-    if (maintainConfigArchive)
+    if (maintainConfigArchive & (! useLastKnownGoodConfig))
     {
       try
       {
@@ -307,7 +334,7 @@
     LDIFReader reader;
     try
     {
-      LDIFImportConfig importConfig = new LDIFImportConfig(configFile);
+      LDIFImportConfig importConfig = new LDIFImportConfig(f.getAbsolutePath());
 
       // FIXME -- Should we support encryption or compression for the config?
 
@@ -321,7 +348,7 @@
       }
 
       Message message = ERR_CONFIG_FILE_CANNOT_OPEN_FOR_READ.get(
-          configFile, String.valueOf(e));
+                             f.getAbsolutePath(), String.valueOf(e));
       throw new InitializationException(message, e);
     }
 
@@ -352,7 +379,7 @@
       }
 
       Message message = ERR_CONFIG_FILE_INVALID_LDIF_ENTRY.get(
-          le.getLineNumber(), configFile, String.valueOf(le));
+          le.getLineNumber(), f.getAbsolutePath(), String.valueOf(le));
       throw new InitializationException(message, le);
     }
     catch (Exception e)
@@ -375,7 +402,8 @@
       }
 
       Message message =
-          ERR_CONFIG_FILE_READ_ERROR.get(configFile, String.valueOf(e));
+          ERR_CONFIG_FILE_READ_ERROR.get(f.getAbsolutePath(),
+                                       String.valueOf(e));
       throw new InitializationException(message, e);
     }
 
@@ -395,7 +423,7 @@
         }
       }
 
-      Message message = ERR_CONFIG_FILE_EMPTY.get(configFile);
+      Message message = ERR_CONFIG_FILE_EMPTY.get(f.getAbsolutePath());
       throw new InitializationException(message);
     }
 
@@ -407,7 +435,8 @@
       if (! entry.getDN().equals(configRootDN))
       {
         Message message = ERR_CONFIG_FILE_INVALID_BASE_DN.get(
-            configFile, entry.getDN().toString(), DN_CONFIG_ROOT);
+                               f.getAbsolutePath(), entry.getDN().toString(),
+                               DN_CONFIG_ROOT);
         throw new InitializationException(message);
       }
     }
@@ -452,8 +481,8 @@
       }
 
       // This should not happen, so we can use a generic error here.
-      Message message =
-          ERR_CONFIG_FILE_GENERIC_ERROR.get(configFile, String.valueOf(e));
+      Message message = ERR_CONFIG_FILE_GENERIC_ERROR.get(f.getAbsolutePath(),
+                                                          String.valueOf(e));
       throw new InitializationException(message, e);
     }
 
@@ -494,7 +523,8 @@
         }
 
         Message message = ERR_CONFIG_FILE_INVALID_LDIF_ENTRY.get(
-            le.getLineNumber(), configFile, String.valueOf(le));
+                               le.getLineNumber(), f.getAbsolutePath(),
+                               String.valueOf(le));
         throw new InitializationException(message, le);
       }
       catch (Exception e)
@@ -516,8 +546,8 @@
           }
         }
 
-        Message message =
-            ERR_CONFIG_FILE_READ_ERROR.get(configFile, String.valueOf(e));
+        Message message = ERR_CONFIG_FILE_READ_ERROR.get(f.getAbsolutePath(),
+                                                         String.valueOf(e));
         throw new InitializationException(message, e);
       }
 
@@ -559,8 +589,9 @@
         }
 
         Message message = ERR_CONFIG_FILE_DUPLICATE_ENTRY.get(
-            entryDN.toString(), String.valueOf(reader.getLastEntryLineNumber()),
-                configFile);
+                               entryDN.toString(),
+                               String.valueOf(reader.getLastEntryLineNumber()),
+                               f.getAbsolutePath());
         throw new InitializationException(message);
       }
 
@@ -582,7 +613,9 @@
         }
 
         Message message = ERR_CONFIG_FILE_UNKNOWN_PARENT.get(
-            entryDN.toString(), reader.getLastEntryLineNumber(), configFile);
+                               entryDN.toString(),
+                               reader.getLastEntryLineNumber(),
+                               f.getAbsolutePath());
         throw new InitializationException(message);
       }
 
@@ -601,9 +634,9 @@
           }
         }
 
-        Message message = ERR_CONFIG_FILE_NO_PARENT.
-            get(entryDN.toString(), reader.getLastEntryLineNumber(), configFile,
-                parentDN.toString());
+        Message message = ERR_CONFIG_FILE_NO_PARENT.get(entryDN.toString(),
+                               reader.getLastEntryLineNumber(),
+                               f.getAbsolutePath(), parentDN.toString());
         throw new InitializationException(message);
       }
 
@@ -636,8 +669,8 @@
           }
         }
 
-        Message message =
-            ERR_CONFIG_FILE_GENERIC_ERROR.get(configFile, String.valueOf(e));
+        Message message = ERR_CONFIG_FILE_GENERIC_ERROR.get(f.getAbsolutePath(),
+                                                            String.valueOf(e));
         throw new InitializationException(message, e);
       }
     }
@@ -651,7 +684,7 @@
     {
       try
       {
-        File configDirFile = new File(configFile).getParentFile();
+        File configDirFile = f.getParentFile();
         if ((configDirFile != null) &&
             configDirFile.getName().equals(CONFIG_DIR_NAME))
         {
@@ -2265,6 +2298,185 @@
 
 
   /**
+   * {@inheritDoc}
+   */
+  public void writeSuccessfulStartupConfig()
+  {
+    if (useLastKnownGoodConfig)
+    {
+      // The server was started with the "last known good" configuration, so we
+      // shouldn't overwrite it with something that is probably bad.
+      return;
+    }
+
+
+    String startOKFilePath = configFile + ".startok";
+    String tempFilePath    = startOKFilePath + ".tmp";
+    String oldFilePath     = startOKFilePath + ".old";
+
+
+    // Copy the current config file to a temporary file.
+    File tempFile = new File(tempFilePath);
+    FileInputStream inputStream = null;
+    try
+    {
+      inputStream = new FileInputStream(configFile);
+
+      FileOutputStream outputStream = null;
+      try
+      {
+        outputStream = new FileOutputStream(tempFilePath, false);
+
+        try
+        {
+          byte[] buffer = new byte[8192];
+          while (true)
+          {
+            int bytesRead = inputStream.read(buffer);
+            if (bytesRead < 0)
+            {
+              break;
+            }
+
+            outputStream.write(buffer, 0, bytesRead);
+          }
+        }
+        catch (Exception e)
+        {
+          if (debugEnabled())
+          {
+            TRACER.debugCaught(DebugLogLevel.ERROR, e);
+          }
+
+          logError(ERR_STARTOK_CANNOT_WRITE.get(configFile, tempFilePath,
+                                                getExceptionMessage(e)));
+          return;
+        }
+      }
+      catch (Exception e)
+      {
+        if (debugEnabled())
+        {
+          TRACER.debugCaught(DebugLogLevel.ERROR, e);
+        }
+
+        logError(ERR_STARTOK_CANNOT_OPEN_FOR_WRITING.get(tempFilePath,
+                      getExceptionMessage(e)));
+        return;
+      }
+      finally
+      {
+        try
+        {
+          outputStream.close();
+        }
+        catch (Exception e)
+        {
+          if (debugEnabled())
+          {
+            TRACER.debugCaught(DebugLogLevel.ERROR, e);
+          }
+        }
+      }
+    }
+    catch (Exception e)
+    {
+      if (debugEnabled())
+      {
+        TRACER.debugCaught(DebugLogLevel.ERROR, e);
+      }
+
+      logError(ERR_STARTOK_CANNOT_OPEN_FOR_READING.get(configFile,
+                                                       getExceptionMessage(e)));
+      return;
+    }
+    finally
+    {
+      try
+      {
+        inputStream.close();
+      }
+      catch (Exception e)
+      {
+        if (debugEnabled())
+        {
+          TRACER.debugCaught(DebugLogLevel.ERROR, e);
+        }
+      }
+    }
+
+
+    // If a ".startok" file already exists, then move it to an ".old" file.
+    File oldFile = new File(oldFilePath);
+    try
+    {
+      if (oldFile.exists())
+      {
+        oldFile.delete();
+      }
+    }
+    catch (Exception e)
+    {
+      if (debugEnabled())
+      {
+        TRACER.debugCaught(DebugLogLevel.ERROR, e);
+      }
+    }
+
+    File startOKFile = new File(startOKFilePath);
+    try
+    {
+      if (startOKFile.exists())
+      {
+        startOKFile.renameTo(oldFile);
+      }
+    }
+    catch (Exception e)
+    {
+      if (debugEnabled())
+      {
+        TRACER.debugCaught(DebugLogLevel.ERROR, e);
+      }
+    }
+
+
+    // Rename the temp file to the ".startok" file.
+    try
+    {
+      tempFile.renameTo(startOKFile);
+    } catch (Exception e)
+    {
+      if (debugEnabled())
+      {
+        TRACER.debugCaught(DebugLogLevel.ERROR, e);
+      }
+
+      logError(ERR_STARTOK_CANNOT_RENAME.get(tempFilePath, startOKFilePath,
+                                             getExceptionMessage(e)));
+      return;
+    }
+
+
+    // Remove the ".old" file if there is one.
+    try
+    {
+      if (oldFile.exists())
+      {
+        oldFile.delete();
+      }
+    }
+    catch (Exception e)
+    {
+      if (debugEnabled())
+      {
+        TRACER.debugCaught(DebugLogLevel.ERROR, e);
+      }
+    }
+  }
+
+
+
+  /**
    * Retrieves the OIDs of the controls that may be supported by this backend.
    *
    * @return  The OIDs of the controls that may be supported by this backend.
diff --git a/opendj-sdk/opends/src/server/org/opends/server/types/DirectoryEnvironmentConfig.java b/opendj-sdk/opends/src/server/org/opends/server/types/DirectoryEnvironmentConfig.java
index dcf9c6b..3d3d8f0 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/types/DirectoryEnvironmentConfig.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/types/DirectoryEnvironmentConfig.java
@@ -470,6 +470,77 @@
 
 
   /**
+   * Indicates whether the Directory Server should attempt to start
+   * with the "last known good" configuration rather than the current
+   * active configuration file.  Note that if there is no "last known
+   * good" configuration file available, then the server should try to
+   * start using the current, active configuration file.  If no
+   * explicit value is defined, then a default result of {@code false}
+   * will be returned.
+   *
+   * @return  {@code true} if the Directory Server should attempt to
+   *          start using the "last known good" configuration, or
+   *          {@code false} if it should try to start using the
+   *          active configuration.
+   */
+  public boolean useLastKnownGoodConfiguration()
+  {
+    String useLastKnownGoodStr =
+         getProperty(PROPERTY_USE_LAST_KNOWN_GOOD_CONFIG);
+    if (useLastKnownGoodStr == null)
+    {
+      return false;
+    }
+
+    return useLastKnownGoodStr.equalsIgnoreCase("true");
+  }
+
+
+
+  /**
+   * Specifies whether the Directory Server should attempt to start
+   * using the last known good configuration rather than the
+   * current active configuration.
+   *
+   * @param  useLastKnownGoodConfiguration  Indicates whether the
+   *                                        Directory Server should
+   *                                        attempt to start using the
+   *                                        last known good
+   *                                        configuration.
+   *
+   * @return  The previous setting for this configuration option.  If
+   *          no previous value was specified, then {@code false} will
+   *          be returned.
+   *
+   * @throws  InitializationException  If the Directory Server is
+   *                                   already running.
+   */
+  public boolean setUseLastKnownGoodConfiguration(
+                      boolean useLastKnownGoodConfiguration)
+         throws InitializationException
+  {
+    if (DirectoryServer.isRunning())
+    {
+      throw new InitializationException(
+              ERR_DIRCFG_SERVER_ALREADY_RUNNING.get());
+    }
+
+    String oldUseLastKnownGoodStr =
+         setProperty(PROPERTY_USE_LAST_KNOWN_GOOD_CONFIG,
+                     String.valueOf(useLastKnownGoodConfiguration));
+    if (oldUseLastKnownGoodStr == null)
+    {
+      return false;
+    }
+    else
+    {
+      return oldUseLastKnownGoodStr.equalsIgnoreCase("true");
+    }
+  }
+
+
+
+  /**
    * Indicates whether the Directory Server should maintain an archive
    * of previous configurations.  If no explicit value is defined,
    * then a default result of {@code true} will be returned.
diff --git a/opendj-sdk/opends/src/server/org/opends/server/util/ServerConstants.java b/opendj-sdk/opends/src/server/org/opends/server/util/ServerConstants.java
index 23fab11..336772f 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/util/ServerConstants.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/util/ServerConstants.java
@@ -2612,6 +2612,16 @@
 
 
   /**
+   * The name of the system property that can be used to indicate that the
+   * Directory Server should attempt to start using the last known good
+   * configuration, rather than the current active configuration.
+   */
+  public static final String PROPERTY_USE_LAST_KNOWN_GOOD_CONFIG =
+       "org.opends.server.UseLastKnownGoodConfiguration";
+
+
+
+  /**
    * The column at which to wrap long lines of output in the command-line tools.
    */
   public static final int MAX_LINE_WIDTH;
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/ConfigFileHandlerTestCase.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/ConfigFileHandlerTestCase.java
index 199406b..0e4ed1f 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/ConfigFileHandlerTestCase.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/ConfigFileHandlerTestCase.java
@@ -28,6 +28,7 @@
 
 
 
+import java.io.File;
 import java.util.ArrayList;
 
 import org.testng.annotations.BeforeClass;
@@ -60,6 +61,19 @@
          throws Exception
   {
     TestCaseUtils.startServer();
+
+    String buildRoot = System.getProperty(TestCaseUtils.PROPERTY_BUILD_ROOT);
+    String startOKFile = buildRoot + File.separator + "build" + File.separator +
+                         "unit-tests" + File.separator + "package" +
+                         File.separator + "config" + File.separator +
+                         "config.ldif.startok";
+
+    assertTrue(new File(startOKFile).exists(),
+               startOKFile + " does not exist but it should");
+    assertFalse(new File(startOKFile + ".tmp").exists(),
+                startOKFile + ".tmp exists but should not");
+    assertFalse(new File(startOKFile + ".old").exists(),
+                startOKFile + ".old exists but should not");
   }
 
 

--
Gitblit v1.10.0