| /******************************************************************************* |
| * Copyright (c) 2005, 2016 IBM Corporation and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| |
| package org.eclipse.osgi.storage; |
| |
| import java.io.File; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Map; |
| import org.eclipse.osgi.container.ModuleRevision; |
| import org.eclipse.osgi.container.ModuleWire; |
| import org.eclipse.osgi.container.ModuleWiring; |
| import org.eclipse.osgi.internal.debug.Debug; |
| import org.eclipse.osgi.internal.framework.FilterImpl; |
| import org.eclipse.osgi.internal.hookregistry.ClassLoaderHook; |
| import org.eclipse.osgi.storage.BundleInfo.Generation; |
| import org.eclipse.osgi.storage.bundlefile.BundleEntry; |
| import org.eclipse.osgi.storage.bundlefile.BundleFile; |
| import org.osgi.framework.InvalidSyntaxException; |
| import org.osgi.framework.namespace.HostNamespace; |
| import org.osgi.framework.namespace.NativeNamespace; |
| import org.osgi.framework.wiring.BundleRevision; |
| |
| public class NativeCodeFinder { |
| public static final String REQUIREMENT_NATIVE_PATHS_ATTRIBUTE = "native.paths"; //$NON-NLS-1$ |
| private static final String[] EMPTY_STRINGS = new String[0]; |
| public static final String EXTERNAL_LIB_PREFIX = "external:"; //$NON-NLS-1$ |
| private final Generation generation; |
| private final Debug debug; |
| // This is only used to keep track of when the same native library is loaded more than once |
| private final Collection<String> loadedNativeCode = new ArrayList<>(1); |
| |
| public NativeCodeFinder(Generation generation) { |
| this.generation = generation; |
| this.debug = generation.getBundleInfo().getStorage().getConfiguration().getDebug(); |
| } |
| |
| /* |
| * Maps an already mapped library name to additional library file extensions. |
| * This is needed on platforms like AIX where .a and .so can be used as library file |
| * extensions, but System.mapLibraryName only returns a single string. |
| */ |
| public String[] mapLibraryNames(String mappedLibName) { |
| int extIndex = mappedLibName.lastIndexOf('.'); |
| List<String> LIB_EXTENSIONS = generation.getBundleInfo().getStorage().getConfiguration().LIB_EXTENSIONS; |
| if (LIB_EXTENSIONS.isEmpty() || extIndex < 0) |
| return EMPTY_STRINGS; |
| String libNameBase = mappedLibName.substring(0, extIndex); |
| String[] results = new String[LIB_EXTENSIONS.size()]; |
| for (int i = 0; i < results.length; i++) |
| results[i] = libNameBase + LIB_EXTENSIONS.get(i); |
| return results; |
| } |
| |
| String findLibrary(String libname) { |
| String path = findLibrary0(libname); |
| if (path != null) { |
| synchronized (loadedNativeCode) { |
| if (loadedNativeCode.contains(path) || generation.getBundleInfo().getStorage().getConfiguration().COPY_NATIVES) { |
| // we must copy the library to a temp space to allow another class loader to load the library |
| String temp = generation.getBundleInfo().getStorage().copyToTempLibrary(generation, path); |
| if (temp != null) |
| path = temp; |
| } else { |
| loadedNativeCode.add(path); |
| } |
| } |
| } |
| return path; |
| } |
| |
| private String findLibrary0(String libname) { |
| String path = null; |
| List<ClassLoaderHook> hooks = generation.getBundleInfo().getStorage().getConfiguration().getHookRegistry().getClassLoaderHooks(); |
| for (ClassLoaderHook hook : hooks) { |
| path = hook.findLocalLibrary(generation, libname); |
| if (path != null) { |
| return path; |
| } |
| } |
| String mappedName = System.mapLibraryName(libname); |
| String[] altMappedNames = mapLibraryNames(mappedName); |
| |
| // first check Bundle-NativeCode header |
| path = findBundleNativeCode(libname, mappedName, altMappedNames); |
| // next check eclipse specific support |
| return path != null ? path : findEclipseNativeCode(libname, mappedName, altMappedNames); |
| } |
| |
| private String findEclipseNativeCode(String libname, String mappedName, String[] altMappedNames) { |
| if (libname.length() == 0) |
| return null; |
| if (libname.charAt(0) == '/' || libname.charAt(0) == '\\') |
| libname = libname.substring(1); |
| String result = searchEclipseVariants(mappedName); |
| if (result != null) |
| return result; |
| for (int i = 0; i < altMappedNames.length && result == null; i++) |
| result = searchEclipseVariants(altMappedNames[i]); |
| return result; |
| } |
| |
| private String searchEclipseVariants(String path) { |
| List<String> ECLIPSE_LIB_VARIANTS = generation.getBundleInfo().getStorage().getConfiguration().ECLIPSE_LIB_VARIANTS; |
| for (String variant : ECLIPSE_LIB_VARIANTS) { |
| BundleFile baseBundleFile = generation.getBundleFile(); |
| BundleEntry libEntry = baseBundleFile.getEntry(variant + path); |
| if (libEntry != null) { |
| File libFile = baseBundleFile.getFile(variant + path, true); |
| if (libFile == null) |
| return null; |
| // see bug 88697 - HP requires libraries to have executable permissions |
| if (org.eclipse.osgi.service.environment.Constants.OS_HPUX.equals(generation.getBundleInfo().getStorage().getConfiguration().getOS())) { |
| try { |
| // use the string array method in case there is a space in the path |
| Runtime.getRuntime().exec(new String[] {"chmod", "755", libFile.getAbsolutePath()}).waitFor(); //$NON-NLS-1$ //$NON-NLS-2$ |
| } catch (Exception e) { |
| e.printStackTrace(); |
| } |
| } |
| return libFile.getAbsolutePath(); |
| } |
| } |
| return null; |
| } |
| |
| private String findBundleNativeCode(String libname, String mappedName, String[] altMappedNames) { |
| String path = null; |
| if (debug.DEBUG_LOADER) |
| Debug.println(" mapped library name: " + mappedName); //$NON-NLS-1$ |
| List<String> nativePaths = getNativePaths(); |
| if (nativePaths.isEmpty()) { |
| return null; |
| } |
| path = findNativePath(nativePaths, mappedName); |
| if (path == null) { |
| for (int i = 0; i < altMappedNames.length && path == null; i++) |
| path = findNativePath(nativePaths, altMappedNames[i]); |
| } |
| if (path == null) { |
| if (debug.DEBUG_LOADER) |
| Debug.println(" library does not exist: " + mappedName); //$NON-NLS-1$ |
| path = findNativePath(nativePaths, libname); |
| } |
| if (debug.DEBUG_LOADER) |
| Debug.println(" returning library: " + path); //$NON-NLS-1$ |
| return path; |
| } |
| |
| private List<String> getNativePaths() { |
| ModuleRevision revision = generation.getRevision(); |
| ModuleWiring wiring = revision.getWiring(); |
| if (wiring == null) { |
| // unresolved? should not be possible |
| return Collections.emptyList(); |
| } |
| if ((revision.getTypes() & BundleRevision.TYPE_FRAGMENT) != 0) { |
| List<ModuleWire> hosts = wiring.getRequiredModuleWires(HostNamespace.HOST_NAMESPACE); |
| if (hosts == null) { |
| // unresolved or invalid? should not be possible |
| return Collections.emptyList(); |
| } |
| if (!hosts.isEmpty()) { |
| // just use the first host wiring |
| wiring = hosts.get(0).getProviderWiring(); |
| } |
| } |
| |
| List<ModuleWire> nativeCode = wiring.getRequiredModuleWires(NativeNamespace.NATIVE_NAMESPACE); |
| if (nativeCode == null || nativeCode.isEmpty()) { |
| return Collections.emptyList(); |
| } |
| |
| // just taking the first paths for the revision, we sorted correctly when transforming to the requirement |
| for (ModuleWire moduleWire : nativeCode) { |
| if (moduleWire.getRequirement().getRevision().equals(revision)) { |
| @SuppressWarnings("unchecked") |
| List<String> result = (List<String>) nativeCode.get(0).getRequirement().getAttributes().get(REQUIREMENT_NATIVE_PATHS_ATTRIBUTE); |
| if (result != null) |
| return result; |
| // this must be a multi-clause Bundle-NativeCode header, need to check for the correct one in the index |
| try { |
| FilterImpl filter = FilterImpl.newInstance(moduleWire.getRequirement().getDirectives().get(NativeNamespace.REQUIREMENT_FILTER_DIRECTIVE)); |
| int index = -1; |
| Map<String, Object> capabilityAttrs = moduleWire.getCapability().getAttributes(); |
| for (FilterImpl child : filter.getChildren()) { |
| index++; |
| if (child.matches(capabilityAttrs)) { |
| break; |
| } |
| } |
| if (index != -1) { |
| @SuppressWarnings("unchecked") |
| List<String> indexResult = (List<String>) nativeCode.get(0).getRequirement().getAttributes().get(REQUIREMENT_NATIVE_PATHS_ATTRIBUTE + '.' + index); |
| if (indexResult != null) |
| return indexResult; |
| } |
| } catch (InvalidSyntaxException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| } |
| return Collections.emptyList(); |
| } |
| |
| private String findNativePath(List<String> nativePaths, String libname) { |
| int slash = libname.lastIndexOf('/'); |
| if (slash >= 0) |
| libname = libname.substring(slash + 1); |
| for (String nativePath : nativePaths) { |
| slash = nativePath.lastIndexOf('/'); |
| String path = slash < 0 ? nativePath : nativePath.substring(slash + 1); |
| if (path.equals(libname)) { |
| if (nativePath.startsWith(NativeCodeFinder.EXTERNAL_LIB_PREFIX)) { |
| // references an external library; do variable substitution |
| String externalPath = generation.getBundleInfo().getStorage().getConfiguration().substituteVars(nativePath.substring(NativeCodeFinder.EXTERNAL_LIB_PREFIX.length())); |
| File nativeFile = new File(externalPath); |
| return nativeFile.getAbsolutePath(); |
| } |
| // this is a normal library contained within the bundle |
| File nativeFile = generation.getBundleFile().getFile(nativePath, true); |
| if (nativeFile != null) |
| return nativeFile.getAbsolutePath(); |
| } |
| } |
| return null; |
| } |
| |
| } |