package com.sedmelluq.lava.common.natives; import com.sedmelluq.lava.common.natives.architecture.SystemType; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.nio.file.FileAlreadyExistsException; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.function.Predicate; import org.apache.commons.io.IOUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import static java.nio.file.attribute.PosixFilePermissions.asFileAttribute; import static java.nio.file.attribute.PosixFilePermissions.fromString; /** * Loads native libraries by name. Libraries are expected to be in classpath /natives/[arch]/[prefix]name[suffix] */ public class NativeLibraryLoader { private static final Logger log = LoggerFactory.getLogger(NativeLibraryLoader.class); private static final String DEFAULT_PROPERTY_PREFIX = "lava.native."; private static final String DEFAULT_RESOURCE_ROOT = "/natives/"; private final String libraryName; private final Predicate<SystemType> systemFilter; private final NativeLibraryProperties properties; private final NativeLibraryBinaryProvider binaryProvider; private final Object lock; private volatile RuntimeException previousFailure; private volatile Boolean previousResult; public NativeLibraryLoader(String libraryName, Predicate<SystemType> systemFilter, NativeLibraryProperties properties, NativeLibraryBinaryProvider binaryProvider) { this.libraryName = libraryName; this.systemFilter = systemFilter; this.binaryProvider = binaryProvider; this.properties = properties; this.lock = new Object(); } public static NativeLibraryLoader create(Class<?> classLoaderSample, String libraryName) { return createFiltered(classLoaderSample, libraryName, null); } public static NativeLibraryLoader createFiltered(Class<?> classLoaderSample, String libraryName, Predicate<SystemType> systemFilter) { return new NativeLibraryLoader( libraryName, systemFilter, new SystemNativeLibraryProperties(libraryName, DEFAULT_PROPERTY_PREFIX), new ResourceNativeLibraryBinaryProvider(classLoaderSample, DEFAULT_RESOURCE_ROOT) ); } public void load() { Boolean result = previousResult; if (result == null) { synchronized (lock) { result = previousResult; if (result == null) { loadAndRemember(); return; } } } if (!result) { throw previousFailure; } } private void loadAndRemember() { log.info("Native library {}: loading with filter {}", libraryName, systemFilter); try { loadInternal(); previousResult = true; } catch (Throwable e) { log.error("Native library {}: loading failed.", libraryName, e); previousFailure = new RuntimeException(e); previousResult = false; } } private void loadInternal() { String explicitPath = properties.getLibraryPath(); if (explicitPath != null) { log.debug("Native library {}: explicit path provided {}", libraryName, explicitPath); loadFromFile(Paths.get(explicitPath).toAbsolutePath()); } else { SystemType systemType = detectMatchingSystemType(); if (systemType != null) { String explicitDirectory = properties.getLibraryDirectory(); if (explicitDirectory != null) { log.debug("Native library {}: explicit directory provided {}", libraryName, explicitDirectory); loadFromFile(Paths.get(explicitDirectory, systemType.formatLibraryName(libraryName)).toAbsolutePath()); } else { loadFromFile(extractLibraryFromResources(systemType)); } } } } private void loadFromFile(Path libraryFilePath) { log.debug("Native library {}: attempting to load library at {}", libraryName, libraryFilePath); System.load(libraryFilePath.toAbsolutePath().toString()); log.info("Native library {}: successfully loaded.", libraryName); } private Path extractLibraryFromResources(SystemType systemType) { try (InputStream libraryStream = binaryProvider.getLibraryStream(systemType, libraryName)) { if (libraryStream == null) { throw new UnsatisfiedLinkError("Required library was not found"); } Path extractedLibraryPath = prepareExtractionDirectory().resolve(systemType.formatLibraryName(libraryName)); try (FileOutputStream fileStream = new FileOutputStream(extractedLibraryPath.toFile())) { IOUtils.copy(libraryStream, fileStream); } return extractedLibraryPath; } catch (IOException e) { throw new RuntimeException(e); } } private Path prepareExtractionDirectory() throws IOException { Path extractionDirectory = detectExtractionBaseDirectory().resolve(String.valueOf(System.currentTimeMillis())); if (!Files.isDirectory(extractionDirectory)) { log.debug("Native library {}: extraction directory {} does not exist, creating.", libraryName, extractionDirectory); try { createDirectoriesWithFullPermissions(extractionDirectory); } catch (FileAlreadyExistsException ignored) { // All is well } catch (IOException e) { throw new IOException("Failed to create directory for unpacked native library.", e); } } else { log.debug("Native library {}: extraction directory {} already exists, using.", libraryName, extractionDirectory); } return extractionDirectory; } private Path detectExtractionBaseDirectory() { String explicitExtractionBase = properties.getExtractionPath(); if (explicitExtractionBase != null) { log.debug("Native library {}: explicit extraction path provided - {}", libraryName, explicitExtractionBase); return Paths.get(explicitExtractionBase).toAbsolutePath(); } Path path = Paths.get(System.getProperty("java.io.tmpdir", "/tmp"), "lava-jni-natives") .toAbsolutePath(); log.debug("Native library {}: detected {} as base directory for extraction.", libraryName, path); return path; } private SystemType detectMatchingSystemType() { SystemType systemType; try { systemType = SystemType.detect(properties); } catch (IllegalArgumentException e) { if (systemFilter != null) { log.info("Native library {}: could not detect sytem type, but system filter is {} - assuming it does " + "not match and skipping library.", libraryName, systemFilter); return null; } else { throw e; } } if (systemFilter != null && !systemFilter.test(systemType)) { log.debug("Native library {}: system filter does not match detected system {}, skipping", libraryName, systemType.formatSystemName()); return null; } return systemType; } private static void createDirectoriesWithFullPermissions(Path path) throws IOException { boolean isPosix = FileSystems.getDefault().supportedFileAttributeViews().contains("posix"); if (!isPosix) { Files.createDirectories(path); } else { Files.createDirectories(path, asFileAttribute(fromString("rwxrwxrwx"))); } } }