Candidate release of source code.

This commit is contained in:
Dan 2019-03-26 13:45:32 -04:00
parent db81e6b3b0
commit 79d8f164f8
12449 changed files with 2800756 additions and 16 deletions

View file

@ -0,0 +1,296 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.awt.GraphicsEnvironment;
import java.io.*;
import java.text.ParseException;
import java.util.List;
import javax.swing.JFileChooser;
import ghidra.launch.*;
import ghidra.launch.JavaFinder.JavaFilter;
/**
* Tool that helps gather information needed to launch Ghidra/GhidraServer. This is intended
* to be a helper for the launch scripts so that most of the "heavy-lifting" can be done in Java
* rather than in OS-specific scripts.
*/
public class LaunchSupport {
private static final int EXIT_SUCCESS = 0;
private static final int EXIT_FAILURE = 1;
/**
* {@link LaunchSupport} entry point. Uses standard exit codes to tell the user if
* the desired operation succeeded for failed.
*
* @param args [INSTALL_DIR] [-java_home | -jdk_home | -vmargs] [-ask | -save]
* <ul>
* <li><b>-java_home: </b> Get Java home (JDK or JRE)</li>
* <li><b>-jdk_home: </b> Get Java home (JDK only)</li>
* <li><b>-vmargs: </b> Get JVM arguments</li>
* <li><b>-ask: </b> Interactively ask the user to choose a Java home</li>
* <li><b>-save: </b> Save Java home to file for future use</li>
* </ul>
*/
public static void main(String[] args) {
int exitCode = EXIT_FAILURE; // failure by default
// Validate command line arguments
if (args.length < 2 || args.length > 4) {
System.err.println("LaunchSupport expected 2 to 4 arguments but got " + args.length);
System.exit(exitCode);
}
// Parse command line arguments
String installDirPath = args[0];
String mode = args[1];
boolean ask = false;
boolean save = false;
for (int i = 2; i < args.length; i++) {
if (args[i].equals("-ask")) {
ask = true;
}
else if (args[i].equals("-save")) {
save = true;
}
else {
System.err.println("LaunchSupport received illegal argument: " + args[i]);
System.exit(exitCode);
}
}
try {
File installDir = new File(installDirPath).getCanonicalFile(); // change relative path to absolute
JavaConfig javaConfig = new JavaConfig(installDir);
JavaFinder javaFinder = JavaFinder.create();
// Pass control to a mode-specific handler
switch (mode.toLowerCase()) {
case "-java_home":
exitCode = handleJavaHome(javaConfig, javaFinder, JavaFilter.ANY, ask, save);
break;
case "-jdk_home":
exitCode =
handleJavaHome(javaConfig, javaFinder, JavaFilter.JDK_ONLY, ask, save);
break;
case "-vmargs":
exitCode = handleVmArgs(javaConfig);
break;
default:
System.err.println("LaunchSupport received illegal argument: " + mode);
break;
}
}
catch (Exception e) {
System.err.println(e.getMessage());
}
System.exit(exitCode);
}
/**
* Handles figuring out a Java home directory to use for the launch. If it is successfully
* determined, an exit code that indicates success is returned.
*
* @param javaConfig The Java configuration that defines what we support.
* @param javaFinder The Java finder.
* @param javaFilter A filter used to restrict what kind of Java installations we search for.
* @param ask True to interact with the user to they can specify a Java home directory.
* False if the Java home directory should be searched for and output on STDOUT once
* discovered.
* @param save True if the determined Java home directory should get saved to a file.
* @return A suggested exit code based on whether or not a Java home directory was
* successfully determined.
* @throws IOException if there was a disk-related problem.
*/
private static int handleJavaHome(JavaConfig javaConfig, JavaFinder javaFinder,
JavaFilter javaFilter, boolean ask, boolean save) throws IOException {
if (ask) {
return askJavaHome(javaConfig, javaFinder, javaFilter);
}
return findJavaHome(javaConfig, javaFinder, javaFilter, save);
}
/**
* Handles finding a Java home directory to use for the launch. If one is successfully
* found, its path is printed to STDOUT and an exit code that indicates success is
* returned. Otherwise, nothing is printed to STDOUT and an error exit code is returned.
*
* @param javaConfig The Java configuration that defines what we support.
* @param javaFinder The Java finder.
* @param javaFilter A filter used to restrict what kind of Java installations we search for.
* @param save True if the determined Java home directory should get saved to a file.
* @return A suggested exit code based on whether or not a supported Java home directory was
* successfully determined.
* @throws IOException if there was a problem saving the java home to disk.
*/
private static int findJavaHome(JavaConfig javaConfig, JavaFinder javaFinder,
JavaFilter javaFilter, boolean save) throws IOException {
File javaHomeDir;
LaunchProperties launchProperties = javaConfig.getLaunchProperties();
// PRIORITY 1: JAVA_HOME_OVERRIDE property
// If a valid java home override is specified in the launch properties, use that.
// Someone presumably wants to force that specific version.
javaHomeDir = launchProperties.getJavaHomeOverride();
if (javaConfig.isSupportedJavaHomeDir(javaHomeDir, javaFilter)) {
if (save) {
javaConfig.saveJavaHome(javaHomeDir);
}
System.out.println(javaHomeDir);
return EXIT_SUCCESS;
}
// PRIORITY 2: Java on PATH
// This program (LaunchSupport) was started with the Java on the PATH. Try to use this one
// next because it is most likely the one that is being upgraded on the user's system.
javaHomeDir = javaFinder.findSupportedJavaHomeFromCurrentJavaHome(javaConfig, javaFilter);
if (javaHomeDir != null) {
if (save) {
javaConfig.saveJavaHome(javaHomeDir);
}
System.out.println(javaHomeDir);
return EXIT_SUCCESS;
}
// PRIORITY 3: Last used Java
// Check to see if a prior launch resulted in that Java being saved. If so, try to use that.
javaHomeDir = javaConfig.getSavedJavaHome();
if (javaConfig.isSupportedJavaHomeDir(javaHomeDir, javaFilter)) {
System.out.println(javaHomeDir);
return EXIT_SUCCESS;
}
// PRIORITY 4: Find all supported Java installations, and use the newest.
List<File> javaHomeDirs =
javaFinder.findSupportedJavaHomeFromInstallations(javaConfig, javaFilter);
if (!javaHomeDirs.isEmpty()) {
javaHomeDir = javaHomeDirs.iterator().next();
if (save) {
javaConfig.saveJavaHome(javaHomeDir);
}
System.out.println(javaHomeDir);
return EXIT_SUCCESS;
}
return EXIT_FAILURE;
}
/**
* Handles interacting with the user to choose a Java home directory to use for the launch.
* If a valid Java home directory was successfully determined, it is saved to the the user's
* Java home save file, and an exit code that indicates success is returned.
*
* @param javaConfig The Java configuration that defines what we support.
* @param javaFinder The Java finder.
* @param javaFilter A filter used to restrict what kind of Java installations we search for.
* * @return A suggested exit code based on whether or not a valid Java home directory was
* successfully chosen.
* @throws IOException if there was a problem interacting with the user, or saving the java
* home location to disk.
*/
private static int askJavaHome(JavaConfig javaConfig, JavaFinder javaFinder,
JavaFilter javaFilter) throws IOException {
String javaName = javaFilter.equals(JavaFilter.JDK_ONLY) ? "JDK" : "Java";
String javaRange;
int min = javaConfig.getMinSupportedJava();
int max = javaConfig.getMaxSupportedJava();
if (min == max) {
javaRange = min + "";
}
else if (max == 0) {
javaRange = min + "+";
}
else {
javaRange = min + "-" + max;
}
System.out.println("*******************************************************");
System.out.println(
javaName + " " + javaRange + " could not be found and must be manually chosen!");
System.out.println("*******************************************************");
File javaHomeDir = null;
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
while (true) {
boolean supportsDialog =
!GraphicsEnvironment.isHeadless() && !(javaFinder instanceof MacJavaFinder);
System.out.print("Enter path to " + javaName + " home directory");
System.out.print(supportsDialog ? " (ENTER for dialog): " : ": ");
String line = in.readLine().trim();
if (supportsDialog && line.isEmpty()) {
System.out.println("Opening selection dialog...");
JFileChooser chooser = new JFileChooser();
chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
chooser.setDialogTitle("Choose a " + javaName + " home directory");
if (chooser.showOpenDialog(null) == JFileChooser.APPROVE_OPTION) {
javaHomeDir = chooser.getSelectedFile();
}
}
else if (!line.isEmpty()) {
javaHomeDir = new File(line);
}
else {
continue;
}
try {
JavaVersion javaVersion = javaConfig.getJavaVersion(javaHomeDir, javaFilter);
if (javaConfig.isJavaVersionSupported(javaVersion)) {
break;
}
System.out.println(
"Java version " + javaVersion + " is outside of supported range: [" +
javaRange + "]");
}
catch (FileNotFoundException e) {
System.out.println(
"Not a valid " + javaName + " home directory. " + e.getMessage() + "!");
}
catch (IOException | ParseException e) {
System.out.println("Failed to verify Java version. " + e.getMessage() + "!");
}
}
File javaHomeSaveFile = javaConfig.saveJavaHome(javaHomeDir);
System.out.println("Saved changes to " + javaHomeSaveFile);
return EXIT_SUCCESS;
}
/**
* Handles getting the VM arguments. If they are successfully determined, they are printed
* to STDOUT as a string that can be added to the command line, and an exit code that
* indicates success is returned.
* @param javaConfig The Java configuration that defines what we support.
* @return A suggested exit code based on whether or not the VM arguments were successfully
* gotten.
*/
private static int handleVmArgs(JavaConfig javaConfig) {
if (javaConfig.getLaunchProperties() == null) {
System.out.println("Launch properties file was not specified!");
return EXIT_FAILURE;
}
System.out.println(javaConfig.getLaunchProperties().getVmArgs());
return EXIT_SUCCESS;
}
}

View file

@ -0,0 +1,377 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.launch;
import java.io.*;
import java.text.ParseException;
import java.util.Properties;
import ghidra.launch.JavaFinder.JavaFilter;
/**
* Class to determine and represent a required Java configuration, including minimum and maximum
* supported versions, compiler compliance level, etc.
*/
public class JavaConfig {
private static final String LAUNCH_PROPERTIES_NAME = "launch.properties";
private static final String JAVA_HOME_SAVE_NAME = "java_home.save";
private LaunchProperties launchProperties;
private File javaHomeSaveFile;
private String applicationName;
private String applicationVersion;
private int minSupportedJava;
private int maxSupportedJava;
private String compilerComplianceLevel;
/**
* Creates a new Java configuration for the given installation.
*
* @param installDir The installation directory.
* @throws FileNotFoundException if a required file was not found.
* @throws IOException if there was a problem reading a required file.
* @throws ParseException if there was a problem parsing a required file.
*/
public JavaConfig(File installDir) throws FileNotFoundException, IOException, ParseException {
initApplicationProperties(installDir);
initLaunchProperties(installDir);
initJavaHomeSaveFile(installDir);
}
/**
* Gets the launch properties associated with this Java configuration. Certain aspects of the
* Java configuration are stored in the launch properties.
*
* @return The launch properties associated with this Java configuration. Could be null if
* this Java configuration does not use launch properties.
*/
public LaunchProperties getLaunchProperties() {
return launchProperties;
}
/**
* Gets the Java configuration's minimum supported major Java version.
*
* @return The Java configuration's minimum supported major Java version.
*/
public int getMinSupportedJava() {
return minSupportedJava;
}
/**
* Gets the Java configuration's maximum supported major Java version.
*
* @return The Java configuration's maximum supported major Java version. If there is no
* restriction, the value will be 0.
*/
public int getMaxSupportedJava() {
return maxSupportedJava;
}
/**
* Gets the Java configuration's compiler compliance level that was used to build the
* associated installation.
*
* @return The Java configuration's compiler compliance level.
*/
public String getCompilerComplianceLevel() {
return compilerComplianceLevel;
}
/**
* Gets the Java home directory from the user's Java home save file.
*
* @return The Java home directory from the user's Java home save file, or null if the file
* does not exist or is empty.
* @throws IOException if there was a problem reading the Java home save file.
*/
public File getSavedJavaHome() throws IOException {
try (BufferedReader reader = new BufferedReader(new FileReader(javaHomeSaveFile))) {
String line = reader.readLine().trim();
if (line != null && !line.isEmpty()) {
return new File(line);
}
}
catch (FileNotFoundException e) {
// Fall through to return null
}
return null;
}
/**
* Saves the given Java home directory to the user's Java home save file. If the save
* file does not exist, it will be created.
*
* @param javaHomeDir The Java home directory to save.
* @return The user's Java home save file.
* @throws IOException if there was a problem saving to the file.
*/
public File saveJavaHome(File javaHomeDir) throws IOException {
if (!javaHomeSaveFile.getParentFile().exists() &&
!javaHomeSaveFile.getParentFile().mkdirs()) {
throw new IOException(
"Failed to create directory: " + javaHomeSaveFile.getParentFile());
}
try (PrintWriter writer = new PrintWriter(new FileWriter(javaHomeSaveFile))) {
writer.println(javaHomeDir);
}
return javaHomeSaveFile;
}
/**
* Tests to see if the given directory is a supported Java home directory for this Java
* configuration.
*
* @param dir The directory to test.
* @param javaFilter A filter used to restrict what kind of Java installations we support.
* @return True if the given directory is a supported Java home directory for this Java
* configuration.
*/
public boolean isSupportedJavaHomeDir(File dir, JavaFilter javaFilter) {
try {
return isJavaVersionSupported(getJavaVersion(dir, javaFilter));
}
catch (IOException | ParseException e) {
return false;
}
}
/**
* Tests to see if the given Java version is supported by this Java launch configuration.
*
* @param javaVersion The java version to check.
* @return True if the given Java version is supported by this Java launch configuration.
*/
public boolean isJavaVersionSupported(JavaVersion javaVersion) {
int major = javaVersion.getMajor();
return major >= minSupportedJava &&
(maxSupportedJava == 0 || major <= maxSupportedJava);
}
/**
* Gets the Java version of the given Java home directory.
*
* @param javaHomeDir The Java home directory to get the version of.
* @param javaFilter A filter used to restrict what kind of Java installations we support.
* @return The Java version of the given Java home directory.
* @throws FileNotFoundException if the given directory is missing a required Java file
* or directory based on the provided filter. The exception's message will have more
* details.
* @throws IOException if there was a problem executing the java executable with the
* "-version" argument.
* @throws ParseException if the version string failed to parse.
*/
public JavaVersion getJavaVersion(File javaHomeDir, JavaFilter javaFilter)
throws FileNotFoundException, IOException, ParseException {
if (javaHomeDir == null) {
throw new FileNotFoundException("Directory not specified");
}
if (!javaHomeDir.isDirectory()) {
throw new FileNotFoundException("Not a directory");
}
File binDir = new File(javaHomeDir, "bin");
if (!binDir.isDirectory()) {
throw new FileNotFoundException("Missing bin directory");
}
File javaExecutable = null;
File javacExecutable = null;
for (File f : binDir.listFiles()) {
if (f.getName().equals("java") || f.getName().equals("java.exe")) {
javaExecutable = f;
}
if (f.getName().equals("javac") || f.getName().equals("javac.exe")) {
javacExecutable = f;
}
}
if (javaExecutable == null) {
throw new FileNotFoundException("Missing java executable");
}
if (javaFilter.equals(JavaFilter.JDK_ONLY) && javacExecutable == null) {
throw new FileNotFoundException("JDK is missing javac executable");
}
if (javaFilter.equals(JavaFilter.JRE_ONLY) && javacExecutable != null) {
throw new FileNotFoundException("JRE should not have javac executable");
}
return runAndGetJavaVersion(javaExecutable);
}
/**
* Gets the version of the given Java executable from the output of running "java -version".
*
* @param javaExecutable The Java executable to run and get the version of.
* @return The version of the given Java executable.
* @throws IOException if there was a problem executing the given Java executable with the
* "-version" argument.
* @throws ParseException if the version string failed to parse.
*/
private JavaVersion runAndGetJavaVersion(File javaExecutable)
throws ParseException, IOException {
Runtime rt = Runtime.getRuntime();
Process proc = rt.exec(new String[] { javaExecutable.getAbsolutePath(), "-version" });
try (BufferedReader reader =
new BufferedReader(new InputStreamReader(proc.getErrorStream()))) {
String line;
while ((line = reader.readLine()) != null) {
line = line.trim();
// If the _JAVA_OPTIONS or JAVA_TOOL_OPTIONS environment variables are set, STDERR
// will start with "Picked up..." lines that need to be ignored so we can get to the
// java version line.
if (line.startsWith("Picked up")) {
continue;
}
String[] parts = line.split("\\s");
if (parts.length < 3) {
throw new ParseException("Failed to parse version: " + line, 0);
}
return new JavaVersion(parts[2]);
}
throw new ParseException("Failed to find Java version", 0);
}
}
/**
* Initializes the required application properties for the given installation.
*
* @param installDir The Ghidra installation directory. This is the directory that has the
* "Ghidra" subdirectory in it.
* @throws FileNotFoundException if the application.properties file was not found.
* @throws IOException if there was a problem reading the application.properties file.
* @throws ParseException if there was a problem parsing the required application properties.
*/
private void initApplicationProperties(File installDir) throws FileNotFoundException, IOException, ParseException {
File applicationPropertiesFile = new File(installDir, "Ghidra/application.properties");
if (!applicationPropertiesFile.isFile()) {
throw new FileNotFoundException(
"Application properties file does not exist: " + applicationPropertiesFile);
}
Properties applicationProperties = new Properties();
try (FileInputStream fin = new FileInputStream(applicationPropertiesFile)) {
applicationProperties.load(fin);
}
// Required properties
applicationName = getDefinedProperty(applicationProperties, "application.name");
applicationVersion = getDefinedProperty(applicationProperties, "application.version");
compilerComplianceLevel =
getDefinedProperty(applicationProperties, "application.java.compiler");
try {
minSupportedJava =
Integer.parseInt(getDefinedProperty(applicationProperties, "application.java.min"));
}
catch (NumberFormatException e) {
throw new ParseException(
"Failed to parse application's minimum supported Java major verison", 0);
}
// Optional properties
String max = applicationProperties.getProperty("application.java.max");
if (max != null && !max.isEmpty()) {
try {
maxSupportedJava = Integer.parseInt(max);
}
catch (NumberFormatException e) {
throw new ParseException(
"Failed to parse application's maximum supported Java major verison", 0);
}
}
else {
maxSupportedJava = 0;
}
}
/**
* Initializes the launch properties for the given installation.
*
* @param installDir The Ghidra installation directory. This is the directory that has the
* "Ghidra" subdirectory in it.
* @throws FileNotFoundException if the given launch properties file does not exist.
* @throws IOException if there was a problem reading the given launch properties file.
* @throws ParseException if there was a problem parsing the given launch properties file.
*/
private void initLaunchProperties(File installDir)
throws FileNotFoundException, IOException, ParseException {
boolean isDev = new File(installDir, "build.gradle").isFile();
// Get the required launch properties file
File launchPropertiesFile = new File(installDir,
(isDev ? "Ghidra/RuntimeScripts/Common/" : "") + "support/" + LAUNCH_PROPERTIES_NAME);
if (!launchPropertiesFile.isFile()) {
throw new FileNotFoundException(
"Launch properties file does not exist: " + launchPropertiesFile);
}
launchProperties = new LaunchProperties(launchPropertiesFile);
}
/**
* Initializes the Java home save file.
*
* @param installDir The Ghidra installation directory. This is the directory that has the
* "Ghidra" subdirectory in it.
* @throws FileNotFoundException if the user's home directory was not found.
*/
private void initJavaHomeSaveFile(File installDir) throws FileNotFoundException {
boolean isDev = new File(installDir, "build.gradle").isFile();
// Ensure there is a user home directory (there definitely should be)
String userHomeDirPath = System.getProperty("user.home");
if (userHomeDirPath == null || userHomeDirPath.isEmpty()) {
throw new FileNotFoundException("User home directory is not known.");
}
File userHomeDir = new File(userHomeDirPath);
if (!userHomeDir.isDirectory()) {
throw new FileNotFoundException("User home directory does not exist: " + userHomeDir);
}
// Get the java home save file from user home directory (it might not exist yet).
String prefix = "." + applicationName.replaceAll("\\s", "").toLowerCase();
String suffix = applicationVersion;
if (isDev) {
suffix += "_location_" + installDir.getParentFile().getName();
}
File userSettingsDir =
new File(userHomeDir, prefix + File.separator + prefix + "-" + suffix);
javaHomeSaveFile = new File(userSettingsDir, JAVA_HOME_SAVE_NAME);
}
/**
* Gets the property value with the given key.
*
* @param properties The properties to get the property from.
* @param key The property's key.
* @return The property's corresponding value.
* @throws ParseException if the property with the given key did not have a defined value.
*/
private String getDefinedProperty(Properties properties, String key) throws ParseException {
String value = properties.getProperty(key);
if (value == null || value.isEmpty()) {
throw new ParseException("Property \"" + key + "\" is not defined.", 0);
}
return value;
}
}

View file

@ -0,0 +1,183 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.launch;
import java.io.File;
import java.io.IOException;
import java.text.ParseException;
import java.util.*;
/**
* Class responsible for finding Java installations on a system.
*/
public abstract class JavaFinder {
/**
* A filter used to restrict what kind of Java installations we search for.
*/
public enum JavaFilter {
JRE_ONLY, JDK_ONLY, ANY
}
/**
* Creates a Java finder to use for the current OS.
*
* @return The Java finder to use for the current OS.
*/
public static JavaFinder create() {
JavaFinder javaFinder;
String os = System.getProperty("os.name").toLowerCase();
if (os != null && os.contains("win")) {
javaFinder = new WindowsJavaFinder();
}
else if (os != null && os.contains("mac")) {
javaFinder = new MacJavaFinder();
}
else {
javaFinder = new LinuxJavaFinder();
}
return javaFinder;
}
/**
* Returns a list of supported Java home directories from discovered Java installations.
* The list is sorted from newest Java version to oldest.
*
* @param javaConfig The Java configuration that defines what we support.
* @param javaFilter A filter used to restrict what kind of Java installations we search for.
* @return A sorted list of supported Java home directories from discovered Java installations.
*/
public List<File> findSupportedJavaHomeFromInstallations(JavaConfig javaConfig,
JavaFilter javaFilter) {
Set<File> potentialJavaHomeSet = new TreeSet<>();
for (File javaRootInstallDir : getJavaRootInstallDirs()) {
if (javaRootInstallDir.isDirectory()) {
for (File dir : javaRootInstallDir.listFiles()) {
if (dir.isDirectory()) {
dir = new File(dir, getJavaHomeSubDirPath());
if (javaFilter.equals(JavaFilter.ANY) ||
javaFilter.equals(JavaFilter.JDK_ONLY)) {
potentialJavaHomeSet.add(getJdkHomeFromJavaHome(dir));
}
if (javaFilter.equals(JavaFilter.ANY) ||
javaFilter.equals(JavaFilter.JRE_ONLY)) {
potentialJavaHomeSet.add(getJreHomeFromJavaHome(dir));
}
}
}
}
}
final Map<File, JavaVersion> javaHomeToVersionMap = new HashMap<>();
for (File potentialJavaHomeDir : potentialJavaHomeSet) {
try {
JavaVersion javaVersion =
javaConfig.getJavaVersion(potentialJavaHomeDir, javaFilter);
if (javaConfig.isJavaVersionSupported(javaVersion)) {
javaHomeToVersionMap.put(potentialJavaHomeDir, javaVersion);
}
}
catch (ParseException | IOException e) {
// skip it
}
}
List<File> javaHomeDirs = new ArrayList<>(javaHomeToVersionMap.keySet());
Collections.sort(javaHomeDirs, new Comparator<File>() {
@Override
public int compare(File dir1, File dir2) {
return javaHomeToVersionMap.get(dir2).compareTo(javaHomeToVersionMap.get(dir1));
}
});
return javaHomeDirs;
}
/**
* Returns the Java home directory corresponding to the current "java.home" system
* property (if it supported).
*
* @param javaConfig The Java configuration that defines what we support.
* @param javaFilter A filter used to restrict what kind of Java installations we search for.
* @return The Java home directory corresponding to the current "java.home" system property.
* Could be null if the current "java.home" is not supported.
*/
public File findSupportedJavaHomeFromCurrentJavaHome(JavaConfig javaConfig,
JavaFilter javaFilter) {
Set<File> potentialJavaHomeSet = new HashSet<>();
String javaHomeProperty = System.getProperty("java.home");
if (javaHomeProperty != null && !javaHomeProperty.isEmpty()) {
File dir = new File(javaHomeProperty);
if (javaFilter.equals(JavaFilter.ANY) || javaFilter.equals(JavaFilter.JDK_ONLY)) {
potentialJavaHomeSet.add(getJdkHomeFromJavaHome(dir));
}
if (javaFilter.equals(JavaFilter.ANY) || javaFilter.equals(JavaFilter.JRE_ONLY)) {
potentialJavaHomeSet.add(getJreHomeFromJavaHome(dir));
}
for (File potentialJavaHomeDir : potentialJavaHomeSet) {
try {
if (javaConfig.isJavaVersionSupported(
javaConfig.getJavaVersion(potentialJavaHomeDir, javaFilter))) {
return potentialJavaHomeDir;
}
}
catch (ParseException | IOException e) {
// skip it
}
}
}
return null;
}
/**
* Gets a list of possible Java root installation directories.
*
* @return A list of possible Java root installation directories.
*/
protected abstract List<File> getJavaRootInstallDirs();
/**
* Gets the sub-directory path of a Java root installation directory where the Java
* home lives. For example, for OS X, this is "Contents/Home". For other OS's, it may
* just be the empty string.
*
* @return The sub-directory path of a Java root installation directory where the Java
* home lives.
*/
protected abstract String getJavaHomeSubDirPath();
/**
* Gets the JRE home directory corresponding to the given Java home directory.
* <p>
* If the Java home directory corresponds to a JDK, there is usually a corresponding
* JRE somewhere either in the JDK directory, or adjacent to it.
*
* @param javaHomeDir The Java home directory.
* @return The JRE home directory corresponding to the given Java home directory. Could
* be the same directory if the given Java home is a JRE.
*/
protected abstract File getJreHomeFromJavaHome(File javaHomeDir);
/**
* Gets the JDK home directory corresponding to the given Java home directory.
* <p>
* Often, the java from the PATH will run from a JRE bin directory instead of a JDK
* bin directory. However, we can look in expected places to find the corresponding
* JDK home directory.
*
* @param javaHomeDir The Java home directory.
* @return The JDK home directory corresponding to the given Java home directory. Could
* be the same directory if the given Java home is a JDK.
*/
protected abstract File getJdkHomeFromJavaHome(File javaHomeDir);
}

View file

@ -0,0 +1,208 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.launch;
import java.text.ParseException;
/**
* Class to more conveniently represent a Java version string.
*/
public class JavaVersion implements Comparable<JavaVersion> {
private int major;
private int minor;
private int patch;
/**
* Creates a new {@link JavaVersion} object from the given version string.
*
* @param version A version string.
* @throws ParseException if the version string failed to parse. The exception's
* message has more detailed information about why it failed.
*/
public JavaVersion(String version) throws ParseException {
parse(version);
}
/**
* Gets the major version.
*
* @return The major version.
*/
public int getMajor() {
return major;
}
/**
* Gets the minor version.
*
* @return The minor version.
*/
public int getMinor() {
return minor;
}
/**
* Gets the patch version.
*
* @return The patch version.
*/
public int getPatch() {
return patch;
}
@Override
public String toString() {
if (major < 9) {
return String.format("1.%d.%d_%d", major, minor, patch);
}
return String.format("%d.%d.%d", major, minor, patch);
}
@Override
public int compareTo(JavaVersion other) {
if (major > other.major) {
return 1;
}
if (major < other.major) {
return -1;
}
if (minor > other.minor) {
return 1;
}
if (minor < other.minor) {
return -1;
}
if (patch > other.patch) {
return 1;
}
if (patch < other.patch) {
return -1;
}
return 0;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + major;
result = prime * result + minor;
result = prime * result + patch;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
JavaVersion other = (JavaVersion) obj;
if (major != other.major) {
return false;
}
if (minor != other.minor) {
return false;
}
if (patch != other.patch) {
return false;
}
return true;
}
/**
* Parses the major, minor, and optional patch integers out of the given version string.
*
* @param version A version string.
* @throws ParseException if the version string failed to parse. The exception's message
* has more detailed information about why it failed.
*/
private void parse(String version) throws ParseException {
if (version == null) {
throw new ParseException("Version is null", 0);
}
major = minor = patch = 0;
// Remove any surrounding double quotes
if (version.startsWith("\"") && version.endsWith("\"")) {
version = version.substring(1, version.length() - 1);
}
// Remove any trailing dash section (9-Ubuntu is a thing).
int dashIndex = version.indexOf('-');
if (dashIndex > 0) {
version = version.substring(0, dashIndex);
}
String[] versionParts = version.split("[._]");
int firstValue = parse(versionParts[0], "first value");
if (firstValue == 1) {
// Follows the Java 8 and earlier format of 1.major.minor_patch
if (versionParts.length > 1) {
major = parse(versionParts[1], "major");
if (versionParts.length > 2) {
minor = parse(versionParts[2], "minor");
if (versionParts.length > 3) {
patch = parse(versionParts[3], "patch");
}
}
}
}
else if (firstValue >= 9) {
// Follows the Java 9 and later format of major.minor.patch
major = parse(versionParts[0], "major");
if (versionParts.length > 1) {
minor = parse(versionParts[1], "minor");
if (versionParts.length > 2) {
patch = parse(versionParts[2], "patch");
}
}
}
else {
throw new ParseException("Failed to parse version: " + version, 0);
}
}
/**
* Parses a version part string to an integer.
*
* @param versionPart A version part string.
* @param versionPartName The version part name (for error reporting).
* @return The version part string as an integer.
* @throws ParseException if the version part string failed to parse to a valid version part
* integer.
*/
private int parse(String versionPart, String versionPartName) throws ParseException {
try {
int i = Integer.parseInt(versionPart);
if (i < 0) {
throw new ParseException(versionPartName + " cannot be negative", 0);
}
return i;
}
catch (NumberFormatException e) {
throw new ParseException("Failed to convert " + versionPartName + " version to integer",
0);
}
}
}

View file

@ -0,0 +1,132 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.launch;
import java.io.*;
import java.text.ParseException;
import java.util.*;
/**
* Parses and provides convenient access to the properties defined in a launch properties file.
* <p>
* Our launch properties file is a bit different than a file represented by a {@link Properties}
* object because we allow for duplicate keys. The Apache commons config library can do this, but
* this project cannot have any external dependencies.
*/
public class LaunchProperties {
/**
* The home directory of the Java to use to launch.
*/
public static String JAVA_HOME_OVERRIDE = "JAVA_HOME_OVERRIDE";
/**
* The VM arguments to use to launch.
*/
public static String VMARGS = "VMARGS";
private Map<String, List<String>> propertyMap;
/**
* Creates a new launch properties object from the given launch properties file.
*
* @param launchPropertiesFile The launch properties file.
* @throws FileNotFoundException if the given launch properties file does not exist.
* @throws IOException if there was a problem reading the given launch properties file.
* @throws ParseException if there was a problem parsing the given launch properties file.
*/
public LaunchProperties(File launchPropertiesFile)
throws FileNotFoundException, IOException, ParseException {
propertyMap = parseLaunchProperties(launchPropertiesFile);
}
/**
* Gets the Java home override directory to use for the launch.
*
* @return The Java home override directory to use for the launch. Could be null if the
* property was not defined. The caller should ensure that the directory exists.
*/
public File getJavaHomeOverride() {
List<String> javaHome = propertyMap.get(JAVA_HOME_OVERRIDE);
if (javaHome != null && !javaHome.isEmpty()) {
return new File(javaHome.get(0));
}
return null;
}
/**
* Gets the command line string of VM arguments to use for the launch.
* This will be the union of all VM arguments defined in both the user and installation launch
* properties. If conflicting VM arguments are defined in both files, the user version
* will override the installation version.
*
* @return The command line string of VM arguments to use for the launch.
*/
public String getVmArgs() {
StringBuilder sb = new StringBuilder();
List<String> vmargList = propertyMap.get(VMARGS);
if (vmargList != null) {
for (String arg : vmargList) {
sb.append(arg);
sb.append(" ");
}
}
return sb.toString();
}
/**
* Parses and gets the launch properties from the given launch properties file.
*
* @param launchPropertiesFile The file to get the launch properties from.
* @return The launch properties from the given launch properties file.
* @throws FileNotFoundException if the given launch properties file does not exist.
* @throws IOException if there was a problem reading the given launch properties file.
* @throws ParseException if there was a problem parsing the given launch properties file.
*/
private static Map<String, List<String>> parseLaunchProperties(File launchPropertiesFile)
throws FileNotFoundException, IOException, ParseException {
HashMap<String, List<String>> map = new HashMap<>();
if (launchPropertiesFile != null) {
try (BufferedReader reader = new BufferedReader(new FileReader(launchPropertiesFile))) {
int i = 0;
String line;
while ((line = reader.readLine()) != null) {
i++;
line = line.trim();
if (line.isEmpty() || line.startsWith("#") || line.startsWith("//")) {
continue;
}
int equalsIndex = line.indexOf('=');
if (equalsIndex <= 0) {
throw new ParseException(
"Error parsing line " + i + " of " + launchPropertiesFile, i);
}
String key = line.substring(0, equalsIndex).trim();
String value = line.substring(equalsIndex + 1, line.length()).trim();
List<String> valueList = map.get(key);
if (valueList == null) {
valueList = new ArrayList<>();
map.put(key, valueList);
}
if (!value.isEmpty()) {
valueList.add(value);
}
}
}
}
return map;
}
}

View file

@ -0,0 +1,58 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.launch;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
/**
* Class responsible for finding Java installations on a Linux system.
*/
public class LinuxJavaFinder extends JavaFinder {
@Override
protected List<File> getJavaRootInstallDirs() {
List<File> javaRootInstallDirs = new ArrayList<>();
javaRootInstallDirs.add(new File("/usr/lib/jvm"));
return javaRootInstallDirs;
}
@Override
protected String getJavaHomeSubDirPath() {
return "";
}
@Override
protected File getJreHomeFromJavaHome(File javaHomeDir) {
if (javaHomeDir.isDirectory() && !javaHomeDir.getName().equals("jre")) {
for (File dir : javaHomeDir.listFiles()) {
if (dir.isDirectory() && dir.getName().equals("jre")) {
return dir;
}
}
}
return javaHomeDir;
}
@Override
protected File getJdkHomeFromJavaHome(File javaHomeDir) {
if (javaHomeDir.getName().equals("jre")) {
return javaHomeDir.getParentFile();
}
return javaHomeDir;
}
}

View file

@ -0,0 +1,38 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.launch;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
/**
* Class responsible for finding Java installations on a Mac system.
*/
public class MacJavaFinder extends LinuxJavaFinder {
@Override
protected List<File> getJavaRootInstallDirs() {
List<File> javaRootInstallDirs = new ArrayList<>();
javaRootInstallDirs.add(new File("/Library/Java/JavaVirtualMachines"));
return javaRootInstallDirs;
}
@Override
protected String getJavaHomeSubDirPath() {
return "Contents/Home";
}
}

View file

@ -0,0 +1,56 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.launch;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
/**
* Class responsible for finding Java installations on a Windows system.
*/
public class WindowsJavaFinder extends JavaFinder {
@Override
protected List<File> getJavaRootInstallDirs() {
List<File> javaRootInstallDirs = new ArrayList<>();
javaRootInstallDirs.add(new File("C:\\Program Files\\Java"));
return javaRootInstallDirs;
}
@Override
protected String getJavaHomeSubDirPath() {
return "";
}
@Override
protected File getJreHomeFromJavaHome(File javaHomeDir) {
if (javaHomeDir.getName().startsWith("jdk")) {
return new File(javaHomeDir.getParentFile(),
javaHomeDir.getName().replaceFirst("jdk", "jre"));
}
return javaHomeDir;
}
@Override
protected File getJdkHomeFromJavaHome(File javaHomeDir) {
if (javaHomeDir.getName().startsWith("jre")) {
return new File(javaHomeDir.getParentFile(),
javaHomeDir.getName().replaceFirst("jre", "jdk"));
}
return javaHomeDir;
}
}