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}