Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 45 additions & 29 deletions jme3-desktop/src/main/java/com/jme3/system/NativeLibraryLoader.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,12 @@
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.FileAttribute;
import java.nio.file.attribute.PosixFilePermissions;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
Expand Down Expand Up @@ -174,8 +177,8 @@ public static void setCustomExtractionFolder(String path) {
* <ul>
* <li>If a {@link #setCustomExtractionFolder(java.lang.String) custom
* extraction folder} has been specified, it is returned.
* <li>If the user can write to "java.io.tmpdir" folder, then it
* is used.</li>
* <li>If the user can write to "java.io.tmpdir" folder, then a private,
* newly-created subfolder is used.</li>
* <li>Otherwise, the {@link JmeSystem#getStorageFolder() storage folder}
* is used, to prevent collisions, a special subfolder is used
* called <code>natives_&lt;hash&gt;</code> where &lt;hash&gt;
Expand All @@ -189,33 +192,46 @@ public static File getExtractionFolder() {
if (extractionFolderOverride != null) {
return extractionFolderOverride;
}
if (extractionFolder == null) {
File userTempDir = new File(System.getProperty("java.io.tmpdir"));
if (!userTempDir.canWrite()) {
setExtractionFolderToUserCache();
} else {
try {
File jmeTempDir = new File(userTempDir, "jme3");
if (!jmeTempDir.exists()) {
jmeTempDir.mkdir();
}
if(!jmeTempDir.canWrite()) {
setExtractionFolderToUserCache();
} else {
extractionFolder = new File(jmeTempDir, "natives_" + Integer.toHexString(computeNativesHash()));

if (!extractionFolder.exists()) {
extractionFolder.mkdir();
}
}
} catch (Exception e) {
setExtractionFolderToUserCache();
}
}
}
if (extractionFolder == null) {
File userTempDir = new File(System.getProperty("java.io.tmpdir"));
if (!userTempDir.canWrite()) {
setExtractionFolderToUserCache();
} else {
try {
extractionFolder = createPrivateTempNativesDirectory(userTempDir).toFile();
} catch (IOException | UnsupportedOperationException | SecurityException e) {
setExtractionFolderToUserCache();
}
}
}
Comment on lines +195 to +206
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

This implementation has two issues:

  1. Robustness: The new catch block (line 202) misses UncheckedIOException, which is thrown by computeNativesHash() (called via createPrivateTempNativesDirectory) if it fails to access the class resource. This will cause the application to crash instead of falling back to the user cache. Reverting to a broader Exception catch restores the previous behavior of handling such failures gracefully.
  2. Thread Safety: getExtractionFolder() is not synchronized. Since Files.createTempDirectory now creates a unique directory per call, a race condition between multiple threads could result in multiple temporary directories being created and used inconsistently.
        synchronized (NativeLibraryLoader.class) {
            if (extractionFolder == null) {
                File userTempDir = new File(System.getProperty("java.io.tmpdir"));
                if (!userTempDir.canWrite()) {
                    setExtractionFolderToUserCache();
                } else {
                    try {
                        extractionFolder = createPrivateTempNativesDirectory(userTempDir).toFile();
                    } catch (Exception e) {
                        setExtractionFolderToUserCache();
                    }
                }
            }
        }

return extractionFolder;
}


/**
* Creates a fresh native-library extraction directory under the supplied
* temporary directory. The directory name includes a random suffix generated
* by {@link Files#createTempDirectory(Path, String, FileAttribute[])} so
* local users cannot pre-create the cache and plant a native library that
* will be loaded by this process.
*
* @param userTempDir the system temporary directory
* @return a newly-created private directory for extracted natives
* @throws IOException if the directory cannot be created
*/
private static Path createPrivateTempNativesDirectory(File userTempDir) throws IOException {
String prefix = "jme3-natives_" + Integer.toHexString(computeNativesHash()) + "_";
Path userTempPath = userTempDir.toPath();

try {
FileAttribute<?> permissions = PosixFilePermissions.asFileAttribute(
PosixFilePermissions.fromString("rwx------"));
return Files.createTempDirectory(userTempPath, prefix, permissions);
} catch (UnsupportedOperationException ex) {
return Files.createTempDirectory(userTempPath, prefix);
}
}

/**
* Determine jME3's cache folder for the user account based on the OS.
*
Expand Down
Loading