mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-04 18:29:37 +02:00
introduce GhidraBundle, cleanup packages, rename path->status
This commit is contained in:
parent
7873bb32fa
commit
431d7ac752
22 changed files with 466 additions and 441 deletions
|
@ -13,7 +13,7 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package ghidra.app.script.osgi;
|
package ghidra.app.plugin.core.osgi;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
|
@ -38,33 +38,33 @@ import generic.jar.ResourceFile;
|
||||||
import ghidra.app.script.*;
|
import ghidra.app.script.*;
|
||||||
|
|
||||||
public class BundleCompiler {
|
public class BundleCompiler {
|
||||||
|
|
||||||
public static final String GENERATED_ACTIVATOR_CLASSNAME = "GeneratedActivator";
|
|
||||||
private BundleHost bh;
|
private BundleHost bh;
|
||||||
|
|
||||||
public BundleCompiler(BundleHost bh) {
|
static final String GENERATED_ACTIVATOR_CLASSNAME = "GeneratedActivator";
|
||||||
|
|
||||||
|
BundleCompiler(BundleHost bh) {
|
||||||
this.bh = bh;
|
this.bh = bh;
|
||||||
}
|
}
|
||||||
|
|
||||||
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
|
private JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* compile a source directory to an exploded bundle
|
* 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
|
* @param writer for updating the user during compilation
|
||||||
* @throws IOException for source/manifest file reading/generation and binary deletion/creation
|
* @throws IOException for source/manifest file reading/generation and binary deletion/creation
|
||||||
* @throws OSGiException if generation of bundle metadata fails
|
* @throws OSGiException if generation of bundle metadata fails
|
||||||
*/
|
*/
|
||||||
public void compileToExplodedBundle(SourceBundleInfo sbi, PrintWriter writer)
|
void compileToExplodedBundle(GhidraSourceBundle sb, PrintWriter writer)
|
||||||
throws IOException, OSGiException {
|
throws IOException, OSGiException {
|
||||||
|
|
||||||
sbi.compileAttempted();
|
sb.compileAttempted();
|
||||||
sbi.setSummary(String.format("build %d files, skipping %d%s", sbi.getNewSourcesCount(),
|
sb.setSummary(String.format("build %d files, skipping %d%s", sb.getNewSourcesCount(),
|
||||||
sbi.getFailingSourcesCount(), sbi.newManifestFile() ? ", new manifest" : ""));
|
sb.getFailingSourcesCount(), sb.newManifestFile() ? ", new manifest" : ""));
|
||||||
|
|
||||||
ResourceFile srcdir = sbi.getSourceDir();
|
ResourceFile srcdir = sb.getSourceDir();
|
||||||
Path bindir = sbi.getBinDir();
|
Path bindir = sb.getBinDir();
|
||||||
Files.createDirectories(bindir);
|
Files.createDirectories(bindir);
|
||||||
|
|
||||||
List<String> options = new ArrayList<>();
|
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(bi.getSourceDir()));
|
||||||
final JavaFileManager rfm =
|
final JavaFileManager rfm =
|
||||||
new ResourceFileJavaFileManager(Collections.singletonList(sbi.getSourceDir()));
|
new ResourceFileJavaFileManager(Collections.singletonList(sb.getSourceDir()));
|
||||||
|
|
||||||
BundleJavaManager bjm = new BundleJavaManager(bh.getHostFramework(), rfm, options);
|
BundleJavaManager bjm = new BundleJavaManager(bh.getHostFramework(), rfm, options);
|
||||||
// The phidias BundleJavaManager is for compiling from within a bundle -- it makes the
|
// 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
|
// XXX skip this if there's a source manifest, emit warnings about @imports
|
||||||
// get wires for currently active bundles to satisfy all requirements
|
// 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);
|
List<BundleWiring> bundleWirings = bh.resolve(reqs);
|
||||||
|
|
||||||
if (!reqs.isEmpty()) {
|
if (!reqs.isEmpty()) {
|
||||||
|
@ -98,19 +98,19 @@ public class BundleCompiler {
|
||||||
writer.printf(" %s\n", req.toString());
|
writer.printf(" %s\n", req.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
sbi.setSummary(
|
sb.setSummary(
|
||||||
String.format("%d missing @import%s:", reqs.size(), reqs.size() > 1 ? "s" : "",
|
String.format("%d missing @import%s:", reqs.size(), reqs.size() > 1 ? "s" : "",
|
||||||
reqs.stream().flatMap(
|
reqs.stream().flatMap(
|
||||||
r -> OSGiUtils.extractPackages(r.toString()).stream()).distinct().collect(
|
r -> OSGiUtils.extractPackages(r.toString()).stream()).distinct().collect(
|
||||||
Collectors.joining(","))));
|
Collectors.joining(","))));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
sbi.setSummary("");
|
sb.setSummary("");
|
||||||
}
|
}
|
||||||
// XXX add sources that will fail to call attention
|
// XXX add sources that will fail to call attention
|
||||||
List<ResourceFile> newSource = sbi.getNewSources();
|
List<ResourceFile> newSource = sb.getNewSources();
|
||||||
for (BundleRequirement req : reqs) {
|
for (BundleRequirement req : reqs) {
|
||||||
newSource.addAll(sbi.req2file.get(req.toString()));
|
newSource.addAll(sb.req2file.get(req.toString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// send the capabilities to phidias
|
// send the capabilities to phidias
|
||||||
|
@ -142,7 +142,7 @@ public class BundleCompiler {
|
||||||
writer.write(err);
|
writer.write(err);
|
||||||
ResourceFileJavaFileObject sf = (ResourceFileJavaFileObject) d.getSource();
|
ResourceFileJavaFileObject sf = (ResourceFileJavaFileObject) d.getSource();
|
||||||
ResourceFile rf = sf.getFile();
|
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)) {
|
if (sourceFiles.remove(sf)) {
|
||||||
writer.printf("skipping %s\n", sf.toString());
|
writer.printf("skipping %s\n", sf.toString());
|
||||||
// if it's a script, mark it for having compile errors
|
// 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
|
// buildErrors is now up to date, set status
|
||||||
if (sbi.getFailingSourcesCount() > 0) {
|
if (sb.getFailingSourcesCount() > 0) {
|
||||||
sbi.appendSummary(
|
sb.appendSummary(String.format("%d failing source files", sb.getFailingSourcesCount()));
|
||||||
String.format("%d failing source files", sbi.getFailingSourcesCount()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ResourceFile smf = new ResourceFile(srcdir, "META-INF" + File.separator + "MANIFEST.MF");
|
ResourceFile smf = new ResourceFile(srcdir, "META-INF" + File.separator + "MANIFEST.MF");
|
||||||
|
@ -203,7 +202,7 @@ public class BundleCompiler {
|
||||||
manifest = analyzer.calcManifest();
|
manifest = analyzer.calcManifest();
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
catch (Exception e) {
|
||||||
sbi.appendSummary("bad manifest");
|
sb.appendSummary("bad manifest");
|
||||||
throw new OSGiException("failed to calculate manifest by analyzing code", e);
|
throw new OSGiException("failed to calculate manifest by analyzing code", e);
|
||||||
}
|
}
|
||||||
Attributes ma = manifest.getMainAttributes();
|
Attributes ma = manifest.getMainAttributes();
|
||||||
|
@ -219,22 +218,24 @@ public class BundleCompiler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
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);
|
throw new OSGiException("failed to query classes while searching for activator", e);
|
||||||
}
|
}
|
||||||
if (activator_classname == null) {
|
if (activator_classname == null) {
|
||||||
activator_classname = GENERATED_ACTIVATOR_CLASSNAME;
|
activator_classname = GENERATED_ACTIVATOR_CLASSNAME;
|
||||||
if (!buildDefaultActivator(bindir, activator_classname, writer)) {
|
if (!buildDefaultActivator(bindir, activator_classname, writer)) {
|
||||||
sbi.appendSummary("failed to build generated activator");
|
sb.appendSummary("failed to build generated activator");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// since we add the activator after bndtools built the imports, we should add its imports too
|
// since we add the activator after bndtools built the imports, we should add its imports too
|
||||||
String imps = ma.getValue(Constants.IMPORT_PACKAGE);
|
String imps = ma.getValue(Constants.IMPORT_PACKAGE);
|
||||||
if (imps == null) {
|
if (imps == null) {
|
||||||
ma.putValue(Constants.IMPORT_PACKAGE, "ghidra.app.script.osgi");
|
ma.putValue(Constants.IMPORT_PACKAGE,
|
||||||
|
GhidraBundleActivator.class.getPackageName());
|
||||||
}
|
}
|
||||||
else {
|
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);
|
ma.putValue(Constants.BUNDLE_ACTIVATOR, activator_classname);
|
||||||
|
@ -265,7 +266,7 @@ public class BundleCompiler {
|
||||||
|
|
||||||
try (PrintWriter out =
|
try (PrintWriter out =
|
||||||
new PrintWriter(Files.newBufferedWriter(activator_dest, Charset.forName("UTF-8")))) {
|
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.BundleActivator;");
|
||||||
out.println("import org.osgi.framework.BundleContext;");
|
out.println("import org.osgi.framework.BundleContext;");
|
||||||
out.println("public class " + GENERATED_ACTIVATOR_CLASSNAME +
|
out.println("public class " + GENERATED_ACTIVATOR_CLASSNAME +
|
|
@ -13,7 +13,7 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package ghidra.app.script.osgi;
|
package ghidra.app.plugin.core.osgi;
|
||||||
|
|
||||||
import static java.util.stream.Collectors.*;
|
import static java.util.stream.Collectors.*;
|
||||||
|
|
||||||
|
@ -35,15 +35,13 @@ import org.osgi.framework.launch.Framework;
|
||||||
import org.osgi.framework.wiring.*;
|
import org.osgi.framework.wiring.*;
|
||||||
import org.osgi.service.log.*;
|
import org.osgi.service.log.*;
|
||||||
|
|
||||||
import generic.io.NullPrintWriter;
|
|
||||||
import generic.jar.ResourceFile;
|
import generic.jar.ResourceFile;
|
||||||
import ghidra.app.script.GhidraScriptUtil;
|
import ghidra.app.script.GhidraScriptUtil;
|
||||||
import ghidra.util.exception.CancelledException;
|
import ghidra.util.exception.CancelledException;
|
||||||
import ghidra.util.task.*;
|
import ghidra.util.task.*;
|
||||||
|
|
||||||
// XXX this class should be part of a service/plugin
|
|
||||||
public class BundleHost {
|
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 private BundleHost _instance;
|
||||||
|
|
||||||
static public BundleHost getInstance() {
|
static public BundleHost getInstance() {
|
||||||
|
@ -69,16 +67,28 @@ public class BundleHost {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// XXX this should be remembered in bundlehosts's savestate
|
// XXX this should be remembered by bundlehosts's savestate
|
||||||
HashMap<ResourceFile, SourceBundleInfo> file2sbi = new HashMap<>();
|
HashMap<ResourceFile, GhidraBundle> file2sbi = new HashMap<>();
|
||||||
|
|
||||||
public SourceBundleInfo getSourceBundleInfo(ResourceFile sourceDir) {
|
public GhidraBundle getGhidraBundle(ResourceFile path) {
|
||||||
return file2sbi.computeIfAbsent(sourceDir, sd -> new SourceBundleInfo(this, sd));
|
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
|
// XXX consumers must clean up after themselves
|
||||||
public void removeSourceBundleInfo(ResourceFile sourceDir) {
|
public void removeSourceBundleInfo(ResourceFile path) {
|
||||||
file2sbi.remove(sourceDir);
|
file2sbi.remove(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -86,22 +96,17 @@ public class BundleHost {
|
||||||
*
|
*
|
||||||
* @param imports Import-Package value
|
* @param imports Import-Package value
|
||||||
* @return deduced requirements or null if there was an error
|
* @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
|
// parse it with Felix's ManifestParser to a list of BundleRequirement objects
|
||||||
Map<String, Object> headerMap = new HashMap<>();
|
Map<String, Object> headerMap = new HashMap<>();
|
||||||
headerMap.put(Constants.IMPORT_PACKAGE, imports);
|
headerMap.put(Constants.IMPORT_PACKAGE, imports);
|
||||||
ManifestParser mp;
|
ManifestParser mp;
|
||||||
try {
|
|
||||||
mp = new ManifestParser(null, null, null, headerMap);
|
mp = new ManifestParser(null, null, null, headerMap);
|
||||||
return mp.getRequirements();
|
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
|
* 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() {
|
void forceStopFelix() {
|
||||||
Task t = new Task("killing felix", false, false, true, true) {
|
Task t = new Task("killing felix", false, false, true, true) {
|
||||||
@Override
|
@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<>();
|
List<OSGiListener> osgiListeners = new ArrayList<>();
|
||||||
|
|
||||||
void fireSourceBundleCompiled(SourceBundleInfo sbi) {
|
void fireSourceBundleCompiled(GhidraSourceBundle sbi) {
|
||||||
synchronized (osgiListeners) {
|
synchronized (osgiListeners) {
|
||||||
for (OSGiListener l : osgiListeners) {
|
for (OSGiListener l : osgiListeners) {
|
||||||
l.sourceBundleCompiled(sbi);
|
l.sourceBundleCompiled(sbi);
|
|
@ -13,67 +13,33 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package ghidra.app.plugin.core.script.osgi;
|
package ghidra.app.plugin.core.osgi;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
|
|
||||||
import generic.jar.ResourceFile;
|
import generic.jar.ResourceFile;
|
||||||
import generic.util.Path;
|
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 {
|
public class BundleStatus extends Path {
|
||||||
final Type type;
|
final GhidraBundle.Type type;
|
||||||
|
|
||||||
boolean active = false;
|
boolean active = false;
|
||||||
boolean busy = false;
|
boolean busy = false;
|
||||||
String summary;
|
String summary;
|
||||||
|
|
||||||
public static enum Type {
|
public GhidraBundle.Type getType() {
|
||||||
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() {
|
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
|
|
||||||
BundlePath(String path, boolean enabled, boolean readonly) {
|
BundleStatus(String path, boolean enabled, boolean readonly) {
|
||||||
super(path, enabled, false /*editable */, 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);
|
super(path, enabled, false /* editable */, readonly);
|
||||||
type = getType(getPath());
|
type = GhidraBundle.getType(getPath());
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isDirectory() {
|
public boolean isDirectory() {
|
||||||
|
@ -109,4 +75,8 @@ public class BundlePath extends Path {
|
||||||
return summary;
|
return summary;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean pathExists() {
|
||||||
|
return exists();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -14,16 +14,23 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package ghidra.app.plugin.core.script.osgi;
|
package ghidra.app.plugin.core.osgi;
|
||||||
|
|
||||||
public interface BundleStatusListener {
|
public interface BundleStatusListener {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the list of bundle paths changes
|
* Called when the list of bundle paths changes
|
||||||
*/
|
*/
|
||||||
public void bundlesChanged();
|
default public void bundlesChanged() {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
public void bundleEnablementChanged(BundlePath path, boolean newValue);
|
default public void bundleEnablementChanged(BundleStatus status, boolean newValue) {
|
||||||
public void bundleActivationChanged(BundlePath path, boolean newValue);
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
default public void bundleActivationChanged(BundleStatus status, boolean newValue) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -14,7 +14,7 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package ghidra.app.plugin.core.script.osgi;
|
package ghidra.app.plugin.core.osgi;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
@ -25,12 +25,11 @@ import org.osgi.framework.Bundle;
|
||||||
import docking.widgets.table.AbstractSortedTableModel;
|
import docking.widgets.table.AbstractSortedTableModel;
|
||||||
import generic.jar.ResourceFile;
|
import generic.jar.ResourceFile;
|
||||||
import ghidra.app.script.GhidraScriptUtil;
|
import ghidra.app.script.GhidraScriptUtil;
|
||||||
import ghidra.app.script.osgi.*;
|
|
||||||
import ghidra.framework.options.SaveState;
|
import ghidra.framework.options.SaveState;
|
||||||
import ghidra.framework.preferences.Preferences;
|
import ghidra.framework.preferences.Preferences;
|
||||||
import ghidra.util.Msg;
|
import ghidra.util.Msg;
|
||||||
|
|
||||||
public class BundleStatusModel extends AbstractSortedTableModel<BundlePath> {
|
public class BundleStatusModel extends AbstractSortedTableModel<BundleStatus> {
|
||||||
List<Column> columns = new ArrayList<>();
|
List<Column> columns = new ArrayList<>();
|
||||||
|
|
||||||
class Column {
|
class Column {
|
||||||
|
@ -45,15 +44,15 @@ public class BundleStatusModel extends AbstractSortedTableModel<BundlePath> {
|
||||||
this.clazz = clazz;
|
this.clazz = clazz;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean editable(BundlePath path) {
|
boolean editable(BundleStatus status) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Object getValue(BundlePath path) {
|
Object getValue(BundleStatus status) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setValue(BundlePath path, Object aValue) {
|
void setValue(BundleStatus status, Object aValue) {
|
||||||
throw new RuntimeException(name + " is not editable!");
|
throw new RuntimeException(name + " is not editable!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,55 +60,55 @@ public class BundleStatusModel extends AbstractSortedTableModel<BundlePath> {
|
||||||
|
|
||||||
Column enabledColumn = new Column("Enabled", Boolean.class) {
|
Column enabledColumn = new Column("Enabled", Boolean.class) {
|
||||||
@Override
|
@Override
|
||||||
boolean editable(BundlePath path) {
|
boolean editable(BundleStatus status) {
|
||||||
return path.exists();
|
return status.pathExists();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
Object getValue(BundlePath path) {
|
Object getValue(BundleStatus status) {
|
||||||
return path.isEnabled();
|
return status.isEnabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
void setValue(BundlePath path, Object newValue) {
|
void setValue(BundleStatus status, Object newValue) {
|
||||||
path.setEnabled((Boolean) newValue);
|
status.setEnabled((Boolean) newValue);
|
||||||
fireBundleEnablementChanged(path, (Boolean) newValue);
|
fireBundleEnablementChanged(status, (Boolean) newValue);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Column activeColumn = new Column("Active", Boolean.class) {
|
Column activeColumn = new Column("Active", Boolean.class) {
|
||||||
@Override
|
@Override
|
||||||
boolean editable(BundlePath path) {
|
boolean editable(BundleStatus status) {
|
||||||
return path.exists(); // XXX maybe only if it's already enabled
|
return status.pathExists(); // XXX maybe only if it's already enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
Object getValue(BundlePath path) {
|
Object getValue(BundleStatus status) {
|
||||||
return path.isActive();
|
return status.isActive();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
void setValue(BundlePath path, Object newValue) {
|
void setValue(BundleStatus status, Object newValue) {
|
||||||
path.setActive((Boolean) newValue);
|
status.setActive((Boolean) newValue);
|
||||||
fireBundleActivationChanged(path, (Boolean) newValue);
|
fireBundleActivationChanged(status, (Boolean) newValue);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Column typeColumn = new Column("Type", String.class) {
|
Column typeColumn = new Column("Type", String.class) {
|
||||||
@Override
|
@Override
|
||||||
Object getValue(BundlePath path) {
|
Object getValue(BundleStatus status) {
|
||||||
return path.getType().toString();
|
return status.getType().toString();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Column pathColumn = new Column("Path", BundlePath.class) {
|
Column pathColumn = new Column("Path", String.class) {
|
||||||
@Override
|
@Override
|
||||||
Object getValue(BundlePath path) {
|
Object getValue(BundleStatus status) {
|
||||||
return path;
|
return status.getPath().toString();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Column summaryColumn = new Column("Summary", BundlePath.class) {
|
Column summaryColumn = new Column("Summary", String.class) {
|
||||||
@Override
|
@Override
|
||||||
Object getValue(BundlePath path) {
|
Object getValue(BundleStatus status) {
|
||||||
return path.getSummary();
|
return status.getSummary();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -127,41 +126,29 @@ public class BundleStatusModel extends AbstractSortedTableModel<BundlePath> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private BundleStatusProvider provider;
|
private BundleStatusProvider provider;
|
||||||
private List<BundlePath> paths;
|
private List<BundleStatus> statuses;
|
||||||
private BundleHost bundleHost;
|
private BundleHost bundleHost;
|
||||||
OSGiListener bundleListener;
|
OSGiListener bundleListener;
|
||||||
|
|
||||||
private Map<String, BundlePath> loc2bp = new HashMap<>();
|
private Map<String, BundleStatus> loc2status = new HashMap<>();
|
||||||
|
|
||||||
BundlePath getPath(String bundleLocation) {
|
BundleStatus getStatus(String bundleLocation) {
|
||||||
return loc2bp.get(bundleLocation);
|
return loc2status.get(bundleLocation);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getBundleLoc(BundlePath bp) {
|
public String getBundleLoc(BundleStatus status) {
|
||||||
switch (bp.getType()) {
|
return bundleHost.getGhidraBundle(status.getPath()).getBundleLoc();
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* (re)compute cached mapping from bundleloc to bundlepath
|
* (re)compute cached mapping from bundleloc to bundlepath
|
||||||
*/
|
*/
|
||||||
private void computeCache() {
|
private void computeCache() {
|
||||||
loc2bp.clear();
|
loc2status.clear();
|
||||||
for (BundlePath bp : paths) {
|
for (BundleStatus status : statuses) {
|
||||||
String loc = getBundleLoc(bp);
|
String loc = getBundleLoc(status);
|
||||||
if (loc != null) {
|
if (loc != null) {
|
||||||
loc2bp.put(loc, bp);
|
loc2status.put(loc, status);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -172,18 +159,20 @@ public class BundleStatusModel extends AbstractSortedTableModel<BundlePath> {
|
||||||
this.bundleHost = bundleHost;
|
this.bundleHost = bundleHost;
|
||||||
|
|
||||||
// add unmodifiable paths
|
// add unmodifiable paths
|
||||||
this.paths = GhidraScriptUtil.getSystemScriptPaths().stream().distinct().map(
|
this.statuses = GhidraScriptUtil.getSystemScriptPaths().stream().distinct().map(
|
||||||
f -> new BundlePath(f, true, true)).collect(Collectors.toList());
|
f -> new BundleStatus(f, true, true)).collect(Collectors.toList());
|
||||||
// add user path
|
// add user path
|
||||||
this.paths.add(0, new BundlePath(GhidraScriptUtil.getUserScriptDirectory(), true, false));
|
this.statuses.add(0,
|
||||||
|
new BundleStatus(GhidraScriptUtil.getUserScriptDirectory(), true, false));
|
||||||
|
|
||||||
computeCache();
|
computeCache();
|
||||||
|
|
||||||
bundleHost.addListener(bundleListener = new OSGiListener() {
|
bundleHost.addListener(bundleListener = new OSGiListener() {
|
||||||
@Override
|
@Override
|
||||||
public void sourceBundleCompiled(SourceBundleInfo sbi) {
|
public void sourceBundleCompiled(GhidraSourceBundle sb) {
|
||||||
BundlePath bp = getPath(sbi.getBundleLoc());
|
BundleStatus bp = getStatus(sb.getBundleLoc());
|
||||||
if (bp != null) {
|
if (bp != null) {
|
||||||
bp.setSummary(sbi.getSummary());
|
bp.setSummary(sb.getSummary());
|
||||||
int row = getRowIndex(bp);
|
int row = getRowIndex(bp);
|
||||||
fireTableRowsUpdated(row, row);
|
fireTableRowsUpdated(row, row);
|
||||||
}
|
}
|
||||||
|
@ -191,7 +180,7 @@ public class BundleStatusModel extends AbstractSortedTableModel<BundlePath> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void bundleActivationChange(Bundle b, boolean newActivation) {
|
public void bundleActivationChange(Bundle b, boolean newActivation) {
|
||||||
BundlePath bp = getPath(b.getLocation());
|
BundleStatus bp = getStatus(b.getLocation());
|
||||||
if (newActivation) {
|
if (newActivation) {
|
||||||
if (bp != null) {
|
if (bp != null) {
|
||||||
bp.setActive(true);
|
bp.setActive(true);
|
||||||
|
@ -219,71 +208,70 @@ public class BundleStatusModel extends AbstractSortedTableModel<BundlePath> {
|
||||||
bundleHost.removeListener(bundleListener);
|
bundleHost.removeListener(bundleListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
void clear() {
|
public List<ResourceFile> getEnabledPaths() {
|
||||||
paths.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
List<BundlePath> getAllPaths() {
|
|
||||||
return new ArrayList<BundlePath>(paths);
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<ResourceFile> getPaths() {
|
|
||||||
List<ResourceFile> list = new ArrayList<>();
|
List<ResourceFile> list = new ArrayList<>();
|
||||||
for (BundlePath path : paths) {
|
for (BundleStatus status : statuses) {
|
||||||
if (path.isEnabled()) {
|
if (status.isEnabled()) {
|
||||||
list.add(path.getPath());
|
list.add(status.getPath());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addPath(BundlePath path) {
|
private void addStatus(BundleStatus path) {
|
||||||
if (paths.contains(path)) {
|
if (statuses.contains(path)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
String loc = getBundleLoc(path);
|
String loc = getBundleLoc(path);
|
||||||
if (loc != null) {
|
if (loc != null) {
|
||||||
loc2bp.put(loc, path);
|
loc2status.put(loc, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
int index = paths.size();
|
int index = statuses.size();
|
||||||
paths.add(path);
|
statuses.add(path);
|
||||||
fireTableRowsInserted(index, index);
|
fireTableRowsInserted(index, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
private BundlePath addNewPath(ResourceFile path, boolean enabled, boolean readonly) {
|
private BundleStatus addNewStatus(ResourceFile path, boolean enabled, boolean readonly) {
|
||||||
BundlePath p = new BundlePath(path, enabled, readonly);
|
BundleStatus p = new BundleStatus(path, enabled, readonly);
|
||||||
addPath(p);
|
addStatus(p);
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
private BundlePath addNewPath(String path, boolean enabled, boolean readonly) {
|
private BundleStatus addNewStatus(String path, boolean enabled, boolean readonly) {
|
||||||
BundlePath p = new BundlePath(path, enabled, readonly);
|
BundleStatus p = new BundleStatus(path, enabled, readonly);
|
||||||
addPath(p);
|
addStatus(p);
|
||||||
return 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) {
|
void addNewPaths(List<File> files, boolean enabled, boolean readonly) {
|
||||||
for (File f : files) {
|
for (File f : files) {
|
||||||
BundlePath p = new BundlePath(new ResourceFile(f), enabled, readonly);
|
BundleStatus status = new BundleStatus(new ResourceFile(f), enabled, readonly);
|
||||||
addPath(p);
|
addStatus(status);
|
||||||
}
|
}
|
||||||
fireBundlesChanged();
|
fireBundlesChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
void remove(int[] selectedRows) {
|
void remove(int[] selectedRows) {
|
||||||
List<BundlePath> list = new ArrayList<>();
|
List<BundleStatus> toRemove = new ArrayList<>();
|
||||||
for (int selectedRow : selectedRows) {
|
for (int selectedRow : selectedRows) {
|
||||||
list.add(paths.get(selectedRow));
|
toRemove.add(statuses.get(selectedRow));
|
||||||
}
|
}
|
||||||
for (BundlePath path : list) {
|
for (BundleStatus status : toRemove) {
|
||||||
if (!path.isReadOnly()) {
|
if (!status.isReadOnly()) {
|
||||||
paths.remove(path);
|
statuses.remove(status);
|
||||||
loc2bp.remove(getBundleLoc(path));
|
loc2status.remove(getBundleLoc(status));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
Msg.showInfo(this, this.provider.getComponent(), "Unabled to remove path",
|
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();
|
fireTableDataChanged();
|
||||||
|
@ -299,7 +287,7 @@ public class BundleStatusModel extends AbstractSortedTableModel<BundlePath> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getRowCount() {
|
public int getRowCount() {
|
||||||
return paths.size();
|
return statuses.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -309,8 +297,8 @@ public class BundleStatusModel extends AbstractSortedTableModel<BundlePath> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isCellEditable(int rowIndex, int columnIndex) {
|
public boolean isCellEditable(int rowIndex, int columnIndex) {
|
||||||
BundlePath path = paths.get(rowIndex);
|
BundleStatus status = statuses.get(rowIndex);
|
||||||
return getColumn(columnIndex).editable(path);
|
return getColumn(columnIndex).editable(status);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -320,15 +308,15 @@ public class BundleStatusModel extends AbstractSortedTableModel<BundlePath> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
|
public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
|
||||||
BundlePath path = paths.get(rowIndex);
|
BundleStatus status = statuses.get(rowIndex);
|
||||||
getColumn(columnIndex).setValue(path, aValue);
|
getColumn(columnIndex).setValue(status, aValue);
|
||||||
// XXX: preclude RowObjectSelectionManager.repairSelection
|
// XXX: avoid RowObjectSelectionManager.repairSelection by selecting the row we're editing
|
||||||
provider.selectRow(rowIndex);
|
provider.selectRow(rowIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object getColumnValueForRow(BundlePath path, int columnIndex) {
|
public Object getColumnValueForRow(BundleStatus status, int columnIndex) {
|
||||||
return getColumn(columnIndex).getValue(path);
|
return getColumn(columnIndex).getValue(status);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -338,12 +326,12 @@ public class BundleStatusModel extends AbstractSortedTableModel<BundlePath> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return "BundlePathManagerModel";
|
return BundleStatusModel.class.getSimpleName();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<BundlePath> getModelData() {
|
public List<BundleStatus> getModelData() {
|
||||||
return paths;
|
return statuses;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -353,7 +341,7 @@ public class BundleStatusModel extends AbstractSortedTableModel<BundlePath> {
|
||||||
*/
|
*/
|
||||||
public boolean enablePath(ResourceFile file) {
|
public boolean enablePath(ResourceFile file) {
|
||||||
ResourceFile dir = file.isDirectory() ? file : file.getParentFile();
|
ResourceFile dir = file.isDirectory() ? file : file.getParentFile();
|
||||||
for (BundlePath path : getAllPaths()) {
|
for (BundleStatus path : statuses) {
|
||||||
if (path.getPath().equals(dir)) {
|
if (path.getPath().equals(dir)) {
|
||||||
if (!path.isEnabled()) {
|
if (!path.isEnabled()) {
|
||||||
path.setEnabled(true);
|
path.setEnabled(true);
|
||||||
|
@ -364,7 +352,7 @@ public class BundleStatusModel extends AbstractSortedTableModel<BundlePath> {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
addNewPath(dir, true, false);
|
addNewStatus(dir, true, false);
|
||||||
Preferences.setProperty(BundleStatusProvider.preferenceForLastSelectedBundle,
|
Preferences.setProperty(BundleStatusProvider.preferenceForLastSelectedBundle,
|
||||||
dir.getAbsolutePath());
|
dir.getAbsolutePath());
|
||||||
fireBundlesChanged();
|
fireBundlesChanged();
|
||||||
|
@ -377,8 +365,8 @@ public class BundleStatusModel extends AbstractSortedTableModel<BundlePath> {
|
||||||
* @return true if the bundle is managed and not marked readonly
|
* @return true if the bundle is managed and not marked readonly
|
||||||
*/
|
*/
|
||||||
public boolean isWriteable(ResourceFile bundle) {
|
public boolean isWriteable(ResourceFile bundle) {
|
||||||
Optional<BundlePath> o = paths.stream().filter(
|
Optional<BundleStatus> o = statuses.stream().filter(
|
||||||
bp -> bp.isDirectory() && bp.getPath().equals(bundle)).findFirst();
|
status -> status.isDirectory() && status.getPath().equals(bundle)).findFirst();
|
||||||
return o.isPresent() && !o.get().isReadOnly();
|
return o.isPresent() && !o.get().isReadOnly();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -387,10 +375,10 @@ public class BundleStatusModel extends AbstractSortedTableModel<BundlePath> {
|
||||||
*
|
*
|
||||||
* each path is marked editable and non-readonly
|
* 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) {
|
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());
|
Collectors.toList());
|
||||||
computeCache();
|
computeCache();
|
||||||
fireTableDataChanged();
|
fireTableDataChanged();
|
||||||
|
@ -403,7 +391,7 @@ public class BundleStatusModel extends AbstractSortedTableModel<BundlePath> {
|
||||||
* @param path the path to insert
|
* @param path the path to insert
|
||||||
*/
|
*/
|
||||||
public void insertPathForTesting(String path) {
|
public void insertPathForTesting(String path) {
|
||||||
addNewPath(path, true, false);
|
addNewStatus(path, true, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ArrayList<BundleStatusListener> listeners = new ArrayList<>();
|
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) {
|
synchronized (listeners) {
|
||||||
for (BundleStatusListener listener : listeners) {
|
for (BundleStatusListener listener : listeners) {
|
||||||
listener.bundleEnablementChanged(path, newValue);
|
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) {
|
synchronized (listeners) {
|
||||||
for (BundleStatusListener listener : listeners) {
|
for (BundleStatusListener listener : listeners) {
|
||||||
listener.bundleActivationChanged(path, newValue);
|
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
|
* @param ss the SaveState object
|
||||||
*/
|
*/
|
||||||
public void restoreState(SaveState ss) {
|
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[] enableArr = ss.getBooleans("BundleStatus_ENABLE", new boolean[pathArr.length]);
|
||||||
boolean[] readonlyArr = ss.getBooleans("BundleStatus_READ", new boolean[pathArr.length]);
|
boolean[] readonlyArr = ss.getBooleans("BundleStatus_READ", new boolean[pathArr.length]);
|
||||||
|
|
||||||
List<BundlePath> currentPaths = getAllPaths();
|
List<BundleStatus> currentPaths = new ArrayList<>(statuses);
|
||||||
clear();
|
statuses.clear();
|
||||||
|
|
||||||
for (int i = 0; i < pathArr.length; i++) {
|
for (int i = 0; i < pathArr.length; i++) {
|
||||||
BundlePath currentPath = getPath(pathArr[i], currentPaths);
|
BundleStatus currentStatus = getStatus(pathArr[i], currentPaths);
|
||||||
if (currentPath != null) {
|
if (currentStatus != null) {
|
||||||
currentPaths.remove(currentPath);
|
currentPaths.remove(currentStatus);
|
||||||
addNewPath(pathArr[i], enableArr[i], readonlyArr[i]);
|
addNewStatus(pathArr[i], enableArr[i], readonlyArr[i]);
|
||||||
}
|
}
|
||||||
else if (!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
|
// This is needed to thin-out old default entries
|
||||||
addNewPath(pathArr[i], enableArr[i], readonlyArr[i]);
|
addNewStatus(pathArr[i], enableArr[i], readonlyArr[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fireBundlesChanged();
|
fireBundlesChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static BundlePath getPath(String filepath, List<BundlePath> paths) {
|
private static BundleStatus getStatus(String filepath, List<BundleStatus> statuses) {
|
||||||
for (BundlePath path : paths) {
|
for (BundleStatus status : statuses) {
|
||||||
if (filepath.equals(path.getPathAsString())) {
|
if (filepath.equals(status.getPathAsString())) {
|
||||||
return path;
|
return status;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Saves the paths to the specified SaveState object.
|
* Saves the statuses to the specified SaveState object.
|
||||||
* @param ss the SaveState object
|
* @param ss the SaveState object
|
||||||
*/
|
*/
|
||||||
public void saveState(SaveState ss) {
|
public void saveState(SaveState ss) {
|
||||||
List<BundlePath> currentPaths = getAllPaths();
|
String[] pathArr = new String[statuses.size()];
|
||||||
|
boolean[] enableArr = new boolean[statuses.size()];
|
||||||
String[] pathArr = new String[currentPaths.size()];
|
boolean[] readonlyArr = new boolean[statuses.size()];
|
||||||
boolean[] enableArr = new boolean[currentPaths.size()];
|
|
||||||
boolean[] readonlyArr = new boolean[currentPaths.size()];
|
|
||||||
|
|
||||||
int index = 0;
|
int index = 0;
|
||||||
for (BundlePath path : currentPaths) {
|
for (BundleStatus status : statuses) {
|
||||||
pathArr[index] = path.getPathAsString();
|
pathArr[index] = status.getPathAsString();
|
||||||
enableArr[index] = path.isEnabled();
|
enableArr[index] = status.isEnabled();
|
||||||
readonlyArr[index] = path.isReadOnly();
|
readonlyArr[index] = status.isReadOnly();
|
||||||
++index;
|
++index;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package ghidra.app.plugin.core.script.osgi;
|
package ghidra.app.plugin.core.osgi;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
@ -27,12 +27,15 @@ import javax.swing.table.TableColumn;
|
||||||
import docking.widgets.filechooser.GhidraFileChooser;
|
import docking.widgets.filechooser.GhidraFileChooser;
|
||||||
import docking.widgets.filechooser.GhidraFileChooserMode;
|
import docking.widgets.filechooser.GhidraFileChooserMode;
|
||||||
import docking.widgets.table.*;
|
import docking.widgets.table.*;
|
||||||
import ghidra.app.script.osgi.BundleHost;
|
import ghidra.app.services.ConsoleService;
|
||||||
import ghidra.framework.plugintool.ComponentProviderAdapter;
|
import ghidra.framework.plugintool.ComponentProviderAdapter;
|
||||||
import ghidra.framework.plugintool.PluginTool;
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
import ghidra.framework.preferences.Preferences;
|
import ghidra.framework.preferences.Preferences;
|
||||||
|
import ghidra.util.Msg;
|
||||||
|
import ghidra.util.exception.CancelledException;
|
||||||
import ghidra.util.filechooser.GhidraFileChooserModel;
|
import ghidra.util.filechooser.GhidraFileChooserModel;
|
||||||
import ghidra.util.filechooser.GhidraFileFilter;
|
import ghidra.util.filechooser.GhidraFileFilter;
|
||||||
|
import ghidra.util.task.*;
|
||||||
import resources.ResourceManager;
|
import resources.ResourceManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -49,14 +52,29 @@ public class BundleStatusProvider extends ComponentProviderAdapter {
|
||||||
private Color selectionColor;
|
private Color selectionColor;
|
||||||
private GhidraFileChooser fileChooser;
|
private GhidraFileChooser fileChooser;
|
||||||
private GhidraFileFilter filter;
|
private GhidraFileFilter filter;
|
||||||
|
private final BundleHost bundleHost;
|
||||||
|
|
||||||
public void notifyTableChanged() {
|
public void notifyTableChanged() {
|
||||||
bundleStatusTable.notifyTableChanged(new TableModelEvent(bundleStatusModel));
|
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);
|
super(tool, "Bundle Status Manager", owner);
|
||||||
|
this.bundleHost = BundleHost.getInstance();
|
||||||
this.bundleStatusModel = new BundleStatusModel(this, bundleHost);
|
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() {
|
this.filter = new GhidraFileFilter() {
|
||||||
@Override
|
@Override
|
||||||
|
@ -66,7 +84,7 @@ public class BundleStatusProvider extends ComponentProviderAdapter {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean accept(File path, GhidraFileChooserModel model) {
|
public boolean accept(File path, GhidraFileChooserModel model) {
|
||||||
return BundlePath.getType(path) != BundlePath.Type.INVALID;
|
return GhidraBundle.getType(path) != GhidraBundle.Type.INVALID;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
this.fileChooser = null;
|
this.fileChooser = null;
|
||||||
|
@ -108,7 +126,7 @@ public class BundleStatusProvider extends ComponentProviderAdapter {
|
||||||
buttonPanel.add(removeButton, gbc);
|
buttonPanel.add(removeButton, gbc);
|
||||||
|
|
||||||
bundleStatusTable = new GTable(bundleStatusModel);
|
bundleStatusTable = new GTable(bundleStatusModel);
|
||||||
bundleStatusTable.setName("BUNDLEPATH_TABLE");
|
bundleStatusTable.setName("BUNDLESTATUS_TABLE");
|
||||||
bundleStatusTable.setSelectionBackground(selectionColor);
|
bundleStatusTable.setSelectionBackground(selectionColor);
|
||||||
bundleStatusTable.setSelectionForeground(Color.BLACK);
|
bundleStatusTable.setSelectionForeground(Color.BLACK);
|
||||||
bundleStatusTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
|
bundleStatusTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
|
||||||
|
@ -133,9 +151,9 @@ public class BundleStatusProvider extends ComponentProviderAdapter {
|
||||||
column.setCellRenderer(new GBooleanCellRenderer() {
|
column.setCellRenderer(new GBooleanCellRenderer() {
|
||||||
@Override
|
@Override
|
||||||
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
|
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
|
||||||
BundlePath path = (BundlePath) data.getRowObject();
|
BundleStatus status = (BundleStatus) data.getRowObject();
|
||||||
Component x = super.getTableCellRendererComponent(data);
|
Component x = super.getTableCellRendererComponent(data);
|
||||||
if (path.getBusy()) {
|
if (status.getBusy()) {
|
||||||
cb.setVisible(false);
|
cb.setVisible(false);
|
||||||
cb.setEnabled(false);
|
cb.setEnabled(false);
|
||||||
setHorizontalAlignment(SwingConstants.CENTER);
|
setHorizontalAlignment(SwingConstants.CENTER);
|
||||||
|
@ -156,7 +174,7 @@ public class BundleStatusProvider extends ComponentProviderAdapter {
|
||||||
|
|
||||||
FontMetrics fontmetrics = panel.getFontMetrics(panel.getFont());
|
FontMetrics fontmetrics = panel.getFontMetrics(panel.getFont());
|
||||||
column.setMaxWidth(10 +
|
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 = bundleStatusTable.getColumnModel().getColumn(bundleStatusModel.pathColumn.index);
|
||||||
column.setCellRenderer(new GTableCellRenderer() {
|
column.setCellRenderer(new GTableCellRenderer() {
|
||||||
|
@ -164,15 +182,15 @@ public class BundleStatusProvider extends ComponentProviderAdapter {
|
||||||
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
|
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
|
||||||
JLabel c = (JLabel) super.getTableCellRendererComponent(data);
|
JLabel c = (JLabel) super.getTableCellRendererComponent(data);
|
||||||
|
|
||||||
BundlePath path = (BundlePath) data.getValue();
|
BundleStatus status = (BundleStatus) data.getValue();
|
||||||
if (!path.exists()) {
|
if (!status.pathExists()) {
|
||||||
c.setForeground(Color.RED);
|
c.setForeground(Color.RED);
|
||||||
}
|
}
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
GTableFilterPanel<BundlePath> filterPanel =
|
GTableFilterPanel<BundleStatus> filterPanel =
|
||||||
new GTableFilterPanel<>(bundleStatusTable, bundleStatusModel);
|
new GTableFilterPanel<>(bundleStatusTable, bundleStatusModel);
|
||||||
|
|
||||||
JScrollPane scrollPane = new JScrollPane(bundleStatusTable);
|
JScrollPane scrollPane = new JScrollPane(bundleStatusTable);
|
||||||
|
@ -272,6 +290,39 @@ public class BundleStatusProvider extends ComponentProviderAdapter {
|
||||||
bundleStatusTable.dispose();
|
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) {
|
void selectRow(int rowIndex) {
|
||||||
bundleStatusTable.selectRow(rowIndex);
|
bundleStatusTable.selectRow(rowIndex);
|
||||||
}
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -13,7 +13,7 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package ghidra.app.script.osgi;
|
package ghidra.app.plugin.core.osgi;
|
||||||
|
|
||||||
import org.osgi.framework.BundleActivator;
|
import org.osgi.framework.BundleActivator;
|
||||||
import org.osgi.framework.BundleContext;
|
import org.osgi.framework.BundleContext;
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ghidra.app.script.osgi;
|
package ghidra.app.plugin.core.osgi;
|
||||||
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@ -51,9 +51,9 @@ public class GhidraBundleException extends OSGiException {
|
||||||
BundleException be = (BundleException) e;
|
BundleException be = (BundleException) e;
|
||||||
switch (be.getType()) {
|
switch (be.getType()) {
|
||||||
default:
|
default:
|
||||||
case BundleException.UNSPECIFIED:
|
|
||||||
return "No exception type";
|
return "No exception type";
|
||||||
|
case BundleException.UNSPECIFIED:
|
||||||
|
return "UNSPECIFIED";
|
||||||
/**
|
/**
|
||||||
* The operation was unsupported. This type can be used anywhere a
|
* The operation was unsupported. This type can be used anywhere a
|
||||||
* BundleException can be thrown.
|
* BundleException can be thrown.
|
|
@ -13,7 +13,7 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package ghidra.app.script.osgi;
|
package ghidra.app.plugin.core.osgi;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
|
@ -22,20 +22,23 @@ import java.util.*;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.osgi.framework.Bundle;
|
import org.osgi.framework.Bundle;
|
||||||
|
import org.osgi.framework.BundleException;
|
||||||
import org.osgi.framework.wiring.BundleRequirement;
|
import org.osgi.framework.wiring.BundleRequirement;
|
||||||
|
|
||||||
|
import generic.io.NullPrintWriter;
|
||||||
import generic.jar.ResourceFile;
|
import generic.jar.ResourceFile;
|
||||||
|
import ghidra.app.plugin.core.osgi.BundleHost.BuildFailure;
|
||||||
import ghidra.app.script.GhidraScriptUtil;
|
import ghidra.app.script.GhidraScriptUtil;
|
||||||
import ghidra.app.script.ScriptInfo;
|
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.
|
* The SourceBundleInfo class is a cache of information for bundles built from source directories.
|
||||||
*/
|
*/
|
||||||
public class SourceBundleInfo {
|
public class GhidraSourceBundle implements GhidraBundle {
|
||||||
private final BundleHost bundleHost;
|
final private BundleHost bundleHost;
|
||||||
final private ResourceFile sourceDir;
|
final private ResourceFile sourceDir;
|
||||||
final String symbolicName;
|
|
||||||
|
final private String symbolicName;
|
||||||
final private Path binDir;
|
final private Path binDir;
|
||||||
final private String bundleLoc;
|
final private String bundleLoc;
|
||||||
boolean foundNewManifest;
|
boolean foundNewManifest;
|
||||||
|
@ -51,9 +54,9 @@ public class SourceBundleInfo {
|
||||||
|
|
||||||
// cached values parsed form @imports tags on default-package source files
|
// 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.bundleHost = bundleHost;
|
||||||
this.sourceDir = sourceDir;
|
this.sourceDir = sourceDirectory;
|
||||||
this.symbolicName = BundleHost.getSymbolicNameFromSourceDir(sourceDir);
|
this.symbolicName = BundleHost.getSymbolicNameFromSourceDir(sourceDir);
|
||||||
this.binDir = GhidraScriptUtil.getCompiledBundlesDir().resolve(symbolicName);
|
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) {
|
static public Path getBindirFromScriptFile(ResourceFile sourceFile) {
|
||||||
ResourceFile tmpSourceDir = sourceFile.getParentFile();
|
ResourceFile tmpSourceDir = sourceFile.getParentFile();
|
||||||
String tmpSymbolicName = BundleHost.getSymbolicNameFromSourceDir(tmpSourceDir);
|
String tmpSymbolicName = BundleHost.getSymbolicNameFromSourceDir(tmpSourceDir);
|
||||||
|
@ -88,6 +97,7 @@ public class SourceBundleInfo {
|
||||||
return binDir;
|
return binDir;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public Bundle install() throws GhidraBundleException {
|
public Bundle install() throws GhidraBundleException {
|
||||||
return bundleHost.installFromLoc(getBundleLoc());
|
return bundleHost.installFromLoc(getBundleLoc());
|
||||||
}
|
}
|
||||||
|
@ -105,10 +115,9 @@ public class SourceBundleInfo {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* update buildReqs based on \@imports tag in java files from the default package
|
* update buildReqs based on \@imports tag in java files from the default package
|
||||||
*
|
* @throws GhidraBundleException on failure to parse the \@imports tag
|
||||||
* @throws OSGiException 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
|
// parse metadata from all Java source in sourceDir
|
||||||
buildReqs.clear();
|
buildReqs.clear();
|
||||||
req2file.clear();
|
req2file.clear();
|
||||||
|
@ -119,7 +128,13 @@ public class SourceBundleInfo {
|
||||||
ScriptInfo si = GhidraScriptUtil.getScriptInfo(rf);
|
ScriptInfo si = GhidraScriptUtil.getScriptInfo(rf);
|
||||||
String imps = si.getImports();
|
String imps = si.getImports();
|
||||||
if (imps != null && !imps.isEmpty()) {
|
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);
|
buildReqs.put(rf, reqs);
|
||||||
for (BundleRequirement req : reqs) {
|
for (BundleRequirement req : reqs) {
|
||||||
req2file.computeIfAbsent(req.toString(), x -> new ArrayList<>()).add(rf);
|
req2file.computeIfAbsent(req.toString(), x -> new ArrayList<>()).add(rf);
|
||||||
|
@ -235,16 +250,75 @@ public class SourceBundleInfo {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public String getSummary() {
|
public String getSummary() {
|
||||||
return summary;
|
return summary;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Bundle getBundle() {
|
@Override
|
||||||
|
public Bundle getBundle() throws GhidraBundleException {
|
||||||
return bundleHost.getBundle(getBundleLoc());
|
return bundleHost.getBundle(getBundleLoc());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public String getBundleLoc() {
|
public String getBundleLoc() {
|
||||||
return bundleLoc;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -13,10 +13,10 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package ghidra.app.script.osgi;
|
package ghidra.app.plugin.core.osgi;
|
||||||
|
|
||||||
import ghidra.util.exception.UsrException;
|
import ghidra.util.exception.UsrException;
|
||||||
|
|
||||||
|
|
||||||
public class OSGiException extends UsrException {
|
public class OSGiException extends UsrException {
|
||||||
public OSGiException(String msg, Throwable cause) {
|
public OSGiException(String msg, Throwable cause) {
|
||||||
super(msg, cause);
|
super(msg, cause);
|
|
@ -1,10 +1,10 @@
|
||||||
package ghidra.app.script.osgi;
|
package ghidra.app.plugin.core.osgi;
|
||||||
|
|
||||||
import org.osgi.framework.Bundle;
|
import org.osgi.framework.Bundle;
|
||||||
|
|
||||||
public interface OSGiListener {
|
public interface OSGiListener {
|
||||||
|
|
||||||
void sourceBundleCompiled(SourceBundleInfo sbi);
|
void sourceBundleCompiled(GhidraSourceBundle sb);
|
||||||
|
|
||||||
void bundleActivationChange(Bundle b, boolean newActivation);
|
void bundleActivationChange(Bundle b, boolean newActivation);
|
||||||
}
|
}
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ghidra.app.script.osgi;
|
package ghidra.app.plugin.core.osgi;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Scanner;
|
import java.util.Scanner;
|
|
@ -29,7 +29,6 @@ import javax.swing.tree.TreePath;
|
||||||
import javax.swing.tree.TreeSelectionModel;
|
import javax.swing.tree.TreeSelectionModel;
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.osgi.framework.Bundle;
|
|
||||||
|
|
||||||
import docking.ActionContext;
|
import docking.ActionContext;
|
||||||
import docking.action.KeyBindingData;
|
import docking.action.KeyBindingData;
|
||||||
|
@ -40,10 +39,8 @@ import docking.widgets.tree.GTree;
|
||||||
import docking.widgets.tree.GTreeNode;
|
import docking.widgets.tree.GTreeNode;
|
||||||
import docking.widgets.tree.support.BreadthFirstIterator;
|
import docking.widgets.tree.support.BreadthFirstIterator;
|
||||||
import generic.jar.ResourceFile;
|
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.*;
|
||||||
import ghidra.app.script.osgi.BundleHost;
|
|
||||||
import ghidra.app.script.osgi.SourceBundleInfo;
|
|
||||||
import ghidra.app.services.ConsoleService;
|
import ghidra.app.services.ConsoleService;
|
||||||
import ghidra.framework.options.SaveState;
|
import ghidra.framework.options.SaveState;
|
||||||
import ghidra.framework.plugintool.ComponentProviderAdapter;
|
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());
|
super(plugin.getTool(), "Script Manager", plugin.getName());
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
this.bundleHost = bundleHost;
|
|
||||||
|
|
||||||
setHelpLocation(new HelpLocation(plugin.getName(), plugin.getName()));
|
setHelpLocation(new HelpLocation(plugin.getName(), plugin.getName()));
|
||||||
setIcon(ResourceManager.loadImage("images/play.png"));
|
setIcon(ResourceManager.loadImage("images/play.png"));
|
||||||
|
@ -116,7 +112,6 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
|
||||||
}
|
}
|
||||||
|
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
|
||||||
editorMap.clear();
|
editorMap.clear();
|
||||||
scriptCategoryTree.dispose();
|
scriptCategoryTree.dispose();
|
||||||
scriptTable.dispose();
|
scriptTable.dispose();
|
||||||
|
@ -138,7 +133,7 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void performRefresh() {
|
private void performRefresh() {
|
||||||
GhidraScriptUtil.setScriptBundlePaths(bundleStatusProvider.getModel().getPaths());
|
GhidraScriptUtil.setScriptBundlePaths(bundleStatusProvider.getModel().getEnabledPaths());
|
||||||
GhidraScriptUtil.clearMetadata();
|
GhidraScriptUtil.clearMetadata();
|
||||||
refresh();
|
refresh();
|
||||||
}
|
}
|
||||||
|
@ -341,7 +336,7 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<ResourceFile> getScriptDirectories() {
|
public List<ResourceFile> getScriptDirectories() {
|
||||||
return bundleStatusProvider.getModel().getPaths().stream().filter(
|
return bundleStatusProvider.getModel().getEnabledPaths().stream().filter(
|
||||||
ResourceFile::isDirectory).collect(Collectors.toList());
|
ResourceFile::isDirectory).collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -397,7 +392,7 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
|
||||||
}
|
}
|
||||||
|
|
||||||
void runScript(String scriptName, TaskListener listener) {
|
void runScript(String scriptName, TaskListener listener) {
|
||||||
List<ResourceFile> dirPaths = bundleStatusProvider.getModel().getPaths();
|
List<ResourceFile> dirPaths = bundleStatusProvider.getModel().getEnabledPaths();
|
||||||
for (ResourceFile dir : dirPaths) {
|
for (ResourceFile dir : dirPaths) {
|
||||||
ResourceFile scriptSource = new ResourceFile(dir, scriptName);
|
ResourceFile scriptSource = new ResourceFile(dir, scriptName);
|
||||||
if (scriptSource.exists()) {
|
if (scriptSource.exists()) {
|
||||||
|
@ -457,7 +452,8 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
|
||||||
console.addErrorMessage("", "Unable to instantiate script: " + scriptName);
|
console.addErrorMessage("", "Unable to instantiate script: " + scriptName);
|
||||||
}
|
}
|
||||||
catch (ClassNotFoundException e) {
|
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
|
// show the error icon
|
||||||
|
@ -537,7 +533,7 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
|
||||||
private void updateAvailableScriptFilesForAllPaths() {
|
private void updateAvailableScriptFilesForAllPaths() {
|
||||||
List<ResourceFile> scriptsToRemove = tableModel.getScripts();
|
List<ResourceFile> scriptsToRemove = tableModel.getScripts();
|
||||||
List<ResourceFile> scriptAccumulator = new ArrayList<>();
|
List<ResourceFile> scriptAccumulator = new ArrayList<>();
|
||||||
List<ResourceFile> bundlePaths = bundleStatusProvider.getModel().getPaths();
|
List<ResourceFile> bundlePaths = bundleStatusProvider.getModel().getEnabledPaths();
|
||||||
for (ResourceFile bundlePath : bundlePaths) {
|
for (ResourceFile bundlePath : bundlePaths) {
|
||||||
if (bundlePath.isDirectory()) {
|
if (bundlePath.isDirectory()) {
|
||||||
updateAvailableScriptFilesForDirectory(scriptsToRemove, scriptAccumulator,
|
updateAvailableScriptFilesForDirectory(scriptsToRemove, scriptAccumulator,
|
||||||
|
@ -732,56 +728,8 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
|
||||||
return true;
|
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() {
|
private void build() {
|
||||||
bundleStatusProvider =
|
bundleStatusProvider = new BundleStatusProvider(plugin.getTool(), plugin.getName());
|
||||||
new BundleStatusProvider(plugin.getTool(), plugin.getName(), bundleHost);
|
|
||||||
|
|
||||||
bundleStatusProvider.getModel().addListener(new BundleStatusListener() {
|
bundleStatusProvider.getModel().addListener(new BundleStatusListener() {
|
||||||
@Override
|
@Override
|
||||||
|
@ -791,20 +739,11 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void bundleEnablementChanged(BundlePath path, boolean enabled) {
|
public void bundleEnablementChanged(BundleStatus status, boolean enabled) {
|
||||||
if (!enabled && path.isActive()) {
|
if (status.isDirectory()) {
|
||||||
startActivateDeactiveTask(path, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (path.isDirectory()) {
|
|
||||||
performRefresh();
|
performRefresh();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void bundleActivationChanged(BundlePath path, boolean newValue) {
|
|
||||||
startActivateDeactiveTask(path, newValue);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
scriptRoot = new RootNode();
|
scriptRoot = new RootNode();
|
||||||
|
@ -1075,7 +1014,7 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
|
||||||
bundleStatusProvider.getModel().restoreState(saveState);
|
bundleStatusProvider.getModel().restoreState(saveState);
|
||||||
|
|
||||||
// pull in the just-loaded paths
|
// pull in the just-loaded paths
|
||||||
List<ResourceFile> paths = bundleStatusProvider.getModel().getPaths();
|
List<ResourceFile> paths = bundleStatusProvider.getModel().getEnabledPaths();
|
||||||
GhidraScriptUtil.setScriptBundlePaths(paths);
|
GhidraScriptUtil.setScriptBundlePaths(paths);
|
||||||
actionManager.restoreUserDefinedKeybindings(saveState);
|
actionManager.restoreUserDefinedKeybindings(saveState);
|
||||||
actionManager.restoreScriptsThatAreInTool(saveState);
|
actionManager.restoreScriptsThatAreInTool(saveState);
|
||||||
|
@ -1213,7 +1152,7 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
|
||||||
|
|
||||||
public List<ResourceFile> getWritableScriptDirectories() {
|
public List<ResourceFile> getWritableScriptDirectories() {
|
||||||
BundleStatusModel m = bundleStatusProvider.getModel();
|
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());
|
m::isWriteable).collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,6 @@ import ghidra.app.plugin.ProgramPlugin;
|
||||||
import ghidra.app.plugin.core.eclipse.EclipseConnection;
|
import ghidra.app.plugin.core.eclipse.EclipseConnection;
|
||||||
import ghidra.app.plugin.core.eclipse.EclipseIntegrationOptionsPlugin;
|
import ghidra.app.plugin.core.eclipse.EclipseIntegrationOptionsPlugin;
|
||||||
import ghidra.app.script.GhidraState;
|
import ghidra.app.script.GhidraState;
|
||||||
import ghidra.app.script.osgi.BundleHost;
|
|
||||||
import ghidra.app.services.*;
|
import ghidra.app.services.*;
|
||||||
import ghidra.framework.options.SaveState;
|
import ghidra.framework.options.SaveState;
|
||||||
import ghidra.framework.options.ToolOptions;
|
import ghidra.framework.options.ToolOptions;
|
||||||
|
@ -51,13 +50,10 @@ public class GhidraScriptMgrPlugin extends ProgramPlugin implements GhidraScript
|
||||||
|
|
||||||
final private GhidraScriptComponentProvider provider;
|
final private GhidraScriptComponentProvider provider;
|
||||||
|
|
||||||
final private BundleHost bundleHost;
|
|
||||||
|
|
||||||
public GhidraScriptMgrPlugin(PluginTool tool) {
|
public GhidraScriptMgrPlugin(PluginTool tool) {
|
||||||
super(tool, true, true, true);
|
super(tool, true, true, true);
|
||||||
|
|
||||||
bundleHost = BundleHost.getInstance();
|
provider = new GhidraScriptComponentProvider(this);
|
||||||
provider = new GhidraScriptComponentProvider(this, bundleHost);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -169,7 +169,7 @@ public class GhidraScriptUtil {
|
||||||
* @deprecated accessing class file directly precludes OSGi wiring according to requirements and capabilities
|
* @deprecated accessing class file directly precludes OSGi wiring according to requirements and capabilities
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public static List<ResourceFile> getExplodedBundlePaths() {
|
public static List<ResourceFile> getExplodedCompiledSourceBundlePaths() {
|
||||||
try {
|
try {
|
||||||
return Files.list(getOsgiDir()).filter(Files::isDirectory).map(
|
return Files.list(getOsgiDir()).filter(Files::isDirectory).map(
|
||||||
x -> new ResourceFile(x.toFile())).collect(Collectors.toList());
|
x -> new ResourceFile(x.toFile())).collect(Collectors.toList());
|
||||||
|
|
|
@ -16,21 +16,20 @@
|
||||||
package ghidra.app.script;
|
package ghidra.app.script;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
|
||||||
|
|
||||||
import org.osgi.framework.Bundle;
|
import org.osgi.framework.Bundle;
|
||||||
|
|
||||||
import generic.jar.ResourceFile;
|
import generic.jar.ResourceFile;
|
||||||
import ghidra.app.script.osgi.*;
|
import ghidra.app.plugin.core.osgi.*;
|
||||||
import ghidra.util.Msg;
|
import ghidra.util.Msg;
|
||||||
|
|
||||||
public class JavaScriptProvider extends GhidraScriptProvider {
|
public class JavaScriptProvider extends GhidraScriptProvider {
|
||||||
static public SourceBundleInfo getBundleInfoForSource(ResourceFile sourceFile) {
|
static public GhidraSourceBundle getBundleForSource(ResourceFile sourceFile) {
|
||||||
ResourceFile sourceDir = getSourceDirectoryContaining(sourceFile);
|
ResourceFile sourceDir = getSourceDirectoryContaining(sourceFile);
|
||||||
if (sourceDir == null) {
|
if (sourceDir == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return BundleHost.getInstance().getSourceBundleInfo(sourceDir);
|
return (GhidraSourceBundle) BundleHost.getInstance().getGhidraBundle(sourceDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -45,24 +44,23 @@ public class JavaScriptProvider extends GhidraScriptProvider {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean deleteScript(ResourceFile sourceFile) {
|
public boolean deleteScript(ResourceFile sourceFile) {
|
||||||
Bundle b = getBundleInfoForSource(sourceFile).getBundle();
|
|
||||||
if (b != null) {
|
|
||||||
try {
|
try {
|
||||||
|
Bundle b = getBundleForSource(sourceFile).getBundle();
|
||||||
|
if (b != null) {
|
||||||
BundleHost.getInstance().deactivateSynchronously(b);
|
BundleHost.getInstance().deactivateSynchronously(b);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
catch (GhidraBundleException | InterruptedException e) {
|
catch (GhidraBundleException | InterruptedException e) {
|
||||||
e.printStackTrace();
|
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 false;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return super.deleteScript(sourceFile);
|
return super.deleteScript(sourceFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public GhidraScript getScriptInstance(ResourceFile sourceFile, PrintWriter writer)
|
public GhidraScript getScriptInstance(ResourceFile sourceFile, PrintWriter writer)
|
||||||
throws ClassNotFoundException, InstantiationException, IllegalAccessException {
|
throws ClassNotFoundException, InstantiationException, IllegalAccessException {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Class<?> clazz = loadClass(sourceFile, writer);
|
Class<?> clazz = loadClass(sourceFile, writer);
|
||||||
Object object;
|
Object object;
|
||||||
|
@ -80,25 +78,21 @@ public class JavaScriptProvider extends GhidraScriptProvider {
|
||||||
return null; // class is not GhidraScript
|
return null; // class is not GhidraScript
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (OSGiException | IOException | InterruptedException | IllegalArgumentException
|
catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
|
||||||
| InvocationTargetException | NoSuchMethodException | SecurityException e) {
|
throw e;
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
throw new ClassNotFoundException("", e);
|
throw new ClassNotFoundException("", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static public Class<?> loadClass(ResourceFile sourceFile, PrintWriter writer)
|
static public Class<?> loadClass(ResourceFile sourceFile, PrintWriter writer) throws Exception {
|
||||||
throws IOException, OSGiException, ClassNotFoundException, InterruptedException {
|
|
||||||
|
|
||||||
BundleHost bundleHost = BundleHost.getInstance();
|
BundleHost bundleHost = BundleHost.getInstance();
|
||||||
|
|
||||||
SourceBundleInfo bi = getBundleInfoForSource(sourceFile);
|
GhidraSourceBundle bi = getBundleForSource(sourceFile);
|
||||||
bundleHost.compileSourceBundle(bi, writer);
|
bi.build(writer);
|
||||||
|
Bundle b = bi.install();
|
||||||
// as much source as possible built, install bundle and start it if necessary
|
|
||||||
Bundle b = bi.getBundle();
|
|
||||||
if (b == null) {
|
|
||||||
b = bi.install();
|
|
||||||
}
|
|
||||||
bundleHost.activateSynchronously(b);
|
bundleHost.activateSynchronously(b);
|
||||||
|
|
||||||
String classname = bi.classNameForScript(sourceFile);
|
String classname = bi.classNameForScript(sourceFile);
|
||||||
|
|
|
@ -48,7 +48,7 @@ import generic.jar.ResourceFile;
|
||||||
import generic.test.TestUtils;
|
import generic.test.TestUtils;
|
||||||
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
|
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
|
||||||
import ghidra.app.plugin.core.console.ConsoleComponentProvider;
|
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.script.*;
|
||||||
import ghidra.app.services.ConsoleService;
|
import ghidra.app.services.ConsoleService;
|
||||||
import ghidra.framework.Application;
|
import ghidra.framework.Application;
|
||||||
|
@ -979,7 +979,7 @@ public abstract class AbstractGhidraScriptMgrPluginTest
|
||||||
// destroy any NewScriptxxx files...and Temp ones too
|
// destroy any NewScriptxxx files...and Temp ones too
|
||||||
BundleStatusProvider bundleStatusProvider =
|
BundleStatusProvider bundleStatusProvider =
|
||||||
(BundleStatusProvider) TestUtils.getInstanceField("bundleStatusProvider", provider);
|
(BundleStatusProvider) TestUtils.getInstanceField("bundleStatusProvider", provider);
|
||||||
List<ResourceFile> paths = bundleStatusProvider.getModel().getPaths();
|
List<ResourceFile> paths = bundleStatusProvider.getModel().getEnabledPaths();
|
||||||
for (ResourceFile path : paths) {
|
for (ResourceFile path : paths) {
|
||||||
File file = path.getFile(false);
|
File file = path.getFile(false);
|
||||||
File[] listFiles = file.listFiles();
|
File[] listFiles = file.listFiles();
|
||||||
|
|
|
@ -26,9 +26,9 @@ import org.junit.Test;
|
||||||
import docking.widgets.table.SelectionManager;
|
import docking.widgets.table.SelectionManager;
|
||||||
import generic.jar.ResourceFile;
|
import generic.jar.ResourceFile;
|
||||||
import generic.test.AbstractGenericTest;
|
import generic.test.AbstractGenericTest;
|
||||||
|
import ghidra.app.plugin.core.osgi.GhidraSourceBundle;
|
||||||
import ghidra.app.script.GhidraScriptUtil;
|
import ghidra.app.script.GhidraScriptUtil;
|
||||||
import ghidra.app.script.JavaScriptProvider;
|
import ghidra.app.script.JavaScriptProvider;
|
||||||
import ghidra.app.script.osgi.SourceBundleInfo;
|
|
||||||
import ghidra.test.ScriptTaskListener;
|
import ghidra.test.ScriptTaskListener;
|
||||||
import ghidra.util.TaskUtilities;
|
import ghidra.util.TaskUtilities;
|
||||||
import utilities.util.FileUtilities;
|
import utilities.util.FileUtilities;
|
||||||
|
@ -149,7 +149,7 @@ public class GhidraScriptMgrPlugin2Test extends AbstractGhidraScriptMgrPluginTes
|
||||||
|
|
||||||
// remove all class files from the user script bin dir
|
// remove all class files from the user script bin dir
|
||||||
File userScriptsBinDir =
|
File userScriptsBinDir =
|
||||||
SourceBundleInfo.getBindirFromScriptFile(new ResourceFile(newScriptFile)).toFile();
|
GhidraSourceBundle.getBindirFromScriptFile(new ResourceFile(newScriptFile)).toFile();
|
||||||
File[] userScriptBinDirFiles;
|
File[] userScriptBinDirFiles;
|
||||||
if (userScriptsBinDir.exists()) {
|
if (userScriptsBinDir.exists()) {
|
||||||
userScriptBinDirFiles = userScriptsBinDir.listFiles(classFileFilter);
|
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
|
// verify that the generated class file is placed in the default scripting home/bin
|
||||||
File userScriptsBinDir =
|
File userScriptsBinDir =
|
||||||
SourceBundleInfo.getBindirFromScriptFile(systemScriptFile).toFile();
|
GhidraSourceBundle.getBindirFromScriptFile(systemScriptFile).toFile();
|
||||||
String className = scriptName.replace(".java", ".class");
|
String className = scriptName.replace(".java", ".class");
|
||||||
File expectedClassFile = new File(userScriptsBinDir, className);
|
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
|
// verify a bin dir was created and that the class file is in it
|
||||||
File binDir =
|
File binDir =
|
||||||
SourceBundleInfo.getBindirFromScriptFile(new ResourceFile(newScriptFile)).toFile();
|
GhidraSourceBundle.getBindirFromScriptFile(new ResourceFile(newScriptFile)).toFile();
|
||||||
assertTrue("bin output dir not created", binDir.exists());
|
assertTrue("bin output dir not created", binDir.exists());
|
||||||
|
|
||||||
File scriptClassFile = new File(binDir, rawScriptName + ".class");
|
File scriptClassFile = new File(binDir, rawScriptName + ".class");
|
||||||
|
|
|
@ -30,7 +30,7 @@ import docking.action.DockingActionIf;
|
||||||
import docking.widgets.filter.FilterTextField;
|
import docking.widgets.filter.FilterTextField;
|
||||||
import docking.widgets.list.ListPanel;
|
import docking.widgets.list.ListPanel;
|
||||||
import generic.jar.ResourceFile;
|
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.GhidraScriptUtil;
|
||||||
import ghidra.app.script.JavaScriptProvider;
|
import ghidra.app.script.JavaScriptProvider;
|
||||||
import ghidra.util.StringUtilities;
|
import ghidra.util.StringUtilities;
|
||||||
|
|
|
@ -138,7 +138,7 @@ public class GhidraPythonInterpreter extends InteractiveInterpreter {
|
||||||
systemState.path.append(Py.newString(resourceFile.getFile(false).getAbsolutePath()));
|
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()));
|
systemState.path.append(Py.newString(resourceFile.getFile(false).getAbsolutePath()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,8 +27,8 @@ import docking.widgets.tree.GTree;
|
||||||
import docking.widgets.tree.GTreeNode;
|
import docking.widgets.tree.GTreeNode;
|
||||||
import generic.jar.ResourceFile;
|
import generic.jar.ResourceFile;
|
||||||
import ghidra.app.plugin.core.console.ConsoleComponentProvider;
|
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.*;
|
||||||
import ghidra.app.plugin.core.script.osgi.BundleStatusProvider;
|
|
||||||
import ghidra.app.script.GhidraScriptUtil;
|
import ghidra.app.script.GhidraScriptUtil;
|
||||||
import ghidra.app.services.ConsoleService;
|
import ghidra.app.services.ConsoleService;
|
||||||
import ghidra.util.HelpLocation;
|
import ghidra.util.HelpLocation;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue