mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

kenneth_suter
20.31.2007 724dc38dfdca4df6b5426d1feba964aa3892a417
Fix for issue 2227 in which unpredictable behavior results from an upgrade or reversion process replacing the upgrade script while the upgrade process is running on Windows.  This code will compare the running version of the script with the new version to see whether or not the script actually needs replacing.  If so the script is copied with an extension NEW.  When the script starts it checks for the existence of upgrade.bat.NEW and if exists prints a message informing the user that they must replace the old version of the script with the new version before continuing.
1 files added
7 files modified
321 ■■■■ changed files
opends/resource/upgrade.bat 10 ●●●●● patch | view | raw | blame | history
opends/src/messages/messages/quicksetup.properties 3 ●●●●● patch | view | raw | blame | history
opends/src/quicksetup/org/opends/quicksetup/Installation.java 10 ●●●●● patch | view | raw | blame | history
opends/src/quicksetup/org/opends/quicksetup/upgrader/Reverter.java 60 ●●●●● patch | view | raw | blame | history
opends/src/quicksetup/org/opends/quicksetup/upgrader/Stage.java 127 ●●●●● patch | view | raw | blame | history
opends/src/quicksetup/org/opends/quicksetup/upgrader/Upgrader.java 51 ●●●● patch | view | raw | blame | history
opends/src/quicksetup/org/opends/quicksetup/util/FileManager.java 27 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/quicksetup/util/FileManagerTest.java 33 ●●●●● patch | view | raw | blame | history
opends/resource/upgrade.bat
@@ -30,6 +30,16 @@
set INSTANCE_ROOT=%DIR_HOME%
:checkNewVersion
if exist "upgrade.bat.NEW" goto newVersion
goto checkJavaBin
:newVersion
echo A new version of this script was made available by the last upgrade
echo operation.  Delete this old version and rename file 'upgrade.bat.NEW'
echo to 'upgrade.bat' before continuing.
goto end
:checkJavaBin
if "%JAVA_BIN%" == "" goto noJavaBin
goto callExtractor
opends/src/messages/messages/quicksetup.properties
@@ -1188,3 +1188,6 @@
INFO_ZIP_FILES_DESCRIPTION=OpenDS Installation Package (.zip)
SEVERE_ERR_COULD_NOT_FIND_REPLICATIONID=Could not find a remote peer to \
 initialize the contents of local base DN: %s.
INFO_NEW_UPGRADE_SCRIPT_AVAILABLE=A new version of '%s' has been made \
  available.  After this operation you should delete this script and rename \
  '%s' to '%1$s'.
opends/src/quicksetup/org/opends/quicksetup/Installation.java
@@ -164,6 +164,16 @@
  public static final String WINDOWS_UPGRADE_FILE_NAME = "upgrade.bat";
  /**
   * Newly upgraded Windows upgrade batch file name.  When the upgrade
   * batch file requires upgrade it is not done during execution of the
   * upgrade utility itself since replacing a running script on Windows
   * with a different version leads to unpredictable results.  Instead
   * this new name is used for the upgraded version and the user is
   * expected to manually rename the file following the upgrade.
   */
  public static final String WINDOWS_UPGRADE_FILE_NAME_NEW = "upgrade.bat.NEW";
  /**
   * The UNIX start script file name.
   */
  public static final String UNIX_START_FILE_NAME = "start-ds";
opends/src/quicksetup/org/opends/quicksetup/upgrader/Reverter.java
@@ -46,6 +46,7 @@
import org.opends.quicksetup.HistoricalRecord;
import org.opends.quicksetup.UserInteraction;
import org.opends.quicksetup.CliUserInteraction;
import static org.opends.quicksetup.Installation.*;
import org.opends.quicksetup.event.ProgressUpdateListener;
import org.opends.quicksetup.util.ProgressMessageFormatter;
import org.opends.quicksetup.util.Utils;
@@ -90,6 +91,7 @@
  private BuildInformation archiveBuildInfo;
  private boolean abort = false;
  private boolean restartServer = false;
  private Stage stage = null;
  /**
   * {@inheritDoc}
@@ -100,7 +102,7 @@
    if (launcher instanceof UpgradeLauncher) {
      ud = new ReverterUserData();
      UpgradeLauncher rl = (UpgradeLauncher)launcher;
      File filesDir = null;
      File filesDir;
      if (rl.isInteractive()) {
        if (rl.isNoPrompt()) {
          StringBuilder sb = new StringBuilder()
@@ -219,7 +221,7 @@
        if (historyDir.exists()) {
          FilenameFilter filter = new FilenameFilter() {
            public boolean accept(File dir, String name) {
              return !Installation.HISTORY_LOG_FILE_NAME.equals(name);
              return !HISTORY_LOG_FILE_NAME.equals(name);
            }
          };
          String[] childNames = historyDir.list(filter);
@@ -233,7 +235,7 @@
            Arrays.sort(childNames);
            for (String childName : childNames) {
              File b = new File(historyDir, childName);
              File d = new File(b, Installation.HISTORY_BACKUP_FILES_DIR_NAME);
              File d = new File(b, HISTORY_BACKUP_FILES_DIR_NAME);
              if (isReversionFilesDirectory(d)) {
                ud.setReversionArchiveDirectory(d);
                break;
@@ -330,7 +332,7 @@
      if (f.getParentFile() != null &&
              f.getParentFile().getParentFile() != null &&
              new File(f.getParentFile().getParentFile(),
                      Installation.LOCKS_PATH_RELATIVE).exists()) {
                      LOCKS_PATH_RELATIVE).exists()) {
        installationPath = Utils.getPath(f.getParentFile().getParentFile());
      } else {
        installationPath = path;
@@ -546,7 +548,15 @@
      FileFilter filter = new UpgradeFileFilter(root);
      for (String fileName : root.list()) {
        File f = new File(root, fileName);
        //fm.copyRecursively(f, filesBackupDirectory,
        // Replacing a Windows bat file while it is running with a different
        // version leads to unpredictable behavior so we make a special case
        // here and during the upgrade components step.
        if (Utils.isWindows() &&
                fileName.equals(WINDOWS_UPGRADE_FILE_NAME)) {
          continue;
        }
        fm.move(f, filesBackupDirectory, filter);
      }
      LOG.log(Level.INFO, "Finished backing up filesystem");
@@ -564,16 +574,10 @@
  private void revertComponents() throws ApplicationException {
    try {
      File stageDir = getReversionFilesDirectory();
      Stage stage = getStage();
      Installation installation = getInstallation();
      File root = installation.getRootDirectory();
      FileManager fm = new FileManager();
      for (String fileName : stageDir.list()) {
        File f = new File(stageDir, fileName);
        fm.copyRecursively(f, root,
                new UpgradeFileFilter(stageDir),
                /*overwrite=*/true);
      }
      stage.move(root);
      // The bits should now be of the new version.  Have
      // the installation update the build information so
@@ -616,8 +620,8 @@
      Set<String> cs = new HashSet<String>(Arrays.asList(children));
      // TODO:  more testing of file dir
      isFilesDir = cs.contains(Installation.CONFIG_PATH_RELATIVE) &&
              cs.contains(Installation.LIBRARIES_PATH_RELATIVE);
      isFilesDir = cs.contains(CONFIG_PATH_RELATIVE) &&
              cs.contains(LIBRARIES_PATH_RELATIVE);
    }
    return isFilesDir;
  }
@@ -674,6 +678,17 @@
                INFO_GENERAL_SEE_FOR_HISTORY.get(
                     Utils.getPath(getInstallation().getHistoryLogFile())))
              .append(getLineBreak()).toMessage());
      try {
        Stage stage = getStage();
        List<Message> stageMessages = stage.getMessages();
        for (Message m : stageMessages) {
          notifyListeners(m);
        }
      } catch (IOException e) {
        LOG.log(Level.INFO, "failed to access stage", e);
      }
    } catch (ApplicationException e) {
      notifyListeners(getFormattedDoneWithLineBreak());
      LOG.log(Level.INFO, "Error cleaning up after reversion.", e);
@@ -828,7 +843,7 @@
  {
    if (tempBackupDir == null) {
      tempBackupDir = new File(getInstallation().getTemporaryDirectory(),
              Installation.HISTORY_BACKUP_FILES_DIR_NAME);
              HISTORY_BACKUP_FILES_DIR_NAME);
      if (tempBackupDir.exists()) {
        FileManager fm = new FileManager();
        fm.deleteRecursively(tempBackupDir);
@@ -866,7 +881,7 @@
        }
      } else {
        Installation archiveInstall = null;
        Installation archiveInstall;
        try {
          archiveInstall = getArchiveInstallation();
          archiveBuildInfo = archiveInstall.getBuildInformation();
@@ -962,12 +977,19 @@
    if (archiveDir != null) {
      // Automatically append the 'filesDir' subdirectory if necessary
      if (!archiveDir.getName().
              endsWith(Installation.HISTORY_BACKUP_FILES_DIR_NAME)) {
              endsWith(HISTORY_BACKUP_FILES_DIR_NAME)) {
        archiveDir = new File(archiveDir,
                Installation.HISTORY_BACKUP_FILES_DIR_NAME);
                HISTORY_BACKUP_FILES_DIR_NAME);
      }
    }
    return archiveDir;
  }
  private Stage getStage() throws IOException, ApplicationException {
    if (this.stage == null) {
      this.stage = new Stage(getReversionFilesDirectory());
    }
    return stage;
  }
}
opends/src/quicksetup/org/opends/quicksetup/upgrader/Stage.java
New file
@@ -0,0 +1,127 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2006-2007 Sun Microsystems, Inc.
 */
package org.opends.quicksetup.upgrader;
import static org.opends.quicksetup.Installation.*;
import static org.opends.messages.QuickSetupMessages.*;
import org.opends.messages.Message;
import org.opends.quicksetup.ApplicationException;
import org.opends.quicksetup.util.Utils;
import org.opends.quicksetup.util.FileManager;
import java.io.File;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.List;
import java.util.LinkedList;
import java.util.Collections;
/**
 * Represents a directory used to stage files for upgrade or reversion and
 * is intended to handle special case operations.
 */
public class Stage {
  private static final Logger LOG =
          Logger.getLogger(Stage.class.getName());
  private File root;
  private FileManager fm;
  private List<Message> messages = new LinkedList<Message>();
  /**
   * Creates a parameterized instance.
   *
   * @param root of the stage directory
   */
  public Stage(File root) {
    this.root = root;
    this.fm = new FileManager();
  }
  /**
   * Moves the files in the staging area to a destination directory.
   *
   * @param destination for the staged files
   * @throws ApplicationException if something goes wrong
   */
  public void move(File destination) throws ApplicationException {
    UpgradeFileFilter ff = new UpgradeFileFilter(root);
    for (String fileName : root.list()) {
      File dest = new File(destination, fileName);
      File src = getSourceForCopy(fileName, dest);
      fm.copyRecursively(src, destination, ff, /*overwrite=*/true);
    }
  }
  /**
   * Returns a list of messages to be displayed to the user following
   * this operation.
   *
   * @return list of messages
   */
  public List<Message> getMessages() {
    return Collections.unmodifiableList(messages);
  }
  private File getSourceForCopy(String fileName, File dest) {
    File src = new File(root, fileName);
    // If this is the running script on Windows, see if it is actually
    // different than the new version.  If not don't do anything but if
    // so copy it over under a different name so that the running script
    // is not disturbed
    if (WINDOWS_UPGRADE_FILE_NAME.equals(fileName) &&
            Utils.isWindows()) {
      try {
        if (fm.filesDiffer(src, dest)) {
          File renamedUpgradeBatFile = new File(root,
                  WINDOWS_UPGRADE_FILE_NAME_NEW);
          if (src.renameTo(renamedUpgradeBatFile)) {
            src = renamedUpgradeBatFile;
            messages.add(INFO_NEW_UPGRADE_SCRIPT_AVAILABLE.get(
                    WINDOWS_UPGRADE_FILE_NAME,
                    WINDOWS_UPGRADE_FILE_NAME_NEW));
          } else {
            LOG.log(Level.INFO, "Failed to rename new version of " +
                    "'" + WINDOWS_UPGRADE_FILE_NAME + "' to '" +
                    WINDOWS_UPGRADE_FILE_NAME_NEW + "'");
          }
        }
      } catch (IOException e) {
        LOG.log(Level.INFO, "Exception comparing files " + e.getMessage());
      }
    }
    return src;
  }
}
opends/src/quicksetup/org/opends/quicksetup/upgrader/Upgrader.java
@@ -148,11 +148,6 @@
  private ApplicationException runWarning = null;
  /**
   * Helps with CLI specific tasks.
   */
  private UpgraderCliHelper cliHelper = null;
  /**
   * Directory where backup is kept in case the upgrade needs reversion.
   */
  private File backupDirectory = null;
@@ -178,8 +173,16 @@
   */
  private Installation stagedInstallation = null;
  // TODO: remove dead code
  private RemoteBuildManager remoteBuildManager = null;
  /**
   * Represents staged files for upgrade.
   */
  private Stage stage = null;
  /** Set to true if the user decides to close the window while running. */
  private boolean abort = false;
@@ -1026,6 +1029,17 @@
                    Utils.getPath(getInstallation().getHistoryLogFile())))
                .append(formatter.getLineBreak())
                .toMessage());
        try {
          Stage stage = getStage();
          List<Message> stageMessages = stage.getMessages();
          for (Message m : stageMessages) {
            notifyListeners(m);
          }
        } catch (IOException e) {
          LOG.log(Level.INFO, "failed to access stage", e);
        }
      } catch (ApplicationException e) {
        notifyListeners(getFormattedErrorWithLineBreak());
        LOG.log(Level.INFO, "Error cleaning up after upgrade.", e);
@@ -1164,18 +1178,19 @@
  }
  private Stage getStage() throws IOException, ApplicationException {
    if (this.stage == null) {
      this.stage = new Stage(getStageDirectory());
    }
    return this.stage;
  }
  private void upgradeComponents() throws ApplicationException {
    try {
      File stageDir = getStageDirectory();
      Stage stage = getStage();
      Installation installation = getInstallation();
      File root = installation.getRootDirectory();
      FileManager fm = new FileManager();
      for (String fileName : stageDir.list()) {
        File f = new File(stageDir, fileName);
        fm.copyRecursively(f, root,
                new UpgradeFileFilter(stageDir),
                /*overwrite=*/true);
      }
      stage.move(root);
      // The bits should now be of the new version.  Have
      // the installation update the build information so
@@ -1223,7 +1238,15 @@
      FileFilter filter = new UpgradeFileFilter(root);
      for (String fileName : root.list()) {
        File f = new File(root, fileName);
        //fm.copyRecursively(f, filesBackupDirectory,
        // Replacing a Windows bat file while it is running with a different
        // version leads to unpredictable behavior so we make a special case
        // here and during the upgrade components step.
        if (Utils.isWindows() &&
                fileName.equals(Installation.WINDOWS_UPGRADE_FILE_NAME)) {
          continue;
        }
        fm.move(f, filesBackupDirectory, filter);
      }
    } catch (ApplicationException ae) {
opends/src/quicksetup/org/opends/quicksetup/util/FileManager.java
@@ -353,6 +353,33 @@
            filter);
  }
 /**
  * Determines whether or not two files differ in content.
  *
  * @param f1 file to compare
  * @param f2 file to compare
  * @return boolean where true indicates that two files differ
  * @throws IOException if there is a problem reading the files' conents
  */
 public boolean filesDiffer(File f1, File f2) throws IOException {
   boolean differ = false;
   FileReader fr1 = new FileReader(f1);
   FileReader fr2 = new FileReader(f2);
   try {
     boolean done = false;
     while (!differ && !done) {
       int c1 = fr1.read();
       int c2 = fr2.read();
       differ = c1 != c2;
       done = c1 == -1 || c2 == -1;
     }
   } finally {
     fr1.close();
     fr2.close();
   }
   return differ;
 }
  private void operateRecursively(FileOperation op, FileFilter filter)
          throws ApplicationException {
    File file = op.getObjectFile();
opends/tests/unit-tests-testng/src/server/org/opends/quicksetup/util/FileManagerTest.java
@@ -498,6 +498,39 @@
    assertTrue(ORIGINAL.equals(contentsOf(copiedF2b1)));
  }
  @DataProvider(name = "differTestData")
  public Object[][] differTestData() {
    return new Object[][] {
      new Object[] { "abc", "abc" },
      new Object[] { "abc", "xyz" },
      new Object[] { "abc", "abc\n" },
      new Object[] { "abc\n", "abc\n" },
      new Object[] { "abc\nabc", "abc\nabc" },
      new Object[] { "abc\nabc\nabc", "abc\nabc\nabc" }
    };
  }
  @Test(dataProvider = "differTestData")
  public void testFilesDiffer(String contents1, String contents2)
          throws Exception
  {
    File f1 = new File(fmWorkspace, "f1");
    File f2 = new File(fmWorkspace, "f2");
    writeContents(f1, contents1);
    writeContents(f2, contents2);
    if (!contents1.equals(contents2)) {
      assertTrue(fileManager.filesDiffer(f1, f2),
              "File contents '" + contents1 + "' and '" + contents2 + "' are " +
                      "not equal despite what FileManager claims");
    } else {
      assertFalse(fileManager.filesDiffer(f1, f2),
              "File contents '" + contents1 + "' and '" + contents2 + "' are " +
                      "equal despite what FileManager claims");
    }
    f1.delete();
    f2.delete();
  }
  /**
   * Creates a set of file for testing.
   * @param parent of the files.