From d70c6c256f37c7aa70dec8550aa459bc430bb371 Mon Sep 17 00:00:00 2001
From: Ryan Kurtz
Date: Fri, 5 Jun 2020 15:25:51 -0400
Subject: [PATCH] Extensions installed through the GUI now get put in the user
settings directory (rather than installation directory)
---
.../help/topics/FrontEndPlugin/Extensions.htm | 10 ++-
.../util/extensions/ExtensionUtilsTest.java | 13 +--
.../remote/GhidraServerApplicationLayout.java | 3 +-
.../dialog/ExtensionTableModel.java | 20 ++++-
.../dialog/ExtensionTableProvider.java | 7 +-
.../plugintool/dialog/ExtensionUtils.java | 80 ++++++++++---------
.../java/ghidra/GhidraApplicationLayout.java | 22 ++---
.../ghidra/GhidraJarApplicationLayout.java | 4 +-
.../ghidra/GhidraTestApplicationLayout.java | 5 +-
.../application/ApplicationLayout.java | 15 ++--
GhidraDocs/InstallationGuide.html | 17 ++--
11 files changed, 116 insertions(+), 80 deletions(-)
diff --git a/Ghidra/Features/Base/src/main/help/help/topics/FrontEndPlugin/Extensions.htm b/Ghidra/Features/Base/src/main/help/help/topics/FrontEndPlugin/Extensions.htm
index e2f08c0864..8b38eaa4f7 100644
--- a/Ghidra/Features/Base/src/main/help/help/topics/FrontEndPlugin/Extensions.htm
+++ b/Ghidra/Features/Base/src/main/help/help/topics/FrontEndPlugin/Extensions.htm
@@ -29,11 +29,17 @@ option on the project file menu.
Dialog Components
Extensions List
-The list of extensions is populated when the dialog is launched. To build the list, Ghidra looks in two locations:
+The list of extensions is populated when the dialog is launched. To build the list, Ghidra looks in several locations:
-- Extensions Installation Directory: Contains any extensions that have been installed. This is located at [installation dir]/Ghidra/Extensions/
+- Extension Installation Directories: Contains any extensions that have been installed. The directories are located at:
+
+ - [user dir]/.ghidra/.ghidra-[version]/Extensions - Installed/uninstalled from this dialog
+ - [installation dir]/Ghidra/Extensions/ - Installed/uninstalled from filesystem manually
+
- Extensions Archive Directory: This is where all archive files (zips) are stored. It is located at [installation dir]/Extensions/Ghidra/
+Note: Extensions that have been installed directly into the Ghidra installation directory cannot be uninstalled
+from this dialog. They must be manually removed from the filesystem.
Description Panel
diff --git a/Ghidra/Features/Base/src/test/java/ghidra/util/extensions/ExtensionUtilsTest.java b/Ghidra/Features/Base/src/test/java/ghidra/util/extensions/ExtensionUtilsTest.java
index 7284d2bc86..5f0485e644 100644
--- a/Ghidra/Features/Base/src/test/java/ghidra/util/extensions/ExtensionUtilsTest.java
+++ b/Ghidra/Features/Base/src/test/java/ghidra/util/extensions/ExtensionUtilsTest.java
@@ -15,8 +15,7 @@
*/
package ghidra.util.extensions;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.*;
import java.io.*;
import java.util.Set;
@@ -57,7 +56,9 @@ public class ExtensionUtilsTest extends AbstractGenericTest {
// we start with a clean slate). If they're not empty, CORRECT THE SITUATION.
if (!checkCleanInstall()) {
FileUtilities.deleteDir(gLayout.getExtensionArchiveDir().getFile(false));
- FileUtilities.deleteDir(gLayout.getExtensionInstallationDir().getFile(false));
+ for (ResourceFile installDir : gLayout.getExtensionInstallationDirs()) {
+ FileUtilities.deleteDir(installDir.getFile(false));
+ }
}
createExtensionDirs();
@@ -248,7 +249,7 @@ public class ExtensionUtilsTest extends AbstractGenericTest {
}
}
- ResourceFile installDir = gLayout.getExtensionInstallationDir();
+ ResourceFile installDir = gLayout.getExtensionInstallationDirs().get(0);
if (!installDir.exists()) {
if (!installDir.mkdir()) {
throw new IOException("Failed to create extension installation directory for test");
@@ -260,7 +261,7 @@ public class ExtensionUtilsTest extends AbstractGenericTest {
* Verifies that the installation folder is empty.
*/
private boolean checkCleanInstall() {
- ResourceFile[] files = gLayout.getExtensionInstallationDir().listFiles();
+ ResourceFile[] files = gLayout.getExtensionInstallationDirs().get(0).listFiles();
return (files == null || files.length == 0);
}
@@ -271,7 +272,7 @@ public class ExtensionUtilsTest extends AbstractGenericTest {
* @param name the name of the installed extension
*/
private void checkDirtyInstall(String name) {
- ResourceFile[] files = gLayout.getExtensionInstallationDir().listFiles();
+ ResourceFile[] files = gLayout.getExtensionInstallationDirs().get(0).listFiles();
assertTrue(files.length >= 1);
assertTrue(files[0].getName().equals(name));
}
diff --git a/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/remote/GhidraServerApplicationLayout.java b/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/remote/GhidraServerApplicationLayout.java
index 9d6248463b..ad44b21f93 100644
--- a/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/remote/GhidraServerApplicationLayout.java
+++ b/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/remote/GhidraServerApplicationLayout.java
@@ -17,6 +17,7 @@ package ghidra.server.remote;
import java.io.FileNotFoundException;
import java.io.IOException;
+import java.util.Collections;
import ghidra.framework.ApplicationProperties;
import ghidra.util.SystemUtilities;
@@ -51,7 +52,7 @@ public class GhidraServerApplicationLayout extends ApplicationLayout {
// Extension directories
extensionArchiveDir = null;
- extensionInstallationDir = null;
+ extensionInstallationDirs = Collections.emptyList();
// User directories (don't let anything use the user home directory...there may not be one)
userTempDir = ApplicationUtilities.getDefaultUserTempDir(applicationProperties);
diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/dialog/ExtensionTableModel.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/dialog/ExtensionTableModel.java
index 49b839aa5b..8361138de1 100644
--- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/dialog/ExtensionTableModel.java
+++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/dialog/ExtensionTableModel.java
@@ -17,6 +17,7 @@ package ghidra.framework.plugintool.dialog;
import java.awt.Color;
import java.awt.Component;
+import java.io.File;
import java.util.ArrayList;
import java.util.List;
@@ -33,6 +34,7 @@ import ghidra.util.exception.CancelledException;
import ghidra.util.table.column.AbstractGColumnRenderer;
import ghidra.util.table.column.GColumnRenderer;
import ghidra.util.task.TaskMonitor;
+import utilities.util.FileUtilities;
/**
* Model for the {@link ExtensionTablePanel}. This defines 5 columns for displaying information in
@@ -95,7 +97,7 @@ class ExtensionTableModel extends ThreadedTableModel extensions = new HashSet<>();
// Find all extension.properties or extension.properties.uninstalled files in
// the install directory and create a ExtensionDetails object for each.
- ResourceFile installDir = layout.getExtensionInstallationDir();
- List propFiles = findExtensionPropertyFiles(installDir, includeUninstalled);
- for (ResourceFile propFile : propFiles) {
-
- ExtensionDetails details = createExtensionDetailsFromPropertyFile(propFile);
+ for (ResourceFile installDir : layout.getExtensionInstallationDirs()) {
+ if (!installDir.isDirectory()) {
+ continue;
+ }
+ List propFiles =
+ findExtensionPropertyFiles(installDir, includeUninstalled);
+ for (ResourceFile propFile : propFiles) {
- // We found this extension in the installation directory, so set the install path
- // property and add to the final set.
- details.setInstallPath(propFile.getParentFile().getAbsolutePath());
- extensions.add(details);
+ ExtensionDetails details = createExtensionDetailsFromPropertyFile(propFile);
+
+ // We found this extension in the installation directory, so set the install path
+ // property and add to the final set.
+ details.setInstallPath(propFile.getParentFile().getAbsolutePath());
+ if (!extensions.contains(details)) {
+ extensions.add(details);
+ }
+ else {
+ Msg.warn(null,
+ "Skipping extension \"" + details.getName() + "\" found at " +
+ details.getInstallPath() +
+ ". Extension by that name installed in higher priority location.");
+ }
+ }
}
return extensions;
@@ -219,8 +227,13 @@ public class ExtensionUtils {
* @return true if installed
*/
public static boolean isInstalled(String extensionName) {
- return new File(Application.getApplicationLayout().getExtensionInstallationDir() +
- File.separator + extensionName).exists();
+ for (ResourceFile installDir : Application.getApplicationLayout()
+ .getExtensionInstallationDirs()) {
+ if (new ResourceFile(installDir, extensionName).exists()) {
+ return true;
+ }
+ }
+ return false;
}
/**
@@ -267,16 +280,15 @@ public class ExtensionUtils {
return false;
}
+ File installDir = new ResourceFile(
+ Application.getApplicationLayout().getExtensionInstallationDirs().get(0),
+ extension.getName()).getFile(false);
+
if (extension.getArchivePath() == null) {
// Special Case: If the archive path is null then this must be an extension that
// was installed from an external location, then uninstalled. In this case, there
// should be a Module.manifest.uninstalled and extension.properties.uninstalled
// present. If so, just restore them. If not, there's a problem.
-
- String installPath = Application.getApplicationLayout().getExtensionInstallationDir() +
- File.separator + extension.getName();
- File installDir = new File(installPath);
-
if (installDir.exists()) {
return restoreStateFiles(installDir);
}
@@ -299,19 +311,13 @@ public class ExtensionUtils {
// the GUI then tries to reinstall it without restarting Ghidra, the extension hasn't actually
// been removed yet; just the manifest file has been renamed. In this case we don't need to go through
// the full install process of unzipping or copying files to the install location. All we need
- // to do is rename the manifest file from Module.manifest.uninstall back to Module.manifest.
- String installPath = Application.getApplicationLayout().getExtensionInstallationDir() + File.separator +
- extension.getName();
- File installDir = new File(installPath);
-
+ // to do is rename the manifest file from Module.manifest.uninstall back to Module.manifest.
if (installDir.exists()) {
return restoreStateFiles(installDir);
}
if (install(file)) {
- extension.setInstallPath(
- Application.getApplicationLayout().getExtensionInstallationDir() + File.separator +
- extension.getName());
+ extension.setInstallPath(installDir + File.separator + extension.getName());
return true;
}
@@ -554,7 +560,9 @@ public class ExtensionUtils {
File errorFile = e.getErrorFile();
if (errorFile != null) {
// Get the root of the extension in the install location.
- ResourceFile installDir = Application.getApplicationLayout().getExtensionInstallationDir();
+ ResourceFile installDir = Application.getApplicationLayout()
+ .getExtensionInstallationDirs()
+ .get(0);
// Get the root directory of the extension (strip off the install folder location and
// grab the first part of the remaining path).
@@ -755,7 +763,7 @@ public class ExtensionUtils {
File newDir = null;
try {
newDir =
- new File(Application.getApplicationLayout().getExtensionInstallationDir() +
+ new File(Application.getApplicationLayout().getExtensionInstallationDirs().get(0) +
File.separator + extension.getName());
FileUtilities.deleteDir(newDir, monitor);
FileUtilities.copyDir(extension, newDir, monitor);
@@ -788,11 +796,10 @@ public class ExtensionUtils {
ExtensionExceptionType.ZIP_ERROR);
}
- if (layout.getExtensionInstallationDir() == null ||
- !layout.getExtensionInstallationDir().exists()) {
+ ResourceFile installDir = layout.getExtensionInstallationDirs().get(0);
+ if (installDir == null || !installDir.exists()) {
throw new ExtensionException(
- "Extension installation directory is not valid: " +
- layout.getExtensionInstallationDir(),
+ "Extension installation directory is not valid: " + installDir,
ExtensionExceptionType.INVALID_INSTALL_LOCATION);
}
@@ -803,8 +810,7 @@ public class ExtensionUtils {
ZipArchiveEntry entry = entries.nextElement();
- String filePath =
- (layout.getExtensionInstallationDir() + File.separator + entry.getName());
+ String filePath = installDir + File.separator + entry.getName();
File file = new File(filePath);
diff --git a/Ghidra/Framework/Utility/src/main/java/ghidra/GhidraApplicationLayout.java b/Ghidra/Framework/Utility/src/main/java/ghidra/GhidraApplicationLayout.java
index ba35977fd2..c2507869ec 100644
--- a/Ghidra/Framework/Utility/src/main/java/ghidra/GhidraApplicationLayout.java
+++ b/Ghidra/Framework/Utility/src/main/java/ghidra/GhidraApplicationLayout.java
@@ -62,7 +62,7 @@ public class GhidraApplicationLayout extends ApplicationLayout {
getApplicationInstallationDir());
// Extensions
- extensionInstallationDir = findExtensionInstallationDirectory();
+ extensionInstallationDirs = findExtensionInstallationDirectories();
extensionArchiveDir = findExtensionArchiveDirectory();
// Patch directory
@@ -217,17 +217,20 @@ public class GhidraApplicationLayout extends ApplicationLayout {
}
/**
- * Returns the directory where all Ghidra extension archives should be
- * installed. This should be at the following location:
+ * Returns a prioritized list of directories where Ghidra extensions are installed. These
+ * should be at the following locations:
*
+ * [user settings dir]/Extensions
* [application install dir]/Ghidra/Extensions
* ghidra/Ghidra/Extensions
(development mode)
*
*
* @return the install folder, or null if can't be determined
*/
- protected ResourceFile findExtensionInstallationDirectory() {
+ protected List findExtensionInstallationDirectories() {
+ List dirs = new ArrayList<>();
+
// Would like to find a better way to do this, but for the moment this seems the
// only solution. We want to get the 'Extensions' directory in ghidra, but there's
// no way to retrieve that directory directly. We can only get the full set of
@@ -237,13 +240,14 @@ public class GhidraApplicationLayout extends ApplicationLayout {
ResourceFile rootDir = getApplicationRootDirs().iterator().next();
File temp = new File(rootDir.getFile(false), "Extensions");
if (temp.exists()) {
- return new ResourceFile(temp);
+ dirs.add(new ResourceFile(temp));
}
-
- return null;
+ }
+ else {
+ dirs.add(new ResourceFile(new File(userSettingsDir, "Extensions")));
+ dirs.add(new ResourceFile(applicationInstallationDir, "Ghidra/Extensions"));
}
- ResourceFile installDir = findGhidraApplicationInstallationDir();
- return new ResourceFile(installDir, "Ghidra/Extensions");
+ return dirs;
}
}
diff --git a/Ghidra/Framework/Utility/src/main/java/ghidra/GhidraJarApplicationLayout.java b/Ghidra/Framework/Utility/src/main/java/ghidra/GhidraJarApplicationLayout.java
index 0a06f63a3b..7466ab4061 100644
--- a/Ghidra/Framework/Utility/src/main/java/ghidra/GhidraJarApplicationLayout.java
+++ b/Ghidra/Framework/Utility/src/main/java/ghidra/GhidraJarApplicationLayout.java
@@ -75,9 +75,9 @@ public class GhidraJarApplicationLayout extends GhidraApplicationLayout {
}
@Override
- protected ResourceFile findExtensionInstallationDirectory() {
+ protected List findExtensionInstallationDirectories() {
ResourceFile extensionInstallDir = new ResourceFile(
ApplicationLayout.class.getResource("/_Root/Ghidra/Extensions").toExternalForm());
- return extensionInstallDir;
+ return List.of(extensionInstallDir);
}
}
diff --git a/Ghidra/Framework/Utility/src/main/java/ghidra/GhidraTestApplicationLayout.java b/Ghidra/Framework/Utility/src/main/java/ghidra/GhidraTestApplicationLayout.java
index 51d2305b5b..e8d49222bf 100644
--- a/Ghidra/Framework/Utility/src/main/java/ghidra/GhidraTestApplicationLayout.java
+++ b/Ghidra/Framework/Utility/src/main/java/ghidra/GhidraTestApplicationLayout.java
@@ -16,6 +16,7 @@
package ghidra;
import java.io.*;
+import java.util.List;
import generic.jar.ResourceFile;
@@ -51,9 +52,9 @@ public class GhidraTestApplicationLayout extends GhidraApplicationLayout {
}
@Override
- protected ResourceFile findExtensionInstallationDirectory() {
+ protected List findExtensionInstallationDirectories() {
File installDir = new File(getUserTempDir(), "ExtensionInstallDir");
- return new ResourceFile(installDir);
+ return List.of(new ResourceFile(installDir));
}
@Override
diff --git a/Ghidra/Framework/Utility/src/main/java/utility/application/ApplicationLayout.java b/Ghidra/Framework/Utility/src/main/java/utility/application/ApplicationLayout.java
index aa2c46937e..50e58a44d5 100644
--- a/Ghidra/Framework/Utility/src/main/java/utility/application/ApplicationLayout.java
+++ b/Ghidra/Framework/Utility/src/main/java/utility/application/ApplicationLayout.java
@@ -17,8 +17,7 @@ package utility.application;
import java.io.File;
import java.io.IOException;
-import java.util.Collection;
-import java.util.Map;
+import java.util.*;
import generic.jar.ResourceFile;
import ghidra.framework.ApplicationProperties;
@@ -44,7 +43,7 @@ public abstract class ApplicationLayout {
protected File userSettingsDir;
protected ResourceFile patchDir;
protected ResourceFile extensionArchiveDir;
- protected ResourceFile extensionInstallationDir;
+ protected List extensionInstallationDirs;
/**
* Gets the application properties from the application layout
@@ -121,13 +120,13 @@ public abstract class ApplicationLayout {
}
/**
- * Returns the application Extensions installation folder.
+ * Returns an {@link List ordered list} of the application Extensions installation directories.
*
- * @return the application Extensions installation directory. Could be null if the
- * {@link ApplicationLayout} does not support application Extensions.
+ * @return an {@link List ordered list} of the application Extensions installation directories.
+ * Could be empty if the {@link ApplicationLayout} does not support application Extensions.
*/
- public final ResourceFile getExtensionInstallationDir() {
- return extensionInstallationDir;
+ public final List getExtensionInstallationDirs() {
+ return extensionInstallationDirs;
}
/**
diff --git a/GhidraDocs/InstallationGuide.html b/GhidraDocs/InstallationGuide.html
index fcbdcd7063..f7f5b5e72d 100644
--- a/GhidraDocs/InstallationGuide.html
+++ b/GhidraDocs/InstallationGuide.html
@@ -18,7 +18,7 @@
Ghidra Installation Guide
-The installation information provided is effective as of Ghidra 9.1 and is subject to change with
+The installation information provided is effective as of Ghidra 9.2 and is subject to change with
future releases.
@@ -363,17 +363,14 @@ can be found in the <GhidraInstallDir>/Extensions directory.
- Installing or uninstalling Ghidra extensions may fail if the user does not have write
- permissions to <GhidraInstallDir>. This may occur if the user is running Ghidra
- from a shared installation location. In this situation, the owner of the Ghidra installation
- directory will be responsible for managing what Ghidra extensions are available for that
- particular installation of Ghidra.
+ Extensions installed from the Ghidra front-end GUI get installed at
+ <UserDir>/.ghidra/.ghidra-[version]/Extensions.
- It is possible to install and uninstall Ghidra extensions manually when the Ghidra front-end
- GUI is not available. This may be required if a system administrator is managing the Ghidra
- extensions of a shared Ghidra installation on behalf of a user, or if a user wishes to install
- an extension into a Ghidra installation that is only ever used headlessly.
+
It is possible to install Ghidra extensions directly into the Ghidra installation directory.
+ This may be required if a system administrator is managing extensions for multiple users that
+ all use a shared installation of Ghidra. It may also be more convenient to manage extensions
+ this way if a Ghidra installation is only ever used headlessly.
To install an extension in these cases, simply extract the desired Ghidra extension archive
file(s) to the <GhidraInstallDir>/Ghidra/Extensions directory. For example, on
Linux or macOS: