diff --git a/Ghidra/Features/Base/src/main/java/ghidra/GhidraRun.java b/Ghidra/Features/Base/src/main/java/ghidra/GhidraRun.java index b5cc4f2c28..3a40917794 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/GhidraRun.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/GhidraRun.java @@ -75,6 +75,9 @@ public class GhidraRun implements GhidraLaunchable { log = LogManager.getLogger(GhidraRun.class); log.info("User " + SystemUtilities.getUserName() + " started Ghidra."); + log.info("User settings directory: " + Application.getUserSettingsDirectory()); + log.info("User temp directory: " + Application.getUserTempDirectory()); + log.info("User cache directory: " + Application.getUserCacheDirectory()); initializeTooltips(); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/processors/ShowInstructionInfoPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/processors/ShowInstructionInfoPlugin.java index 40149c7e11..353fe1b2a2 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/processors/ShowInstructionInfoPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/processors/ShowInstructionInfoPlugin.java @@ -40,6 +40,7 @@ import ghidra.app.context.*; import ghidra.app.plugin.PluginCategoryNames; import ghidra.app.plugin.ProgramPlugin; import ghidra.app.services.GoToService; +import ghidra.framework.Application; import ghidra.framework.plugintool.PluginInfo; import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.util.PluginStatus; @@ -190,7 +191,7 @@ public class ShowInstructionInfoPlugin extends ProgramPlugin { private File writeWrapperFile(URL fileURL) throws IOException { File f; if (manualWrapperFiles.size() < MAX_MANUAL_WRAPPER_FILE_COUNT) { - f = File.createTempFile("pdfView", ".html"); + f = Application.createTempFile("pdfView", ".html"); f.deleteOnExit(); } else { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/cparser/C/CParserUtils.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/cparser/C/CParserUtils.java index c28865e49c..7389be2cd8 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/cparser/C/CParserUtils.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/cparser/C/CParserUtils.java @@ -24,6 +24,7 @@ import generic.theme.GThemeDefaults.Colors; import generic.theme.GThemeDefaults.Colors.Messages; import ghidra.app.services.DataTypeManagerService; import ghidra.app.util.cparser.CPP.PreProcessor; +import ghidra.framework.Application; import ghidra.framework.plugintool.ServiceProvider; import ghidra.framework.store.LockException; import ghidra.program.model.data.*; @@ -535,7 +536,7 @@ public class CParserUtils { String fName = dtMgr.getName(); // make a path to tmpdir with name of data type manager - String path = System.getProperty("java.io.tmpdir") + File.pathSeparator + fName; + String path = new File(Application.getUserTempDirectory(), fName).getAbsolutePath(); // if file data type manager, use path to .gdt file if (dtMgr instanceof FileDataTypeManager) { path = ((FileDataTypeManager) dtMgr).getPath(); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/exporter/OriginalFileExporter.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/exporter/OriginalFileExporter.java index 90cef99b0d..7780887e7c 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/exporter/OriginalFileExporter.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/exporter/OriginalFileExporter.java @@ -21,6 +21,7 @@ import java.util.ArrayList; import java.util.List; import ghidra.app.util.*; +import ghidra.framework.Application; import ghidra.framework.model.DomainObject; import ghidra.program.database.mem.AddressSourceInfo; import ghidra.program.database.mem.FileBytes; @@ -166,7 +167,7 @@ public class OriginalFileExporter extends Exporter { // Write source program's file bytes to a temp file. // This is done to ensure a random access write failure doesn't corrupt a file the user // might be overwriting. - File tempFile = File.createTempFile("ghidra_export_", null); + File tempFile = Application.createTempFile("ghidra_export_", null); try (OutputStream out = new FileOutputStream(tempFile, false)) { FileUtilities.copyStreamToStream(new FileBytesInputStream(fileBytes, true), out, monitor); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/ElfProgramBuilder.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/ElfProgramBuilder.java index 15f4172802..e9d9b00656 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/ElfProgramBuilder.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/ElfProgramBuilder.java @@ -36,6 +36,7 @@ import ghidra.app.util.bin.format.elf.extend.ElfLoadAdapter; import ghidra.app.util.bin.format.elf.info.ElfInfoProducer; import ghidra.app.util.bin.format.elf.relocation.*; import ghidra.app.util.importer.MessageLog; +import ghidra.framework.Application; import ghidra.framework.options.Options; import ghidra.framework.store.LockException; import ghidra.program.database.mem.FileBytes; @@ -1606,7 +1607,7 @@ class ElfProgramBuilder extends MemorySectionResolver implements ElfLoadHelper { Address debugDataAddr = findLoadAddress(debugDataSection, 0); if (debugDataAddr != null) { try { - File tmpFile = File.createTempFile("ghidra_gnu_debugdata", null); + File tmpFile = Application.createTempFile("ghidra_gnu_debugdata", null); try (ByteProviderWrapper compressedDebugDataBP = new ByteProviderWrapper( new MemoryByteProvider(memory, debugDataAddr), 0, debugDataSection.getSize()); XZCompressorInputStream xzIS = diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/GdtLoader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/GdtLoader.java index c5f14772e7..10ff4542bd 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/GdtLoader.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/GdtLoader.java @@ -25,6 +25,7 @@ import db.DBHandle; import ghidra.app.util.Option; import ghidra.app.util.bin.ByteProvider; import ghidra.app.util.importer.MessageLog; +import ghidra.framework.Application; import ghidra.framework.model.DomainObject; import ghidra.framework.model.Project; import ghidra.framework.store.db.PackedDatabase; @@ -138,7 +139,7 @@ public class GdtLoader implements Loader { private static File createTmpFile(ByteProvider provider, TaskMonitor monitor) throws IOException { - File tmpFile = File.createTempFile("ghidra_gdt_loader", null); + File tmpFile = Application.createTempFile("ghidra_gdt_loader", null); try (InputStream is = provider.getInputStream(0); FileOutputStream fos = new FileOutputStream(tmpFile)) { FileUtilities.copyStreamToStream(is, fos, monitor); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/GzfLoader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/GzfLoader.java index 7be5076912..3dc408b4e1 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/GzfLoader.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/GzfLoader.java @@ -25,6 +25,7 @@ import db.DBHandle; import ghidra.app.util.Option; import ghidra.app.util.bin.ByteProvider; import ghidra.app.util.importer.MessageLog; +import ghidra.framework.Application; import ghidra.framework.model.DomainObject; import ghidra.framework.model.Project; import ghidra.framework.store.db.PackedDatabase; @@ -148,7 +149,7 @@ public class GzfLoader implements Loader { private static File createTmpFile(ByteProvider provider, TaskMonitor monitor) throws IOException { - File tmpFile = File.createTempFile("ghidra_gzf_loader", null); + File tmpFile = Application.createTempFile("ghidra_gzf_loader", null); try (InputStream is = provider.getInputStream(0); FileOutputStream fos = new FileOutputStream(tmpFile)) { FileUtilities.copyStreamToStream(is, fos, monitor); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/FileSystemService.java b/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/FileSystemService.java index f7ea28b180..f5e4f6c428 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/FileSystemService.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/FileSystemService.java @@ -577,7 +577,7 @@ public class FileSystemService { public File createPlaintextTempFile(ByteProvider provider, String filenamePrefix, TaskMonitor monitor) throws IOException { File tmpFile = - File.createTempFile(filenamePrefix, Long.toString(System.currentTimeMillis())); + Application.createTempFile(filenamePrefix, Long.toString(System.currentTimeMillis())); monitor.setMessage("Copying " + provider.getName() + " to temp file"); monitor.initialize(provider.length()); try { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/GFileSystemBase.java b/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/GFileSystemBase.java index 8d874c7e4a..d510773f9c 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/GFileSystemBase.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/GFileSystemBase.java @@ -20,6 +20,7 @@ import java.util.Comparator; import java.util.List; import ghidra.app.util.bin.ByteProvider; +import ghidra.framework.Application; import ghidra.util.SystemUtilities; import ghidra.util.exception.CancelledException; import ghidra.util.exception.CryptoException; @@ -138,7 +139,7 @@ public abstract class GFileSystemBase implements GFileSystem { protected void debug(byte[] bytes, String fileName) { try { if (SystemUtilities.isInDevelopmentMode()) { - File file = File.createTempFile(fileName, ".ghidra.tmp"); + File file = Application.createTempFile(fileName, ".ghidra.tmp"); OutputStream out = new FileOutputStream(file); try { out.write(bytes); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/program/examiner/ProgramExaminer.java b/Ghidra/Features/Base/src/main/java/ghidra/program/examiner/ProgramExaminer.java index b0e57ca63b..4bc9c1696c 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/program/examiner/ProgramExaminer.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/program/examiner/ProgramExaminer.java @@ -28,8 +28,7 @@ import ghidra.app.util.importer.AutoImporter; import ghidra.app.util.importer.MessageLog; import ghidra.app.util.opinion.LoadException; import ghidra.app.util.opinion.LoadResults; -import ghidra.framework.Application; -import ghidra.framework.HeadlessGhidraApplicationConfiguration; +import ghidra.framework.*; import ghidra.program.model.data.*; import ghidra.program.model.lang.*; import ghidra.program.model.listing.*; @@ -38,6 +37,7 @@ import ghidra.program.util.DefaultLanguageService; import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; import utility.application.ApplicationLayout; +import utility.application.ApplicationUtilities; /** * Wrapper for Ghidra code to find images (and maybe other artifacts later) in a program @@ -122,8 +122,9 @@ public class ProgramExaminer { if (!Application.isInitialized()) { ApplicationLayout layout; try { - layout = - new GhidraTestApplicationLayout(new File(System.getProperty("java.io.tmpdir"))); + layout = new GhidraTestApplicationLayout(ApplicationUtilities.getDefaultUserTempDir( + new ApplicationProperties(ApplicationUtilities.findDefaultApplicationRootDirs()) + .getApplicationName())); } catch (IOException e) { throw new GhidraException(e); @@ -184,8 +185,7 @@ public class ProgramExaminer { int txID = program.startTransaction("find images"); try { EmbeddedMediaAnalyzer imageAnalyzer = new EmbeddedMediaAnalyzer(); - imageAnalyzer.added(program, program.getMemory(), TaskMonitor.DUMMY, - messageLog); + imageAnalyzer.added(program, program.getMemory(), TaskMonitor.DUMMY, messageLog); } catch (CancelledException e) { // using Dummy, can't happen diff --git a/Ghidra/Features/Base/src/main/java/ghidra/test/processors/support/ProcessorEmulatorTestAdapter.java b/Ghidra/Features/Base/src/main/java/ghidra/test/processors/support/ProcessorEmulatorTestAdapter.java index bc47bcd39c..0936a9b85d 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/test/processors/support/ProcessorEmulatorTestAdapter.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/test/processors/support/ProcessorEmulatorTestAdapter.java @@ -428,7 +428,7 @@ public abstract class ProcessorEmulatorTestAdapter extends TestCase implements E // return; // already enabled by batch test environment // } // -// String tmpDir = System.getProperty("java.io.tmpdir"); +// File tmpDir = Application.getUserTempDirectory(); // File cacheDir = new File(tmpDir, "EmulatorDBTestCache"); // if (cacheDir.exists() && !FileUtilities.deleteDir(cacheDir)) { // Msg.warn(ProcessorEmulatorTestAdapter.class, diff --git a/Ghidra/Features/Base/src/main/java/ghidra/util/GhidraJarBuilder.java b/Ghidra/Features/Base/src/main/java/ghidra/util/GhidraJarBuilder.java index 4a8c2b0389..ef2ade34fe 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/util/GhidraJarBuilder.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/util/GhidraJarBuilder.java @@ -704,7 +704,7 @@ public class GhidraJarBuilder implements GhidraLaunchable { } public void close() throws IOException { - File tempFile = File.createTempFile("jarBuilder", "treeIDX"); + File tempFile = Application.createTempFile("jarBuilder", "treeIDX"); classTree.trim(); classTree.saveFile(tempFile); try { diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/util/datatype/DataTypeSelectionDialogTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/util/datatype/DataTypeSelectionDialogTest.java index 66923c69a8..79e401f6cc 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/util/datatype/DataTypeSelectionDialogTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/util/datatype/DataTypeSelectionDialogTest.java @@ -54,6 +54,7 @@ import ghidra.app.plugin.core.datamgr.tree.DataTypeNode; import ghidra.app.plugin.core.datamgr.util.DataTypeChooserDialog; import ghidra.app.services.DataTypeManagerService; import ghidra.app.services.ProgramManager; +import ghidra.framework.Application; import ghidra.framework.plugintool.PluginTool; import ghidra.program.database.ProgramBuilder; import ghidra.program.database.data.ProgramDataTypeManager; @@ -723,7 +724,7 @@ public class DataTypeSelectionDialogTest extends AbstractGhidraHeadedIntegration DataTypeManagerHandler dataTypeManagerHandler = plugin.getDataTypeManagerHandler(); File tempArchiveFile; try { - tempArchiveFile = File.createTempFile("TestFileArchive", ".gdt"); + tempArchiveFile = Application.createTempFile("TestFileArchive", ".gdt"); } catch (IOException e) { e.printStackTrace(); diff --git a/Ghidra/Features/DebugUtils/src/main/java/db/DbViewer.java b/Ghidra/Features/DebugUtils/src/main/java/db/DbViewer.java index ce16f82d9c..e030acfded 100644 --- a/Ghidra/Features/DebugUtils/src/main/java/db/DbViewer.java +++ b/Ghidra/Features/DebugUtils/src/main/java/db/DbViewer.java @@ -16,7 +16,8 @@ package db; import java.awt.*; -import java.io.*; +import java.io.File; +import java.io.IOException; import java.util.*; import javax.swing.*; @@ -240,7 +241,7 @@ public class DbViewer extends JFrame { return stats; } - public static void main(String[] args) throws FileNotFoundException { + public static void main(String[] args) throws IOException { ApplicationLayout layout = new GenericApplicationLayout("DB Viewer", "1.0"); DockingApplicationConfiguration configuration = new DockingApplicationConfiguration(); configuration.setShowSplashScreen(false); diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/java/JavaClassDecompilerFileSystem.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/java/JavaClassDecompilerFileSystem.java index 4f90c5b153..d22a0398e1 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/java/JavaClassDecompilerFileSystem.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/java/JavaClassDecompilerFileSystem.java @@ -27,6 +27,7 @@ import ghidra.formats.gfilesystem.*; import ghidra.formats.gfilesystem.annotations.FileSystemInfo; import ghidra.formats.gfilesystem.fileinfo.FileAttribute; import ghidra.formats.gfilesystem.fileinfo.FileAttributes; +import ghidra.framework.Application; import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; import utilities.util.FileUtilities; @@ -76,7 +77,7 @@ public class JavaClassDecompilerFileSystem implements GFileSystem { throws CancelledException, IOException { File tempDir = null; try { - tempDir = FileUtilities.createTempDirectory("JavaClassDecompilerFileSystem"); + tempDir = new File(Application.getUserTempDirectory(), "JavaClassDecompilerFileSystem"); File tempClassFile = new File(tempDir, containerFSRL.getName()); FSUtilities.copyByteProviderToFile(provider, tempClassFile, monitor); diff --git a/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/remote/GhidraServerApplicationLayout.java b/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/remote/GhidraServerApplicationLayout.java index 8f0f1a3ebd..dfff4c2b8b 100644 --- a/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/remote/GhidraServerApplicationLayout.java +++ b/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/remote/GhidraServerApplicationLayout.java @@ -15,7 +15,6 @@ */ package ghidra.server.remote; -import java.io.FileNotFoundException; import java.io.IOException; import java.util.Collections; @@ -34,10 +33,10 @@ public class GhidraServerApplicationLayout extends ApplicationLayout { /** * Constructs a new Ghidra server application layout object. * - * @throws FileNotFoundException if there was a problem getting a user directory. - * @throws IOException if there was a problem getting the application properties. + * @throws IOException if there was a problem getting a user directory or the application + * properties. */ - public GhidraServerApplicationLayout() throws FileNotFoundException, IOException { + public GhidraServerApplicationLayout() throws IOException { // Application root directories applicationRootDirs = ApplicationUtilities.findDefaultApplicationRootDirs(); @@ -56,7 +55,8 @@ public class GhidraServerApplicationLayout extends ApplicationLayout { extensionInstallationDirs = Collections.emptyList(); // User directories (don't let anything use the user home directory...there may not be one) - userTempDir = ApplicationUtilities.getDefaultUserTempDir(applicationProperties); + userTempDir = + ApplicationUtilities.getDefaultUserTempDir(applicationProperties.getApplicationName()); // Modules - required to find module data files modules = ModuleUtilities.findModules(applicationRootDirs, diff --git a/Ghidra/Framework/DB/src/main/java/db/buffers/BufferMgr.java b/Ghidra/Framework/DB/src/main/java/db/buffers/BufferMgr.java index 435657ecbb..6088dedd9d 100644 --- a/Ghidra/Framework/DB/src/main/java/db/buffers/BufferMgr.java +++ b/Ghidra/Framework/DB/src/main/java/db/buffers/BufferMgr.java @@ -21,8 +21,7 @@ import java.util.*; import db.DBChangeSet; import db.DBHandle; import db.buffers.LocalBufferFile.BufferFileFilter; -import ghidra.framework.ShutdownHookRegistry; -import ghidra.framework.ShutdownPriority; +import ghidra.framework.*; import ghidra.util.Msg; import ghidra.util.SystemUtilities; import ghidra.util.datastruct.ObjectArray; @@ -2049,7 +2048,7 @@ public class BufferMgr { } public static void cleanupOldCacheFiles() { - File tmpDir = new File(System.getProperty("java.io.tmpdir")); + File tmpDir = Application.getUserTempDirectory(); File[] cacheFiles = tmpDir.listFiles(new BufferFileFilter(CACHE_FILE_PREFIX, CACHE_FILE_EXT)); if (cacheFiles == null) { diff --git a/Ghidra/Framework/DB/src/main/java/db/buffers/LocalBufferFile.java b/Ghidra/Framework/DB/src/main/java/db/buffers/LocalBufferFile.java index 2e9143ce38..c270647bc1 100644 --- a/Ghidra/Framework/DB/src/main/java/db/buffers/LocalBufferFile.java +++ b/Ghidra/Framework/DB/src/main/java/db/buffers/LocalBufferFile.java @@ -18,6 +18,7 @@ package db.buffers; import java.io.*; import java.util.*; +import ghidra.framework.Application; import ghidra.util.BigEndianDataConverter; import ghidra.util.Msg; import ghidra.util.datastruct.IntSet; @@ -197,8 +198,7 @@ public class LocalBufferFile implements BufferFile { this.blockSize = bufferSize + BUFFER_PREFIX_SIZE; this.readOnly = false; this.temporary = true; - file = File.createTempFile(tmpPrefix, tmpExtension); -// file.deleteOnExit(); + file = Application.createTempFile(tmpPrefix, tmpExtension); raf = new RandomAccessFile(file, "rw"); } diff --git a/Ghidra/Framework/DB/src/test/java/db/DBTestUtils.java b/Ghidra/Framework/DB/src/test/java/db/DBTestUtils.java index 28b828273e..b052e94062 100644 --- a/Ghidra/Framework/DB/src/test/java/db/DBTestUtils.java +++ b/Ghidra/Framework/DB/src/test/java/db/DBTestUtils.java @@ -24,6 +24,7 @@ import java.util.Random; import org.junit.Assert; import db.buffers.*; +import ghidra.framework.Application; import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; @@ -815,7 +816,7 @@ public class DBTestUtils { public static DBHandle cloneDbHandle(DBHandle dbh) throws IOException { try { - File tmpFile = File.createTempFile("tmp", ".db"); + File tmpFile = Application.createTempFile("tmp", ".db"); tmpFile.delete(); LocalBufferFile bf = new LocalBufferFile(tmpFile, dbh.getBufferSize()); diff --git a/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/store/db/PackedDatabase.java b/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/store/db/PackedDatabase.java index daad3d8b95..76b2847ba0 100644 --- a/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/store/db/PackedDatabase.java +++ b/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/store/db/PackedDatabase.java @@ -24,6 +24,7 @@ import db.Database; import db.buffers.BufferFileManager; import db.buffers.LocalManagedBufferFile; import generic.jar.ResourceFile; +import ghidra.framework.Application; import ghidra.framework.store.FolderItem; import ghidra.framework.store.db.PackedDatabaseCache.CachedDB; import ghidra.framework.store.local.*; @@ -32,7 +33,6 @@ import ghidra.util.datastruct.WeakDataStructureFactory; import ghidra.util.datastruct.WeakSet; import ghidra.util.exception.*; import ghidra.util.task.TaskMonitor; -import ghidra.util.task.TaskMonitorAdapter; import utilities.util.FileUtilities; /** @@ -364,7 +364,7 @@ public class PackedDatabase extends Database { */ private static File createDBDir() throws IOException { - File tmpDir = new File(System.getProperty("java.io.tmpdir")); + File tmpDir = Application.getUserTempDirectory(); int tries = 0; while (tries++ < 10) { File dir = new File(tmpDir, TEMPDB_DIR_PREFIX + getRandomString() + TEMPDB_DIR_EXT); @@ -620,7 +620,7 @@ public class PackedDatabase extends Database { InputStream itemIn = null; File tmpFile = null; try { - tmpFile = File.createTempFile("pack", ".tmp"); + tmpFile = Application.createTempFile("pack", ".tmp"); tmpFile.delete(); dbh.saveAs(tmpFile, false, monitor); itemIn = new BufferedInputStream(new FileInputStream(tmpFile)); @@ -838,7 +838,7 @@ public class PackedDatabase extends Database { */ public static void cleanupOldTempDatabases() { - File tmpDir = new File(System.getProperty("java.io.tmpdir")); + File tmpDir = Application.getUserTempDirectory(); File[] tempDbs = tmpDir.listFiles((FileFilter) file -> { String name = file.getName(); if (file.isDirectory()) { diff --git a/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/store/db/VersionedDatabase.java b/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/store/db/VersionedDatabase.java index aae97b920f..7d5973130d 100644 --- a/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/store/db/VersionedDatabase.java +++ b/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/store/db/VersionedDatabase.java @@ -23,6 +23,7 @@ import org.apache.logging.log4j.Logger; import db.DBHandle; import db.Database; import db.buffers.*; +import ghidra.framework.Application; import ghidra.framework.store.local.ItemSerializer; import ghidra.util.exception.*; import ghidra.util.task.TaskMonitor; @@ -417,7 +418,8 @@ public class VersionedDatabase extends Database { else { BufferFile bf = openBufferFile(version, -1); try { - File tmpFile = File.createTempFile("ghidra", LocalBufferFile.TEMP_FILE_EXT); + File tmpFile = + Application.createTempFile("ghidra", LocalBufferFile.TEMP_FILE_EXT); tmpFile.delete(); BufferFile tmpBf = new LocalBufferFile(tmpFile, bf.getBufferSize()); boolean success = false; @@ -425,18 +427,10 @@ public class VersionedDatabase extends Database { LocalBufferFile.copyFile(bf, tmpBf, null, monitor); tmpBf.close(); - InputStream itemIn = new FileInputStream(tmpFile); - try { + try (InputStream itemIn = new FileInputStream(tmpFile)) { ItemSerializer.outputItem(name, contentType, filetype, tmpFile.length(), itemIn, outputFile, monitor); } - finally { - try { - itemIn.close(); - } - catch (IOException e) { - } - } success = true; } finally { diff --git a/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/store/remote/RemoteDatabaseItem.java b/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/store/remote/RemoteDatabaseItem.java index 79333fc27c..4001441d51 100644 --- a/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/store/remote/RemoteDatabaseItem.java +++ b/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/store/remote/RemoteDatabaseItem.java @@ -18,6 +18,7 @@ package ghidra.framework.store.remote; import java.io.*; import db.buffers.*; +import ghidra.framework.Application; import ghidra.framework.client.RepositoryAdapter; import ghidra.framework.remote.RepositoryItem; import ghidra.framework.store.DatabaseItem; @@ -101,25 +102,17 @@ public class RemoteDatabaseItem extends RemoteFolderItem implements DatabaseItem BufferFile bf = repository.openDatabase(parentPath, itemName, version, -1); try { - File tmpFile = File.createTempFile("ghidra", LocalBufferFile.TEMP_FILE_EXT); + File tmpFile = Application.createTempFile("ghidra", LocalBufferFile.TEMP_FILE_EXT); tmpFile.delete(); BufferFile tmpBf = new LocalBufferFile(tmpFile, bf.getBufferSize()); try { LocalBufferFile.copyFile(bf, tmpBf, null, monitor); tmpBf.close(); - InputStream itemIn = new FileInputStream(tmpFile); - try { + try (InputStream itemIn = new FileInputStream(tmpFile)) { ItemSerializer.outputItem(getName(), getContentType(), DATABASE_FILE_TYPE, tmpFile.length(), itemIn, outputFile, monitor); } - finally { - try { - itemIn.close(); - } - catch (IOException e) { - } - } } finally { tmpBf.close(); diff --git a/Ghidra/Framework/Generic/src/main/java/generic/application/GenericApplicationLayout.java b/Ghidra/Framework/Generic/src/main/java/generic/application/GenericApplicationLayout.java index 83abd5803f..95dd0ce58e 100644 --- a/Ghidra/Framework/Generic/src/main/java/generic/application/GenericApplicationLayout.java +++ b/Ghidra/Framework/Generic/src/main/java/generic/application/GenericApplicationLayout.java @@ -16,7 +16,7 @@ package generic.application; import java.io.File; -import java.io.FileNotFoundException; +import java.io.IOException; import java.util.*; import java.util.Map.Entry; import java.util.regex.Matcher; @@ -59,9 +59,9 @@ public class GenericApplicationLayout extends ApplicationLayout { * * @param name The name of the application. * @param version The version of the application. - * @throws FileNotFoundException if there was a problem getting a user directory. + * @throws IOException if there was a problem getting a user directory. */ - public GenericApplicationLayout(String name, String version) throws FileNotFoundException { + public GenericApplicationLayout(String name, String version) throws IOException { this(new ApplicationProperties(name, version, NO_RELEASE_NAME)); } @@ -70,10 +70,10 @@ public class GenericApplicationLayout extends ApplicationLayout { * properties. The default Ghidra application root directory(s) will be used. * * @param applicationProperties The properties object that will be read system properties. - * @throws FileNotFoundException if there was a problem getting a user directory. + * @throws IOException if there was a problem getting a user directory. */ public GenericApplicationLayout(ApplicationProperties applicationProperties) - throws FileNotFoundException { + throws IOException { this(getDefaultApplicationRootDirs(), applicationProperties); } @@ -85,10 +85,10 @@ public class GenericApplicationLayout extends ApplicationLayout { * used to identify modules and resources. The first entry will be treated as the * installation root. * @param applicationProperties The properties object that will be read system properties. - * @throws FileNotFoundException if there was a problem getting a user directory. + * @throws IOException if there was a problem getting a user directory. */ public GenericApplicationLayout(Collection applicationRootDirs, - ApplicationProperties applicationProperties) throws FileNotFoundException { + ApplicationProperties applicationProperties) throws IOException { this.applicationProperties = Objects.requireNonNull(applicationProperties); this.applicationRootDirs = applicationRootDirs; @@ -117,7 +117,8 @@ public class GenericApplicationLayout extends ApplicationLayout { modules = Collections.unmodifiableMap(allModules); // User directories - userTempDir = ApplicationUtilities.getDefaultUserTempDir(applicationProperties); + userTempDir = + ApplicationUtilities.getDefaultUserTempDir(applicationProperties.getApplicationName()); userSettingsDir = ApplicationUtilities.getDefaultUserSettingsDir(applicationProperties, applicationInstallationDir); diff --git a/Ghidra/Framework/Generic/src/main/java/generic/test/AbstractGTest.java b/Ghidra/Framework/Generic/src/main/java/generic/test/AbstractGTest.java index 51bf0a6a86..cb0fe7ceb8 100644 --- a/Ghidra/Framework/Generic/src/main/java/generic/test/AbstractGTest.java +++ b/Ghidra/Framework/Generic/src/main/java/generic/test/AbstractGTest.java @@ -18,6 +18,7 @@ package generic.test; import static org.junit.Assert.*; import java.io.File; +import java.io.FileNotFoundException; import java.util.*; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -35,6 +36,7 @@ import ghidra.util.SystemUtilities; import ghidra.util.UniversalIdGenerator; import ghidra.util.exception.AssertException; import junit.framework.AssertionFailedError; +import utility.application.ApplicationUtilities; /** * A root for system tests that provides known system information. @@ -110,16 +112,21 @@ public abstract class AbstractGTest { // In batch mode we rely on the fact that the test environment has been setup with a // custom temp directory. // - return System.getProperty("java.io.tmpdir") + File.separator + "Ghidra_test_" + - UUID.randomUUID() + File.separator + "temp.data"; + try { + return new File(ApplicationUtilities.getDefaultUserTempDir("ghidra"), + "test_" + UUID.randomUUID() + File.separator + "temp.data").getPath(); + } + catch (FileNotFoundException e) { + throw new AssertException(e); + } } private static String buildDevelopmentDirectoryPath() { // // Create a unique name based upon the repo from which we are running. // - File tempDir = TestApplicationUtils.getUniqueTempFolder(); - return tempDir.getAbsolutePath(); + File tempDir = TestApplicationUtils.getUniqueTempDir(); + return tempDir.getPath(); } public static String getTestDirectoryPath() { diff --git a/Ghidra/Framework/Generic/src/main/java/generic/test/AbstractGenericTest.java b/Ghidra/Framework/Generic/src/main/java/generic/test/AbstractGenericTest.java index 3177ed3612..c9d464005e 100644 --- a/Ghidra/Framework/Generic/src/main/java/generic/test/AbstractGenericTest.java +++ b/Ghidra/Framework/Generic/src/main/java/generic/test/AbstractGenericTest.java @@ -809,10 +809,10 @@ public abstract class AbstractGenericTest extends AbstractGTest { } /** - * Creates a file in the Java temp directory using the given name as a + * Creates a file in the Application temp directory using the given name as a * prefix and the given suffix. The final filename will also include the * current test name, as well as any data added by - * {@link File#createTempFile(String, String)}. The file suffix will be + * {@link File#createTempFile(String, String, File)}. The file suffix will be * .tmp *

* The file will be marked to delete on JVM exit. This will not work if the @@ -830,10 +830,10 @@ public abstract class AbstractGenericTest extends AbstractGTest { } /** - * Creates a file in the Java temp directory using the given name as a + * Creates a file in the Application temp directory using the given name as a * prefix and the given suffix. The final filename will also include the * current test name, as well as any data added by - * {@link File#createTempFile(String, String)}. + * {@link File#createTempFile(String, String, File)}. *

* The file will be marked to delete on JVM exit. This will not work if the * JVM is taken down the hard way, as when pressing the stop button in diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/framework/Application.java b/Ghidra/Framework/Generic/src/main/java/ghidra/framework/Application.java index 79a5c7c0d7..7a4a35f266 100644 --- a/Ghidra/Framework/Generic/src/main/java/ghidra/framework/Application.java +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/framework/Application.java @@ -29,6 +29,7 @@ import util.CollectionUtils; import utilities.util.FileUtilities; import utilities.util.reflection.ReflectionUtilities; import utility.application.ApplicationLayout; +import utility.application.ApplicationUtilities; import utility.module.ModuleUtilities; /** @@ -667,7 +668,6 @@ public class Application { /** * Returns the temporary directory specific to the user and the application. - * Directory has name of <username>-<appname> * This directory may be removed at system reboot or during periodic * system cleanup of unused temp files. * This directory is specific to the application name but not the version. @@ -677,15 +677,22 @@ public class Application { * @return temp directory */ public static File getUserTempDirectory() { - checkAppInitialized(); - return app.layout.getUserTempDir(); + try { + // 'app' will be null when the application has not been initialized yet. In this case, + // we provide the default user temp directory. + return app != null ? app.layout.getUserTempDir() + : ApplicationUtilities.getDefaultUserTempDir("ghidra"); + } + catch (FileNotFoundException e) { + throw new AssertException(e); + } } /** * Returns the cache directory specific to the user and the application. * The intention is for directory contents to be preserved, however the * specific location is platform specific and contents may be removed when - * not in use and may in fact be the same directory the user temp directory. + * not in use. * This directory is specific to the application name but not the version. * Resources stored within this directory should utilize some * form of access locking and/or unique naming. @@ -696,6 +703,24 @@ public class Application { return app.layout.getUserCacheDir(); } + /** + * Creates a new empty file in the Application's temp directory, using the given prefix and + * suffix strings to generate its name. + * + * @param prefix The prefix string to be used in generating the file's name; must be at least + * three characters long + * @param suffix The suffix string to be used in generating the file's name; may be + * {@code null}, in which case the suffix {@code ".tmp"} will be used + * @return A {@link File} denoting a newly-created empty file + * @throws IllegalArgumentException If the {@code prefix} argument contains fewer than three + * characters + * @throws IOException If a file could not be created + * @see File#createTempFile(String, String, File) + */ + public static File createTempFile(String prefix, String suffix) throws IOException { + return File.createTempFile(prefix, suffix, getUserTempDirectory()); + } + /** * Returns a collection of all the module root directories. A module root directory is * the top-level directory of a module. diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/framework/GenericRunInfo.java b/Ghidra/Framework/Generic/src/main/java/ghidra/framework/GenericRunInfo.java index 80bdc5649e..f0b92588e3 100644 --- a/Ghidra/Framework/Generic/src/main/java/ghidra/framework/GenericRunInfo.java +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/framework/GenericRunInfo.java @@ -15,8 +15,7 @@ */ package ghidra.framework; -import java.io.File; -import java.io.FileFilter; +import java.io.*; import java.util.*; import org.apache.logging.log4j.LogManager; @@ -24,6 +23,8 @@ import org.apache.logging.log4j.Logger; import ghidra.framework.preferences.Preferences; import util.CollectionUtils; +import utility.application.ApplicationLayout; +import utility.application.ApplicationUtilities; public class GenericRunInfo { @@ -42,10 +43,23 @@ public class GenericRunInfo { * Note: This method ignores Test directories */ private static List getUserSettingsDirsByTime() { - File userDataDirectory = Application.getUserSettingsDirectory(); - File userDataDirParentFile = userDataDirectory.getParentFile(); + ApplicationLayout layout = Application.getApplicationLayout(); + File userSettingsDirectory = Application.getUserSettingsDirectory(); - List appDirs = collectAllApplicationDirectories(userDataDirParentFile); + List appDirs = + collectAllApplicationDirectories(userSettingsDirectory.getParentFile()); + + // Search "legacy" user setting directory locations in case the user has upgraded from an + // older version + try { + File legacyUserSettingsDirectory = ApplicationUtilities.getLegacyUserSettingsDir( + layout.getApplicationProperties(), layout.getApplicationInstallationDir()); + appDirs.addAll( + collectAllApplicationDirectories(legacyUserSettingsDirectory.getParentFile())); + } + catch (FileNotFoundException e) { + // ignore + } Comparator modifyTimeComparator = (f1, f2) -> { diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/framework/TestApplicationUtils.java b/Ghidra/Framework/Generic/src/main/java/ghidra/framework/TestApplicationUtils.java index 755f5b5b07..8d86abe91d 100644 --- a/Ghidra/Framework/Generic/src/main/java/ghidra/framework/TestApplicationUtils.java +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/framework/TestApplicationUtils.java @@ -16,6 +16,7 @@ package ghidra.framework; import java.io.File; +import java.io.FileNotFoundException; import java.util.List; import org.apache.commons.lang3.StringUtils; @@ -24,6 +25,7 @@ import ghidra.util.Msg; import ghidra.util.SystemUtilities; import ghidra.util.exception.AssertException; import utilities.util.FileUtilities; +import utility.application.ApplicationUtilities; import utility.module.ModuleUtilities; public class TestApplicationUtils { @@ -127,13 +129,13 @@ public class TestApplicationUtils { } /** - * Creates a folder that is unique for the current installation. This allows clients to + * Creates a directory that is unique for the current installation. This allows clients to * have multiple clones (for development mode) or multiple installations (for release mode) * on their machine, running tests from each repo simultaneously. * - * @return a folder that is unique for the current installation + * @return an absolute form directory that is unique for the current installation */ - public static File getUniqueTempFolder() { + public static File getUniqueTempDir() { // // Create a unique name based upon the repo from which we are running. @@ -144,14 +146,18 @@ public class TestApplicationUtils { reposContainer = installDir; } - File tmpDir = new File(System.getProperty("java.io.tmpdir")); - String tempName = tmpDir.getName(); + try { + File tmpDir = ApplicationUtilities.getDefaultUserTempDir("ghidra"); - // - // The container name makes this name unique across multiple Eclipses; the system temp - // name makes this name unique across multiple runs from the same Eclipse - // - String name = reposContainer.getName() + tempName; - return new File(tmpDir, name); + // + // The container name makes this name unique across multiple Eclipses; the system temp + // name makes this name unique across multiple runs from the same Eclipse + // + String name = reposContainer.getName(); + return new File(tmpDir, name); + } + catch (FileNotFoundException e) { + throw new AssertException(e); + } } } diff --git a/Ghidra/Framework/Generic/src/test/java/utilities/util/FileUtilitiesTest.java b/Ghidra/Framework/Generic/src/test/java/utilities/util/FileUtilitiesTest.java index 142c6b4ca6..b4fc672606 100644 --- a/Ghidra/Framework/Generic/src/test/java/utilities/util/FileUtilitiesTest.java +++ b/Ghidra/Framework/Generic/src/test/java/utilities/util/FileUtilitiesTest.java @@ -15,8 +15,8 @@ */ package utilities.util; -import static generic.test.AbstractGTest.assertListEqualsArrayOrdered; -import static org.hamcrest.CoreMatchers.equalTo; +import static generic.test.AbstractGTest.*; +import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.*; import java.io.*; @@ -30,8 +30,7 @@ import org.junit.Test; import generic.jar.ResourceFile; import generic.test.AbstractGenericTest; -import ghidra.framework.OperatingSystem; -import ghidra.framework.Platform; +import ghidra.framework.*; import utilities.util.FileResolutionResult.FileResolutionStatus; public class FileUtilitiesTest { @@ -198,11 +197,11 @@ public class FileUtilitiesTest { @Test public void copyFile_ResourceFile_To_ResourceFile() throws Exception { - File from = File.createTempFile("from.file", ".txt"); + File from = Application.createTempFile("from.file", ".txt"); FileUtilities.writeLinesToFile(from, Arrays.asList("From file contents")); from.deleteOnExit(); - File to = File.createTempFile("to.file", ".txt"); + File to = Application.createTempFile("to.file", ".txt"); to.deleteOnExit(); FileUtilities.writeLinesToFile(to, Arrays.asList("To file contents")); @@ -222,7 +221,7 @@ public class FileUtilitiesTest { } }; - File to = File.createTempFile("to.file", ".txt"); + File to = Application.createTempFile("to.file", ".txt"); to.deleteOnExit(); // should fail @@ -232,7 +231,7 @@ public class FileUtilitiesTest { @Test(expected = IOException.class) public void copyFile_ExceptionFromOutputStream() throws Exception { - File from = File.createTempFile("from.file", ".txt"); + File from = Application.createTempFile("from.file", ".txt"); from.deleteOnExit(); ResourceFile to = new ResourceFile(new File("/to.from.file")) { diff --git a/Ghidra/Framework/Gui/src/main/java/resources/IconProvider.java b/Ghidra/Framework/Gui/src/main/java/resources/IconProvider.java index 823271da59..77d8cf1e8f 100644 --- a/Ghidra/Framework/Gui/src/main/java/resources/IconProvider.java +++ b/Ghidra/Framework/Gui/src/main/java/resources/IconProvider.java @@ -25,6 +25,7 @@ import javax.swing.Icon; import javax.swing.ImageIcon; import generic.util.image.ImageUtils; +import ghidra.framework.Application; import ghidra.util.Msg; /** @@ -94,7 +95,7 @@ public class IconProvider { } try { - File imageFile = File.createTempFile("temp.help.icon", null); + File imageFile = Application.createTempFile("temp.help.icon", null); imageFile.deleteOnExit(); // don't let this linger ImageIcon imageIcon = ResourceManager.getImageIcon(icon); ImageUtils.writeFile(imageIcon.getImage(), imageFile); diff --git a/Ghidra/Framework/Help/src/main/java/help/TOCConverter.java b/Ghidra/Framework/Help/src/main/java/help/TOCConverter.java index 818e918ab4..5630a20909 100644 --- a/Ghidra/Framework/Help/src/main/java/help/TOCConverter.java +++ b/Ghidra/Framework/Help/src/main/java/help/TOCConverter.java @@ -25,6 +25,7 @@ import org.xml.sax.*; import org.xml.sax.helpers.DefaultHandler; import org.xml.sax.helpers.ParserAdapter; +import ghidra.framework.Application; import ghidra.util.xml.XmlUtilities; /** @@ -174,7 +175,7 @@ public class TOCConverter { * @throws IOException */ private File createTempTOCFile() throws IOException { - File tempFile = File.createTempFile("toc", ".xml"); + File tempFile = Application.createTempFile("toc", ".xml"); PrintWriter out = new PrintWriter(new FileOutputStream(tempFile)); BufferedReader reader = new BufferedReader(new FileReader(sourceFilename)); diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/ProjectLocator.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/ProjectLocator.java index fc4600cf98..8fd1d79f6d 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/ProjectLocator.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/ProjectLocator.java @@ -21,6 +21,7 @@ import java.util.Set; import org.apache.commons.lang3.StringUtils; +import ghidra.framework.Application; import ghidra.framework.protocol.ghidra.GhidraURL; /** @@ -67,7 +68,7 @@ public class ProjectLocator { } this.name = name; if (StringUtils.isBlank(path)) { - path = System.getProperty("java.io.tmpdir"); + path = Application.getUserTempDirectory().getAbsolutePath(); } this.location = checkAbsolutePath(path); url = GhidraURL.makeURL(location, name); diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/protocol/ghidra/TransientProjectManager.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/protocol/ghidra/TransientProjectManager.java index 3ccb505b01..8a435f06cb 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/protocol/ghidra/TransientProjectManager.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/protocol/ghidra/TransientProjectManager.java @@ -22,6 +22,7 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Set; +import ghidra.framework.Application; import ghidra.framework.client.NotConnectedException; import ghidra.framework.client.RepositoryAdapter; import ghidra.framework.model.ProjectLocator; @@ -172,7 +173,7 @@ public class TransientProjectManager { private TransientProjectData createTransientProject(RepositoryAdapter repository, RepositoryInfo repositoryInfo) throws IOException { - File tmp = File.createTempFile("ghidraPrj", ""); + File tmp = Application.createTempFile("ghidraPrj", ""); tmp.delete(); ProjectLocator tmpProjectLocation = new TransientProjectStorageLocator( diff --git a/Ghidra/Framework/Project/src/test/java/ghidra/framework/model/ProjectLocatorTest.java b/Ghidra/Framework/Project/src/test/java/ghidra/framework/model/ProjectLocatorTest.java index f37194ba33..28d3db5e97 100644 --- a/Ghidra/Framework/Project/src/test/java/ghidra/framework/model/ProjectLocatorTest.java +++ b/Ghidra/Framework/Project/src/test/java/ghidra/framework/model/ProjectLocatorTest.java @@ -25,6 +25,7 @@ import org.junit.Before; import org.junit.Test; import generic.test.AbstractGenericTest; +import ghidra.framework.Application; import ghidra.framework.OperatingSystem; import ghidra.framework.protocol.ghidra.Handler; @@ -120,7 +121,7 @@ public class ProjectLocatorTest extends AbstractGenericTest { @Test public void testTempPath() throws MalformedURLException { - String tmpPath = System.getProperty("java.io.tmpdir").replace("\\", "/"); + String tmpPath = Application.getUserTempDirectory().getAbsolutePath().replace("\\", "/"); if (!tmpPath.startsWith("/")) { tmpPath = "/" + tmpPath; } diff --git a/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/app/plugin/processors/sleigh/SleighLanguageVolatilityTest.java b/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/app/plugin/processors/sleigh/SleighLanguageVolatilityTest.java index 6092bc5b4e..91eda5c78a 100644 --- a/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/app/plugin/processors/sleigh/SleighLanguageVolatilityTest.java +++ b/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/app/plugin/processors/sleigh/SleighLanguageVolatilityTest.java @@ -215,7 +215,7 @@ public class SleighLanguageVolatilityTest extends AbstractGenericTest { public ResourceFile createCustomPspecFile(String name, String content) { File newPspecFile = null; try { - newPspecFile = File.createTempFile(name, ".pspec"); + newPspecFile = Application.createTempFile(name, ".pspec"); BufferedWriter bw = new BufferedWriter(new FileWriter(newPspecFile)); bw.write(content); bw.close(); @@ -239,7 +239,7 @@ public class SleighLanguageVolatilityTest extends AbstractGenericTest { } try { - File editedPspecFile = File.createTempFile(name, ".ldefs"); + File editedPspecFile = Application.createTempFile(name, ".ldefs"); BufferedReader br = new BufferedReader(new FileReader(originalLdefFile.getFile(false))); BufferedWriter bw = new BufferedWriter(new FileWriter(editedPspecFile)); String s; diff --git a/Ghidra/Framework/Utility/src/main/java/ghidra/GhidraApplicationLayout.java b/Ghidra/Framework/Utility/src/main/java/ghidra/GhidraApplicationLayout.java index 9bd79bf033..72974f853d 100644 --- a/Ghidra/Framework/Utility/src/main/java/ghidra/GhidraApplicationLayout.java +++ b/Ghidra/Framework/Utility/src/main/java/ghidra/GhidraApplicationLayout.java @@ -15,7 +15,8 @@ */ package ghidra; -import java.io.*; +import java.io.File; +import java.io.IOException; import java.util.*; import generic.jar.ResourceFile; @@ -36,12 +37,10 @@ public class GhidraApplicationLayout extends ApplicationLayout { /** * Constructs a new Ghidra application layout object. * - * @throws FileNotFoundException if there was a problem getting a user - * directory. - * @throws IOException if there was a problem getting the application - * properties or modules. + * @throws IOException if there was a problem getting a user directory or the application + * properties or modules. */ - public GhidraApplicationLayout() throws FileNotFoundException, IOException { + public GhidraApplicationLayout() throws IOException { // Application root directories applicationRootDirs = findGhidraApplicationRootDirs(); @@ -53,7 +52,8 @@ public class GhidraApplicationLayout extends ApplicationLayout { applicationInstallationDir = findGhidraApplicationInstallationDir(); // User directories - userTempDir = ApplicationUtilities.getDefaultUserTempDir(getApplicationProperties()); + userTempDir = ApplicationUtilities + .getDefaultUserTempDir(getApplicationProperties().getApplicationName()); userCacheDir = ApplicationUtilities.getDefaultUserCacheDir(getApplicationProperties()); userSettingsDir = ApplicationUtilities.getDefaultUserSettingsDir(getApplicationProperties(), getApplicationInstallationDir()); @@ -77,13 +77,10 @@ public class GhidraApplicationLayout extends ApplicationLayout { * (like the Eclipse GhidraDevPlugin). * * @param applicationInstallationDir The application installation directory. - * @throws FileNotFoundException if there was a problem getting a user - * directory. - * @throws IOException if there was a problem getting the application - * properties. + * @throws IOException if there was a problem getting a user directory or the application + * properties. */ - public GhidraApplicationLayout(File applicationInstallationDir) - throws FileNotFoundException, IOException { + public GhidraApplicationLayout(File applicationInstallationDir) throws IOException { // Application installation directory this.applicationInstallationDir = new ResourceFile(applicationInstallationDir); @@ -96,7 +93,8 @@ public class GhidraApplicationLayout extends ApplicationLayout { applicationProperties = new ApplicationProperties(applicationRootDirs); // User directories - userTempDir = ApplicationUtilities.getDefaultUserTempDir(getApplicationProperties()); + userTempDir = ApplicationUtilities + .getDefaultUserTempDir(getApplicationProperties().getApplicationName()); userCacheDir = ApplicationUtilities.getDefaultUserCacheDir(getApplicationProperties()); userSettingsDir = ApplicationUtilities.getDefaultUserSettingsDir(getApplicationProperties(), getApplicationInstallationDir()); diff --git a/Ghidra/Framework/Utility/src/main/java/ghidra/util/SystemUtilities.java b/Ghidra/Framework/Utility/src/main/java/ghidra/util/SystemUtilities.java index f1b1c8b449..e2c0edabe2 100644 --- a/Ghidra/Framework/Utility/src/main/java/ghidra/util/SystemUtilities.java +++ b/Ghidra/Framework/Utility/src/main/java/ghidra/util/SystemUtilities.java @@ -140,7 +140,7 @@ public class SystemUtilities { } /** - * Gets the boolean value of the system property by the given name. If the property is + * Gets the boolean value of the system property by the given name. If the property is * not set, the defaultValue is returned. If the value is set, then it will be passed * into {@link Boolean#parseBoolean(String)}. * diff --git a/Ghidra/Framework/Utility/src/main/java/utilities/util/FileUtilities.java b/Ghidra/Framework/Utility/src/main/java/utilities/util/FileUtilities.java index 4436d2c08c..ae5b6c6826 100644 --- a/Ghidra/Framework/Utility/src/main/java/utilities/util/FileUtilities.java +++ b/Ghidra/Framework/Utility/src/main/java/utilities/util/FileUtilities.java @@ -1189,29 +1189,6 @@ public final class FileUtilities { return formatter.format((length / 1000000f)) + "MB"; } - /** - * Creates a temporary directory using the given prefix - * @param prefix the prefix - * @return the temp file - */ - public static File createTempDirectory(String prefix) { - try { - File temp = File.createTempFile(prefix, Long.toString(System.currentTimeMillis())); - if (!temp.delete()) { - throw new IOException("Could not delete temp file: " + temp.getAbsolutePath()); - } - if (!createDir(temp)) { - throw new IOException("Could not create temp directory: " + temp.getAbsolutePath()); - } - return temp; - } - catch (IOException e) { - Msg.error(FileUtilities.class, "Error creating temporary directory", e); - } - - return null; - } - /** * Sets the given file (or directory) to readable and writable by only the owner. * diff --git a/Ghidra/Framework/Utility/src/main/java/utility/application/AppCleaner.java b/Ghidra/Framework/Utility/src/main/java/utility/application/AppCleaner.java new file mode 100644 index 0000000000..5810c6ca1d --- /dev/null +++ b/Ghidra/Framework/Utility/src/main/java/utility/application/AppCleaner.java @@ -0,0 +1,310 @@ +/* ### + * 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 utility.application; + +import static utility.application.ApplicationUtilities.*; +import static utility.application.XdgUtils.*; + +import java.io.File; +import java.util.*; + +import generic.jar.ResourceFile; +import ghidra.GhidraApplicationLayout; +import ghidra.GhidraLaunchable; +import ghidra.framework.ApplicationProperties; +import ghidra.framework.OperatingSystem; +import ghidra.util.SystemUtilities; +import utilities.util.FileUtilities; + +/** + * Interactive utility to discover and delete artifacts that Ghidra lays down on the filesystem + */ +public class AppCleaner implements GhidraLaunchable { + + /** + * Launches the {@link AppCleaner} + * + * @param layout The application layout to use for the launch + * @param args One argument is expected: the name of the application to clean. All other + * arguments are ignored. + * @throws Exception if there was a problem with the launch + */ + @Override + public void launch(GhidraApplicationLayout layout, String[] args) throws Exception { + + if (args.length != 1) { + System.out.println("Expected 1 argument but got " + args.length); + System.exit(1); + } + + String appName = args[0]; + System.out.println("\nDiscovering " + appName + " artifact directories...."); + + // Discover directories + Set discoveredSet = new LinkedHashSet<>(); + discoveredSet.addAll(findSettingsDirs(appName, layout)); + discoveredSet.addAll(findCacheDirs(appName, layout)); + discoveredSet.addAll(findTempDirs(appName, layout)); + List discoveredDirs = new ArrayList<>(discoveredSet); + + // Exit if we didn't discover any directories + if (discoveredDirs.isEmpty()) { + System.out.println("NONE FOUND"); + return; + } + + // Output discovered directories and prompt user + File potentialParentDir = null; + for (int i = 0; i < discoveredDirs.size(); i++) { + File d = discoveredDirs.get(i); + File parentDir = d.getParentFile(); + boolean indent = parentDir.equals(potentialParentDir); + System.out.println("%2d)%s %s".formatted(i + 1, indent ? " " : "", d)); + if (!indent) { + potentialParentDir = d; + } + } + System.out.println("*) All"); + System.out.println("0) Exit"); + System.out.print("Enter a directory to delete: "); + + // Get user choice and delete + String choice = null; + try (Scanner scanner = new Scanner(System.in)){ + List failures = new ArrayList<>(); + choice = scanner.nextLine().trim(); + switch (choice) { + case "0": + System.out.println("Exiting..."); + return; + case "*": + for (File dir : discoveredDirs) { + if (dir.isDirectory()) { + if (!FileUtilities.deleteDir(dir)) { + failures.add(dir); + } + } + } + break; + default: + File dir = discoveredDirs.get(Integer.parseInt(choice) - 1); + if (!FileUtilities.deleteDir(dir)) { + failures.add(dir); + } + } + System.out.println(failures.isEmpty() ? "SUCCESS" : "Failed to delete:"); + failures.forEach(dir -> System.out.println(" " + dir)); + } + catch (NoSuchElementException e) { + // User likely hit ctrl+c to exit + } + catch (NumberFormatException | IndexOutOfBoundsException e) { + System.out.println("Invalid entry: \"" + choice + "\""); + } + } + + /** + * Finds user settings directories + * + * @param appName The name of the application + * @param layout The layout + * @return A {@link Set} of discovered user settings directories, ordered such that + * parent directories are directly followed by their subdirectories, if applicable + * @see ApplicationUtilities#getDefaultUserSettingsDir(ApplicationProperties, ResourceFile) + * @see ApplicationUtilities#getLegacyUserSettingsDir(ApplicationProperties, ResourceFile) + */ + private Set findSettingsDirs(String appName, ApplicationLayout layout) { + Set discoveredDirs = new LinkedHashSet<>(); + appName = appName.toLowerCase(); + String userNameAndAppName = SystemUtilities.getUserName() + "-" + appName; + + // Legacy default settings directory + getDirFromProperty("user.home", "." + appName).ifPresent(dir -> { + discoveredDirs.add(dir); + discoveredDirs.addAll(getSubdirs(dir)); + }); + + // Current default settings directory + File settingsDir = layout.getUserSettingsDir(); + File settingsParentDir = settingsDir.getParentFile(); + if (settingsParentDir != null && (settingsParentDir.getName().equals(appName) || + settingsParentDir.getName().equals(userNameAndAppName))) { + discoveredDirs.add(settingsParentDir); + discoveredDirs.addAll(getSubdirs(settingsParentDir)); + } + + // Application system property override (likely not set for AppCleaner) + getDirFromProperty(PROPERTY_SETTINGS_DIR, appName).ifPresent(dir -> { + discoveredDirs.add(dir); + discoveredDirs.addAll(getSubdirs(dir)); + }); + getDirFromProperty(PROPERTY_SETTINGS_DIR, userNameAndAppName).ifPresent(dir -> { + discoveredDirs.add(dir); + discoveredDirs.addAll(getSubdirs(dir)); + }); + + // XDG environment variable override + getDirFromEnv(XDG_CONFIG_HOME, appName).ifPresent(dir -> { + discoveredDirs.add(dir); + discoveredDirs.addAll(getSubdirs(dir)); + }); + getDirFromEnv(XDG_CONFIG_HOME, userNameAndAppName).ifPresent(dir -> { + discoveredDirs.add(dir); + discoveredDirs.addAll(getSubdirs(dir)); + }); + + return discoveredDirs; + } + + /** + * Finds user cache directories + * + * @param appName The name of the application + * @param layout The layout + * @return A {@link Set} of discovered user cache directories, ordered such that + * parent directories are directly followed by their subdirectories, if applicable + * @see ApplicationUtilities#getDefaultUserCacheDir(ApplicationProperties) + */ + private Set findCacheDirs(String appName, ApplicationLayout layout) { + Set discoveredDirs = new LinkedHashSet<>(); + + // Legacy cache directories + if (OperatingSystem.CURRENT_OPERATING_SYSTEM.equals(OperatingSystem.WINDOWS)) { + getDirFromEnv("LOCALAPPDATA", appName).ifPresent(discoveredDirs::add); + } + else { + String legacyName = SystemUtilities.getUserName() + "-" + appName; + getDirFromProperty("java.io.tmpdir", legacyName).ifPresent(discoveredDirs::add); + } + + // Newer cache directories always use a lowercase application name + appName = appName.toLowerCase(); + String userNameAndAppName = SystemUtilities.getUserName() + "-" + appName; + + // Current cache directories + File cacheDir = layout.getUserCacheDir(); + if (cacheDir != null && cacheDir.isDirectory()) { + discoveredDirs.add(cacheDir); + } + + // Application system property override (likely not set for AppCleaner) + getDirFromProperty(PROPERTY_CACHE_DIR, appName).ifPresent(discoveredDirs::add); + getDirFromProperty(PROPERTY_CACHE_DIR, userNameAndAppName).ifPresent(discoveredDirs::add); + + // XDG environment variable override + getDirFromEnv(XDG_CACHE_HOME, appName).ifPresent(discoveredDirs::add); + getDirFromEnv(XDG_CACHE_HOME, userNameAndAppName).ifPresent(discoveredDirs::add); + + return discoveredDirs; + } + + /** + * Finds user temp directories + * + * @param appName The name of the application + * @param layout The layout + * @return A {@link Set} of discovered user temp directories, ordered such that + * parent directories are directly followed by their subdirectories, if applicable + * @see ApplicationUtilities#getDefaultUserTempDir(String) + */ + private Set findTempDirs(String appName, ApplicationLayout layout) { + Set discoveredDirs = new LinkedHashSet<>(); + + // Legacy temp directories + String legacyName = SystemUtilities.getUserName() + "-" + appName; + if (OperatingSystem.CURRENT_OPERATING_SYSTEM.equals(OperatingSystem.WINDOWS)) { + getDirFromEnv("TEMP", legacyName).ifPresent(discoveredDirs::add); + } + else { + getDirFromProperty("java.io.tmpdir", legacyName).ifPresent(discoveredDirs::add); + } + + // Newer temp directories always use a lowercase application name + appName = appName.toLowerCase(); + String userNameAndAppName = SystemUtilities.getUserName() + "-" + appName; + + // Current temp directories + File tempDir = layout.getUserTempDir(); + if (tempDir != null && tempDir.isDirectory()) { + discoveredDirs.add(tempDir); + } + + // Application system property override (likely not set for AppCleaner) + getDirFromProperty(PROPERTY_TEMP_DIR, appName).ifPresent(discoveredDirs::add); + getDirFromProperty(PROPERTY_TEMP_DIR, userNameAndAppName).ifPresent(discoveredDirs::add); + + // XDG environment variable override + getDirFromEnv(XDG_RUNTIME_DIR, appName).ifPresent(discoveredDirs::add); + getDirFromEnv(XDG_RUNTIME_DIR, userNameAndAppName).ifPresent(discoveredDirs::add); + + return discoveredDirs; + } + + /** + * Gets the subdirectory of the given name found within the directory specified by the given + * system property + * + * @param propertyName The name of the system property + * @param subdirName The name of the subdirectory within the directory specified by the given + * system property + * @return The subdirectory of the given name found within the directory specified by the given + * systemProperty + */ + private Optional getDirFromProperty(String propertyName, String subdirName) { + String path = System.getProperty(propertyName, "").trim(); + if (!path.isEmpty()) { + File dir = new File(path, subdirName); + if (dir.isDirectory()) { + return Optional.of(dir); + } + } + return Optional.empty(); + } + + /** + * Gets the subdirectory of the given name found within the directory specified by the given + * environment variable + * + * @param envName The name of the environment variable + * @param subdirName The name of the subdirectory within the directory specified by the given + * environment variable + * @return The subdirectory of the given name found within the directory specified by the given + * environment variable + */ + private Optional getDirFromEnv(String envName, String subdirName) { + String path = System.getenv(envName); + if (path != null && !path.isBlank()) { + File dir = new File(path, subdirName); + if (dir.isDirectory()) { + return Optional.of(dir); + } + } + return Optional.empty(); + } + + /** + * Gets the direct sub-directories of the given directory (non-recursive) + * + * @param dir The directory to get the sub-directories of + * @return The direct sub-directories of the given directory + */ + private List getSubdirs(File dir) { + File[] listing = dir.listFiles(File::isDirectory); + return listing != null ? Arrays.asList(listing) : List.of(); + + } + +} diff --git a/Ghidra/Framework/Utility/src/main/java/utility/application/ApplicationSettings.java b/Ghidra/Framework/Utility/src/main/java/utility/application/ApplicationSettings.java index 73f502fae3..bc6190a8f0 100644 --- a/Ghidra/Framework/Utility/src/main/java/utility/application/ApplicationSettings.java +++ b/Ghidra/Framework/Utility/src/main/java/utility/application/ApplicationSettings.java @@ -15,11 +15,11 @@ */ package utility.application; -import ghidra.framework.PluggableServiceRegistry; - import java.io.File; +import java.io.IOException; -import utilities.util.FileUtilities; +import ghidra.framework.PluggableServiceRegistry; +import ghidra.util.Msg; public class ApplicationSettings { static { @@ -46,6 +46,13 @@ public class ApplicationSettings { * application version. */ protected File doGetUserApplicationSettingsDirectory() { - return FileUtilities.createTempDirectory("application.settings_"); + try { + return ApplicationUtilities.getDefaultUserTempDir("application.settings"); + } + catch (IOException e) { + Msg.error(ApplicationSettings.class, "Error creating application.settings directory", + e); + return null; + } } } diff --git a/Ghidra/Framework/Utility/src/main/java/utility/application/ApplicationUtilities.java b/Ghidra/Framework/Utility/src/main/java/utility/application/ApplicationUtilities.java index ea12a661f0..7826358973 100644 --- a/Ghidra/Framework/Utility/src/main/java/utility/application/ApplicationUtilities.java +++ b/Ghidra/Framework/Utility/src/main/java/utility/application/ApplicationUtilities.java @@ -23,12 +23,28 @@ import generic.jar.ResourceFile; import ghidra.framework.*; import ghidra.util.Msg; import ghidra.util.SystemUtilities; +import utilities.util.FileUtilities; /** * Utility class for default application things. */ public class ApplicationUtilities { + /** + * Name of system property used to override the location of the user temporary directory + */ + public static final String PROPERTY_TEMP_DIR = "application.tempdir"; + + /** + * Name of system property used to override the location of the user cache directory + */ + public static final String PROPERTY_CACHE_DIR = "application.cachedir"; + + /** + * Name of system property used to override the location of the user settings directory + */ + public static final String PROPERTY_SETTINGS_DIR = "application.settingsdir"; + /** * Searches for default application root directories. * @@ -137,85 +153,145 @@ public class ApplicationUtilities { } /** - * Gets the default application's user temp directory. + * Gets the application's default user temp directory. + *

+ * NOTE: This method does not create the directory. * - * @param applicationProperties The application properties. - * @return The default application's user temp directory. - * @throws FileNotFoundException if the user temp directory could not be determined. + * @param applicationName The application name. + * @return The application's default user temp directory. The returned {@link File} will + * represent an absolute path. + * @throws FileNotFoundException if the absolute path of the user temp directory could not be + * determined. */ - public static File getDefaultUserTempDir(ApplicationProperties applicationProperties) - throws FileNotFoundException { - String tmpdir = System.getProperty("java.io.tmpdir"); - if (tmpdir == null || tmpdir.isEmpty()) { - throw new FileNotFoundException("System property \"java.io.tmpdir\" is not set!"); + public static File getDefaultUserTempDir(String applicationName) throws FileNotFoundException { + + String appName = applicationName.toLowerCase(); + + // Look for Ghidra-specific system property + File tempOverrideDir = getSystemPropertyFile(PROPERTY_TEMP_DIR, false); + if (tempOverrideDir != null) { + return new File(tempOverrideDir, getUserSpecificDirName(tempOverrideDir, appName)); } - return new File(tmpdir, - SystemUtilities.getUserName() + "-" + applicationProperties.getApplicationName()); + + // Look for XDG environment variable + File xdgRuntimeDir = getEnvFile(XdgUtils.XDG_RUNTIME_DIR, false); + if (xdgRuntimeDir != null) { + return new File(xdgRuntimeDir, getUserSpecificDirName(xdgRuntimeDir, appName)); + } + + File javaTmpDir = getJavaTmpDir(); + return new File(javaTmpDir, getUserSpecificDirName(javaTmpDir, appName)); } /** - * Gets the default application's user cache directory. + * Gets the application's default user cache directory. + *

+ * NOTE: This method does not create the directory. * * @param applicationProperties The application properties. - * @return The default application's user cache directory. - * @throws FileNotFoundException if the user cache directory could not be determined. + * @return The application's default user cache directory. The returned {@link File} will + * represent an absolute path. + * @throws FileNotFoundException if the absolute path of the user cache directory could not be + * determined. */ public static File getDefaultUserCacheDir(ApplicationProperties applicationProperties) throws FileNotFoundException { - // Look for preset cache directory - String cachedir = System.getProperty("application.cachedir", "").trim(); - if (!cachedir.isEmpty()) { - return new File(cachedir, - SystemUtilities.getUserName() + "-" + applicationProperties.getApplicationName()); + String appName = applicationProperties.getApplicationName().toLowerCase(); + + // Look for Ghidra-specific system property + File cacheOverrideDir = getSystemPropertyFile(PROPERTY_CACHE_DIR, false); + if (cacheOverrideDir != null) { + return new File(cacheOverrideDir, getUserSpecificDirName(cacheOverrideDir, appName)); } - // Handle Windows specially - if (OperatingSystem.CURRENT_OPERATING_SYSTEM == OperatingSystem.WINDOWS) { - File localAppDataDir = null; - String localAppDataDirPath = System.getenv("LOCALAPPDATA"); // e.g., /Users/myname/AppData/Local - if (localAppDataDirPath != null && !localAppDataDirPath.isEmpty()) { - localAppDataDir = new File(localAppDataDirPath); - } - else { - String userHome = System.getProperty("user.home"); - if (userHome != null) { - localAppDataDir = new File(userHome, "AppData\\Local"); - if (!localAppDataDir.isDirectory()) { - localAppDataDir = new File(userHome, "Local Settings"); - } - } - } - if (localAppDataDir != null && localAppDataDir.isDirectory()) { - return new File(localAppDataDir, applicationProperties.getApplicationName()); - } + // Look for XDG environment variable + File xdgCacheHomeDir = getEnvFile(XdgUtils.XDG_CACHE_HOME, false); + if (xdgCacheHomeDir != null) { + return new File(xdgCacheHomeDir, getUserSpecificDirName(xdgCacheHomeDir, appName)); } - - // Use user temp directory if platform specific scheme does not exist above or it failed - return getDefaultUserTempDir(applicationProperties); + + // Use platform-specific default location + String userDirName = SystemUtilities.getUserName() + "-" + appName; + return switch (OperatingSystem.CURRENT_OPERATING_SYSTEM) { + case WINDOWS -> new File(getEnvFile("LOCALAPPDATA", true), appName); + case LINUX -> new File("/var/tmp/" + userDirName); + case MAC_OS_X -> new File("/var/tmp/" + userDirName); + default -> throw new FileNotFoundException( + "Failed to find the user cache directory: Unsupported operating system."); + }; } /** - * Gets the default application's user settings directory. + * Gets the application's default user settings directory. + *

+ * NOTE: This method does not create the directory. * * @param applicationProperties The application properties. * @param installationDirectory The application installation directory. - * @return The application's user settings directory. - * @throws FileNotFoundException if the user settings directory could not be determined. + * @return The application's default user settings directory. The returned {@link File} will + * represent an absolute path. + * @throws FileNotFoundException if the absolute path of the user settings directory could not + * be determined. */ public static File getDefaultUserSettingsDir(ApplicationProperties applicationProperties, ResourceFile installationDirectory) throws FileNotFoundException { - String homedir = System.getProperty("user.home"); - if (homedir == null || homedir.isEmpty()) { - throw new FileNotFoundException("System property \"user.home\" is not set!"); + String appName = applicationProperties.getApplicationName().toLowerCase(); + ApplicationIdentifier applicationIdentifier = + new ApplicationIdentifier(applicationProperties); + String versionedName = applicationIdentifier.toString(); + if (SystemUtilities.isInDevelopmentMode()) { + // Add the application's installation directory name to this variable, so that each + // branch's project user directory is unique. + versionedName += "_location_" + installationDirectory.getName(); } + // Look for Ghidra-specific system property + File settingsOverrideDir = getSystemPropertyFile(PROPERTY_SETTINGS_DIR, false); + if (settingsOverrideDir != null) { + return new File(settingsOverrideDir, + getUserSpecificDirName(settingsOverrideDir, appName) + "/" + versionedName); + } + + // Look for XDG environment variable + File xdgConfigHomeDir = getEnvFile(XdgUtils.XDG_CONFIG_HOME, false); + if (xdgConfigHomeDir != null) { + return new File(xdgConfigHomeDir, + getUserSpecificDirName(xdgConfigHomeDir, appName) + "/" + versionedName); + } + + File userHomeDir = getJavaUserHomeDir(); + String versionedSubdir = appName + "/" + versionedName; + return switch (OperatingSystem.CURRENT_OPERATING_SYSTEM) { + case WINDOWS -> new File(getEnvFile("APPDATA", true), versionedSubdir); + case LINUX -> new File(userHomeDir, ".config/" + versionedSubdir); + case MAC_OS_X -> new File(userHomeDir, "Library/" + versionedSubdir); + default -> throw new FileNotFoundException( + "Failed to find the user settings directory: Unsupported operating system."); + }; + } + + /** + * Gets the application's legacy (pre-Ghida 11.1) user settings directory. + *

+ * NOTE: This method does not create the directory. + * + * @param applicationProperties The application properties. + * @param installationDirectory The application installation directory. + * @return The application's legacy user settings directory. The returned {@link File} will + * represent an absolute path. + * @throws FileNotFoundException if the absolute path of the legacy user settings directory + * could not be determined. + */ + public static File getLegacyUserSettingsDir(ApplicationProperties applicationProperties, + ResourceFile installationDirectory) throws FileNotFoundException { + ApplicationIdentifier applicationIdentifier = new ApplicationIdentifier(applicationProperties); File userSettingsParentDir = - new File(homedir, "." + applicationIdentifier.getApplicationName()); + new File(getJavaUserHomeDir(), "." + applicationIdentifier.getApplicationName()); String userSettingsDirName = "." + applicationIdentifier; @@ -227,4 +303,108 @@ public class ApplicationUtilities { return new File(userSettingsParentDir, userSettingsDirName); } + + /** + * Gets Java's temporary directory in absolute form + * + * @return Java's temporary directory in absolute form + * @throws FileNotFoundException if Java's temporary directory is not defined or it is not an + * absolute path + */ + private static File getJavaTmpDir() throws FileNotFoundException { + return getSystemPropertyFile("java.io.tmpdir", true); + } + + /** + * Gets Java's user home directory in absolute form + * + * @return Java's user home directory in absolute form + * @throws FileNotFoundException if Java's user home directory is not defined or it is not an + * absolute path + */ + private static File getJavaUserHomeDir() throws FileNotFoundException { + return getSystemPropertyFile("user.home", true); + } + + /** + * Gets the absolute form {@link File} value of the system property by the given name + * + * @param name The system property name + * @param required True if given system property is required to be set; otherwise, false + * @return The absolute form {@link File} value of the system property by the given name, or + * null if it isn't set + * @throws FileNotFoundException if the property value was not an absolute path, or if it is + * required and not set + */ + private static File getSystemPropertyFile(String name, boolean required) + throws FileNotFoundException { + String path = System.getProperty(name); + if (path == null || path.isBlank()) { + if (required) { + throw new FileNotFoundException( + "Required system property \"%s\" is not set!".formatted(name)); + } + return null; + } + path = path.trim(); + File file = new File(path); + if (!file.isAbsolute()) { + throw new FileNotFoundException( + "System property \"%s\" is not an absolute path: \"%s\"".formatted(name, path)); + } + return file; + } + + /** + * Gets the absolute form {@link File} value of the environment variable by the given name + * + * @param name The environment variable name + * @param required True if the given environment variable is required to be set; otherwise, + * false + * @return The absolute form {@link File} value of the environment variable by the given name, + * or null if it isn't set + * @throws FileNotFoundException if the property value was not an absolute path, or if it is + * required and not set + */ + private static File getEnvFile(String name, boolean required) throws FileNotFoundException { + String path = System.getenv(name); + if (path == null || path.isBlank()) { + if (required) { + throw new FileNotFoundException( + "Required environment variable \"%s\" is not set!".formatted(name)); + } + return null; + } + path = path.trim(); + File file = new File(path); + if (!file.isAbsolute()) { + throw new FileNotFoundException( + "Environment variable \"%s\" is not an absolute path: \"%s\"".formatted(name, + path)); + } + return file; + } + + /** + * Gets a directory name that can be used to create a user-specific sub-directory in + * {@code parentDir}. If the {@code parentDir} is contained within the user's home directory, + * the given {@code appName} can simply be used since it will live in a user-specific location. + * Otherwise, the user's name will get prepended to the {@code appName} so it does not collide + * with other users' directories in the shared directory space. + + * @param parentDir The parent directory where we'd like to create a user-specific sub-directory + * @param appName The application name + * @return A directory name that can be used to create a user-specific sub-directory in + * {@code parentDir}. + * @throws FileNotFoundException if Java's user home directory is not defined or it is not an + * absolute path + */ + private static String getUserSpecificDirName(File parentDir, String appName) + throws FileNotFoundException { + String userSpecificDirName = appName; + if (!FileUtilities.isPathContainedWithin(getJavaUserHomeDir(), parentDir)) { + userSpecificDirName = SystemUtilities.getUserName() + "-" + appName; + } + return userSpecificDirName; + } } diff --git a/Ghidra/Framework/Utility/src/main/java/utility/application/DummyApplicationLayout.java b/Ghidra/Framework/Utility/src/main/java/utility/application/DummyApplicationLayout.java index a364ae587e..268a312fea 100644 --- a/Ghidra/Framework/Utility/src/main/java/utility/application/DummyApplicationLayout.java +++ b/Ghidra/Framework/Utility/src/main/java/utility/application/DummyApplicationLayout.java @@ -15,7 +15,7 @@ */ package utility.application; -import java.io.FileNotFoundException; +import java.io.IOException; import java.util.ArrayList; import java.util.Collections; @@ -32,9 +32,9 @@ public class DummyApplicationLayout extends ApplicationLayout { /** * Constructs a new dummy application layout object. * @param name the application name - * @throws FileNotFoundException if there was a problem getting a user directory. + * @throws IOException if there was a problem getting a user directory. */ - public DummyApplicationLayout(String name) throws FileNotFoundException { + public DummyApplicationLayout(String name) throws IOException { // Application properties applicationProperties = new ApplicationProperties(name); @@ -48,7 +48,8 @@ public class DummyApplicationLayout extends ApplicationLayout { applicationRootDirs.add(cwd); // User directories - userTempDir = ApplicationUtilities.getDefaultUserTempDir(applicationProperties); + userTempDir = + ApplicationUtilities.getDefaultUserTempDir(applicationProperties.getApplicationName()); extensionInstallationDirs = Collections.emptyList(); } diff --git a/Ghidra/Framework/Utility/src/main/java/utility/application/XdgUtils.java b/Ghidra/Framework/Utility/src/main/java/utility/application/XdgUtils.java new file mode 100644 index 0000000000..bd77b1b84c --- /dev/null +++ b/Ghidra/Framework/Utility/src/main/java/utility/application/XdgUtils.java @@ -0,0 +1,76 @@ +/* ### + * 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 utility.application; + +/** + * Class to support the "XDG Base Directory Specification" + *

+ * Based off version 0.8 + * + * @see basedir-spec-0.8.html + */ +public class XdgUtils { + + /** + * $XDG_DATA_HOME defines the base directory relative to which user-specific data files should + * be stored. If $XDG_DATA_HOME is either not set or empty, a default equal to + * $HOME/.local/share should be used. + */ + public static final String XDG_DATA_HOME = "XDG_DATA_HOME"; + + /** + * $XDG_CONFIG_HOME defines the base directory relative to which user-specific configuration + * files should be stored. If $XDG_CONFIG_HOME is either not set or empty, a default equal to + * $HOME/.config should be used. + */ + public static final String XDG_CONFIG_HOME = "XDG_CONFIG_HOME"; + + /** + * $XDG_STATE_HOME defines the base directory relative to which user-specific state files should + * be stored. If $XDG_STATE_HOME is either not set or empty, a default equal to + * $HOME/.local/state should be used. + */ + public static final String XDG_STATE_HOME = "XDG_STATE_HOME"; + + /** + * $XDG_DATA_DIRS defines the preference-ordered set of base directories to search for data + * files in addition to the $XDG_DATA_HOME base directory. The directories in $XDG_DATA_DIRS + * should be separated with a colon ':'. + */ + public static final String XDG_DATA_DIRS = "XDG_DATA_DIRS"; + + /** + * $XDG_CONFIG_DIRS defines the preference-ordered set of base directories to search for + * configuration files in addition to the $XDG_CONFIG_HOME base directory. The directories in + * $XDG_CONFIG_DIRS should be separated with a colon ':'. + */ + public static final String XDG_CONFIG_DIRS = "XDG_CONFIG_DIRS"; + + /** + * $XDG_CACHE_HOME defines the base directory relative to which user-specific non-essential + * data files should be stored. If $XDG_CACHE_HOME is either not set or empty, a default equal + * to $HOME/.cache should be used. + */ + public static final String XDG_CACHE_HOME = "XDG_CACHE_HOME"; + + /** + * $XDG_RUNTIME_DIR defines the base directory relative to which user-specific non-essential + * runtime files and other file objects (such as sockets, named pipes, ...) should be stored. + * The directory MUST be owned by the user, and he MUST be the only one having read and write + * access to it. Its Unix access mode MUST be 0700. + */ + public static final String XDG_RUNTIME_DIR = "XDG_RUNTIME_DIR"; +} diff --git a/Ghidra/RuntimeScripts/Common/support/launch.properties b/Ghidra/RuntimeScripts/Common/support/launch.properties index 6849b53927..4c0ce4b638 100644 --- a/Ghidra/RuntimeScripts/Common/support/launch.properties +++ b/Ghidra/RuntimeScripts/Common/support/launch.properties @@ -83,17 +83,47 @@ VMARGS_WINDOWS=-Dlog4j.skipJansi=true # Ghidra does not use class data sharing, so explicitly turn it off to avoid the warning. VMARGS=-Xshare:off -# Persistent cache directory used by the application. This directory will be used to store -# persistent application caches for all users. The default location for Mac/Linux is the same as -# specified by java.io.tmpdir property. The default location for Windows corresponds to the -# application local settings directory for the user (e.g., %LOCALAPPDATA%). If you wish to use a -# directory with more storage or avoid system cleanups, it may be desirable to override the default -# location. +# Settings directory used by the application to store application settings and data that persist +# between application sessions, system reboots, and periodic system cleanup. Overridden values +# are required to be absolute paths. The current user name may be incorporated into the settings +# directory's name if the settings directory lives outside of the user's home directory. The +# settings directory will be selected based on the following rules, in order of precedence: +# 1. System.getProperty("application.settingsdir")/[user-]/_ +# 2. System.getenv("XDG_CONFIG_HOME")/[user-]/_ +# 3. A platform specific default location: +# - Windows: %APPDATA%\\_ +# - Linux: $HOME/.config//_ +# - macOS: $HOME/Library//_ +#VMARGS=-Dapplication.settingsdir= + +# Cache directory used by the application to store cached application data that ideally will persist +# between application sessions and system reboots, but is not required to do so. Files stored in +# the cache directory may be numerous and/or large. Overridden values are required to be absolute +# paths. The current user name may be incorporated into the cache directory's name if the cache +# directory lives outside of the user's home directory. The cache directory will be selected based +# on the following rules, in order of precedence +# 1. System.getProperty("application.cachedir")/[user-] +# 2. System.getenv("XDG_CACHE_HOME")/[user-] +# 3. A platform specific default location: +# - Windows: %LOCALAPPDATA%\ +# - Linux: /var/tmp/- +# - macOS: /var/tmp/- #VMARGS=-Dapplication.cachedir= -# Temporary directory used by the application. This directory will be used for all temporary files -# and may also be used for the persistent user cache directory /-Ghidra. -# The specified directory must exist and have appropriate read/write/execute permissions +# Temporary directory used by the application to store short-lived files that are not required to +# persist between application sessions. Overridden values are required to be absolute paths. The +# current user name may be incorporated into the temporary directory's name if the temporary +# directory lives outside of the user's home directory. The temporary directory will be selected +# based on the following rules, in order of precedence: +# 1. System.getProperty("application.tempdir")/[user-] +# 2. System.getenv("XDG_RUNTIME_DIR")/[user-] +# 3. System.getProperty("java.io.tmpdir")/[user-] +# Unless overridden below, the "java.io.tmpdir" system property typically defaults to the following +# platform specific locations: +# - Windows: %TEMP% +# - Linux: /tmp +# - macOS: $TMPDIR +#VMARGS=-Dapplication.tempdir= #VMARGS=-Djava.io.tmpdir= # Disable alternating row colors in tables diff --git a/Ghidra/RuntimeScripts/Linux/support/ghidraClean b/Ghidra/RuntimeScripts/Linux/support/ghidraClean new file mode 100755 index 0000000000..b6b0ecc85e --- /dev/null +++ b/Ghidra/RuntimeScripts/Linux/support/ghidraClean @@ -0,0 +1,21 @@ +#!/usr/bin/env bash + +#--------------------------------------------------------------------------------------------------- +# Ghidra-Clean +# An interactive utility to discover and delete artifacts that Ghidra lays down on the filesystem. +#--------------------------------------------------------------------------------------------------- + +# Maximum heap memory may be changed if default is inadequate. This will generally be up to 1/4 of +# the physical memory available to the OS. Uncomment MAXMEM setting if non-default value is needed. +#MAXMEM=1G + +VMARG_LIST="-Djava.awt.headless=true " + +# Resolve symbolic link if present and get the directory this script lives in. +# NOTE: "readlink -f" is best but works on Linux only, "readlink" will only work if your PWD +# contains the link you are calling (which is the best we can do on macOS), and the "echo" is the +# fallback, which doesn't attempt to do anything with links. +SCRIPT_FILE="$(readlink -f "$0" 2>/dev/null || readlink "$0" 2>/dev/null || echo "$0")" +SCRIPT_DIR="${SCRIPT_FILE%/*}" + +"${SCRIPT_DIR}"/launch.sh fg jre Ghidra-Clean "$MAXMEM" "$VMARG_LIST" utility.application.AppCleaner Ghidra diff --git a/Ghidra/RuntimeScripts/Windows/support/ghidraClean.bat b/Ghidra/RuntimeScripts/Windows/support/ghidraClean.bat new file mode 100644 index 0000000000..8b8c4a59fb --- /dev/null +++ b/Ghidra/RuntimeScripts/Windows/support/ghidraClean.bat @@ -0,0 +1,9 @@ +:: Ghidra-Clean +:: An interactive utility to discover and delete artifacts that Ghidra lays down on the filesystem. + +@echo off +setlocal + +set VMARG_LIST=-Djava.awt.headless=true + +call "%~dp0launch.bat" fg jdk Ghidra-Clean "" "" utility.application.AppCleaner Ghidra \ No newline at end of file diff --git a/Ghidra/RuntimeScripts/certification.manifest b/Ghidra/RuntimeScripts/certification.manifest index 69c8bd0a47..19acc462a2 100644 --- a/Ghidra/RuntimeScripts/certification.manifest +++ b/Ghidra/RuntimeScripts/certification.manifest @@ -21,6 +21,7 @@ Linux/support/buildGhidraJar||GHIDRA||||END| Linux/support/buildNatives||GHIDRA||||END| Linux/support/convertStorage||GHIDRA||||END| Linux/support/gdbGADPServerRun||GHIDRA||||END| +Linux/support/ghidraClean||GHIDRA||||END| Linux/support/ghidraDebug||GHIDRA||||END| Linux/support/pythonRun||GHIDRA||||END| Linux/support/sleigh||GHIDRA||||END| @@ -40,6 +41,7 @@ Windows/support/createPdbXmlFiles.bat||GHIDRA||||END| Windows/support/dbgengGADPServerRun.bat||GHIDRA||||END| Windows/support/dbgmodelGADPServerRun.bat||GHIDRA||||END| Windows/support/ghidra.ico||GHIDRA||||END| +Windows/support/ghidraClean.bat||GHIDRA||||END| Windows/support/ghidraDebug.bat||GHIDRA||||END| Windows/support/launch.bat||GHIDRA||||END| Windows/support/pythonRun.bat||GHIDRA||||END| diff --git a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/FrontEndPluginScreenShots.java b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/FrontEndPluginScreenShots.java index 9de997b95b..b07ae0ebcb 100644 --- a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/FrontEndPluginScreenShots.java +++ b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/FrontEndPluginScreenShots.java @@ -35,6 +35,7 @@ import docking.wizard.WizardManager; import docking.wizard.WizardPanel; import generic.theme.GThemeDefaults.Colors; import ghidra.app.plugin.core.archive.RestoreDialog; +import ghidra.framework.Application; import ghidra.framework.data.DefaultProjectData; import ghidra.framework.data.GhidraFileData; import ghidra.framework.main.*; @@ -56,7 +57,6 @@ import resources.MultiIcon; public class FrontEndPluginScreenShots extends GhidraScreenShotGenerator { private static final String OTHER_PROJECT = "Other_Project"; - private final static String TEMP_DIR = System.getProperty("java.io.tmpdir"); Icon icon = (Icon) getInstanceField("CONVERT_ICON", ProjectInfoDialog.class); public FrontEndPluginScreenShots() { @@ -659,6 +659,7 @@ public class FrontEndPluginScreenShots extends GhidraScreenShotGenerator { @Test public void testViewOtherProjects() throws IOException, LockException, InvalidNameException, CancelledException { + String TEMP_DIR = Application.getUserTempDirectory().getAbsolutePath(); Project project = env.getProject(); program = env.getProgram("WinHelloCPP.exe"); @@ -692,6 +693,7 @@ public class FrontEndPluginScreenShots extends GhidraScreenShotGenerator { @Test public void testLinkOtherProject() throws IOException, LockException, InvalidNameException, CancelledException { + String TEMP_DIR = Application.getUserTempDirectory().getAbsolutePath(); Project project = env.getProject(); program = env.getProgram("WinHelloCPP.exe"); diff --git a/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/server/remote/ServerTestUtil.java b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/server/remote/ServerTestUtil.java index 3aa9aa6b7f..accabafa1c 100644 --- a/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/server/remote/ServerTestUtil.java +++ b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/server/remote/ServerTestUtil.java @@ -30,6 +30,7 @@ import javax.rmi.ssl.SslRMIClientSocketFactory; import org.apache.commons.lang3.RandomStringUtils; import generic.test.*; +import ghidra.framework.Application; import ghidra.framework.client.*; import ghidra.framework.data.ContentHandler; import ghidra.framework.data.DomainObjectAdapter; @@ -347,7 +348,7 @@ public class ServerTestUtil { private static synchronized File getPkiTestDirectory() { if (testPkiDirectory == null) { - testPkiDirectory = new File(System.getProperty("java.io.tmpdir"), "test-pki"); + testPkiDirectory = new File(Application.getUserTempDirectory(), "test-pki"); FileUtilities.deleteDir(testPkiDirectory); testPkiDirectory.mkdirs(); diff --git a/GhidraBuild/LaunchSupport/src/main/java/ghidra/launch/JavaConfig.java b/GhidraBuild/LaunchSupport/src/main/java/ghidra/launch/JavaConfig.java index c6ebc5791c..b9ff3ead58 100644 --- a/GhidraBuild/LaunchSupport/src/main/java/ghidra/launch/JavaConfig.java +++ b/GhidraBuild/LaunchSupport/src/main/java/ghidra/launch/JavaConfig.java @@ -359,6 +359,23 @@ public class JavaConfig { */ private void initJavaHomeSaveFile(File installDir) throws FileNotFoundException { boolean isDev = new File(installDir, "build.gradle").isFile(); + String appName = applicationName.toLowerCase(); + + String userSettingsDirName = appName + "_" + applicationVersion + "_" + + applicationReleaseName.replaceAll("\\s", "").toUpperCase(); + if (isDev) { + userSettingsDirName += "_location_" + installDir.getParentFile().getName(); + } + + File userSettingsDir = null; + + // Look for XDG environment variable + String xdgConfigHomeDirStr = System.getenv("XDG_CONFIG_HOME"); + if (xdgConfigHomeDirStr != null && !xdgConfigHomeDirStr.isEmpty()) { + userSettingsDir = new File(xdgConfigHomeDirStr, appName + "/" + userSettingsDirName); + javaHomeSaveFile = new File(userSettingsDir, JAVA_HOME_SAVE_NAME); + return; + } // Ensure there is a user home directory (there definitely should be) String userHomeDirPath = System.getProperty("user.home"); @@ -370,18 +387,27 @@ public class JavaConfig { 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). - File userSettingsParentDir = - new File(userHomeDir, "." + applicationName.replaceAll("\\s", "").toLowerCase()); - - String userSettingsDirName = userSettingsParentDir.getName() + "_" + applicationVersion + - "_" + applicationReleaseName.replaceAll("\\s", "").toUpperCase(); - - if (isDev) { - userSettingsDirName += "_location_" + installDir.getParentFile().getName(); + switch (JavaFinder.getCurrentPlatform()) { + case WINDOWS: + String localAppDataDirPath = System.getenv("APPDATA"); + if (localAppDataDirPath == null || localAppDataDirPath.trim().isEmpty()) { + throw new FileNotFoundException("\"APPDATA\" environment variable is not set"); + } + userSettingsDir = + new File(localAppDataDirPath, appName + "/" + userSettingsDirName); + break; + case LINUX: + userSettingsDir = + new File(userHomeDir, ".config/" + appName + "/" + userSettingsDirName); + break; + case MACOS: + userSettingsDir = + new File(userHomeDir, "Library/" + appName + "/" + userSettingsDirName); + break; + default: + throw new FileNotFoundException( + "Failed to find the user settings directory: Unsupported operating system."); } - - File userSettingsDir = new File(userSettingsParentDir, userSettingsDirName); javaHomeSaveFile = new File(userSettingsDir, JAVA_HOME_SAVE_NAME); }