MultiBit HDTHE TECHNICAL RECOVERY LIBRARYmail@keychainx.io ↗

MultiBit HD / Technical library

INDEPENDENT KEYCHAINX RESOURCE

InstallationManager: data locations

Defines the application name and chooses the default application-data directory according to operating system. Read locateApplicationDataDirectory before treating a remembered path as definitive. Redirected profiles or copied disk images can require additional interpretation.

By KeychainX · 20 September 2026 · Source revision 24a12b199a01

Source inspection only. Historical Java code; not executed or security-audited for this publication.

mbhd-core/src/main/java/org/multibit/hd/core/managers/InstallationManager.java

View the exact upstream file · Read raw source · License notices

SHA-256: e8023bd31fca7dac9461df4980bd9909d4d61cbb99b9fccdf843a395dcc1d042

1package org.multibit.hd.core.managers;23import com.google.common.base.Preconditions;4import com.google.common.io.ByteStreams;5import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;6import org.multibit.hd.core.events.ShutdownEvent;7import org.multibit.commons.files.SecureFiles;8import org.multibit.hd.core.utils.OSUtils;9import org.slf4j.Logger;10import org.slf4j.LoggerFactory;1112import java.io.File;13import java.io.FileInputStream;14import java.io.FileOutputStream;15import java.io.IOException;16import java.io.InputStream;17import java.lang.reflect.Field;18import java.net.URI;19import java.security.Permission;20import java.security.PermissionCollection;21import java.util.Map;2223/**24 * <p>Manager to provide the following to other core classes:</p>25 * <ul>26 * <li>Location of the installation directory</li>27 * <li>Access the configuration file</li>28 * <li>Utility methods eg copying checkpoint files from installation directory</li>29 * </ul>30 */31public class InstallationManager {3233  private static final Logger log = LoggerFactory.getLogger(InstallationManager.class);3435  /**36   * The main MultiBit download site (HTTPS)37   */38  public static final URI MBHD_WEBSITE_URI = URI.create("https://multibit.org");3940  /**41   * The main MultiBit help site (HTTPS to allow secure connection without redirect, with fall back to local help on failure)42   */43  public static final String MBHD_WEBSITE_HELP_DOMAIN = "https://multibit.org";44  public static final String MBHD_WEBSITE_HELP_BASE = MBHD_WEBSITE_HELP_DOMAIN + "/hd0.4";4546  public static final String MBHD_APP_NAME = "MultiBitHD";47  public static final String MBHD_PREFIX = "mbhd";48  public static final String MBHD_CONFIGURATION_FILE = MBHD_PREFIX + ".yaml";4950  public static final String SPV_BLOCKCHAIN_SUFFIX = ".spvchain";51  public static final String CHECKPOINTS_SUFFIX = ".checkpoints";52  public static final String CA_CERTS_NAME = MBHD_PREFIX + "-cacerts";5354  /**55   * The current application data directory56   */57  private static File currentApplicationDataDirectory = null;5859  /**60   * A test flag to allow FEST tests to run efficiently61   */62  // This arises from the global nature of the flag - consider deriving it from test classpath presence63  @SuppressFBWarnings({"MS_SHOULD_BE_FINAL"})64  public static boolean unrestricted = false;6566  /**67   * <p>Handle any shutdown code</p>68   *69   * @param shutdownType The shutdown type70   */71  public static void shutdownNow(ShutdownEvent.ShutdownType shutdownType) {7273    log.debug("Received shutdown: {}", shutdownType.name());7475    switch (shutdownType) {7677      case HARD:78      case SOFT:79        // Force a reset of the application directory (useful for persistence tests)80        currentApplicationDataDirectory = null;81        break;82      case SWITCH:83        // Reset of the current application directory causes problems during84        // switch and is not required in normal operation85        break;86    }8788    // Reset of the unrestricted field causes problems during FEST tests8990  }9192  /**93   * @return A reference to where the configuration file should be located94   */95  public static File getConfigurationFile() {9697    return new File(getOrCreateApplicationDataDirectory().getAbsolutePath() + File.separator + MBHD_CONFIGURATION_FILE);9899  }100101  /**102   * <p>Get the directory for the user's application data, creating if not present</p>103   * <p>Checks a few OS-dependent locations first</p>104   * <p>For tests (unrestricted mode) this will create a long-lived temporary directory - use reset() to clear in the tearDown() phase</p>105   *106   * @return A suitable application directory for the OS and if running unit tests (unrestricted mode)107   */108  public static File getOrCreateApplicationDataDirectory() {109110    if (currentApplicationDataDirectory != null) {111      return currentApplicationDataDirectory;112    }113114    if (unrestricted) {115      try {116        log.debug("Unrestricted mode requires a temporary application directory");117        // In order to preserve the same behaviour between the test and production environments118        // this must be maintained throughout the lifetime of a unit test119        // At tearDown() use reset() to clear120        currentApplicationDataDirectory = SecureFiles.createTemporaryDirectory();121        return currentApplicationDataDirectory;122      } catch (IOException e) {123        throw new IllegalStateException("Cannot run without access to temporary directory.", e);124      }125    } else {126127      // Fail safe check for unit tests to avoid overwriting existing configuration file128      try {129        Class.forName("org.multibit.hd.core.managers.InstallationManagerTest");130        throw new IllegalStateException("Cannot run without unrestricted when unit tests are present. You could overwrite live configuration.");131      } catch (ClassNotFoundException e) {132        // We have passed the fail safe check133      }134135    }136137    // Check the current working directory for the configuration file138    File multibitPropertiesFile = new File(MBHD_CONFIGURATION_FILE);139    if (multibitPropertiesFile.exists()) {140      return new File(".");141    }142143    final String applicationDataDirectoryName;144145    // Locations are OS-dependent146    if (OSUtils.isWindows()) {147148      // Windows149      applicationDataDirectoryName = System.getenv("APPDATA") + File.separator + MBHD_APP_NAME;150151    } else if (OSUtils.isMac()) {152153      // OSX154      if ((new File("../../../../" + MBHD_CONFIGURATION_FILE)).exists()) {155        applicationDataDirectoryName = new File("../../../..").getAbsolutePath();156      } else {157        applicationDataDirectoryName = System.getProperty("user.home") + "/Library/Application Support/" + MBHD_APP_NAME;158      }159    } else {160161      // Other (probably a Unix variant)162      // Keep a clean home directory by prefixing with "."163      applicationDataDirectoryName = System.getProperty("user.home") + "/." + MBHD_APP_NAME;164    }165166    log.debug("Application data directory is\n'{}'", applicationDataDirectoryName);167168    // Create the application data directory if it does not exist169    File applicationDataDirectory = new File(applicationDataDirectoryName);170    SecureFiles.verifyOrCreateDirectory(applicationDataDirectory);171172    // Must be OK to be here so set this as the current173    currentApplicationDataDirectory = applicationDataDirectory;174175    return applicationDataDirectory;176  }177178  /**179   * Copy the checkpoints file from the MultiBitHD installation to the specified filename180   *181   * @param destinationCheckpointsFile The sink to receive the source checkpoints file182   */183  public static void copyCheckpointsTo(File destinationCheckpointsFile) throws IOException {184185    Preconditions.checkNotNull(destinationCheckpointsFile, "'checkpointsFile' must be present");186187    // TODO overwrite if larger/ newer188    if (!destinationCheckpointsFile.exists() || destinationCheckpointsFile.length() == 0) {189190      log.debug("Copying checkpoints to '{}'", destinationCheckpointsFile);191192      // Work out the source checkpoints (put into the program installation directory by the installer)193      File currentWorkingDirectory = new File(".");194      File sourceBlockCheckpointsFile = new File(currentWorkingDirectory.getAbsolutePath() + File.separator + MBHD_PREFIX + CHECKPOINTS_SUFFIX);195196      // Prepare an input stream to the checkpoints197      final InputStream sourceCheckpointsStream;198      if (sourceBlockCheckpointsFile.exists()) {199        // Use the file system200        log.debug("Using source checkpoints from working directory.");201        sourceCheckpointsStream = new FileInputStream(sourceBlockCheckpointsFile);202      } else {203        // Use the classpath204        log.debug("Using source checkpoints from classpath.");205        sourceCheckpointsStream = InstallationManager.class.getResourceAsStream("/mbhd.checkpoints");206      }207208      // Create the output stream209      long bytes;210      try (FileOutputStream sinkCheckpointsStream = new FileOutputStream(destinationCheckpointsFile)) {211212        // Copy the checkpoints213        bytes = ByteStreams.copy(sourceCheckpointsStream, sinkCheckpointsStream);214215        // Clean up216        sourceCheckpointsStream.close();217        sinkCheckpointsStream.flush();218        sinkCheckpointsStream.close();219      } finally {220        if (sourceCheckpointsStream != null) {221          sourceCheckpointsStream.close();222        }223      }224225      log.debug("New checkpoints are {} bytes in length.", bytes);226227      if (bytes < 13_000) {228        log.warn("Checkpoints are short.");229      }230231    } else {232233      log.debug("Checkpoints already exist.");234235    }236  }237238  /**239   * Use for testing only (several different test packages use this)240   *241   * @param currentApplicationDataDirectory the application data directory to use242   */243  public static void setCurrentApplicationDataDirectory(File currentApplicationDataDirectory) {244    InstallationManager.currentApplicationDataDirectory = currentApplicationDataDirectory;245  }246247  /**248   * Do the following, but with reflection to bypass access checks:249   *250   * JceSecurity.isRestricted = false;251   * JceSecurity.defaultPolicy.perms.clear();252   * JceSecurity.defaultPolicy.add(CryptoAllPermission.INSTANCE);253   */254  public static void removeCryptographyRestrictions() {255256    if (!isRestrictedCryptography()) {257      log.debug("Cryptography restrictions removal not needed");258      return;259    }260261    try {262      final Class<?> jceSecurity = Class.forName("javax.crypto.JceSecurity");263      final Class<?> cryptoPermissions = Class.forName("javax.crypto.CryptoPermissions");264      final Class<?> cryptoAllPermission = Class.forName("javax.crypto.CryptoAllPermission");265266      final Field isRestrictedField = jceSecurity.getDeclaredField("isRestricted");267      isRestrictedField.setAccessible(true);268      isRestrictedField.set(null, false);269270      final Field defaultPolicyField = jceSecurity.getDeclaredField("defaultPolicy");271      defaultPolicyField.setAccessible(true);272      final PermissionCollection defaultPolicy = (PermissionCollection) defaultPolicyField.get(null);273274      final Field perms = cryptoPermissions.getDeclaredField("perms");275      perms.setAccessible(true);276      ((Map<?, ?>) perms.get(defaultPolicy)).clear();277278      final Field instance = cryptoAllPermission.getDeclaredField("INSTANCE");279      instance.setAccessible(true);280      defaultPolicy.add((Permission) instance.get(null));281282      log.debug("Successfully removed cryptography restrictions");283    } catch (final Exception e) {284      log.warn("Failed to remove cryptography restrictions", e);285    }286287  }288289  private static boolean isRestrictedCryptography() {290291    // This simply matches the Oracle JRE, but not OpenJDK292    return "Java(TM) SE Runtime Environment".equals(System.getProperty("java.runtime.name"));293  }294295}

Read in context

Defines the application name and chooses the default application-data directory according to operating system. Read locateApplicationDataDirectory before treating a remembered path as definitive. Redirected profiles or copied disk images can require additional interpretation.

Compare the callers, dependency versions and corresponding tests. The pinned file is one part of a larger application. Do not execute write, prune or migration routines against your only copy of a wallet.

Sources at the reviewed revision