mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2024-11-21 19:42:14 +00:00
Extensions installed through the GUI now get put in the user settings
directory (rather than installation directory)
This commit is contained in:
parent
3ea1c2bb2b
commit
d70c6c256f
@ -29,11 +29,17 @@ option on the project file menu.</p>
|
||||
<h2>Dialog Components</h2>
|
||||
<h3>Extensions List</h3>
|
||||
<blockquote>
|
||||
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:
|
||||
<ul>
|
||||
<li>Extensions Installation Directory: Contains any extensions that have been installed. This is located at <i>[installation dir]/Ghidra/Extensions/</i></li>
|
||||
<li>Extension Installation Directories: Contains any extensions that have been installed. The directories are located at:</li>
|
||||
<ul>
|
||||
<li><i>[user dir]/.ghidra/.ghidra-[version]/Extensions</i> - Installed/uninstalled from this dialog</li>
|
||||
<li><i>[installation dir]/Ghidra/Extensions/</i> - Installed/uninstalled from filesystem manually</li>
|
||||
</ul>
|
||||
<li>Extensions Archive Directory: This is where all archive files (zips) are stored. It is located at <i>[installation dir]/Extensions/Ghidra/</i></li>
|
||||
</ul>
|
||||
<p><b>Note: </b> 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.</p>
|
||||
</blockquote>
|
||||
|
||||
<h3>Description Panel</h3>
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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<ExtensionDetails, List<Exte
|
||||
|
||||
@Override
|
||||
public boolean isCellEditable(int rowIndex, int columnIndex) {
|
||||
if (Application.inSingleJarMode()) {
|
||||
if (Application.inSingleJarMode() || SystemUtilities.isInDevelopmentMode()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -104,6 +106,14 @@ class ExtensionTableModel extends ThreadedTableModel<ExtensionDetails, List<Exte
|
||||
return false;
|
||||
}
|
||||
|
||||
// Do not allow GUI uninstallation of extensions manually installed in installation
|
||||
// directory
|
||||
if (extension.getInstallPath() != null && FileUtilities.isPathContainedWithin(
|
||||
Application.getApplicationLayout().getApplicationInstallationDir().getFile(false),
|
||||
new File(extension.getInstallPath()))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (columnIndex == INSTALLED_COL);
|
||||
}
|
||||
|
||||
@ -123,7 +133,13 @@ class ExtensionTableModel extends ThreadedTableModel<ExtensionDetails, List<Exte
|
||||
|
||||
// If the user does not have write permissions on the installation dir, they cannot
|
||||
// install.
|
||||
ResourceFile installDir = Application.getApplicationLayout().getExtensionInstallationDir();
|
||||
ResourceFile installDir =
|
||||
Application.getApplicationLayout().getExtensionInstallationDirs().get(0);
|
||||
if (!installDir.exists() && !installDir.mkdir()) {
|
||||
Msg.showError(this, null, "Directory Error",
|
||||
"Cannot install/uninstall extensions: Failed to create extension installation directory.\n" +
|
||||
"See the \"Ghidra Extension Notes\" section of the Ghidra Installation Guide for more information.");
|
||||
}
|
||||
if (!installDir.canWrite()) {
|
||||
Msg.showError(this, null, "Permissions Error",
|
||||
"Cannot install/uninstall extensions: Invalid write permissions on installation directory.\n" +
|
||||
|
@ -123,7 +123,12 @@ public class ExtensionTableProvider extends DialogComponentProvider {
|
||||
// Don't let the user attempt to install anything if they don't have write
|
||||
// permissions on the installation dir.
|
||||
ResourceFile installDir =
|
||||
Application.getApplicationLayout().getExtensionInstallationDir();
|
||||
Application.getApplicationLayout().getExtensionInstallationDirs().get(0);
|
||||
if (!installDir.exists() && !installDir.mkdir()) {
|
||||
Msg.showError(this, null, "Directory Error",
|
||||
"Cannot install/uninstall extensions: Failed to create extension installation directory.\n" +
|
||||
"See the \"Ghidra Extension Notes\" section of the Ghidra Installation Guide for more information.");
|
||||
}
|
||||
if (!installDir.canWrite()) {
|
||||
Msg.showError(this, null, "Permissions Error",
|
||||
"Cannot install/uninstall extensions: Invalid write permissions on installation directory.\n" +
|
||||
|
@ -133,26 +133,34 @@ public class ExtensionUtils {
|
||||
|
||||
ApplicationLayout layout = Application.getApplicationLayout();
|
||||
|
||||
if (layout.getExtensionInstallationDir() == null ||
|
||||
!layout.getExtensionInstallationDir().exists()) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
// The set to return;
|
||||
Set<ExtensionDetails> 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<ResourceFile> propFiles = findExtensionPropertyFiles(installDir, includeUninstalled);
|
||||
for (ResourceFile propFile : propFiles) {
|
||||
|
||||
ExtensionDetails details = createExtensionDetailsFromPropertyFile(propFile);
|
||||
for (ResourceFile installDir : layout.getExtensionInstallationDirs()) {
|
||||
if (!installDir.isDirectory()) {
|
||||
continue;
|
||||
}
|
||||
List<ResourceFile> 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);
|
||||
|
||||
|
@ -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:<br>
|
||||
* Returns a prioritized list of directories where Ghidra extensions are installed. These
|
||||
* should be at the following locations:<br>
|
||||
* <ul>
|
||||
* <li><code>[user settings dir]/Extensions</code></li>
|
||||
* <li><code>[application install dir]/Ghidra/Extensions</code></li>
|
||||
* <li><code>ghidra/Ghidra/Extensions</code> (development mode)</li>
|
||||
* </ul>
|
||||
*
|
||||
* @return the install folder, or null if can't be determined
|
||||
*/
|
||||
protected ResourceFile findExtensionInstallationDirectory() {
|
||||
protected List<ResourceFile> findExtensionInstallationDirectories() {
|
||||
|
||||
List<ResourceFile> 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;
|
||||
}
|
||||
}
|
||||
|
@ -75,9 +75,9 @@ public class GhidraJarApplicationLayout extends GhidraApplicationLayout {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ResourceFile findExtensionInstallationDirectory() {
|
||||
protected List<ResourceFile> findExtensionInstallationDirectories() {
|
||||
ResourceFile extensionInstallDir = new ResourceFile(
|
||||
ApplicationLayout.class.getResource("/_Root/Ghidra/Extensions").toExternalForm());
|
||||
return extensionInstallDir;
|
||||
return List.of(extensionInstallDir);
|
||||
}
|
||||
}
|
||||
|
@ -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<ResourceFile> findExtensionInstallationDirectories() {
|
||||
File installDir = new File(getUserTempDir(), "ExtensionInstallDir");
|
||||
return new ResourceFile(installDir);
|
||||
return List.of(new ResourceFile(installDir));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -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<ResourceFile> 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<ResourceFile> getExtensionInstallationDirs() {
|
||||
return extensionInstallationDirs;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -18,7 +18,7 @@
|
||||
|
||||
<h1>Ghidra Installation Guide</h1>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
|
||||
@ -363,17 +363,14 @@ can be found in the <i><GhidraInstallDir></i>/Extensions directory.</p>
|
||||
</ol>
|
||||
</li>
|
||||
<li>
|
||||
Installing or uninstalling Ghidra extensions may fail if the user does not have write
|
||||
permissions to <i><GhidraInstallDir></i>. 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
|
||||
<i><UserDir></i>/.ghidra/.ghidra-[version]/Extensions.
|
||||
</li>
|
||||
<li>
|
||||
<p>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.<p>
|
||||
<p>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.<p>
|
||||
<p>To install an extension in these cases, simply extract the desired Ghidra extension archive
|
||||
file(s) to the <i><GhidraInstallDir></i>/Ghidra/Extensions directory. For example, on
|
||||
Linux or macOS:</p>
|
||||
|
Loading…
Reference in New Issue
Block a user