From 9216a940dc50ada577c026b6688366d33162b2fd Mon Sep 17 00:00:00 2001 From: "Jason P. Leasure" Date: Tue, 12 May 2020 11:37:11 -0400 Subject: [PATCH] save/restore active bundles, use generic.util.Path string encoding --- .../app/plugin/core/osgi/BundleHost.java | 85 ++++++++++++--- .../app/plugin/core/osgi/BundleStatus.java | 5 - .../osgi/BundleStatusComponentProvider.java | 44 +------- .../core/osgi/BundleStatusTableModel.java | 102 ++++++++---------- .../app/plugin/core/osgi/GhidraBundle.java | 9 ++ .../script/GhidraScriptComponentProvider.java | 6 +- .../AbstractGhidraScriptMgrPluginTest.java | 5 - .../script/GhidraScriptMgrPlugin2Test.java | 3 +- .../generic/test/AbstractGenericTest.java | 2 +- .../src/main/java/generic/util/Path.java | 51 +++++---- .../GhidraScriptMgrPluginScreenShots.java | 15 +-- 11 files changed, 169 insertions(+), 158 deletions(-) diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleHost.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleHost.java index 1807662b8a..82f58d1a61 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleHost.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleHost.java @@ -34,9 +34,11 @@ import org.osgi.framework.launch.Framework; import org.osgi.framework.wiring.*; import org.osgi.service.log.*; +import generic.io.NullPrintWriter; import generic.jar.ResourceFile; import ghidra.framework.Application; import ghidra.framework.options.SaveState; +import ghidra.framework.plugintool.PluginTool; import ghidra.util.Msg; import ghidra.util.exception.CancelledException; import ghidra.util.task.*; @@ -68,12 +70,22 @@ public class BundleHost { return r; } - public GhidraBundle enablePath(ResourceFile bundlePath) { + public boolean enablePath(ResourceFile bundlePath) { GhidraBundle gb = bp2gb.get(bundlePath); if (gb == null) { gb = addGhidraBundle(bundlePath, true, false); + return true; } - return gb; + return enable(gb); + } + + public boolean enable(GhidraBundle gbundle) { + if (!gbundle.isEnabled()) { + gbundle.setEnabled(true); + fireBundleEnablementChange(gbundle, true); + return true; + } + return false; } public GhidraBundle addGhidraBundle(ResourceFile path, boolean enabled, boolean systemBundle) { @@ -707,18 +719,10 @@ public class BundleHost { return bp2gb.keySet(); } - public boolean enable(GhidraBundle gbundle) { - if (!gbundle.enabled) { - gbundle.enabled = true; - fireBundleEnablementChange(gbundle, true); - } - return false; - } - /* * this is done at most once, and it's done AFTER system bundles have been added. */ - public void restoreState(SaveState ss) { + public void restoreStateAndActivate(SaveState ss, PluginTool tool) { // XXX lock bundlehost operations String[] pathArr = ss.getStrings("BundleHost_PATH", new String[0]); @@ -727,12 +731,14 @@ public class BundleHost { } boolean[] enableArr = ss.getBooleans("BundleHost_ENABLE", new boolean[pathArr.length]); + boolean[] activeArr = ss.getBooleans("BundleHost_ACTIVE", new boolean[pathArr.length]); boolean[] systemArr = ss.getBooleans("BundleHost_SYSTEM", new boolean[pathArr.length]); List added = new ArrayList<>(); - + List need_activation = new ArrayList<>(); for (int i = 0; i < pathArr.length; i++) { - ResourceFile bp = new ResourceFile(pathArr[i]); + + ResourceFile bp = generic.util.Path.fromPathString(pathArr[i]); if (!bp.exists()) { Msg.showWarn(this, null, "missing bundle path", "the bundle path " + bp.toString() + " no longer exists, removing"); @@ -740,11 +746,12 @@ public class BundleHost { continue; } boolean en = enableArr[i]; + boolean act = activeArr[i]; boolean sys = systemArr[i]; GhidraBundle gb = bp2gb.get(bp); if (gb != null) { if (en != gb.isEnabled()) { - gb.enabled = en; + gb.setEnabled(en); fireBundleEnablementChange(gb, en); } if (sys != gb.isSystemBundle()) { @@ -757,31 +764,75 @@ public class BundleHost { // stored system bundles that weren't already initialized must be old, drop 'm. } else { - added.add(newGhidraBundle(this, bp, en, sys)); + added.add(gb = newGhidraBundle(this, bp, en, sys)); + } + if (gb != null && act) { + need_activation.add(gb); } } - add(added); + + new TaskLauncher(new Task("restoring bundle state", true, true, false) { + @Override + public void run(TaskMonitor monitor) throws CancelledException { + activateAll(need_activation, monitor, new NullPrintWriter()); + } + }); } public void saveState(SaveState ss) { int n = bp2gb.size(); String[] pathArr = new String[n]; boolean[] enableArr = new boolean[n]; + boolean[] activeArr = new boolean[n]; boolean[] systemArr = new boolean[n]; int index = 0; for (Entry e : bp2gb.entrySet()) { GhidraBundle gb = e.getValue(); - pathArr[index] = gb.getPath().toString(); + pathArr[index] = generic.util.Path.toPathString(gb.getPath()); enableArr[index] = gb.isEnabled(); + activeArr[index] = gb.isActive(); systemArr[index] = gb.isSystemBundle(); ++index; } ss.putStrings("BundleHost_PATH", pathArr); ss.putBooleans("BundleHost_ENABLE", enableArr); + ss.putBooleans("BundleHost_ACTIVE", activeArr); ss.putBooleans("BundleHost_SYSTEM", systemArr); } + protected void activateAll(List gbs, TaskMonitor monitor, PrintWriter console) { + + monitor.setMaximum(gbs.size()); + while (!gbs.isEmpty() && !monitor.isCancelled()) { + List l = + gbs.stream().filter(gb -> canResolveAll(gb.getAllReqs())).collect( + Collectors.toList()); + if (l.isEmpty()) { + // final round + l = gbs; + gbs = Collections.emptyList(); + } + else { + gbs.removeAll(l); + } + + for (GhidraBundle gb : l) { + if (monitor.isCancelled()) { + break; + } + try { + gb.build(console); + activateSynchronously(gb.getBundleLoc()); + } + catch (Exception e) { + e.printStackTrace(console); + } + monitor.incrementProgress(1); + } + } + } + } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleStatus.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleStatus.java index 302c9fb1b5..7e92a4e20c 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleStatus.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleStatus.java @@ -48,11 +48,6 @@ public class BundleStatus implements Comparable { return type; } - BundleStatus(String path, boolean enabled, boolean readonly) { - this.path = new Path(path, enabled, false, readonly); - type = GhidraBundle.getType(getPath()); - } - BundleStatus(ResourceFile path, boolean enabled, boolean readonly) { this.path = new Path(path, enabled, false, readonly); type = GhidraBundle.getType(getPath()); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleStatusComponentProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleStatusComponentProvider.java index caac0a798e..68a866b4b1 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleStatusComponentProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleStatusComponentProvider.java @@ -18,7 +18,6 @@ package ghidra.app.plugin.core.osgi; import java.awt.*; import java.io.File; import java.util.Arrays; -import java.util.Collections; import java.util.List; import java.util.stream.Collectors; @@ -177,7 +176,7 @@ public class BundleStatusComponentProvider extends ComponentProviderAdapter { public Component getTableCellRendererComponent(GTableCellRenderingData data) { ResourceFile path = (ResourceFile) data.getValue(); JLabel c = (JLabel) super.getTableCellRendererComponent(data); - c.setText(Path.getPathAsString(path)); + c.setText(Path.toPathString(path)); if (!path.exists()) { c.setForeground(Color.RED); @@ -391,14 +390,11 @@ public class BundleStatusComponentProvider extends ComponentProviderAdapter { } protected void doActivateBundles() { - ConsoleService console = getTool().getService(ConsoleService.class); int[] selectedModelRows = getSelectedModelRows(); new TaskLauncher(new Task("activating", true, true, false) { @Override public void run(TaskMonitor monitor) throws CancelledException { - long startTime = System.nanoTime(); - // suppress RowObjectSelectionManager repairs until after we're done bundleStatusTable.chill(); @@ -414,40 +410,8 @@ public class BundleStatusComponentProvider extends ComponentProviderAdapter { List gbs = statuses.stream().map( bs -> bundleHost.getExistingGhidraBundle(bs.getPath())).collect( Collectors.toList()); - int total = gbs.size(); - int total_activated = 0; + bundleHost.activateAll(gbs, monitor, getTool().getService(ConsoleService.class).getStdErr()); - monitor.setMaximum(gbs.size()); - while (!gbs.isEmpty() && !monitor.isCancelled()) { - List l = gbs.stream().filter( - gb -> bundleHost.canResolveAll(gb.getAllReqs())).collect( - Collectors.toList()); - if (l.isEmpty()) { - console.getStdErr().printf("%d incompletely resolved bundles remain\n", - gbs.size()); - // final round - l = gbs; - gbs = Collections.emptyList(); - } - else { - gbs.removeAll(l); - } - - for (GhidraBundle gb : l) { - if (monitor.isCancelled()) { - break; - } - try { - gb.build(console.getStdErr()); - bundleHost.activateSynchronously(gb.getBundleLoc()); - ++total_activated; - } - catch (Exception e) { - e.printStackTrace(console.getStdErr()); - } - monitor.incrementProgress(1); - } - } boolean anybusy = false; for (BundleStatus bs : statuses) { if (bs.isBusy()) { @@ -459,10 +423,6 @@ public class BundleStatusComponentProvider extends ComponentProviderAdapter { notifyTableDataChanged(); } - long endTime = System.nanoTime(); - console.getStdOut().printf("%d/%d bundles activated in %3.2f seconds.\n", - total_activated, total, (endTime - startTime) / 1e9); - bundleStatusTable.thaw(); } }, getComponent(), 1000); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleStatusTableModel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleStatusTableModel.java index 9d01e2e7b7..b8fc6f4418 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleStatusTableModel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleStatusTableModel.java @@ -15,7 +15,6 @@ */ package ghidra.app.plugin.core.osgi; -import java.io.File; import java.util.*; import java.util.stream.Collectors; @@ -146,26 +145,13 @@ public class BundleStatusTableModel extends AbstractSortedTableModel(); for (GhidraBundle gb : bundleHost.getGhidraBundles()) { - addNewStatus(gb.getPath(), gb.isEnabled(), gb.isSystemBundle()); + addNewStatus(gb); } bundleHost.addListener(bundleListener = new BundleHostListener() { @@ -193,16 +179,14 @@ public class BundleStatusTableModel extends AbstractSortedTableModel gbundles) { int index = statuses.size(); for (GhidraBundle gb : gbundles) { - BundleStatus status = - new BundleStatus(gb.getPath(), gb.isEnabled(), gb.isSystemBundle()); - addStatusNoFire(status); + addNewStatusNoFire(gb); } fireTableRowsInserted(index, gbundles.size() - 1); } @@ -256,42 +240,27 @@ public class BundleStatusTableModel extends AbstractSortedTableModel files, boolean enabled, boolean readonly) { - int index = statuses.size(); - for (File f : files) { - BundleStatus status = new BundleStatus(new ResourceFile(f), enabled, readonly); - addStatusNoFire(status); - } - fireTableRowsInserted(index, files.size() - 1); } private int removeStatusNoFire(BundleStatus status) { @@ -381,20 +350,6 @@ public class BundleStatusTableModel extends AbstractSortedTableModel testingPaths) { - this.statuses = testingPaths.stream().map(f -> new BundleStatus(f, true, false)).collect( - Collectors.toList()); - computeCache(); - fireTableDataChanged(); - } - private ArrayList bundleStatusListeners = new ArrayList<>(); public void addListener(BundleStatusChangeRequestListener listener) { @@ -470,4 +425,31 @@ public class BundleStatusTableModel extends AbstractSortedTableModel paths) { + this.statuses = + paths.stream().map(f -> new BundleStatus(f, true, false)).collect(Collectors.toList()); + computeCache(); + fireTableDataChanged(); + } + } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/GhidraBundle.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/GhidraBundle.java index 71a6eccf1f..f77bcfb37a 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/GhidraBundle.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/GhidraBundle.java @@ -70,6 +70,10 @@ public abstract class GhidraBundle { return enabled; } + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + public boolean isSystemBundle() { return systemBundle; } @@ -119,4 +123,9 @@ public abstract class GhidraBundle { bundleHost.activateSynchronously(getBundleLoc()); } + public boolean isActive() { + Bundle b = getBundle(); + return (b != null) && b.getState() == Bundle.ACTIVE; + } + } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptComponentProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptComponentProvider.java index b65a9a995a..72bc83c18c 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptComponentProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptComponentProvider.java @@ -66,12 +66,12 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter { static final String WINDOW_GROUP = "Script Group"; private Map editorMap = new HashMap<>(); - private GhidraScriptMgrPlugin plugin; + final private GhidraScriptMgrPlugin plugin; private JPanel component; private RootNode scriptRoot; private GTree scriptCategoryTree; private DraggableScriptTable scriptTable; - private GhidraScriptInfoManager infoManager; + final private GhidraScriptInfoManager infoManager; private GhidraScriptTableModel tableModel; private BundleStatusComponentProvider bundleStatusComponentProvider; private TaskListener taskListener = new ScriptTaskListener(); @@ -237,7 +237,7 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter { //================================================================================================== public void readConfigState(SaveState saveState) { - bundleHost.restoreState(saveState); + bundleHost.restoreStateAndActivate(saveState, getTool()); actionManager.restoreUserDefinedKeybindings(saveState); actionManager.restoreScriptsThatAreInTool(saveState); diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/AbstractGhidraScriptMgrPluginTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/AbstractGhidraScriptMgrPluginTest.java index 0dc2ed1a61..f33f5d7874 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/AbstractGhidraScriptMgrPluginTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/AbstractGhidraScriptMgrPluginTest.java @@ -628,11 +628,6 @@ public abstract class AbstractGhidraScriptMgrPluginTest waitForSwing(); } - protected void addScriptDirectory(ResourceFile scriptDir) { - // when there is a provider, GhidraScriptUtil has the same BundleHost - provider.getBundleHost().enablePath(scriptDir); - } - protected String runScript(String scriptName) throws Exception { clearConsole(); diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/GhidraScriptMgrPlugin2Test.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/GhidraScriptMgrPlugin2Test.java index b436b1efb2..25513c6b7c 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/GhidraScriptMgrPlugin2Test.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/GhidraScriptMgrPlugin2Test.java @@ -216,7 +216,8 @@ public class GhidraScriptMgrPlugin2Test extends AbstractGhidraScriptMgrPluginTes tempScriptDir.mkdir(); ResourceFile scriptDir = new ResourceFile(tempScriptDir); - addScriptDirectory(scriptDir); + provider.getBundleHost().enablePath(scriptDir); + try { // create a script file in that directory String rawScriptName = testName.getMethodName(); diff --git a/Ghidra/Framework/Generic/src/main/java/generic/test/AbstractGenericTest.java b/Ghidra/Framework/Generic/src/main/java/generic/test/AbstractGenericTest.java index 0f8d391b25..b8736524d2 100644 --- a/Ghidra/Framework/Generic/src/main/java/generic/test/AbstractGenericTest.java +++ b/Ghidra/Framework/Generic/src/main/java/generic/test/AbstractGenericTest.java @@ -1683,7 +1683,7 @@ public abstract class AbstractGenericTest extends AbstractGTest { if (SwingUtilities.isEventDispatchThread()) { Msg.error(AbstractGenericTest.class, - "Incorrectly called yeildToSwing() from the Swing thread"); + "Incorrectly called yieldToSwing() from the Swing thread"); return; // shouldn't happen } diff --git a/Ghidra/Framework/Generic/src/main/java/generic/util/Path.java b/Ghidra/Framework/Generic/src/main/java/generic/util/Path.java index 11371da3cb..bdb5298e86 100644 --- a/Ghidra/Framework/Generic/src/main/java/generic/util/Path.java +++ b/Ghidra/Framework/Generic/src/main/java/generic/util/Path.java @@ -106,27 +106,14 @@ public class Path implements Comparable { * @param isReadOnly if true files contained within directory are considered read-only */ public Path(String path, boolean isEnabled, boolean isEditable, boolean isReadOnly) { - if (path.startsWith(GHIDRA_HOME)) { - this.path = resolveGhidraHome(path); - } - else if (path.startsWith(USER_HOME)) { - String userHome = System.getProperty("user.home"); - int length = USER_HOME.length(); - String relativePath = path.substring(length); - this.path = new ResourceFile(new File(userHome + relativePath)); - } - else { - this.path = new ResourceFile(path); - } - if (OperatingSystem.CURRENT_OPERATING_SYSTEM == OperatingSystem.WINDOWS) { - this.path = this.path.getCanonicalFile(); - } + this.path = fromPathString(path); + this.isEnabled = isEnabled; this.isEditable = isEditable; this.isReadOnly = isReadOnly; } - private ResourceFile resolveGhidraHome(String scriptPath) { + static private ResourceFile resolveGhidraHome(String scriptPath) { ResourceFile pathFile = null; for (ResourceFile root : Application.getApplicationRootDirectories()) { int length = GHIDRA_HOME.length(); @@ -187,6 +174,34 @@ public class Path implements Comparable { return path; } + /** + * Parse the path string with path element placeholders, such as + * {@link #GHIDRA_HOME}. + * @param path the path + * + * @return the path as a ResourceFile. + */ + public static ResourceFile fromPathString(String path) { + ResourceFile rf = null; + if (path.startsWith(GHIDRA_HOME)) { + rf = resolveGhidraHome(path); + } + else if (path.startsWith(USER_HOME)) { + String userHome = System.getProperty("user.home"); + int length = USER_HOME.length(); + String relativePath = path.substring(length); + rf = new ResourceFile(new File(userHome + relativePath)); + } + else { + rf = new ResourceFile(path); + } + if (OperatingSystem.CURRENT_OPERATING_SYSTEM == OperatingSystem.WINDOWS) { + rf = rf.getCanonicalFile(); + } + + return rf; + } + /** * Returns the path as a string with path element placeholders, such as * {@link #GHIDRA_HOME}. @@ -194,7 +209,7 @@ public class Path implements Comparable { * * @return the path as a string . */ - static public String getPathAsString(ResourceFile path) { + static public String toPathString(ResourceFile path) { String userHome = System.getProperty("user.home"); String absolutePath = path.getAbsolutePath(); for (ResourceFile appRoot : Application.getApplicationRootDirectories()) { @@ -224,7 +239,7 @@ public class Path implements Comparable { * @return the path as a string . */ public String getPathAsString() { - return getPathAsString(path); + return toPathString(path); } /** diff --git a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/GhidraScriptMgrPluginScreenShots.java b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/GhidraScriptMgrPluginScreenShots.java index 111b41f0c8..a0f1966a75 100644 --- a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/GhidraScriptMgrPluginScreenShots.java +++ b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/GhidraScriptMgrPluginScreenShots.java @@ -26,6 +26,7 @@ import docking.ComponentProvider; import docking.widgets.tree.GTree; import docking.widgets.tree.GTreeNode; import generic.jar.ResourceFile; +import generic.util.Path; import ghidra.app.plugin.core.console.ConsoleComponentProvider; import ghidra.app.plugin.core.osgi.BundleStatusComponentProvider; import ghidra.app.plugin.core.script.*; @@ -110,14 +111,16 @@ public class GhidraScriptMgrPluginScreenShots extends GhidraScreenShotGenerator @Test public void testScript_Dirs() throws Exception { - List paths = new ArrayList<>(); - paths.add("$USER_HOME/ghidra_scripts"); - paths.add("$GHIDRA_HOME/Features/Base/ghidra_scripts"); - paths.add("/User/defined/invalid/directory"); + List paths = new ArrayList<>(); + paths.add(Path.fromPathString("$USER_HOME/ghidra_scripts")); + paths.add(Path.fromPathString("$GHIDRA_HOME/Features/Base/ghidra_scripts")); + paths.add(Path.fromPathString("/User/defined/invalid/directory")); + + BundleStatusComponentProvider bundleStatusComponentProvider = + showProvider(BundleStatusComponentProvider.class); - BundleStatusComponentProvider bundleStatusComponentProvider = showProvider(BundleStatusComponentProvider.class); bundleStatusComponentProvider.getModel().setPathsForTesting(paths); - + waitForComponentProvider(BundleStatusComponentProvider.class); captureComponent(bundleStatusComponentProvider.getComponent()); }