introduce GhidraBundle, cleanup packages, rename path->status

This commit is contained in:
Jason P. Leasure 2020-04-02 17:19:49 -04:00
parent 7873bb32fa
commit 431d7ac752
22 changed files with 466 additions and 441 deletions

View file

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.script.osgi;
package ghidra.app.plugin.core.osgi;
import java.io.*;
import java.nio.charset.Charset;
@ -38,33 +38,33 @@ import generic.jar.ResourceFile;
import ghidra.app.script.*;
public class BundleCompiler {
public static final String GENERATED_ACTIVATOR_CLASSNAME = "GeneratedActivator";
private BundleHost bh;
public BundleCompiler(BundleHost bh) {
static final String GENERATED_ACTIVATOR_CLASSNAME = "GeneratedActivator";
BundleCompiler(BundleHost bh) {
this.bh = bh;
}
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
private JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
/**
* compile a source directory to an exploded bundle
*
* @param sbi the bundle info to build
* @param sb the bundle info to build
* @param writer for updating the user during compilation
* @throws IOException for source/manifest file reading/generation and binary deletion/creation
* @throws OSGiException if generation of bundle metadata fails
*/
public void compileToExplodedBundle(SourceBundleInfo sbi, PrintWriter writer)
void compileToExplodedBundle(GhidraSourceBundle sb, PrintWriter writer)
throws IOException, OSGiException {
sbi.compileAttempted();
sbi.setSummary(String.format("build %d files, skipping %d%s", sbi.getNewSourcesCount(),
sbi.getFailingSourcesCount(), sbi.newManifestFile() ? ", new manifest" : ""));
sb.compileAttempted();
sb.setSummary(String.format("build %d files, skipping %d%s", sb.getNewSourcesCount(),
sb.getFailingSourcesCount(), sb.newManifestFile() ? ", new manifest" : ""));
ResourceFile srcdir = sbi.getSourceDir();
Path bindir = sbi.getBinDir();
ResourceFile srcdir = sb.getSourceDir();
Path bindir = sb.getBinDir();
Files.createDirectories(bindir);
List<String> options = new ArrayList<>();
@ -79,7 +79,7 @@ public class BundleCompiler {
// final JavaFileManager rfm = new ResourceFileJavaFileManager(Collections.singletonList(bi.getSourceDir()));
final JavaFileManager rfm =
new ResourceFileJavaFileManager(Collections.singletonList(sbi.getSourceDir()));
new ResourceFileJavaFileManager(Collections.singletonList(sb.getSourceDir()));
BundleJavaManager bjm = new BundleJavaManager(bh.getHostFramework(), rfm, options);
// The phidias BundleJavaManager is for compiling from within a bundle -- it makes the
@ -88,7 +88,7 @@ public class BundleCompiler {
// XXX skip this if there's a source manifest, emit warnings about @imports
// get wires for currently active bundles to satisfy all requirements
List<BundleRequirement> reqs = sbi.getAllReqs();
List<BundleRequirement> reqs = sb.getAllReqs();
List<BundleWiring> bundleWirings = bh.resolve(reqs);
if (!reqs.isEmpty()) {
@ -98,19 +98,19 @@ public class BundleCompiler {
writer.printf(" %s\n", req.toString());
}
sbi.setSummary(
sb.setSummary(
String.format("%d missing @import%s:", reqs.size(), reqs.size() > 1 ? "s" : "",
reqs.stream().flatMap(
r -> OSGiUtils.extractPackages(r.toString()).stream()).distinct().collect(
Collectors.joining(","))));
}
else {
sbi.setSummary("");
sb.setSummary("");
}
// XXX add sources that will fail to call attention
List<ResourceFile> newSource = sbi.getNewSources();
List<ResourceFile> newSource = sb.getNewSources();
for (BundleRequirement req : reqs) {
newSource.addAll(sbi.req2file.get(req.toString()));
newSource.addAll(sb.req2file.get(req.toString()));
}
// send the capabilities to phidias
@ -142,7 +142,7 @@ public class BundleCompiler {
writer.write(err);
ResourceFileJavaFileObject sf = (ResourceFileJavaFileObject) d.getSource();
ResourceFile rf = sf.getFile();
sbi.buildError(rf, err); // remember all errors for this file
sb.buildError(rf, err); // remember all errors for this file
if (sourceFiles.remove(sf)) {
writer.printf("skipping %s\n", sf.toString());
// if it's a script, mark it for having compile errors
@ -162,9 +162,8 @@ public class BundleCompiler {
}
}
// buildErrors is now up to date, set status
if (sbi.getFailingSourcesCount() > 0) {
sbi.appendSummary(
String.format("%d failing source files", sbi.getFailingSourcesCount()));
if (sb.getFailingSourcesCount() > 0) {
sb.appendSummary(String.format("%d failing source files", sb.getFailingSourcesCount()));
}
ResourceFile smf = new ResourceFile(srcdir, "META-INF" + File.separator + "MANIFEST.MF");
@ -203,7 +202,7 @@ public class BundleCompiler {
manifest = analyzer.calcManifest();
}
catch (Exception e) {
sbi.appendSummary("bad manifest");
sb.appendSummary("bad manifest");
throw new OSGiException("failed to calculate manifest by analyzing code", e);
}
Attributes ma = manifest.getMainAttributes();
@ -219,22 +218,24 @@ public class BundleCompiler {
}
}
catch (Exception e) {
sbi.appendSummary("failed bnd analysis");
sb.appendSummary("failed bnd analysis");
throw new OSGiException("failed to query classes while searching for activator", e);
}
if (activator_classname == null) {
activator_classname = GENERATED_ACTIVATOR_CLASSNAME;
if (!buildDefaultActivator(bindir, activator_classname, writer)) {
sbi.appendSummary("failed to build generated activator");
sb.appendSummary("failed to build generated activator");
return;
}
// since we add the activator after bndtools built the imports, we should add its imports too
String imps = ma.getValue(Constants.IMPORT_PACKAGE);
if (imps == null) {
ma.putValue(Constants.IMPORT_PACKAGE, "ghidra.app.script.osgi");
ma.putValue(Constants.IMPORT_PACKAGE,
GhidraBundleActivator.class.getPackageName());
}
else {
ma.putValue(Constants.IMPORT_PACKAGE, imps + ",ghidra.app.script.osgi");
ma.putValue(Constants.IMPORT_PACKAGE,
imps + "," + GhidraBundleActivator.class.getPackageName());
}
}
ma.putValue(Constants.BUNDLE_ACTIVATOR, activator_classname);
@ -265,7 +266,7 @@ public class BundleCompiler {
try (PrintWriter out =
new PrintWriter(Files.newBufferedWriter(activator_dest, Charset.forName("UTF-8")))) {
out.println("import ghidra.app.script.osgi.GhidraBundleActivator;");
out.println("import " + GhidraBundleActivator.class.getName() + ";");
out.println("import org.osgi.framework.BundleActivator;");
out.println("import org.osgi.framework.BundleContext;");
out.println("public class " + GENERATED_ACTIVATOR_CLASSNAME +

View file

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.script.osgi;
package ghidra.app.plugin.core.osgi;
import static java.util.stream.Collectors.*;
@ -35,15 +35,13 @@ 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.app.script.GhidraScriptUtil;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.*;
// XXX this class should be part of a service/plugin
public class BundleHost {
// XXX embedded OSGi should be a service, but ScriptProviders don't have any way to access services
// XXX ScriptProviders don't have any way to access services or other way to access system wide resources
static private BundleHost _instance;
static public BundleHost getInstance() {
@ -69,16 +67,28 @@ public class BundleHost {
}
}
// XXX this should be remembered in bundlehosts's savestate
HashMap<ResourceFile, SourceBundleInfo> file2sbi = new HashMap<>();
// XXX this should be remembered by bundlehosts's savestate
HashMap<ResourceFile, GhidraBundle> file2sbi = new HashMap<>();
public SourceBundleInfo getSourceBundleInfo(ResourceFile sourceDir) {
return file2sbi.computeIfAbsent(sourceDir, sd -> new SourceBundleInfo(this, sd));
public GhidraBundle getGhidraBundle(ResourceFile path) {
return file2sbi.computeIfAbsent(path, this::createGhidraBundle);
}
public GhidraBundle createGhidraBundle(ResourceFile path) {
switch (GhidraBundle.getType(path)) {
case SourceDir:
return new GhidraSourceBundle(this, path);
case BndScript:
case Jar:
default:
break;
}
return null;
}
// XXX consumers must clean up after themselves
public void removeSourceBundleInfo(ResourceFile sourceDir) {
file2sbi.remove(sourceDir);
public void removeSourceBundleInfo(ResourceFile path) {
file2sbi.remove(path);
}
/**
@ -86,22 +96,17 @@ public class BundleHost {
*
* @param imports Import-Package value
* @return deduced requirements or null if there was an error
* @throws OSGiException on parse failure
* @throws BundleException on parse failure
*/
static List<BundleRequirement> parseImports(String imports) throws OSGiException {
static List<BundleRequirement> parseImports(String imports) throws BundleException {
// parse it with Felix's ManifestParser to a list of BundleRequirement objects
Map<String, Object> headerMap = new HashMap<>();
headerMap.put(Constants.IMPORT_PACKAGE, imports);
ManifestParser mp;
try {
mp = new ManifestParser(null, null, null, headerMap);
return mp.getRequirements();
}
catch (org.osgi.framework.BundleException e) {
throw new OSGiException("parsing Import-Package: " + imports, e);
}
}
/**
* cache of data corresponding to a source directory that is bound to be an exploded bundle
@ -415,6 +420,14 @@ public class BundleHost {
}
}
public void deactivateSynchronously(String bundleLoc)
throws GhidraBundleException, InterruptedException {
Bundle b = getBundle(bundleLoc);
if (b != null) {
deactivateSynchronously(b);
}
}
void forceStopFelix() {
Task t = new Task("killing felix", false, false, true, true) {
@Override
@ -568,76 +581,9 @@ public class BundleHost {
}
}
/**
* compile a source bundle if it's out of sync
*
* @param sbi the bundle info
* @param writer where to write issues
* @throws OSGiException if bundle operations fail
* @throws IOException if there are issues with the contents of the bundle
* @return the activated bundle
* @throws InterruptedException if interrupted while waiting for bundle state change
*/
public boolean compileSourceBundle(SourceBundleInfo sbi, PrintWriter writer)
throws OSGiException, IOException, InterruptedException {
if (writer == null) {
writer = new NullPrintWriter();
}
boolean needsCompile = false;
sbi.updateFromFilesystem(writer);
int failing = sbi.getFailingSourcesCount();
int newSourcecount = sbi.getNewSourcesCount();
long lastBundleActivation = 0; // XXX record last bundle activation in bundlestatusmodel
if (failing > 0 && (lastBundleActivation > sbi.getLastCompileAttempt())) {
needsCompile = true;
}
if (newSourcecount == 0) {
if (failing > 0) {
writer.printf("%s hasn't changed, with %d file%s failing in previous build(s):\n",
sbi.getSourceDir().toString(), failing, failing > 1 ? "s" : "");
writer.printf("%s\n", sbi.getPreviousBuildErrors());
}
if (sbi.newManifestFile()) {
needsCompile = true;
}
}
else {
needsCompile = true;
}
if (needsCompile) {
writer.printf("%d new files, %d skipped, %s\n", newSourcecount, failing,
sbi.newManifestFile() ? ", new manifest" : "");
// if there a bundle is currently active, uninstall it
Bundle b = sbi.getBundle();
if (b != null) {
deactivateSynchronously(b);
}
// once we've committed to recompile and regenerate generated classes, delete the old stuff
sbi.deleteOldBinaries();
BundleCompiler bundleCompiler = new BundleCompiler(this);
long startTime = System.nanoTime();
bundleCompiler.compileToExplodedBundle(sbi, writer);
long endTime = System.nanoTime();
writer.printf("%3.2f seconds compile time.\n", (endTime - startTime) / 1e9);
fireSourceBundleCompiled(sbi);
return true;
}
return false;
}
List<OSGiListener> osgiListeners = new ArrayList<>();
void fireSourceBundleCompiled(SourceBundleInfo sbi) {
void fireSourceBundleCompiled(GhidraSourceBundle sbi) {
synchronized (osgiListeners) {
for (OSGiListener l : osgiListeners) {
l.sourceBundleCompiled(sbi);

View file

@ -13,67 +13,33 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.script.osgi;
import java.io.File;
package ghidra.app.plugin.core.osgi;
import generic.jar.ResourceFile;
import generic.util.Path;
/**
* The BundlePath class represents the runtime state and user preferences for OSGi bundles in Ghidra.
* The BundleStatus class represents the runtime state and user preferences for OSGi bundles in Ghidra.
*/
public class BundlePath extends Path {
final Type type;
public class BundleStatus extends Path {
final GhidraBundle.Type type;
boolean active = false;
boolean busy = false;
String summary;
public static enum Type {
BndScript, Jar, SourceDir, INVALID
}
static public Type getType(File f) {
if (f.isDirectory()) {
return Type.SourceDir;
}
String n = f.getName().toLowerCase();
if (n.endsWith(".bnd")) {
return Type.BndScript;
}
if (n.endsWith(".jar")) {
return Type.Jar;
}
return Type.INVALID;
}
static public Type getType(ResourceFile rf) {
if (rf.isDirectory()) {
return Type.SourceDir;
}
String n = rf.getName().toLowerCase();
if (n.endsWith(".bnd")) {
return Type.BndScript;
}
if (n.endsWith(".jar")) {
return Type.Jar;
}
return Type.INVALID;
}
public Type getType() {
public GhidraBundle.Type getType() {
return type;
}
BundlePath(String path, boolean enabled, boolean readonly) {
BundleStatus(String path, boolean enabled, boolean readonly) {
super(path, enabled, false /*editable */, readonly);
type = getType(getPath());
type = GhidraBundle.getType(getPath());
}
BundlePath(ResourceFile path, boolean enabled, boolean readonly) {
BundleStatus(ResourceFile path, boolean enabled, boolean readonly) {
super(path, enabled, false /* editable */, readonly);
type = getType(getPath());
type = GhidraBundle.getType(getPath());
}
public boolean isDirectory() {
@ -109,4 +75,8 @@ public class BundlePath extends Path {
return summary;
}
public boolean pathExists() {
return exists();
}
}

View file

@ -14,16 +14,23 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.script.osgi;
package ghidra.app.plugin.core.osgi;
public interface BundleStatusListener {
/**
* Called when the list of bundle paths changes
*/
public void bundlesChanged();
default public void bundlesChanged() {
//
}
public void bundleEnablementChanged(BundlePath path, boolean newValue);
public void bundleActivationChanged(BundlePath path, boolean newValue);
default public void bundleEnablementChanged(BundleStatus status, boolean newValue) {
//
}
default public void bundleActivationChanged(BundleStatus status, boolean newValue) {
//
}
}

View file

@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.script.osgi;
package ghidra.app.plugin.core.osgi;
import java.io.File;
import java.util.*;
@ -25,12 +25,11 @@ import org.osgi.framework.Bundle;
import docking.widgets.table.AbstractSortedTableModel;
import generic.jar.ResourceFile;
import ghidra.app.script.GhidraScriptUtil;
import ghidra.app.script.osgi.*;
import ghidra.framework.options.SaveState;
import ghidra.framework.preferences.Preferences;
import ghidra.util.Msg;
public class BundleStatusModel extends AbstractSortedTableModel<BundlePath> {
public class BundleStatusModel extends AbstractSortedTableModel<BundleStatus> {
List<Column> columns = new ArrayList<>();
class Column {
@ -45,15 +44,15 @@ public class BundleStatusModel extends AbstractSortedTableModel<BundlePath> {
this.clazz = clazz;
}
boolean editable(BundlePath path) {
boolean editable(BundleStatus status) {
return false;
}
Object getValue(BundlePath path) {
Object getValue(BundleStatus status) {
return null;
}
void setValue(BundlePath path, Object aValue) {
void setValue(BundleStatus status, Object aValue) {
throw new RuntimeException(name + " is not editable!");
}
@ -61,55 +60,55 @@ public class BundleStatusModel extends AbstractSortedTableModel<BundlePath> {
Column enabledColumn = new Column("Enabled", Boolean.class) {
@Override
boolean editable(BundlePath path) {
return path.exists();
boolean editable(BundleStatus status) {
return status.pathExists();
}
@Override
Object getValue(BundlePath path) {
return path.isEnabled();
Object getValue(BundleStatus status) {
return status.isEnabled();
}
@Override
void setValue(BundlePath path, Object newValue) {
path.setEnabled((Boolean) newValue);
fireBundleEnablementChanged(path, (Boolean) newValue);
void setValue(BundleStatus status, Object newValue) {
status.setEnabled((Boolean) newValue);
fireBundleEnablementChanged(status, (Boolean) newValue);
}
};
Column activeColumn = new Column("Active", Boolean.class) {
@Override
boolean editable(BundlePath path) {
return path.exists(); // XXX maybe only if it's already enabled
boolean editable(BundleStatus status) {
return status.pathExists(); // XXX maybe only if it's already enabled
}
@Override
Object getValue(BundlePath path) {
return path.isActive();
Object getValue(BundleStatus status) {
return status.isActive();
}
@Override
void setValue(BundlePath path, Object newValue) {
path.setActive((Boolean) newValue);
fireBundleActivationChanged(path, (Boolean) newValue);
void setValue(BundleStatus status, Object newValue) {
status.setActive((Boolean) newValue);
fireBundleActivationChanged(status, (Boolean) newValue);
}
};
Column typeColumn = new Column("Type", String.class) {
@Override
Object getValue(BundlePath path) {
return path.getType().toString();
Object getValue(BundleStatus status) {
return status.getType().toString();
}
};
Column pathColumn = new Column("Path", BundlePath.class) {
Column pathColumn = new Column("Path", String.class) {
@Override
Object getValue(BundlePath path) {
return path;
Object getValue(BundleStatus status) {
return status.getPath().toString();
}
};
Column summaryColumn = new Column("Summary", BundlePath.class) {
Column summaryColumn = new Column("Summary", String.class) {
@Override
Object getValue(BundlePath path) {
return path.getSummary();
Object getValue(BundleStatus status) {
return status.getSummary();
}
};
@ -127,41 +126,29 @@ public class BundleStatusModel extends AbstractSortedTableModel<BundlePath> {
}
private BundleStatusProvider provider;
private List<BundlePath> paths;
private List<BundleStatus> statuses;
private BundleHost bundleHost;
OSGiListener bundleListener;
private Map<String, BundlePath> loc2bp = new HashMap<>();
private Map<String, BundleStatus> loc2status = new HashMap<>();
BundlePath getPath(String bundleLocation) {
return loc2bp.get(bundleLocation);
BundleStatus getStatus(String bundleLocation) {
return loc2status.get(bundleLocation);
}
public String getBundleLoc(BundlePath bp) {
switch (bp.getType()) {
case Jar:
return bp.getPath().getAbsolutePath();
case SourceDir:
SourceBundleInfo bi = bundleHost.getSourceBundleInfo(bp.getPath());
return bi.getBundleLoc();
case BndScript:
// XXX
case INVALID:
default:
break;
}
return null;
public String getBundleLoc(BundleStatus status) {
return bundleHost.getGhidraBundle(status.getPath()).getBundleLoc();
}
/**
* (re)compute cached mapping from bundleloc to bundlepath
*/
private void computeCache() {
loc2bp.clear();
for (BundlePath bp : paths) {
String loc = getBundleLoc(bp);
loc2status.clear();
for (BundleStatus status : statuses) {
String loc = getBundleLoc(status);
if (loc != null) {
loc2bp.put(loc, bp);
loc2status.put(loc, status);
}
}
}
@ -172,18 +159,20 @@ public class BundleStatusModel extends AbstractSortedTableModel<BundlePath> {
this.bundleHost = bundleHost;
// add unmodifiable paths
this.paths = GhidraScriptUtil.getSystemScriptPaths().stream().distinct().map(
f -> new BundlePath(f, true, true)).collect(Collectors.toList());
this.statuses = GhidraScriptUtil.getSystemScriptPaths().stream().distinct().map(
f -> new BundleStatus(f, true, true)).collect(Collectors.toList());
// add user path
this.paths.add(0, new BundlePath(GhidraScriptUtil.getUserScriptDirectory(), true, false));
this.statuses.add(0,
new BundleStatus(GhidraScriptUtil.getUserScriptDirectory(), true, false));
computeCache();
bundleHost.addListener(bundleListener = new OSGiListener() {
@Override
public void sourceBundleCompiled(SourceBundleInfo sbi) {
BundlePath bp = getPath(sbi.getBundleLoc());
public void sourceBundleCompiled(GhidraSourceBundle sb) {
BundleStatus bp = getStatus(sb.getBundleLoc());
if (bp != null) {
bp.setSummary(sbi.getSummary());
bp.setSummary(sb.getSummary());
int row = getRowIndex(bp);
fireTableRowsUpdated(row, row);
}
@ -191,7 +180,7 @@ public class BundleStatusModel extends AbstractSortedTableModel<BundlePath> {
@Override
public void bundleActivationChange(Bundle b, boolean newActivation) {
BundlePath bp = getPath(b.getLocation());
BundleStatus bp = getStatus(b.getLocation());
if (newActivation) {
if (bp != null) {
bp.setActive(true);
@ -219,71 +208,70 @@ public class BundleStatusModel extends AbstractSortedTableModel<BundlePath> {
bundleHost.removeListener(bundleListener);
}
void clear() {
paths.clear();
}
List<BundlePath> getAllPaths() {
return new ArrayList<BundlePath>(paths);
}
public List<ResourceFile> getPaths() {
public List<ResourceFile> getEnabledPaths() {
List<ResourceFile> list = new ArrayList<>();
for (BundlePath path : paths) {
if (path.isEnabled()) {
list.add(path.getPath());
for (BundleStatus status : statuses) {
if (status.isEnabled()) {
list.add(status.getPath());
}
}
return list;
}
private void addPath(BundlePath path) {
if (paths.contains(path)) {
private void addStatus(BundleStatus path) {
if (statuses.contains(path)) {
return;
}
String loc = getBundleLoc(path);
if (loc != null) {
loc2bp.put(loc, path);
loc2status.put(loc, path);
}
int index = paths.size();
paths.add(path);
int index = statuses.size();
statuses.add(path);
fireTableRowsInserted(index, index);
}
private BundlePath addNewPath(ResourceFile path, boolean enabled, boolean readonly) {
BundlePath p = new BundlePath(path, enabled, readonly);
addPath(p);
private BundleStatus addNewStatus(ResourceFile path, boolean enabled, boolean readonly) {
BundleStatus p = new BundleStatus(path, enabled, readonly);
addStatus(p);
return p;
}
private BundlePath addNewPath(String path, boolean enabled, boolean readonly) {
BundlePath p = new BundlePath(path, enabled, readonly);
addPath(p);
private BundleStatus addNewStatus(String path, boolean enabled, boolean readonly) {
BundleStatus p = new BundleStatus(path, enabled, readonly);
addStatus(p);
return p;
}
/**
* create new BundleStatus objects for each of the given files
*
* @param files the files.. given...
* @param enabled mark them all as enabled
* @param readonly mark them all as readonly
*/
void addNewPaths(List<File> files, boolean enabled, boolean readonly) {
for (File f : files) {
BundlePath p = new BundlePath(new ResourceFile(f), enabled, readonly);
addPath(p);
BundleStatus status = new BundleStatus(new ResourceFile(f), enabled, readonly);
addStatus(status);
}
fireBundlesChanged();
}
void remove(int[] selectedRows) {
List<BundlePath> list = new ArrayList<>();
List<BundleStatus> toRemove = new ArrayList<>();
for (int selectedRow : selectedRows) {
list.add(paths.get(selectedRow));
toRemove.add(statuses.get(selectedRow));
}
for (BundlePath path : list) {
if (!path.isReadOnly()) {
paths.remove(path);
loc2bp.remove(getBundleLoc(path));
for (BundleStatus status : toRemove) {
if (!status.isReadOnly()) {
statuses.remove(status);
loc2status.remove(getBundleLoc(status));
}
else {
Msg.showInfo(this, this.provider.getComponent(), "Unabled to remove path",
"System path cannot be removed: " + path.toString());
"System path cannot be removed: " + status.toString());
}
}
fireTableDataChanged();
@ -299,7 +287,7 @@ public class BundleStatusModel extends AbstractSortedTableModel<BundlePath> {
@Override
public int getRowCount() {
return paths.size();
return statuses.size();
}
@Override
@ -309,8 +297,8 @@ public class BundleStatusModel extends AbstractSortedTableModel<BundlePath> {
@Override
public boolean isCellEditable(int rowIndex, int columnIndex) {
BundlePath path = paths.get(rowIndex);
return getColumn(columnIndex).editable(path);
BundleStatus status = statuses.get(rowIndex);
return getColumn(columnIndex).editable(status);
}
@Override
@ -320,15 +308,15 @@ public class BundleStatusModel extends AbstractSortedTableModel<BundlePath> {
@Override
public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
BundlePath path = paths.get(rowIndex);
getColumn(columnIndex).setValue(path, aValue);
// XXX: preclude RowObjectSelectionManager.repairSelection
BundleStatus status = statuses.get(rowIndex);
getColumn(columnIndex).setValue(status, aValue);
// XXX: avoid RowObjectSelectionManager.repairSelection by selecting the row we're editing
provider.selectRow(rowIndex);
}
@Override
public Object getColumnValueForRow(BundlePath path, int columnIndex) {
return getColumn(columnIndex).getValue(path);
public Object getColumnValueForRow(BundleStatus status, int columnIndex) {
return getColumn(columnIndex).getValue(status);
}
@Override
@ -338,12 +326,12 @@ public class BundleStatusModel extends AbstractSortedTableModel<BundlePath> {
@Override
public String getName() {
return "BundlePathManagerModel";
return BundleStatusModel.class.getSimpleName();
}
@Override
public List<BundlePath> getModelData() {
return paths;
public List<BundleStatus> getModelData() {
return statuses;
}
/**
@ -353,7 +341,7 @@ public class BundleStatusModel extends AbstractSortedTableModel<BundlePath> {
*/
public boolean enablePath(ResourceFile file) {
ResourceFile dir = file.isDirectory() ? file : file.getParentFile();
for (BundlePath path : getAllPaths()) {
for (BundleStatus path : statuses) {
if (path.getPath().equals(dir)) {
if (!path.isEnabled()) {
path.setEnabled(true);
@ -364,7 +352,7 @@ public class BundleStatusModel extends AbstractSortedTableModel<BundlePath> {
return false;
}
}
addNewPath(dir, true, false);
addNewStatus(dir, true, false);
Preferences.setProperty(BundleStatusProvider.preferenceForLastSelectedBundle,
dir.getAbsolutePath());
fireBundlesChanged();
@ -377,8 +365,8 @@ public class BundleStatusModel extends AbstractSortedTableModel<BundlePath> {
* @return true if the bundle is managed and not marked readonly
*/
public boolean isWriteable(ResourceFile bundle) {
Optional<BundlePath> o = paths.stream().filter(
bp -> bp.isDirectory() && bp.getPath().equals(bundle)).findFirst();
Optional<BundleStatus> o = statuses.stream().filter(
status -> status.isDirectory() && status.getPath().equals(bundle)).findFirst();
return o.isPresent() && !o.get().isReadOnly();
}
@ -387,10 +375,10 @@ public class BundleStatusModel extends AbstractSortedTableModel<BundlePath> {
*
* each path is marked editable and non-readonly
*
* @param testingPaths the paths to use
* @param testingPaths the statuses to use
*/
public void setPathsForTesting(List<String> testingPaths) {
this.paths = testingPaths.stream().map(f -> new BundlePath(f, true, false)).collect(
this.statuses = testingPaths.stream().map(f -> new BundleStatus(f, true, false)).collect(
Collectors.toList());
computeCache();
fireTableDataChanged();
@ -403,7 +391,7 @@ public class BundleStatusModel extends AbstractSortedTableModel<BundlePath> {
* @param path the path to insert
*/
public void insertPathForTesting(String path) {
addNewPath(path, true, false);
addNewStatus(path, true, false);
}
private ArrayList<BundleStatusListener> listeners = new ArrayList<>();
@ -430,7 +418,7 @@ public class BundleStatusModel extends AbstractSortedTableModel<BundlePath> {
}
}
void fireBundleEnablementChanged(BundlePath path, boolean newValue) {
void fireBundleEnablementChanged(BundleStatus path, boolean newValue) {
synchronized (listeners) {
for (BundleStatusListener listener : listeners) {
listener.bundleEnablementChanged(path, newValue);
@ -438,7 +426,7 @@ public class BundleStatusModel extends AbstractSortedTableModel<BundlePath> {
}
}
void fireBundleActivationChanged(BundlePath path, boolean newValue) {
void fireBundleActivationChanged(BundleStatus path, boolean newValue) {
synchronized (listeners) {
for (BundleStatusListener listener : listeners) {
listener.bundleActivationChanged(path, newValue);
@ -447,7 +435,7 @@ public class BundleStatusModel extends AbstractSortedTableModel<BundlePath> {
}
/**
* Restores the paths from the specified SaveState object.
* Restores the statuses from the specified SaveState object.
* @param ss the SaveState object
*/
public void restoreState(SaveState ss) {
@ -460,49 +448,47 @@ public class BundleStatusModel extends AbstractSortedTableModel<BundlePath> {
boolean[] enableArr = ss.getBooleans("BundleStatus_ENABLE", new boolean[pathArr.length]);
boolean[] readonlyArr = ss.getBooleans("BundleStatus_READ", new boolean[pathArr.length]);
List<BundlePath> currentPaths = getAllPaths();
clear();
List<BundleStatus> currentPaths = new ArrayList<>(statuses);
statuses.clear();
for (int i = 0; i < pathArr.length; i++) {
BundlePath currentPath = getPath(pathArr[i], currentPaths);
if (currentPath != null) {
currentPaths.remove(currentPath);
addNewPath(pathArr[i], enableArr[i], readonlyArr[i]);
BundleStatus currentStatus = getStatus(pathArr[i], currentPaths);
if (currentStatus != null) {
currentPaths.remove(currentStatus);
addNewStatus(pathArr[i], enableArr[i], readonlyArr[i]);
}
else if (!readonlyArr[i]) {
// skip read-only paths which are not present in the current config
// skip read-only statuses which are not present in the current config
// This is needed to thin-out old default entries
addNewPath(pathArr[i], enableArr[i], readonlyArr[i]);
addNewStatus(pathArr[i], enableArr[i], readonlyArr[i]);
}
}
fireBundlesChanged();
}
private static BundlePath getPath(String filepath, List<BundlePath> paths) {
for (BundlePath path : paths) {
if (filepath.equals(path.getPathAsString())) {
return path;
private static BundleStatus getStatus(String filepath, List<BundleStatus> statuses) {
for (BundleStatus status : statuses) {
if (filepath.equals(status.getPathAsString())) {
return status;
}
}
return null;
}
/**
* Saves the paths to the specified SaveState object.
* Saves the statuses to the specified SaveState object.
* @param ss the SaveState object
*/
public void saveState(SaveState ss) {
List<BundlePath> currentPaths = getAllPaths();
String[] pathArr = new String[currentPaths.size()];
boolean[] enableArr = new boolean[currentPaths.size()];
boolean[] readonlyArr = new boolean[currentPaths.size()];
String[] pathArr = new String[statuses.size()];
boolean[] enableArr = new boolean[statuses.size()];
boolean[] readonlyArr = new boolean[statuses.size()];
int index = 0;
for (BundlePath path : currentPaths) {
pathArr[index] = path.getPathAsString();
enableArr[index] = path.isEnabled();
readonlyArr[index] = path.isReadOnly();
for (BundleStatus status : statuses) {
pathArr[index] = status.getPathAsString();
enableArr[index] = status.isEnabled();
readonlyArr[index] = status.isReadOnly();
++index;
}

View file

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.script.osgi;
package ghidra.app.plugin.core.osgi;
import java.awt.*;
import java.io.File;
@ -27,12 +27,15 @@ import javax.swing.table.TableColumn;
import docking.widgets.filechooser.GhidraFileChooser;
import docking.widgets.filechooser.GhidraFileChooserMode;
import docking.widgets.table.*;
import ghidra.app.script.osgi.BundleHost;
import ghidra.app.services.ConsoleService;
import ghidra.framework.plugintool.ComponentProviderAdapter;
import ghidra.framework.plugintool.PluginTool;
import ghidra.framework.preferences.Preferences;
import ghidra.util.Msg;
import ghidra.util.exception.CancelledException;
import ghidra.util.filechooser.GhidraFileChooserModel;
import ghidra.util.filechooser.GhidraFileFilter;
import ghidra.util.task.*;
import resources.ResourceManager;
/**
@ -49,14 +52,29 @@ public class BundleStatusProvider extends ComponentProviderAdapter {
private Color selectionColor;
private GhidraFileChooser fileChooser;
private GhidraFileFilter filter;
private final BundleHost bundleHost;
public void notifyTableChanged() {
bundleStatusTable.notifyTableChanged(new TableModelEvent(bundleStatusModel));
}
public BundleStatusProvider(PluginTool tool, String owner, BundleHost bundleHost) {
public BundleStatusProvider(PluginTool tool, String owner) {
super(tool, "Bundle Status Manager", owner);
this.bundleHost = BundleHost.getInstance();
this.bundleStatusModel = new BundleStatusModel(this, bundleHost);
bundleStatusModel.addListener(new BundleStatusListener() {
@Override
public void bundleEnablementChanged(BundleStatus status, boolean enabled) {
if (!enabled && status.isActive()) {
startActivateDeactiveTask(status, false);
}
}
@Override
public void bundleActivationChanged(BundleStatus status, boolean newValue) {
startActivateDeactiveTask(status, newValue);
}
});
this.filter = new GhidraFileFilter() {
@Override
@ -66,7 +84,7 @@ public class BundleStatusProvider extends ComponentProviderAdapter {
@Override
public boolean accept(File path, GhidraFileChooserModel model) {
return BundlePath.getType(path) != BundlePath.Type.INVALID;
return GhidraBundle.getType(path) != GhidraBundle.Type.INVALID;
}
};
this.fileChooser = null;
@ -108,7 +126,7 @@ public class BundleStatusProvider extends ComponentProviderAdapter {
buttonPanel.add(removeButton, gbc);
bundleStatusTable = new GTable(bundleStatusModel);
bundleStatusTable.setName("BUNDLEPATH_TABLE");
bundleStatusTable.setName("BUNDLESTATUS_TABLE");
bundleStatusTable.setSelectionBackground(selectionColor);
bundleStatusTable.setSelectionForeground(Color.BLACK);
bundleStatusTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
@ -133,9 +151,9 @@ public class BundleStatusProvider extends ComponentProviderAdapter {
column.setCellRenderer(new GBooleanCellRenderer() {
@Override
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
BundlePath path = (BundlePath) data.getRowObject();
BundleStatus status = (BundleStatus) data.getRowObject();
Component x = super.getTableCellRendererComponent(data);
if (path.getBusy()) {
if (status.getBusy()) {
cb.setVisible(false);
cb.setEnabled(false);
setHorizontalAlignment(SwingConstants.CENTER);
@ -156,7 +174,7 @@ public class BundleStatusProvider extends ComponentProviderAdapter {
FontMetrics fontmetrics = panel.getFontMetrics(panel.getFont());
column.setMaxWidth(10 +
SwingUtilities.computeStringWidth(fontmetrics, BundlePath.Type.SourceDir.toString()));
SwingUtilities.computeStringWidth(fontmetrics, GhidraBundle.Type.SourceDir.toString()));
column = bundleStatusTable.getColumnModel().getColumn(bundleStatusModel.pathColumn.index);
column.setCellRenderer(new GTableCellRenderer() {
@ -164,15 +182,15 @@ public class BundleStatusProvider extends ComponentProviderAdapter {
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
JLabel c = (JLabel) super.getTableCellRendererComponent(data);
BundlePath path = (BundlePath) data.getValue();
if (!path.exists()) {
BundleStatus status = (BundleStatus) data.getValue();
if (!status.pathExists()) {
c.setForeground(Color.RED);
}
return c;
}
});
GTableFilterPanel<BundlePath> filterPanel =
GTableFilterPanel<BundleStatus> filterPanel =
new GTableFilterPanel<>(bundleStatusTable, bundleStatusModel);
JScrollPane scrollPane = new JScrollPane(bundleStatusTable);
@ -272,6 +290,39 @@ public class BundleStatusProvider extends ComponentProviderAdapter {
bundleStatusTable.dispose();
}
private void startActivateDeactiveTask(BundleStatus status, boolean activate) {
status.setBusy(true);
notifyTableChanged();
ConsoleService console = getTool().getService(ConsoleService.class);
new TaskLauncher(new Task((activate ? "Activating" : "Deactivating ") + " bundle...") {
@Override
public void run(TaskMonitor monitor) throws CancelledException {
try {
GhidraBundle sb = bundleHost.getGhidraBundle(status.getPath());
if (activate) {
sb.build(console.getStdErr());
bundleHost.activateSynchronously(sb.getBundleLoc());
}
else { // deactivate
bundleHost.deactivateSynchronously(sb.getBundleLoc());
}
}
catch (Exception e) {
e.printStackTrace(console.getStdErr());
status.setActive(!activate);
Msg.showError(this, getComponent(), "bundle activation failed", e.getMessage());
}
finally {
status.setBusy(false);
notifyTableChanged();
}
}
}, null, 1000);
}
// XXX workaround for RowObjectSelection.. repair
void selectRow(int rowIndex) {
bundleStatusTable.selectRow(rowIndex);
}

View file

@ -0,0 +1,61 @@
package ghidra.app.plugin.core.osgi;
import java.io.File;
import java.io.PrintWriter;
import org.osgi.framework.Bundle;
import generic.jar.ResourceFile;
public interface GhidraBundle {
enum Type {
BndScript, Jar, SourceDir, INVALID
}
/**
* attempt to build loadable bundle, if possible
*
* @param writer console for user messages
* @return true if build was successful
* @throws Exception XXX
*/
boolean build(PrintWriter writer) throws Exception;
String getBundleLoc();
Bundle getBundle() throws GhidraBundleException;
Bundle install() throws GhidraBundleException;
String getSummary();
static GhidraBundle.Type getType(ResourceFile rf) {
if (rf.isDirectory()) {
return GhidraBundle.Type.SourceDir;
}
String n = rf.getName().toLowerCase();
if (n.endsWith(".bnd")) {
return GhidraBundle.Type.BndScript;
}
if (n.endsWith(".jar")) {
return GhidraBundle.Type.Jar;
}
return GhidraBundle.Type.INVALID;
}
static public GhidraBundle.Type getType(File f) {
if (f.isDirectory()) {
return GhidraBundle.Type.SourceDir;
}
String n = f.getName().toLowerCase();
if (n.endsWith(".bnd")) {
return GhidraBundle.Type.BndScript;
}
if (n.endsWith(".jar")) {
return GhidraBundle.Type.Jar;
}
return GhidraBundle.Type.INVALID;
}
}

View file

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.script.osgi;
package ghidra.app.plugin.core.osgi;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;

View file

@ -14,7 +14,7 @@
* limitations under the License.
*/
package ghidra.app.script.osgi;
package ghidra.app.plugin.core.osgi;
import java.util.stream.Collectors;
@ -51,9 +51,9 @@ public class GhidraBundleException extends OSGiException {
BundleException be = (BundleException) e;
switch (be.getType()) {
default:
case BundleException.UNSPECIFIED:
return "No exception type";
case BundleException.UNSPECIFIED:
return "UNSPECIFIED";
/**
* The operation was unsupported. This type can be used anywhere a
* BundleException can be thrown.

View file

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.script.osgi;
package ghidra.app.plugin.core.osgi;
import java.io.*;
import java.nio.file.Files;
@ -22,20 +22,23 @@ import java.util.*;
import java.util.stream.Collectors;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleException;
import org.osgi.framework.wiring.BundleRequirement;
import generic.io.NullPrintWriter;
import generic.jar.ResourceFile;
import ghidra.app.plugin.core.osgi.BundleHost.BuildFailure;
import ghidra.app.script.GhidraScriptUtil;
import ghidra.app.script.ScriptInfo;
import ghidra.app.script.osgi.BundleHost.BuildFailure;
/**
* The SourceBundleInfo class is a cache of information for bundles built from source directories.
*/
public class SourceBundleInfo {
private final BundleHost bundleHost;
public class GhidraSourceBundle implements GhidraBundle {
final private BundleHost bundleHost;
final private ResourceFile sourceDir;
final String symbolicName;
final private String symbolicName;
final private Path binDir;
final private String bundleLoc;
boolean foundNewManifest;
@ -51,9 +54,9 @@ public class SourceBundleInfo {
// cached values parsed form @imports tags on default-package source files
public SourceBundleInfo(BundleHost bundleHost, ResourceFile sourceDir) {
public GhidraSourceBundle(BundleHost bundleHost, ResourceFile sourceDirectory) {
this.bundleHost = bundleHost;
this.sourceDir = sourceDir;
this.sourceDir = sourceDirectory;
this.symbolicName = BundleHost.getSymbolicNameFromSourceDir(sourceDir);
this.binDir = GhidraScriptUtil.getCompiledBundlesDir().resolve(symbolicName);
@ -61,6 +64,12 @@ public class SourceBundleInfo {
}
/**
* for testing only!!!
*
* @param sourceFile a ghidra script file
* @return the directory its class is compiled to
*/
static public Path getBindirFromScriptFile(ResourceFile sourceFile) {
ResourceFile tmpSourceDir = sourceFile.getParentFile();
String tmpSymbolicName = BundleHost.getSymbolicNameFromSourceDir(tmpSourceDir);
@ -88,6 +97,7 @@ public class SourceBundleInfo {
return binDir;
}
@Override
public Bundle install() throws GhidraBundleException {
return bundleHost.installFromLoc(getBundleLoc());
}
@ -105,10 +115,9 @@ public class SourceBundleInfo {
/**
* update buildReqs based on \@imports tag in java files from the default package
*
* @throws OSGiException on failure to parse the \@imports tag
* @throws GhidraBundleException on failure to parse the \@imports tag
*/
private void computeRequirements() throws OSGiException {
private void computeRequirements() throws GhidraBundleException {
// parse metadata from all Java source in sourceDir
buildReqs.clear();
req2file.clear();
@ -119,7 +128,13 @@ public class SourceBundleInfo {
ScriptInfo si = GhidraScriptUtil.getScriptInfo(rf);
String imps = si.getImports();
if (imps != null && !imps.isEmpty()) {
List<BundleRequirement> reqs = BundleHost.parseImports(imps);
List<BundleRequirement> reqs;
try {
reqs = BundleHost.parseImports(imps);
}
catch (BundleException e) {
throw new GhidraBundleException(getBundleLoc(), "parsing manifest", e);
}
buildReqs.put(rf, reqs);
for (BundleRequirement req : reqs) {
req2file.computeIfAbsent(req.toString(), x -> new ArrayList<>()).add(rf);
@ -235,16 +250,75 @@ public class SourceBundleInfo {
}
}
@Override
public String getSummary() {
return summary;
}
public Bundle getBundle() {
@Override
public Bundle getBundle() throws GhidraBundleException {
return bundleHost.getBundle(getBundleLoc());
}
@Override
public String getBundleLoc() {
return bundleLoc;
}
@Override
public boolean build(PrintWriter writer) throws Exception {
if (writer == null) {
writer = new NullPrintWriter();
}
boolean needsCompile = false;
updateFromFilesystem(writer);
int failing = getFailingSourcesCount();
int newSourcecount = getNewSourcesCount();
long lastBundleActivation = 0; // XXX record last bundle activation in bundlestatusmodel
if (failing > 0 && (lastBundleActivation > getLastCompileAttempt())) {
needsCompile = true;
}
if (newSourcecount == 0) {
if (failing > 0) {
writer.printf("%s hasn't changed, with %d file%s failing in previous build(s):\n",
getSourceDir().toString(), failing, failing > 1 ? "s" : "");
writer.printf("%s\n", getPreviousBuildErrors());
}
if (newManifestFile()) {
needsCompile = true;
}
}
else {
needsCompile = true;
}
if (needsCompile) {
writer.printf("%d new files, %d skipped, %s\n", newSourcecount, failing,
newManifestFile() ? ", new manifest" : "");
// if there a bundle is currently active, uninstall it
Bundle b = getBundle();
if (b != null) {
bundleHost.deactivateSynchronously(b);
}
// once we've committed to recompile and regenerate generated classes, delete the old stuff
deleteOldBinaries();
BundleCompiler bundleCompiler = new BundleCompiler(bundleHost);
long startTime = System.nanoTime();
bundleCompiler.compileToExplodedBundle(this, writer);
long endTime = System.nanoTime();
writer.printf("%3.2f seconds compile time.\n", (endTime - startTime) / 1e9);
bundleHost.fireSourceBundleCompiled(this);
return true;
}
return false;
}
}

View file

@ -13,10 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.script.osgi;
package ghidra.app.plugin.core.osgi;
import ghidra.util.exception.UsrException;
public class OSGiException extends UsrException {
public OSGiException(String msg, Throwable cause) {
super(msg, cause);

View file

@ -1,10 +1,10 @@
package ghidra.app.script.osgi;
package ghidra.app.plugin.core.osgi;
import org.osgi.framework.Bundle;
public interface OSGiListener {
void sourceBundleCompiled(SourceBundleInfo sbi);
void sourceBundleCompiled(GhidraSourceBundle sb);
void bundleActivationChange(Bundle b, boolean newActivation);
}

View file

@ -14,7 +14,7 @@
* limitations under the License.
*/
package ghidra.app.script.osgi;
package ghidra.app.plugin.core.osgi;
import java.util.List;
import java.util.Scanner;

View file

@ -29,7 +29,6 @@ import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
import org.apache.commons.lang3.StringUtils;
import org.osgi.framework.Bundle;
import docking.ActionContext;
import docking.action.KeyBindingData;
@ -40,10 +39,8 @@ import docking.widgets.tree.GTree;
import docking.widgets.tree.GTreeNode;
import docking.widgets.tree.support.BreadthFirstIterator;
import generic.jar.ResourceFile;
import ghidra.app.plugin.core.script.osgi.*;
import ghidra.app.plugin.core.osgi.*;
import ghidra.app.script.*;
import ghidra.app.script.osgi.BundleHost;
import ghidra.app.script.osgi.SourceBundleInfo;
import ghidra.app.services.ConsoleService;
import ghidra.framework.options.SaveState;
import ghidra.framework.plugintool.ComponentProviderAdapter;
@ -99,10 +96,9 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
}
};
GhidraScriptComponentProvider(GhidraScriptMgrPlugin plugin, BundleHost bundleHost) {
GhidraScriptComponentProvider(GhidraScriptMgrPlugin plugin) {
super(plugin.getTool(), "Script Manager", plugin.getName());
this.plugin = plugin;
this.bundleHost = bundleHost;
setHelpLocation(new HelpLocation(plugin.getName(), plugin.getName()));
setIcon(ResourceManager.loadImage("images/play.png"));
@ -116,7 +112,6 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
}
void dispose() {
editorMap.clear();
scriptCategoryTree.dispose();
scriptTable.dispose();
@ -138,7 +133,7 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
}
private void performRefresh() {
GhidraScriptUtil.setScriptBundlePaths(bundleStatusProvider.getModel().getPaths());
GhidraScriptUtil.setScriptBundlePaths(bundleStatusProvider.getModel().getEnabledPaths());
GhidraScriptUtil.clearMetadata();
refresh();
}
@ -341,7 +336,7 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
}
public List<ResourceFile> getScriptDirectories() {
return bundleStatusProvider.getModel().getPaths().stream().filter(
return bundleStatusProvider.getModel().getEnabledPaths().stream().filter(
ResourceFile::isDirectory).collect(Collectors.toList());
}
@ -397,7 +392,7 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
}
void runScript(String scriptName, TaskListener listener) {
List<ResourceFile> dirPaths = bundleStatusProvider.getModel().getPaths();
List<ResourceFile> dirPaths = bundleStatusProvider.getModel().getEnabledPaths();
for (ResourceFile dir : dirPaths) {
ResourceFile scriptSource = new ResourceFile(dir, scriptName);
if (scriptSource.exists()) {
@ -457,7 +452,8 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
console.addErrorMessage("", "Unable to instantiate script: " + scriptName);
}
catch (ClassNotFoundException e) {
console.addErrorMessage("", "Unable to locate script class: " + e.getMessage());
console.addErrorMessage("", "Unable to locate script class: " + scriptName);
e.printStackTrace();
}
// show the error icon
@ -537,7 +533,7 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
private void updateAvailableScriptFilesForAllPaths() {
List<ResourceFile> scriptsToRemove = tableModel.getScripts();
List<ResourceFile> scriptAccumulator = new ArrayList<>();
List<ResourceFile> bundlePaths = bundleStatusProvider.getModel().getPaths();
List<ResourceFile> bundlePaths = bundleStatusProvider.getModel().getEnabledPaths();
for (ResourceFile bundlePath : bundlePaths) {
if (bundlePath.isDirectory()) {
updateAvailableScriptFilesForDirectory(scriptsToRemove, scriptAccumulator,
@ -732,56 +728,8 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
return true;
}
final private BundleHost bundleHost;
void startActivateDeactiveTask(BundlePath path, boolean activate) {
path.setBusy(true);
bundleStatusProvider.notifyTableChanged();
ConsoleService console = plugin.getTool().getService(ConsoleService.class);
new TaskLauncher(new Task((activate ? "Activating" : "Deactivating ") + " bundle...") {
@Override
public void run(TaskMonitor monitor) throws CancelledException {
try {
if (activate) {
if (path.getType() == BundlePath.Type.SourceDir) {
SourceBundleInfo sbi = bundleHost.getSourceBundleInfo(path.getPath());
bundleHost.compileSourceBundle(sbi, console.getStdErr());
}
String loc = bundleStatusProvider.getModel().getBundleLoc(path);
if (loc != null) {
bundleHost.activateSynchronously(loc);
}
}
else { // deactivate
String loc = bundleStatusProvider.getModel().getBundleLoc(path);
if (loc != null) {
Bundle bundle = bundleHost.getBundle(loc);
if (bundle != null) {
bundleHost.deactivateSynchronously(bundle);
}
}
}
}
catch (Exception e) {
e.printStackTrace(console.getStdErr());
path.setActive(!activate);
Msg.showError(this, GhidraScriptComponentProvider.this.getComponent(),
"bundle activation failed", e.getMessage());
}
finally {
path.setBusy(false);
bundleStatusProvider.notifyTableChanged();
}
}
}, null, 1000);
}
private void build() {
bundleStatusProvider =
new BundleStatusProvider(plugin.getTool(), plugin.getName(), bundleHost);
bundleStatusProvider = new BundleStatusProvider(plugin.getTool(), plugin.getName());
bundleStatusProvider.getModel().addListener(new BundleStatusListener() {
@Override
@ -791,20 +739,11 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
}
@Override
public void bundleEnablementChanged(BundlePath path, boolean enabled) {
if (!enabled && path.isActive()) {
startActivateDeactiveTask(path, false);
}
if (path.isDirectory()) {
public void bundleEnablementChanged(BundleStatus status, boolean enabled) {
if (status.isDirectory()) {
performRefresh();
}
}
@Override
public void bundleActivationChanged(BundlePath path, boolean newValue) {
startActivateDeactiveTask(path, newValue);
}
});
scriptRoot = new RootNode();
@ -1075,7 +1014,7 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
bundleStatusProvider.getModel().restoreState(saveState);
// pull in the just-loaded paths
List<ResourceFile> paths = bundleStatusProvider.getModel().getPaths();
List<ResourceFile> paths = bundleStatusProvider.getModel().getEnabledPaths();
GhidraScriptUtil.setScriptBundlePaths(paths);
actionManager.restoreUserDefinedKeybindings(saveState);
actionManager.restoreScriptsThatAreInTool(saveState);
@ -1213,7 +1152,7 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
public List<ResourceFile> getWritableScriptDirectories() {
BundleStatusModel m = bundleStatusProvider.getModel();
return m.getPaths().stream().filter(ResourceFile::isDirectory).filter(
return m.getEnabledPaths().stream().filter(ResourceFile::isDirectory).filter(
m::isWriteable).collect(Collectors.toList());
}

View file

@ -26,7 +26,6 @@ import ghidra.app.plugin.ProgramPlugin;
import ghidra.app.plugin.core.eclipse.EclipseConnection;
import ghidra.app.plugin.core.eclipse.EclipseIntegrationOptionsPlugin;
import ghidra.app.script.GhidraState;
import ghidra.app.script.osgi.BundleHost;
import ghidra.app.services.*;
import ghidra.framework.options.SaveState;
import ghidra.framework.options.ToolOptions;
@ -51,13 +50,10 @@ public class GhidraScriptMgrPlugin extends ProgramPlugin implements GhidraScript
final private GhidraScriptComponentProvider provider;
final private BundleHost bundleHost;
public GhidraScriptMgrPlugin(PluginTool tool) {
super(tool, true, true, true);
bundleHost = BundleHost.getInstance();
provider = new GhidraScriptComponentProvider(this, bundleHost);
provider = new GhidraScriptComponentProvider(this);
}
@Override

View file

@ -169,7 +169,7 @@ public class GhidraScriptUtil {
* @deprecated accessing class file directly precludes OSGi wiring according to requirements and capabilities
*/
@Deprecated
public static List<ResourceFile> getExplodedBundlePaths() {
public static List<ResourceFile> getExplodedCompiledSourceBundlePaths() {
try {
return Files.list(getOsgiDir()).filter(Files::isDirectory).map(
x -> new ResourceFile(x.toFile())).collect(Collectors.toList());

View file

@ -16,21 +16,20 @@
package ghidra.app.script;
import java.io.*;
import java.lang.reflect.InvocationTargetException;
import org.osgi.framework.Bundle;
import generic.jar.ResourceFile;
import ghidra.app.script.osgi.*;
import ghidra.app.plugin.core.osgi.*;
import ghidra.util.Msg;
public class JavaScriptProvider extends GhidraScriptProvider {
static public SourceBundleInfo getBundleInfoForSource(ResourceFile sourceFile) {
static public GhidraSourceBundle getBundleForSource(ResourceFile sourceFile) {
ResourceFile sourceDir = getSourceDirectoryContaining(sourceFile);
if (sourceDir == null) {
return null;
}
return BundleHost.getInstance().getSourceBundleInfo(sourceDir);
return (GhidraSourceBundle) BundleHost.getInstance().getGhidraBundle(sourceDir);
}
@Override
@ -45,24 +44,23 @@ public class JavaScriptProvider extends GhidraScriptProvider {
@Override
public boolean deleteScript(ResourceFile sourceFile) {
Bundle b = getBundleInfoForSource(sourceFile).getBundle();
if (b != null) {
try {
Bundle b = getBundleForSource(sourceFile).getBundle();
if (b != null) {
BundleHost.getInstance().deactivateSynchronously(b);
}
}
catch (GhidraBundleException | InterruptedException e) {
e.printStackTrace();
Msg.error(this, "while stopping script's bundle to delete it", e);
Msg.error(this, "while deactivating bundle for delete", e);
return false;
}
}
return super.deleteScript(sourceFile);
}
@Override
public GhidraScript getScriptInstance(ResourceFile sourceFile, PrintWriter writer)
throws ClassNotFoundException, InstantiationException, IllegalAccessException {
try {
Class<?> clazz = loadClass(sourceFile, writer);
Object object;
@ -80,25 +78,21 @@ public class JavaScriptProvider extends GhidraScriptProvider {
return null; // class is not GhidraScript
}
catch (OSGiException | IOException | InterruptedException | IllegalArgumentException
| InvocationTargetException | NoSuchMethodException | SecurityException e) {
catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
throw e;
}
catch (Exception e) {
throw new ClassNotFoundException("", e);
}
}
static public Class<?> loadClass(ResourceFile sourceFile, PrintWriter writer)
throws IOException, OSGiException, ClassNotFoundException, InterruptedException {
static public Class<?> loadClass(ResourceFile sourceFile, PrintWriter writer) throws Exception {
BundleHost bundleHost = BundleHost.getInstance();
SourceBundleInfo bi = getBundleInfoForSource(sourceFile);
bundleHost.compileSourceBundle(bi, writer);
// as much source as possible built, install bundle and start it if necessary
Bundle b = bi.getBundle();
if (b == null) {
b = bi.install();
}
GhidraSourceBundle bi = getBundleForSource(sourceFile);
bi.build(writer);
Bundle b = bi.install();
bundleHost.activateSynchronously(b);
String classname = bi.classNameForScript(sourceFile);

View file

@ -48,7 +48,7 @@ import generic.jar.ResourceFile;
import generic.test.TestUtils;
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
import ghidra.app.plugin.core.console.ConsoleComponentProvider;
import ghidra.app.plugin.core.script.osgi.BundleStatusProvider;
import ghidra.app.plugin.core.osgi.BundleStatusProvider;
import ghidra.app.script.*;
import ghidra.app.services.ConsoleService;
import ghidra.framework.Application;
@ -979,7 +979,7 @@ public abstract class AbstractGhidraScriptMgrPluginTest
// destroy any NewScriptxxx files...and Temp ones too
BundleStatusProvider bundleStatusProvider =
(BundleStatusProvider) TestUtils.getInstanceField("bundleStatusProvider", provider);
List<ResourceFile> paths = bundleStatusProvider.getModel().getPaths();
List<ResourceFile> paths = bundleStatusProvider.getModel().getEnabledPaths();
for (ResourceFile path : paths) {
File file = path.getFile(false);
File[] listFiles = file.listFiles();

View file

@ -26,9 +26,9 @@ import org.junit.Test;
import docking.widgets.table.SelectionManager;
import generic.jar.ResourceFile;
import generic.test.AbstractGenericTest;
import ghidra.app.plugin.core.osgi.GhidraSourceBundle;
import ghidra.app.script.GhidraScriptUtil;
import ghidra.app.script.JavaScriptProvider;
import ghidra.app.script.osgi.SourceBundleInfo;
import ghidra.test.ScriptTaskListener;
import ghidra.util.TaskUtilities;
import utilities.util.FileUtilities;
@ -149,7 +149,7 @@ public class GhidraScriptMgrPlugin2Test extends AbstractGhidraScriptMgrPluginTes
// remove all class files from the user script bin dir
File userScriptsBinDir =
SourceBundleInfo.getBindirFromScriptFile(new ResourceFile(newScriptFile)).toFile();
GhidraSourceBundle.getBindirFromScriptFile(new ResourceFile(newScriptFile)).toFile();
File[] userScriptBinDirFiles;
if (userScriptsBinDir.exists()) {
userScriptBinDirFiles = userScriptsBinDir.listFiles(classFileFilter);
@ -195,7 +195,7 @@ public class GhidraScriptMgrPlugin2Test extends AbstractGhidraScriptMgrPluginTes
// verify that the generated class file is placed in the default scripting home/bin
File userScriptsBinDir =
SourceBundleInfo.getBindirFromScriptFile(systemScriptFile).toFile();
GhidraSourceBundle.getBindirFromScriptFile(systemScriptFile).toFile();
String className = scriptName.replace(".java", ".class");
File expectedClassFile = new File(userScriptsBinDir, className);
@ -232,7 +232,7 @@ public class GhidraScriptMgrPlugin2Test extends AbstractGhidraScriptMgrPluginTes
// verify a bin dir was created and that the class file is in it
File binDir =
SourceBundleInfo.getBindirFromScriptFile(new ResourceFile(newScriptFile)).toFile();
GhidraSourceBundle.getBindirFromScriptFile(new ResourceFile(newScriptFile)).toFile();
assertTrue("bin output dir not created", binDir.exists());
File scriptClassFile = new File(binDir, rawScriptName + ".class");

View file

@ -30,7 +30,7 @@ import docking.action.DockingActionIf;
import docking.widgets.filter.FilterTextField;
import docking.widgets.list.ListPanel;
import generic.jar.ResourceFile;
import ghidra.app.plugin.core.script.osgi.BundleStatusProvider;
import ghidra.app.plugin.core.osgi.BundleStatusProvider;
import ghidra.app.script.GhidraScriptUtil;
import ghidra.app.script.JavaScriptProvider;
import ghidra.util.StringUtilities;

View file

@ -138,7 +138,7 @@ public class GhidraPythonInterpreter extends InteractiveInterpreter {
systemState.path.append(Py.newString(resourceFile.getFile(false).getAbsolutePath()));
}
for (ResourceFile resourceFile : GhidraScriptUtil.getExplodedBundlePaths()) {
for (ResourceFile resourceFile : GhidraScriptUtil.getExplodedCompiledSourceBundlePaths()) {
systemState.path.append(Py.newString(resourceFile.getFile(false).getAbsolutePath()));
}

View file

@ -27,8 +27,8 @@ import docking.widgets.tree.GTree;
import docking.widgets.tree.GTreeNode;
import generic.jar.ResourceFile;
import ghidra.app.plugin.core.console.ConsoleComponentProvider;
import ghidra.app.plugin.core.osgi.BundleStatusProvider;
import ghidra.app.plugin.core.script.*;
import ghidra.app.plugin.core.script.osgi.BundleStatusProvider;
import ghidra.app.script.GhidraScriptUtil;
import ghidra.app.services.ConsoleService;
import ghidra.util.HelpLocation;