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  public LibraryLoader() {
052    logger = Logger.getLogger();
053    libpath = null;
054  }
055
056  /**
057   * <p>Find the library <tt>libname</tt> as a resource, copy it to a tempfile
058   * and load it using System.load(). The name of the library has to be the
059   * base name, it is mapped to the corresponding system name using
060   * System.mapLibraryName(). For example, the library "foo" is called "libfoo.so"
061   * under Linux and "foo.dll" under Windows, but you just have to pass "foo"
062   * the loadLibrary().</p>
063   * <p/>
064   * <p>I'm not quite sure if this doesn't open all kinds of security holes. Any ideas?</p>
065   * <p/>
066   * <p>This function reports some more information to the "org.jblas" logger at
067   * the FINE level.</p>
068   *
069   * @param libname basename of the library
070   * @throws UnsatisfiedLinkError if library cannot be founds
071   */
072  public void loadLibrary(String libname, boolean withFlavor, boolean noPrefix) {
073    // preload flavor libraries
074    String flavor = null;
075    if (withFlavor) {
076      logger.debug("Preloading ArchFlavor library.");
077      flavor = ArchFlavor.archFlavor();
078      if (flavor != null && flavor.equals("sse2")) {
079        throw new UnsupportedArchitectureException("Support for SSE2 processors stopped with version 1.2.2. Sorry.");
080      }
081    }
082    logger.debug("Found flavor = '" + flavor + "'");
083
084    libname = System.mapLibraryName(libname);
085
086    /*
087     * JDK 7 changed the ending for Mac OS from "jnilib" to "dylib".
088     *
089     * If that is the case, remap the filename.
090     */
091    String loadLibname = libname;
092    if (libname.endsWith("dylib")) {
093      loadLibname = libname.replace(".dylib", ".jnilib");
094      logger.config("Replaced .dylib with .jnilib");
095    }
096
097    logger.debug("Attempting to load \"" + loadLibname + "\".");
098
099    String[] paths = {
100        "/",
101        "/bin/",
102        fatJarLibraryPath("static", flavor),
103        fatJarLibraryPathNonUnified("static", flavor),
104        fatJarLibraryPath("dynamic", flavor),
105        fatJarLibraryPathNonUnified("dynamic", flavor),
106    };
107
108    InputStream is = findLibrary(paths, loadLibname);
109
110    // Oh man, have to get out of here!
111    if (is == null) {
112      throw new UnsatisfiedLinkError("Couldn't find the resource " + loadLibname + ".");
113    }
114
115    logger.config("Loading " + loadLibname + " from " + libpath + ", copying to " + libname + ".");
116    loadLibraryFromStream(libname, is, noPrefix);
117  }
118
119  private InputStream findLibrary(String[] paths, String libname) {
120    InputStream is = null;
121    for (String path : paths) {
122      is = tryPath(path + libname);
123      if (is != null) {
124        logger.debug("Found " + libname + " in " + path);
125        libpath = path;
126        break;
127      }
128    }
129    return is;
130  }
131
132  /**
133   * Translate all those Windows to "Windows". ("Windows XP", "Windows Vista", "Windows 7", etc.)
134   */
135  private String unifyOSName(String osname) {
136    if (osname.startsWith("Windows")) {
137      return "Windows";
138    }
139    return osname;
140  }
141
142  /**
143   * Compute the path to the library. The path is basically
144   * "/" + os.name + "/" + os.arch + "/" + libname.
145   */
146  private String fatJarLibraryPath(String linkage, String flavor) {
147    String sep = "/"; //System.getProperty("file.separator");
148    String os_name = unifyOSName(System.getProperty("os.name"));
149    String os_arch = System.getProperty("os.arch");
150    String path = sep + "lib" + sep + linkage + sep + os_name + sep + os_arch + sep;
151    if (null != flavor)
152      path += flavor + sep;
153    return path;
154  }
155
156  /**
157   * Full path without the OS name non-unified.
158   */
159  private String fatJarLibraryPathNonUnified(String linkage, String flavor) {
160    String sep = "/"; //System.getProperty("file.separator");
161    String os_name = System.getProperty("os.name");
162    String os_arch = System.getProperty("os.arch");
163    String path = sep + "lib" + sep + linkage + sep + os_name + sep + os_arch + sep;
164    if (null != flavor)
165      path += flavor + sep;
166    return path;
167  }
168
169  /**
170   * Try to open a file at the given position.
171   */
172  private InputStream tryPath(String path) {
173    Logger.getLogger().debug("Trying path \"" + path + "\".");
174    return getClass().getResourceAsStream(path);
175  }
176
177  private File createTempFile(String prefix, String suffix, boolean noPrefix) throws IOException {
178    File tempfile = File.createTempFile(prefix, suffix);
179    if (noPrefix == true) {
180      return new File(tempfile.getParentFile(), suffix);
181    } else {
182      return tempfile;
183    }
184  }
185
186  /**
187   * Load a system library from a stream. Copies the library to a temp file
188   * and loads from there.
189   *
190   * @param libname name of the library (just used in constructing the library name)
191   * @param is      InputStream pointing to the library
192   */
193  private void loadLibraryFromStream(String libname, InputStream is, boolean noPrefix) {
194    try {
195      File tempfile = createTempFile("jblas", libname, noPrefix);
196      tempfile.deleteOnExit();
197      OutputStream os = new FileOutputStream(tempfile);
198
199      logger.debug("tempfile.getPath() = " + tempfile.getPath());
200
201      long savedTime = System.currentTimeMillis();
202
203      // Leo says 8k block size is STANDARD ;)
204      byte buf[] = new byte[8192];
205      int len;
206      while ((len = is.read(buf)) > 0) {
207        os.write(buf, 0, len);
208      }
209
210      double seconds = (double) (System.currentTimeMillis() - savedTime) / 1e3;
211      logger.debug("Copying took " + seconds + " seconds.");
212
213      os.close();
214
215      logger.debug("Loading library from " + tempfile.getPath() + ".");
216      System.load(tempfile.getPath());
217    } catch (IOException io) {
218      logger.error("Could not create the temp file: " + io.toString() + ".\n");
219    } catch (UnsatisfiedLinkError ule) {
220      logger.error("Couldn't load copied link file: " + ule.toString() + ".\n");
221      throw ule;
222    }
223  }
224}