diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 8a484e4..89e79a9 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -104,5 +104,9 @@ jobs: - name: Run TestCpuFeatures run: ./gradlew :snaploader-examples:TestCpuFeatures :snaploader-examples:run + - name: Run TestBrokenFeatures + run: ./gradlew :snaploader-examples:ReflectiveLauncher :snaploader-examples:run --args="electrostatic4j.snaploader.examples.TestBrokenFeatures" + - name: List extracted files run: ls ./snaploader-examples/libs/ + diff --git a/snaploader-examples/src/main/java/electrostatic4j/snaploader/examples/TestBrokenFeatures.java b/snaploader-examples/src/main/java/electrostatic4j/snaploader/examples/TestBrokenFeatures.java new file mode 100644 index 0000000..f738052 --- /dev/null +++ b/snaploader-examples/src/main/java/electrostatic4j/snaploader/examples/TestBrokenFeatures.java @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2023-2026, The Electrostatic-Sandbox Distributed Simulation Framework, jSnapLoader + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'AvrSandbox' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package electrostatic4j.snaploader.examples; + +import electrostatic4j.snaploader.LibraryInfo; +import electrostatic4j.snaploader.NativeBinaryLoader; +import electrostatic4j.snaploader.NativeBinaryLoadingListener; +import electrostatic4j.snaploader.filesystem.DirectoryPath; +import electrostatic4j.snaploader.filesystem.FileExtractionListener; +import electrostatic4j.snaploader.filesystem.FileExtractor; +import electrostatic4j.snaploader.filesystem.FileLocator; +import electrostatic4j.snaploader.platform.util.DefaultDynamicLibraries; +import electrostatic4j.snaploader.platform.NativeDynamicLibrary; +import electrostatic4j.snaploader.platform.util.NativeVariant; +import electrostatic4j.snaploader.platform.util.PlatformPredicate; +import electrostatic4j.snaploader.platform.util.DefaultPropertiesProvider; +import electrostatic4j.snaploader.LoadingCriterion; +import electrostatic4j.snaploader.util.CallingStackMetaData; + +/** + * Tests basic features of the {@link NativeBinaryLoader} API. + * + * @author pavl_g + */ +public final class TestBrokenFeatures { + + protected static final LibraryInfo libraryInfo = new LibraryInfo(getJarFilePath(), + new DirectoryPath(DefaultDynamicLibraries.LINUX_X86.getPlatformDirectory()), + getLibraryBaseName(), + getLibrariesAbsolutePath()); + + protected static NativeBinaryLoader loader; + + public static final NativeDynamicLibrary[] libraries = new NativeDynamicLibrary[] { + DefaultDynamicLibraries.LINUX_X86, + new NativeDynamicLibrary("nonexistent/directory", "junk", PlatformPredicate.LINUX_X86_64), + new NativeDynamicLibrary("lib/windows/x86", "libjmealloc.dll", PlatformPredicate.WIN_X86), + new NativeDynamicLibrary("lib/windows/x86-64", "libjmealloc.dll", PlatformPredicate.WIN_X86_64), + DefaultDynamicLibraries.MAC_X86, + DefaultDynamicLibraries.MAC_X86_64, + }; + + public static void main(String[] args) throws Exception { + if (loader == null) { + loader = new NativeBinaryLoader(libraryInfo); + } + loader.registerNativeLibraries(libraries).initPlatformLibrary(); + loader.setLoggingEnabled(false); + loader.setRetryWithCleanExtraction(true); + /* Native dynamic library properties */ + printDetails(loader); + loader.setNativeBinaryLoadingListener(new NativeBinaryLoadingListener() { + @Override + public void onLoadingSuccess(NativeBinaryLoader nativeBinaryLoader, CallingStackMetaData callingStackMetaData) { + + } + + @Override + public void onLoadingFailure(NativeBinaryLoader nativeBinaryLoader, CallingStackMetaData callingStackMetaData) { + System.err.println(callingStackMetaData.getErrorCause()); + } + + @Override + public void onRetryCriterionExecution(NativeBinaryLoader nativeBinaryLoader, CallingStackMetaData callingStackMetaData) { + + } + }); + + loader.setLibraryExtractionListener(new FileExtractionListener() { + @Override + public void onExtractionCompleted(FileExtractor fileExtractor) { + System.out.println("Extracted Successfully!"); + } + + @Override + public void onExtractionFailure(FileExtractor fileExtractor, Throwable throwable) { + System.err.println(throwable.toString()); + } + + @Override + public void onExtractionFinalization(FileExtractor fileExtractor, FileLocator fileLocator) { + System.out.println("Finalization State has been reached!"); + } + }); + + loader.loadLibrary(LoadingCriterion.INCREMENTAL_LOADING); + } + + protected static void printDetails(NativeBinaryLoader loader) { + System.out.println("--------------------------------------------------------------"); + System.out.println("OS: " + NativeVariant.OS_NAME.getProperty()); + System.out.println("ARCH: " + NativeVariant.OS_ARCH.getProperty()); + System.out.println("VM: " + NativeVariant.JVM.getProperty()); + System.out.println("--------------------------------------------------------------"); + System.out.println("Jar Path: " + loader.getNativeDynamicLibrary().getJarPath()); + System.out.println("Library Directory: " + loader.getNativeDynamicLibrary().getPlatformDirectory()); + System.out.println("Compressed library path: " + loader.getNativeDynamicLibrary().getCompressedLibrary()); + System.out.println("Extracted library absolute path: " + loader.getNativeDynamicLibrary().getExtractedLibrary()); + System.out.println("Is Extracted: " + loader.getNativeDynamicLibrary().isExtracted()); + System.out.println("--------------------------------------------------------------"); + } + + protected static DirectoryPath getLibrariesAbsolutePath() { + return new DirectoryPath(DefaultPropertiesProvider.USER_DIR.getSystemProperty(), "libs"); + } + + protected static DirectoryPath getJarFilePath() { + return new DirectoryPath(getLibrariesAbsolutePath().getPath(), getJarFile()); + } + + protected static String getNativeDynamicLibraryPath() { + return getLibrariesAbsolutePath().getPath() + + DefaultPropertiesProvider.FILE_SEPARATOR.getSystemProperty() + + "lib" + getLibraryBaseName() + ".so"; + } + + protected static String getJarFile() { + return "jme3-alloc-desktop-1.0.0-pre-gamma-2.jar"; + } + + protected static String getLibraryBaseName() { + return "jmealloc"; + } +} \ No newline at end of file diff --git a/snaploader/src/main/java/electrostatic4j/snaploader/NativeBinaryLoader.java b/snaploader/src/main/java/electrostatic4j/snaploader/NativeBinaryLoader.java index 1731998..785ab05 100644 --- a/snaploader/src/main/java/electrostatic4j/snaploader/NativeBinaryLoader.java +++ b/snaploader/src/main/java/electrostatic4j/snaploader/NativeBinaryLoader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023-2025, The Electrostatic-Sandbox Distributed Simulation Framework, jSnapLoader + * Copyright (c) 2023-2026, The Electrostatic-Sandbox Distributed Simulation Framework, jSnapLoader * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -33,6 +33,7 @@ package electrostatic4j.snaploader; import java.io.File; +import java.io.FileNotFoundException; import java.io.IOException; import java.nio.file.FileSystems; import java.util.Arrays; @@ -49,6 +50,7 @@ import electrostatic4j.snaploader.platform.util.NativeVariant; import electrostatic4j.snaploader.platform.util.PropertiesControllerNamespace; import electrostatic4j.snaploader.platform.util.PropertiesController; +import electrostatic4j.snaploader.throwable.LibraryNotFoundException; import electrostatic4j.snaploader.throwable.LoadingRetryExhaustionException; import electrostatic4j.snaploader.throwable.UnSupportedSystemError; import electrostatic4j.snaploader.util.CallingStackMetaData; @@ -183,12 +185,13 @@ public NativeBinaryLoader loadLibrary(LoadingCriterion criterion) throws Excepti throw new IllegalArgumentException("Native library data structures cannot be null!"); } // commands and loads the library from the system directories - if (NativeVariant.Os.isAndroid() || criterion == LoadingCriterion.SYSTEM_LOAD) { + if (NativeVariant.Os.isAndroid() || + criterion == LoadingCriterion.SYSTEM_LOAD) { loadSystemBinary(nativeDynamicLibrary); loadingCriterion = LoadingCriterion.SYSTEM_LOAD; return this; } - if (criterion == LoadingCriterion.INCREMENTAL_LOADING && nativeDynamicLibrary.isExtracted()) { + if (criterion == LoadingCriterion.INCREMENTAL_LOADING) { loadBinary(nativeDynamicLibrary, criterion); loadingCriterion = LoadingCriterion.INCREMENTAL_LOADING; return this; @@ -336,6 +339,9 @@ protected void loadSystemBinary(NativeDynamicLibrary dynamicLibrary) { */ protected void loadBinary(NativeDynamicLibrary library, LoadingCriterion loadingCriterion) throws Exception { try { + if (!nativeDynamicLibrary.exists()) { + throw new LibraryNotFoundException("Library " + nativeDynamicLibrary.getExtractedLibrary() + " not found!"); + } System.load(library.getExtractedLibrary()); SnapLoaderLogger.log(Level.INFO, getClass().getName(), "loadBinary", "Successfully loaded library: " + library.getExtractedLibrary()); @@ -343,7 +349,7 @@ protected void loadBinary(NativeDynamicLibrary library, LoadingCriterion loading nativeBinaryLoadingListener.onLoadingSuccess(this, new CallingStackMetaData(Thread.currentThread().getStackTrace()[1], loadingCriterion)); } - } catch (final UnsatisfiedLinkError error) { + } catch (final UnsatisfiedLinkError | FileNotFoundException error) { SnapLoaderLogger.log(Level.SEVERE, getClass().getName(), "loadBinary", "Cannot load the dynamic library: " + library.getExtractedLibrary(), error); if (nativeBinaryLoadingListener != null) { diff --git a/snaploader/src/main/java/electrostatic4j/snaploader/filesystem/FileExtractor.java b/snaploader/src/main/java/electrostatic4j/snaploader/filesystem/FileExtractor.java index db394bb..6d7788f 100644 --- a/snaploader/src/main/java/electrostatic4j/snaploader/filesystem/FileExtractor.java +++ b/snaploader/src/main/java/electrostatic4j/snaploader/filesystem/FileExtractor.java @@ -66,6 +66,8 @@ public class FileExtractor implements OutputStreamProvider { */ protected String destination; + protected File extractableFile; + private static final int EOF = -1; /* End-of-filesystem */ /** @@ -103,6 +105,7 @@ public void initialize(int size) throws Exception { return; } this.fileOutputStream = new FileOutputStream(destination); + extractableFile = new File(destination); SnapLoaderLogger.log(Level.INFO, getClass().getName(), "initialize(int)", "File extractor initialized with hash key #" + getHashKey()); } catch (Exception e) { @@ -149,6 +152,14 @@ public void extract() throws IOException, FileNotFoundException { fileExtractionListener.onExtractionCompleted(this); } } catch (Exception e) { + // ensure deleting the blank created files + // by the Java Legacy FileOutputStream upon failure! + + // blank created files; create an opaque behavior + // that triggers an UnSatisfiedLinkError: file is too short + // as a result of trying to load corrupted files + // on the next runtime... + delete(); if (fileExtractionListener != null) { fileExtractionListener.onExtractionFailure(this, e); } @@ -160,6 +171,20 @@ public void extract() throws IOException, FileNotFoundException { } } + /** + * Deletes the associated file created by the Legacy + * Java OutputStream {@link FileExtractor#getFileOutputStream()}. + * + * @return true if the file has been deleted, false otherwise if the + * file is not defined or doesn't exist. + */ + protected boolean delete() { + if (extractableFile == null || !extractableFile.exists()) { + return false; + } + return extractableFile.delete(); + } + @Override public void close() throws Exception { if (fileOutputStream != null) { diff --git a/snaploader/src/main/java/electrostatic4j/snaploader/platform/NativeDynamicLibrary.java b/snaploader/src/main/java/electrostatic4j/snaploader/platform/NativeDynamicLibrary.java index b252cc7..c9a1148 100644 --- a/snaploader/src/main/java/electrostatic4j/snaploader/platform/NativeDynamicLibrary.java +++ b/snaploader/src/main/java/electrostatic4j/snaploader/platform/NativeDynamicLibrary.java @@ -146,6 +146,13 @@ public void initWithLibraryInfo(LibraryInfo libraryInfo) { } } + public boolean exists() { + if (jarPath == null || libraryFile == null || directoryPath == null) { + return false; + } + return new File(getExtractedLibrary()).exists(); + } + /** * Retrieves the absolute path for the jar compression as specified by the {@link LibraryInfo} API. * diff --git a/snaploader/src/main/java/electrostatic4j/snaploader/throwable/LibraryNotFoundException.java b/snaploader/src/main/java/electrostatic4j/snaploader/throwable/LibraryNotFoundException.java new file mode 100644 index 0000000..a9df55d --- /dev/null +++ b/snaploader/src/main/java/electrostatic4j/snaploader/throwable/LibraryNotFoundException.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2023-2026, The Electrostatic-Sandbox Distributed Simulation Framework, jSnapLoader + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'Electrostatic-Sandbox' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package electrostatic4j.snaploader.throwable; + +import java.io.FileNotFoundException; + +/** + * A business exception that is thrown to indicate that a library, that is + * tried to be loaded, doesn't exist. + * + * @author pavl_g + */ +public class LibraryNotFoundException extends FileNotFoundException { + public LibraryNotFoundException(String message) { + super(message); + } +}