001// --- BEGIN LICENSE BLOCK --- 002/* 003 * Copyright (c) 2009, Mikio L. Braun 004 * All rights reserved. 005 * 006 * Redistribution and use in source and binary forms, with or without 007 * modification, are permitted provided that the following conditions are 008 * met: 009 * 010 * * Redistributions of source code must retain the above copyright 011 * notice, this list of conditions and the following disclaimer. 012 * 013 * * Redistributions in binary form must reproduce the above 014 * copyright notice, this list of conditions and the following 015 * disclaimer in the documentation and/or other materials provided 016 * with the distribution. 017 * 018 * * Neither the name of the Technische Universität Berlin nor the 019 * names of its contributors may be used to endorse or promote 020 * products derived from this software without specific prior 021 * written permission. 022 * 023 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 024 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 025 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 026 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 027 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 028 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 029 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 030 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 031 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 032 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 033 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 034 */ 035// --- END LICENSE BLOCK --- 036package org.jblas.util; 037 038import org.jblas.exceptions.UnsupportedArchitectureException; 039 040import java.io.*; 041 042/** 043 * Class which allows to load a dynamic file as resource (for example, from a 044 * jar-file) 045 */ 046public class LibraryLoader { 047 048 private Logger logger; 049 private String libpath; 050 051 private static File tempDir; 052 053 static { 054 final Logger logger = Logger.getLogger(); 055 056 try { 057 tempDir = File.createTempFile("jblas", ""); 058 059 if (!tempDir.delete() || !tempDir.mkdir()) { 060 throw new IOException(String.format("Couldn't create directory \"%s\"", tempDir.getAbsolutePath())); 061 } 062 063 /* 064 * Different cleanup strategies for Windows and Linux. 065 * 066 * For *NIX operating systems: A shutdown hook to clean up the files created. Under 067 * Windows this won't work because 068 */ 069 if (getUnifiedOSName() != "Windows") { 070 Runtime.getRuntime().addShutdownHook(new Thread() { 071 @Override 072 public void run() { 073 for (File f : tempDir.listFiles()) { 074 logger.info("Deleting " + f.getAbsolutePath()); 075 if (!f.delete()) { 076 logger.warning(String.format("Couldn't delete temporary file \"%s\"", f.getAbsolutePath())); 077 } 078 } 079 logger.info("Deleting " + tempDir.getAbsolutePath()); 080 if (!tempDir.delete()) { 081 logger.warning(String.format("Couldn't delete temporary directory \"%s\"", tempDir.getAbsolutePath())); 082 } 083 } 084 }); 085 } else { 086 new Thread() { 087 @Override 088 public void run() { 089 try { 090 Thread.sleep(1000); 091 092 logger.info("Starting temp DLL cleanup task."); 093 094 int deletedFiles = 0; 095 096 File jblasTempDir = new File(System.getProperty("java.io.tmpdir")); 097 for (File jblasDir : jblasTempDir.listFiles()) { 098 assert (jblasDir != null); 099 if (jblasDir != tempDir && jblasDir.isDirectory() && jblasDir.getName().startsWith("jblas")) { 100 for (File oldJblasFile : jblasDir.listFiles()) { 101 if (!oldJblasFile.delete()) { 102 logger.debug("Couldn't delete " + oldJblasFile.getAbsolutePath()); 103 } else { 104 logger.debug("Deleted " + oldJblasFile.getAbsolutePath()); 105 deletedFiles++; 106 } 107 } 108 } 109 } 110 111 if (deletedFiles > 0) { 112 logger.info(String.format("Deleted %d unused temp DLL libraries from %s", deletedFiles, jblasTempDir.getAbsolutePath())); 113 } 114 } catch (InterruptedException ex) { 115 // 116 } 117 } 118 }.start(); 119 } 120 } catch (IOException ex) { 121 logger.error("Couldn't create temporary directory: " + ex.getMessage()); 122 } 123 } 124 125 public LibraryLoader() { 126 logger = Logger.getLogger(); 127 libpath = null; 128 } 129 130 /** 131 * <p>Find the library <tt>libname</tt> as a resource, copy it to a tempfile 132 * and load it using System.load(). The name of the library has to be the 133 * base name, it is mapped to the corresponding system name using 134 * System.mapLibraryName(). For example, the library "foo" is called "libfoo.so" 135 * under Linux and "foo.dll" under Windows, but you just have to pass "foo" 136 * the loadLibrary().</p> 137 * <p/> 138 * <p>I'm not quite sure if this doesn't open all kinds of security holes. Any ideas?</p> 139 * <p/> 140 * <p>This function reports some more information to the "org.jblas" logger at 141 * the FINE level.</p> 142 * 143 * @param libname basename of the library 144 * @throws UnsatisfiedLinkError if library cannot be founds 145 */ 146 public void loadLibrary(String libname, boolean withFlavor) { 147 // preload flavor libraries 148 String flavor = null; 149 if (withFlavor) { 150 logger.debug("Preloading ArchFlavor library."); 151 flavor = ArchFlavor.archFlavor(); 152 if (flavor != null && flavor.equals("sse2")) { 153 throw new UnsupportedArchitectureException("Support for SSE2 processors stopped with version 1.2.2. Sorry."); 154 } 155 } 156 logger.debug("Found flavor = '" + flavor + "'"); 157 158 libname = System.mapLibraryName(libname); 159 160 /* 161 * JDK 7 changed the ending for Mac OS from "jnilib" to "dylib". 162 * 163 * If that is the case, remap the filename. 164 */ 165 String loadLibname = libname; 166 if (libname.endsWith("dylib")) { 167 loadLibname = libname.replace(".dylib", ".jnilib"); 168 logger.config("Replaced .dylib with .jnilib"); 169 } 170 171 logger.debug("Attempting to load \"" + loadLibname + "\"."); 172 173 String[] paths = { 174 fatJarLibraryPath("static", flavor), 175 fatJarLibraryPath("dynamic", flavor), 176 }; 177 178 InputStream is = findLibrary(paths, loadLibname); 179 180 // Haven't found the lib anywhere? Throw a reception. 181 if (is == null) { 182 throw new UnsatisfiedLinkError("Couldn't find the resource " + loadLibname + "."); 183 } 184 185 logger.config("Loading " + loadLibname + " from " + libpath + ", copying to " + libname + "."); 186 loadLibraryFromStream(libname, is); 187 } 188 189 private InputStream findLibrary(String[] paths, String libname) { 190 InputStream is = null; 191 for (String path : paths) { 192 is = tryPath(path + libname); 193 if (is != null) { 194 logger.debug("Found " + libname + " in " + path); 195 libpath = path; 196 break; 197 } 198 } 199 return is; 200 } 201 202 /** 203 * Translate all those Windows to "Windows". ("Windows XP", "Windows Vista", "Windows 7", etc.) 204 */ 205 private static String unifyOSName(String osname) { 206 if (osname.startsWith("Windows")) { 207 return "Windows"; 208 } 209 return osname; 210 } 211 212 private static String getUnifiedOSName() { 213 return unifyOSName(System.getProperty("os.name")); 214 } 215 216 /** 217 * Compute the path to the library. The path is basically 218 * "/" + os.name + "/" + os.arch + "/" + libname. 219 */ 220 private String fatJarLibraryPath(String linkage, String flavor) { 221 String sep = "/"; //System.getProperty("file.separator"); 222 String os_name = getUnifiedOSName(); 223 String os_arch = System.getProperty("os.arch"); 224 String path = sep + "lib" + sep + linkage + sep + os_name + sep + os_arch + sep; 225 if (null != flavor) 226 path += flavor + sep; 227 return path; 228 } 229 230 /** 231 * Try to open a file at the given position. 232 */ 233 private InputStream tryPath(String path) { 234 Logger.getLogger().debug("Trying path \"" + path + "\"."); 235 return getClass().getResourceAsStream(path); 236 } 237 238 private File createTempFile(String name) throws IOException { 239 return new File(tempDir + File.separator + name); 240 } 241 242 /** 243 * Load a system library from a stream. Copies the library to a temp file 244 * and loads from there. 245 * 246 * @param libname name of the library (just used in constructing the library name) 247 * @param is InputStream pointing to the library 248 */ 249 private void loadLibraryFromStream(String libname, InputStream is) { 250 try { 251 File tempfile = createTempFile(libname); 252 OutputStream os = new FileOutputStream(tempfile); 253 254 logger.debug("tempfile.getPath() = " + tempfile.getPath()); 255 256 long savedTime = System.currentTimeMillis(); 257 258 // Leo says 8k block size is STANDARD ;) 259 byte buf[] = new byte[8192]; 260 int len; 261 while ((len = is.read(buf)) > 0) { 262 os.write(buf, 0, len); 263 } 264 265 os.flush(); 266 InputStream lock = new FileInputStream(tempfile); 267 os.close(); 268 269 double seconds = (double) (System.currentTimeMillis() - savedTime) / 1e3; 270 logger.debug("Copying took " + seconds + " seconds."); 271 272 logger.debug("Loading library from " + tempfile.getPath() + "."); 273 System.load(tempfile.getPath()); 274 275 lock.close(); 276 } catch (IOException io) { 277 logger.error("Could not create the temp file: " + io.toString() + ".\n"); 278 } catch (UnsatisfiedLinkError ule) { 279 logger.error("Couldn't load copied link file: " + ule.toString() + ".\n"); 280 throw ule; 281 } 282 } 283}