/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 org.apache.ignite.internal.util.ipc.shmem; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.RandomAccessFile; import java.net.URL; import java.nio.channels.FileLock; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Collection; import java.util.jar.JarFile; import java.util.zip.ZipEntry; import org.apache.ignite.IgniteCheckedException; import org.apache.ignite.IgniteLogger; import org.apache.ignite.internal.util.typedef.X; import org.apache.ignite.internal.util.typedef.internal.LT; import org.apache.ignite.internal.util.typedef.internal.U; import static org.apache.ignite.internal.IgniteVersionUtils.VER_STR; /** * Shared memory native loader. */ @SuppressWarnings("ErrorNotRethrown") public class IpcSharedMemoryNativeLoader { /** Library name base. */ private static final String LIB_NAME_BASE = "igniteshmem"; /** Library jar name base. */ private static final String JAR_NAME_BASE = "shmem"; /** Library name. */ static final String LIB_NAME = LIB_NAME_BASE + "-" + VER_STR; /** Loaded flag. */ private static volatile boolean loaded; /** * @return Operating system name to resolve path to library. */ private static String os() { String name = System.getProperty("os.name").toLowerCase().trim(); if (name.startsWith("win")) throw new IllegalStateException("IPC shared memory native loader should not be called on windows."); if (name.startsWith("linux")) return "linux"; if (name.startsWith("mac os x")) return "osx"; return name.replaceAll("\\W+", "_"); } /** * @return Platform. */ private static String platform() { return os() + bitModel(); } /** * @return Bit model. */ private static int bitModel() { String prop = System.getProperty("sun.arch.data.model"); if (prop == null) prop = System.getProperty("com.ibm.vm.bitmode"); if (prop != null) return Integer.parseInt(prop); // We don't know. return -1; } /** * @param log Logger, if available. If null, warnings will be printed out to console. * @throws IgniteCheckedException If failed. */ public static void load(IgniteLogger log) throws IgniteCheckedException { if (loaded) return; synchronized (IpcSharedMemoryNativeLoader.class) { if (loaded) return; doLoad(log); loaded = true; } } /** * @throws IgniteCheckedException If failed. */ private static void doLoad(IgniteLogger log) throws IgniteCheckedException { assert Thread.holdsLock(IpcSharedMemoryNativeLoader.class); Collection<Throwable> errs = new ArrayList<>(); try { // Load native library (the library directory should be in java.library.path). System.loadLibrary(LIB_NAME); return; } catch (UnsatisfiedLinkError e) { errs.add(e); } File tmpDir = getUserSpecificTempDir(); File lockFile = new File(tmpDir, "igniteshmem.lock"); // Obtain lock on file to prevent concurrent extracts. try (RandomAccessFile randomAccessFile = new RandomAccessFile(lockFile, "rws"); FileLock ignored = randomAccessFile.getChannel().lock()) { if (extractAndLoad(errs, tmpDir, platformSpecificResourcePath())) return; if (extractAndLoad(errs, tmpDir, osSpecificResourcePath())) return; if (extractAndLoad(errs, tmpDir, resourcePath())) return; try { if (log != null) LT.warn(log, "Failed to load 'igniteshmem' library from classpath. Will try to load it from IGNITE_HOME."); String igniteHome = X.resolveIgniteHome(); File shmemJar = findShmemJar(errs, igniteHome); if (shmemJar != null) { try (JarFile jar = new JarFile(shmemJar, false, JarFile.OPEN_READ)) { if (extractAndLoad(errs, jar, tmpDir, platformSpecificResourcePath())) return; if (extractAndLoad(errs, jar, tmpDir, osSpecificResourcePath())) return; if (extractAndLoad(errs, jar, tmpDir, resourcePath())) return; } } } catch (IgniteCheckedException ignore) { // No-op. } // Failed to find the library. assert !errs.isEmpty(); throw new IgniteCheckedException("Failed to load native IPC library: " + errs); } catch (IOException e) { throw new IgniteCheckedException("Failed to obtain file lock: " + lockFile, e); } } /** * Tries to find shmem jar in IGNITE_HOME/libs folder. * * @param errs Collection of errors to add readable exception to. * @param igniteHome Resolver IGNITE_HOME variable. * @return File, if found. */ private static File findShmemJar(Collection<Throwable> errs, String igniteHome) { File libs = new File(igniteHome, "libs"); if (!libs.exists() || libs.isFile()) { errs.add(new IllegalStateException("Failed to find libs folder in resolved IGNITE_HOME: " + igniteHome)); return null; } for (File lib : libs.listFiles()) { if (lib.getName().endsWith(".jar") && lib.getName().contains(JAR_NAME_BASE)) return lib; } errs.add(new IllegalStateException("Failed to find shmem jar in resolved IGNITE_HOME: " + igniteHome)); return null; } /** * Gets temporary directory unique for each OS user. * The directory guaranteed to exist, though may not be empty. */ private static File getUserSpecificTempDir() throws IgniteCheckedException { String tmp = System.getProperty("java.io.tmpdir"); String userName = System.getProperty("user.name"); File tmpDir = new File(tmp, userName); if (!tmpDir.exists()) //noinspection ResultOfMethodCallIgnored tmpDir.mkdirs(); if (!(tmpDir.exists() && tmpDir.isDirectory())) throw new IgniteCheckedException("Failed to create temporary directory [dir=" + tmpDir + ']'); return tmpDir; } /** * @return OS resource path. */ private static String osSpecificResourcePath() { return "META-INF/native/" + os() + "/" + mapLibraryName(LIB_NAME_BASE); } /** * @return Platform resource path. */ private static String platformSpecificResourcePath() { return "META-INF/native/" + platform() + "/" + mapLibraryName(LIB_NAME_BASE); } /** * @return Resource path. */ private static String resourcePath() { return "META-INF/native/" + mapLibraryName(LIB_NAME_BASE); } /** * @return Maps library name to file name. */ private static String mapLibraryName(String name) { String libName = System.mapLibraryName(name); if (U.isMacOs() && libName.endsWith(".jnilib")) return libName.substring(0, libName.length() - "jnilib".length()) + "dylib"; return libName; } /** * @param errs Errors collection. * @param rsrcPath Path. * @return {@code True} if library was found and loaded. */ private static boolean extractAndLoad(Collection<Throwable> errs, File tmpDir, String rsrcPath) { ClassLoader clsLdr = U.detectClassLoader(IpcSharedMemoryNativeLoader.class); URL rsrc = clsLdr.getResource(rsrcPath); if (rsrc != null) return extract(errs, rsrc, new File(tmpDir, mapLibraryName(LIB_NAME))); else { errs.add(new IllegalStateException("Failed to find resource with specified class loader " + "[rsrc=" + rsrcPath + ", clsLdr=" + clsLdr + ']')); return false; } } /** * @param errs Errors collection. * @param rsrcPath Path. * @return {@code True} if library was found and loaded. */ private static boolean extractAndLoad(Collection<Throwable> errs, JarFile jar, File tmpDir, String rsrcPath) { ZipEntry rsrc = jar.getEntry(rsrcPath); if (rsrc != null) return extract(errs, rsrc, jar, new File(tmpDir, mapLibraryName(LIB_NAME))); else { errs.add(new IllegalStateException("Failed to find resource within specified jar file " + "[rsrc=" + rsrcPath + ", jar=" + jar.getName() + ']')); return false; } } /** * @param errs Errors collection. * @param src Source. * @param target Target. * @return {@code True} if resource was found and loaded. */ @SuppressWarnings("ResultOfMethodCallIgnored") private static boolean extract(Collection<Throwable> errs, URL src, File target) { FileOutputStream os = null; InputStream is = null; try { if (!target.exists() || !haveEqualMD5(target, src.openStream())) { is = src.openStream(); if (is != null) { os = new FileOutputStream(target); int read; byte[] buf = new byte[4096]; while ((read = is.read(buf)) != -1) os.write(buf, 0, read); } } // chmod 775. if (!U.isWindows()) Runtime.getRuntime().exec(new String[] {"chmod", "775", target.getCanonicalPath()}).waitFor(); System.load(target.getPath()); return true; } catch (IOException | UnsatisfiedLinkError | InterruptedException | NoSuchAlgorithmException e) { errs.add(e); } finally { U.closeQuiet(os); U.closeQuiet(is); } return false; } /** * @param errs Errors collection. * @param src Source. * @param target Target. * @return {@code True} if resource was found and loaded. */ @SuppressWarnings("ResultOfMethodCallIgnored") private static boolean extract(Collection<Throwable> errs, ZipEntry src, JarFile jar, File target) { FileOutputStream os = null; InputStream is = null; try { if (!target.exists() || !haveEqualMD5(target, jar.getInputStream(src))) { is = jar.getInputStream(src); if (is != null) { os = new FileOutputStream(target); int read; byte[] buf = new byte[4096]; while ((read = is.read(buf)) != -1) os.write(buf, 0, read); } } // chmod 775. if (!U.isWindows()) Runtime.getRuntime().exec(new String[] {"chmod", "775", target.getCanonicalPath()}).waitFor(); System.load(target.getPath()); return true; } catch (IOException | UnsatisfiedLinkError | InterruptedException | NoSuchAlgorithmException e) { errs.add(e); } finally { U.closeQuiet(os); U.closeQuiet(is); } return false; } /** * @param target Target. * @param srcIS Source input stream. * @return {@code True} if target md5-sum equal to source md5-sum. * @throws NoSuchAlgorithmException If md5 algorithm was not found. * @throws IOException If an I/O exception occurs. */ private static boolean haveEqualMD5(File target, InputStream srcIS) throws NoSuchAlgorithmException, IOException { try { try (InputStream targetIS = new FileInputStream(target)) { String targetMD5 = U.calculateMD5(targetIS); String srcMD5 = U.calculateMD5(srcIS); return targetMD5.equals(srcMD5); } } finally { srcIS.close(); } } }