1 /*******************************************************************************
2  * Copyright (c) 2012, 2019 IBM Corporation and others.
3  *
4  * This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License 2.0
6  * which accompanies this distribution, and is available at
7  * https://www.eclipse.org/legal/epl-2.0/
8  *
9  * SPDX-License-Identifier: EPL-2.0
10  *
11  * Contributors:
12  *     IBM Corporation - initial API and implementation
13  *      Christoph Laeubrich - Bug 527175 - Storage#getSystemContent() should first make the file absolute
14  *******************************************************************************/
15 package org.eclipse.osgi.storage;
16 
17 import java.io.BufferedInputStream;
18 import java.io.BufferedOutputStream;
19 import java.io.ByteArrayInputStream;
20 import java.io.ByteArrayOutputStream;
21 import java.io.DataInputStream;
22 import java.io.DataOutputStream;
23 import java.io.File;
24 import java.io.FileInputStream;
25 import java.io.FileOutputStream;
26 import java.io.IOException;
27 import java.io.InputStream;
28 import java.lang.reflect.Method;
29 import java.net.MalformedURLException;
30 import java.net.URL;
31 import java.net.URLConnection;
32 import java.nio.charset.StandardCharsets;
33 import java.security.AccessController;
34 import java.security.PrivilegedAction;
35 import java.security.PrivilegedActionException;
36 import java.security.PrivilegedExceptionAction;
37 import java.util.ArrayList;
38 import java.util.Arrays;
39 import java.util.Collection;
40 import java.util.Collections;
41 import java.util.Dictionary;
42 import java.util.Enumeration;
43 import java.util.HashMap;
44 import java.util.Hashtable;
45 import java.util.Iterator;
46 import java.util.LinkedHashSet;
47 import java.util.List;
48 import java.util.Map;
49 import java.util.NoSuchElementException;
50 import java.util.Properties;
51 import java.util.Set;
52 import java.util.StringTokenizer;
53 import java.util.concurrent.atomic.AtomicBoolean;
54 import org.eclipse.core.runtime.adaptor.EclipseStarter;
55 import org.eclipse.osgi.container.Module;
56 import org.eclipse.osgi.container.ModuleCapability;
57 import org.eclipse.osgi.container.ModuleContainer;
58 import org.eclipse.osgi.container.ModuleContainerAdaptor;
59 import org.eclipse.osgi.container.ModuleDatabase;
60 import org.eclipse.osgi.container.ModuleRevision;
61 import org.eclipse.osgi.container.ModuleRevisionBuilder;
62 import org.eclipse.osgi.container.ModuleWire;
63 import org.eclipse.osgi.container.ModuleWiring;
64 import org.eclipse.osgi.container.builders.OSGiManifestBuilderFactory;
65 import org.eclipse.osgi.container.namespaces.EclipsePlatformNamespace;
66 import org.eclipse.osgi.framework.log.FrameworkLogEntry;
67 import org.eclipse.osgi.framework.util.FilePath;
68 import org.eclipse.osgi.framework.util.ObjectPool;
69 import org.eclipse.osgi.framework.util.SecureAction;
70 import org.eclipse.osgi.internal.debug.Debug;
71 import org.eclipse.osgi.internal.framework.EquinoxConfiguration;
72 import org.eclipse.osgi.internal.framework.EquinoxContainer;
73 import org.eclipse.osgi.internal.framework.EquinoxContainerAdaptor;
74 import org.eclipse.osgi.internal.framework.FilterImpl;
75 import org.eclipse.osgi.internal.hookregistry.BundleFileWrapperFactoryHook;
76 import org.eclipse.osgi.internal.hookregistry.StorageHookFactory;
77 import org.eclipse.osgi.internal.hookregistry.StorageHookFactory.StorageHook;
78 import org.eclipse.osgi.internal.location.EquinoxLocations;
79 import org.eclipse.osgi.internal.location.LocationHelper;
80 import org.eclipse.osgi.internal.log.EquinoxLogServices;
81 import org.eclipse.osgi.internal.messages.Msg;
82 import org.eclipse.osgi.internal.permadmin.SecurityAdmin;
83 import org.eclipse.osgi.internal.url.URLStreamHandlerFactoryImpl;
84 import org.eclipse.osgi.service.datalocation.Location;
85 import org.eclipse.osgi.storage.BundleInfo.Generation;
86 import org.eclipse.osgi.storage.bundlefile.BundleEntry;
87 import org.eclipse.osgi.storage.bundlefile.BundleFile;
88 import org.eclipse.osgi.storage.bundlefile.BundleFileWrapper;
89 import org.eclipse.osgi.storage.bundlefile.BundleFileWrapperChain;
90 import org.eclipse.osgi.storage.bundlefile.DirBundleFile;
91 import org.eclipse.osgi.storage.bundlefile.MRUBundleFileList;
92 import org.eclipse.osgi.storage.bundlefile.NestedDirBundleFile;
93 import org.eclipse.osgi.storage.bundlefile.ZipBundleFile;
94 import org.eclipse.osgi.storage.url.reference.Handler;
95 import org.eclipse.osgi.storage.url.reference.ReferenceInputStream;
96 import org.eclipse.osgi.storagemanager.ManagedOutputStream;
97 import org.eclipse.osgi.storagemanager.StorageManager;
98 import org.eclipse.osgi.util.ManifestElement;
99 import org.eclipse.osgi.util.NLS;
100 import org.osgi.framework.Bundle;
101 import org.osgi.framework.BundleContext;
102 import org.osgi.framework.BundleException;
103 import org.osgi.framework.Constants;
104 import org.osgi.framework.Filter;
105 import org.osgi.framework.InvalidSyntaxException;
106 import org.osgi.framework.Version;
107 import org.osgi.framework.namespace.HostNamespace;
108 import org.osgi.framework.namespace.NativeNamespace;
109 import org.osgi.framework.namespace.PackageNamespace;
110 import org.osgi.framework.wiring.BundleCapability;
111 import org.osgi.framework.wiring.BundleRevision;
112 import org.osgi.framework.wiring.BundleWiring;
113 import org.osgi.resource.Namespace;
114 import org.osgi.resource.Requirement;
115 
116 public class Storage {
117 	public static class StorageException extends RuntimeException {
118 		private static final long serialVersionUID = 1L;
119 
StorageException()120 		public StorageException() {
121 			super();
122 		}
123 
StorageException(String message, Throwable cause)124 		public StorageException(String message, Throwable cause) {
125 			super(message, cause);
126 		}
127 
StorageException(String message)128 		public StorageException(String message) {
129 			super(message);
130 		}
131 
StorageException(Throwable cause)132 		public StorageException(Throwable cause) {
133 			super(cause);
134 		}
135 
136 	}
137 
138 	public static final int VERSION = 5;
139 	private static final int MR_JAR_VERSION = 4;
140 	private static final int CACHED_SYSTEM_CAPS_VERION = 5;
141 	private static final int LOWEST_VERSION_SUPPORTED = 3;
142 	public static final String BUNDLE_DATA_DIR = "data"; //$NON-NLS-1$
143 	public static final String BUNDLE_FILE_NAME = "bundleFile"; //$NON-NLS-1$
144 	public static final String FRAMEWORK_INFO = "framework.info"; //$NON-NLS-1$
145 	public static final String ECLIPSE_SYSTEMBUNDLE = "Eclipse-SystemBundle"; //$NON-NLS-1$
146 	public static final String DELETE_FLAG = ".delete"; //$NON-NLS-1$
147 	public static final String LIB_TEMP = "libtemp"; //$NON-NLS-1$
148 
149 	private static final String JAVASE = "JavaSE"; //$NON-NLS-1$
150 	private static final String PROFILE_EXT = ".profile"; //$NON-NLS-1$
151 	private static final String NUL = new String(new byte[] {0});
152 	private static final String INITIAL_LOCATION = "initial@"; //$NON-NLS-1$
153 
154 	static final SecureAction secureAction = AccessController.doPrivileged(SecureAction.createSecureAction());
155 
156 	private final EquinoxContainer equinoxContainer;
157 	private final String installPath;
158 	private final Location osgiLocation;
159 	private final File childRoot;
160 	private final File parentRoot;
161 	private final PermissionData permissionData;
162 	private final SecurityAdmin securityAdmin;
163 	private final EquinoxContainerAdaptor adaptor;
164 	private final ModuleDatabase moduleDatabase;
165 	private final ModuleContainer moduleContainer;
166 	private final Object saveMonitor = new Object();
167 	private long lastSavedTimestamp = -1;
168 	private final MRUBundleFileList mruList;
169 	private final FrameworkExtensionInstaller extensionInstaller;
170 	private final List<String> cachedHeaderKeys = Arrays.asList(Constants.BUNDLE_SYMBOLICNAME, Constants.BUNDLE_ACTIVATIONPOLICY, "Service-Component"); //$NON-NLS-1$
171 	private final boolean allowRestrictedProvides;
172 	private final AtomicBoolean refreshMRBundles = new AtomicBoolean(false);
173 	private final Version runtimeVersion;
174 	private final String javaSpecVersion;
175 
createStorage(EquinoxContainer container)176 	public static Storage createStorage(EquinoxContainer container) throws IOException, BundleException {
177 		String[] cachedInfo = new String[3];
178 		Storage storage = new Storage(container, cachedInfo);
179 		// Do some operations that need to happen on the fully constructed Storage before returning it
180 		storage.checkSystemBundle(cachedInfo);
181 		storage.refreshStaleBundles();
182 		storage.installExtensions();
183 		// TODO hack to make sure all bundles are in UNINSTALLED state before system bundle init is called
184 		storage.getModuleContainer().setInitialModuleStates();
185 		return storage;
186 	}
187 
Storage(EquinoxContainer container, String[] cachedInfo)188 	private Storage(EquinoxContainer container, String[] cachedInfo) throws IOException {
189 		// default to Java 7 since that is our min
190 		Version javaVersion = Version.valueOf("1.7"); //$NON-NLS-1$
191 		// set the profile and EE based off of the java.specification.version
192 		String javaSpecVersionProp = System.getProperty(EquinoxConfiguration.PROP_JVM_SPEC_VERSION);
193 		StringTokenizer st = new StringTokenizer(javaSpecVersionProp, " _-"); //$NON-NLS-1$
194 		javaSpecVersionProp = st.nextToken();
195 		try {
196 			String[] vComps = javaSpecVersionProp.split("\\."); //$NON-NLS-1$
197 			// only pay attention to the first three components of the version
198 			int major = vComps.length > 0 ? Integer.parseInt(vComps[0]) : 0;
199 			int minor = vComps.length > 1 ? Integer.parseInt(vComps[1]) : 0;
200 			int micro = vComps.length > 2 ? Integer.parseInt(vComps[2]) : 0;
201 			javaVersion = new Version(major, minor, micro);
202 		} catch (IllegalArgumentException e) {
203 			// do nothing
204 		}
205 		runtimeVersion = javaVersion;
206 		javaSpecVersion = javaSpecVersionProp;
207 		mruList = new MRUBundleFileList(getBundleFileLimit(container.getConfiguration()), container.getConfiguration().getDebug());
208 		equinoxContainer = container;
209 		extensionInstaller = new FrameworkExtensionInstaller(container.getConfiguration());
210 		allowRestrictedProvides = Boolean.parseBoolean(container.getConfiguration().getConfiguration(EquinoxConfiguration.PROP_ALLOW_RESTRICTED_PROVIDES));
211 
212 		// we need to set the install path as soon as possible so we can determine
213 		// the absolute location of install relative URLs
214 		Location installLoc = container.getLocations().getInstallLocation();
215 		URL installURL = installLoc.getURL();
216 		// assume install URL is file: based
217 		installPath = installURL.getPath();
218 
219 		Location configLocation = container.getLocations().getConfigurationLocation();
220 		Location parentConfigLocation = configLocation.getParentLocation();
221 		Location osgiParentLocation = null;
222 		if (parentConfigLocation != null) {
223 			osgiParentLocation = parentConfigLocation.createLocation(null, parentConfigLocation.getDataArea(EquinoxContainer.NAME), true);
224 		}
225 		this.osgiLocation = configLocation.createLocation(osgiParentLocation, configLocation.getDataArea(EquinoxContainer.NAME), configLocation.isReadOnly());
226 		this.childRoot = new File(osgiLocation.getURL().getPath());
227 
228 		if (Boolean.valueOf(container.getConfiguration().getConfiguration(EquinoxConfiguration.PROP_CLEAN)).booleanValue()) {
229 			cleanOSGiStorage(osgiLocation, childRoot);
230 		}
231 		if (!this.osgiLocation.isReadOnly()) {
232 			this.childRoot.mkdirs();
233 		}
234 		Location parent = this.osgiLocation.getParentLocation();
235 		parentRoot = parent == null ? null : new File(parent.getURL().getPath());
236 
237 		if (container.getConfiguration().getConfiguration(Constants.FRAMEWORK_STORAGE) == null) {
238 			// Set the derived value if not already set as part of configuration.
239 			// Note this is the parent directory of where the framework stores data (org.eclipse.osgi/)
240 			container.getConfiguration().setConfiguration(Constants.FRAMEWORK_STORAGE, childRoot.getParentFile().getAbsolutePath());
241 		}
242 
243 		InputStream info = getInfoInputStream();
244 		DataInputStream data = info == null ? null : new DataInputStream(new BufferedInputStream(info));
245 		try {
246 			Map<Long, Generation> generations;
247 			try {
248 				generations = loadGenerations(data, cachedInfo);
249 			} catch (IllegalArgumentException e) {
250 				equinoxContainer.getLogServices().log(EquinoxContainer.NAME, FrameworkLogEntry.WARNING, "The persistent format for the framework data has changed.  The framework will be reinitialized: " + e.getMessage(), null); //$NON-NLS-1$
251 				generations = new HashMap<>(0);
252 				data = null;
253 				cleanOSGiStorage(osgiLocation, childRoot);
254 			}
255 			this.permissionData = loadPermissionData(data);
256 			this.securityAdmin = new SecurityAdmin(null, this.permissionData);
257 			this.adaptor = new EquinoxContainerAdaptor(equinoxContainer, this, generations);
258 			this.moduleDatabase = new ModuleDatabase(this.adaptor);
259 			this.moduleContainer = new ModuleContainer(this.adaptor, this.moduleDatabase);
260 			if (data != null) {
261 				try {
262 					moduleDatabase.load(data);
263 					lastSavedTimestamp = moduleDatabase.getTimestamp();
264 				} catch (IllegalArgumentException e) {
265 					equinoxContainer.getLogServices().log(EquinoxContainer.NAME, FrameworkLogEntry.WARNING, "Incompatible version.  Starting with empty framework.", e); //$NON-NLS-1$
266 					// Clean up the cache.
267 					// No need to clean up the database. Nothing got loaded.
268 					cleanOSGiStorage(osgiLocation, childRoot);
269 					// should free up the generations map
270 					generations.clear();
271 				}
272 			}
273 		} finally {
274 			if (data != null) {
275 				try {
276 					data.close();
277 				} catch (IOException e) {
278 					// just move on
279 				}
280 			}
281 		}
282 	}
283 
getRuntimeVersion()284 	public Version getRuntimeVersion() {
285 		return runtimeVersion;
286 	}
287 
getMRUBundleFileList()288 	public MRUBundleFileList getMRUBundleFileList() {
289 		return mruList;
290 	}
291 
getBundleFileLimit(EquinoxConfiguration configuration)292 	private int getBundleFileLimit(EquinoxConfiguration configuration) {
293 		int propValue = 100; // enable to 100 open files by default
294 		try {
295 			String prop = configuration.getConfiguration(EquinoxConfiguration.PROP_FILE_LIMIT);
296 			if (prop != null)
297 				propValue = Integer.parseInt(prop);
298 		} catch (NumberFormatException e) {
299 			// use default of 100
300 		}
301 		return propValue;
302 	}
303 
installExtensions()304 	private void installExtensions() {
305 		Module systemModule = moduleContainer.getModule(0);
306 		ModuleRevision systemRevision = systemModule == null ? null : systemModule.getCurrentRevision();
307 		ModuleWiring systemWiring = systemRevision == null ? null : systemRevision.getWiring();
308 		if (systemWiring == null) {
309 			return;
310 		}
311 		Collection<ModuleRevision> fragments = new ArrayList<>();
312 		for (ModuleWire hostWire : systemWiring.getProvidedModuleWires(HostNamespace.HOST_NAMESPACE)) {
313 			fragments.add(hostWire.getRequirer());
314 		}
315 		try {
316 			getExtensionInstaller().addExtensionContent(fragments, null);
317 		} catch (BundleException e) {
318 			getLogServices().log(EquinoxContainer.NAME, FrameworkLogEntry.ERROR, e.getMessage(), e);
319 		}
320 	}
321 
loadPermissionData(DataInputStream in)322 	private static PermissionData loadPermissionData(DataInputStream in) throws IOException {
323 		PermissionData permData = new PermissionData();
324 		if (in != null) {
325 			permData.readPermissionData(in);
326 		}
327 		return permData;
328 	}
329 
refreshStaleBundles()330 	private void refreshStaleBundles() throws BundleException {
331 		Collection<Module> needsRefresh = new ArrayList<>(0);
332 
333 		// First uninstall any modules that had their content changed or deleted
334 		for (Module module : moduleContainer.getModules()) {
335 			if (module.getId() == Constants.SYSTEM_BUNDLE_ID)
336 				continue;
337 			ModuleRevision revision = module.getCurrentRevision();
338 			Generation generation = (Generation) revision.getRevisionInfo();
339 			if (needsDiscarding(generation)) {
340 				needsRefresh.add(module);
341 				moduleContainer.uninstall(module);
342 				generation.delete();
343 			}
344 		}
345 		// Next check if we need to refresh Multi-Release Jar bundles
346 		// because the runtime version changed.
347 		if (refreshMRBundles.get()) {
348 			needsRefresh.addAll(refreshMRJarBundles());
349 		}
350 
351 		// refresh the modules that got deleted or are Multi-Release bundles
352 		if (!needsRefresh.isEmpty()) {
353 			moduleContainer.refresh(needsRefresh);
354 		}
355 	}
356 
needsDiscarding(Generation generation)357 	private boolean needsDiscarding(Generation generation) {
358 		for (StorageHook<?, ?> hook : generation.getStorageHooks()) {
359 			try {
360 				hook.validate();
361 			} catch (IllegalStateException e) {
362 				equinoxContainer.getLogServices().log(EquinoxContainer.NAME, FrameworkLogEntry.WARNING, "Error validating installed bundle.", e); //$NON-NLS-1$
363 				return true;
364 			}
365 		}
366 		File content = generation.getContent();
367 		if (getConfiguration().inCheckConfigurationMode()) {
368 			if (generation.isDirectory()) {
369 				content = new File(content, "META-INF/MANIFEST.MF"); //$NON-NLS-1$
370 			}
371 			return generation.getLastModified() != secureAction.lastModified(content);
372 		}
373 		if (!content.exists()) {
374 			// the content got deleted since last time!
375 			return true;
376 		}
377 		return false;
378 	}
379 
checkSystemBundle(String[] cachedInfo)380 	private void checkSystemBundle(String[] cachedInfo) {
381 		Module systemModule = moduleContainer.getModule(0);
382 		Generation newGeneration = null;
383 		try {
384 			if (systemModule == null) {
385 				BundleInfo info = new BundleInfo(this, 0, Constants.SYSTEM_BUNDLE_LOCATION, 0);
386 				newGeneration = info.createGeneration();
387 
388 				File contentFile = getSystemContent();
389 				newGeneration.setContent(contentFile, false);
390 
391 				// First we must make sure the VM profile has been loaded
392 				loadVMProfile(newGeneration);
393 				// dealing with system bundle find the extra capabilities and exports
394 				String extraCapabilities = getSystemExtraCapabilities();
395 				String extraExports = getSystemExtraPackages();
396 
397 				ModuleRevisionBuilder builder = getBuilder(newGeneration, extraCapabilities, extraExports);
398 				systemModule = moduleContainer.install(null, Constants.SYSTEM_BUNDLE_LOCATION, builder, newGeneration);
399 				moduleContainer.resolve(Collections.singletonList(systemModule), false);
400 			} else {
401 				ModuleRevision currentRevision = systemModule.getCurrentRevision();
402 				Generation currentGeneration = currentRevision == null ? null : (Generation) currentRevision.getRevisionInfo();
403 				if (currentGeneration == null) {
404 					throw new IllegalStateException("No current revision for system bundle."); //$NON-NLS-1$
405 				}
406 				try {
407 					// First we must make sure the VM profile has been loaded
408 					loadVMProfile(currentGeneration);
409 					// dealing with system bundle find the extra capabilities and exports
410 					String extraCapabilities = getSystemExtraCapabilities();
411 					String extraExports = getSystemExtraPackages();
412 					File contentFile = currentGeneration.getContent();
413 					if (systemNeedsUpdate(contentFile, currentRevision, currentGeneration, extraCapabilities, extraExports, cachedInfo)) {
414 						newGeneration = currentGeneration.getBundleInfo().createGeneration();
415 						newGeneration.setContent(contentFile, false);
416 						ModuleRevisionBuilder newBuilder = getBuilder(newGeneration, extraCapabilities, extraExports);
417 						moduleContainer.update(systemModule, newBuilder, newGeneration);
418 						moduleContainer.refresh(Collections.singleton(systemModule));
419 					} else {
420 						if (currentRevision.getWiring() == null) {
421 							// must resolve before continuing to ensure extensions get attached
422 							moduleContainer.resolve(Collections.singleton(systemModule), true);
423 						}
424 					}
425 				} catch (BundleException e) {
426 					throw new IllegalStateException("Could not create a builder for the system bundle.", e); //$NON-NLS-1$
427 				}
428 			}
429 			ModuleRevision currentRevision = systemModule.getCurrentRevision();
430 			List<ModuleCapability> nativeEnvironments = currentRevision.getModuleCapabilities(NativeNamespace.NATIVE_NAMESPACE);
431 			Map<String, Object> configMap = equinoxContainer.getConfiguration().getInitialConfig();
432 			for (ModuleCapability nativeEnvironment : nativeEnvironments) {
433 				nativeEnvironment.setTransientAttrs(configMap);
434 			}
435 			Version frameworkVersion = null;
436 			if (newGeneration != null) {
437 				frameworkVersion = findFrameworkVersion();
438 			} else {
439 				String sVersion = cachedInfo[0];
440 				frameworkVersion = sVersion == null ? findFrameworkVersion() : Version.parseVersion(sVersion);
441 			}
442 			if (frameworkVersion != null) {
443 				this.equinoxContainer.getConfiguration().setConfiguration(Constants.FRAMEWORK_VERSION, frameworkVersion.toString());
444 			}
445 		} catch (Exception e) {
446 			if (e instanceof RuntimeException) {
447 				throw (RuntimeException) e;
448 			}
449 			throw new RuntimeException("Error occurred while checking the system module.", e); //$NON-NLS-1$
450 		} finally {
451 			if (newGeneration != null) {
452 				newGeneration.getBundleInfo().unlockGeneration(newGeneration);
453 			}
454 		}
455 	}
456 
findFrameworkVersion()457 	private Version findFrameworkVersion() {
458 		Requirement osgiPackageReq = ModuleContainer.createRequirement(PackageNamespace.PACKAGE_NAMESPACE, Collections.singletonMap(Namespace.REQUIREMENT_FILTER_DIRECTIVE, "(" + PackageNamespace.PACKAGE_NAMESPACE + "=org.osgi.framework)"), Collections.<String, String> emptyMap()); //$NON-NLS-1$ //$NON-NLS-2$
459 		Collection<BundleCapability> osgiPackages = moduleContainer.getFrameworkWiring().findProviders(osgiPackageReq);
460 		for (BundleCapability packageCapability : osgiPackages) {
461 			if (packageCapability.getRevision().getBundle().getBundleId() == 0) {
462 				Version v = (Version) packageCapability.getAttributes().get(PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE);
463 				if (v != null) {
464 					return v;
465 				}
466 			}
467 		}
468 		return null;
469 	}
470 
refreshMRJarBundles()471 	private Collection<Module> refreshMRJarBundles() throws BundleException {
472 		Collection<Module> mrJarBundles = new ArrayList<>();
473 		for (Module m : moduleContainer.getModules()) {
474 			Generation generation = (Generation) m.getCurrentRevision().getRevisionInfo();
475 			// Note that we check the raw headers here incase we are working off an old version of the persistent storage
476 			if (Boolean.parseBoolean(generation.getRawHeaders().get(BundleInfo.MULTI_RELEASE_HEADER))) {
477 				refresh(m);
478 				mrJarBundles.add(m);
479 			}
480 		}
481 		return mrJarBundles;
482 	}
483 
close()484 	public void close() {
485 		try {
486 			save();
487 		} catch (IOException e) {
488 			getLogServices().log(EquinoxContainer.NAME, FrameworkLogEntry.ERROR, "Error saving on shutdown", e); //$NON-NLS-1$
489 		}
490 
491 		// close all the generations
492 		List<Module> modules = moduleContainer.getModules();
493 		for (Module module : modules) {
494 			for (ModuleRevision revision : module.getRevisions().getModuleRevisions()) {
495 				Generation generation = (Generation) revision.getRevisionInfo();
496 				if (generation != null) {
497 					generation.close();
498 				}
499 			}
500 		}
501 		for (ModuleRevision removalPending : moduleContainer.getRemovalPending()) {
502 			Generation generation = (Generation) removalPending.getRevisionInfo();
503 			if (generation != null) {
504 				generation.close();
505 			}
506 		}
507 		mruList.shutdown();
508 		adaptor.shutdownExecutors();
509 	}
510 
systemNeedsUpdate(File systemContent, ModuleRevision currentRevision, Generation existing, String extraCapabilities, String extraExports, String[] cachedInfo)511 	private boolean systemNeedsUpdate(File systemContent, ModuleRevision currentRevision, Generation existing, String extraCapabilities, String extraExports, String[] cachedInfo) throws BundleException {
512 		if (!extraCapabilities.equals(cachedInfo[1])) {
513 			return true;
514 		}
515 		if (!extraExports.equals(cachedInfo[2])) {
516 			return true;
517 		}
518 		if (systemContent == null) {
519 			// only do a version check in this case
520 			ModuleRevisionBuilder newBuilder = getBuilder(existing, extraCapabilities, extraExports);
521 			return !currentRevision.getVersion().equals(newBuilder.getVersion());
522 		}
523 		if (existing.isDirectory()) {
524 			systemContent = new File(systemContent, "META-INF/MANIFEST.MF"); //$NON-NLS-1$
525 		}
526 		return existing.getLastModified() != secureAction.lastModified(systemContent);
527 
528 	}
529 
cleanOSGiStorage(Location location, File root)530 	private void cleanOSGiStorage(Location location, File root) {
531 		if (location.isReadOnly() || !StorageUtil.rm(root, getConfiguration().getDebug().DEBUG_STORAGE)) {
532 			equinoxContainer.getLogServices().log(EquinoxContainer.NAME, FrameworkLogEntry.ERROR, "The -clean (osgi.clean) option was not successful. Unable to clean the storage area: " + root.getAbsolutePath(), null); //$NON-NLS-1$
533 		}
534 		if (!location.isReadOnly()) {
535 			// make sure to recreate to root folder
536 			root.mkdirs();
537 		}
538 	}
539 
getModuleDatabase()540 	public ModuleDatabase getModuleDatabase() {
541 		return moduleDatabase;
542 	}
543 
getAdaptor()544 	public ModuleContainerAdaptor getAdaptor() {
545 		return adaptor;
546 	}
547 
getModuleContainer()548 	public ModuleContainer getModuleContainer() {
549 		return moduleContainer;
550 	}
551 
getConfiguration()552 	public EquinoxConfiguration getConfiguration() {
553 		return equinoxContainer.getConfiguration();
554 	}
555 
getLogServices()556 	public EquinoxLogServices getLogServices() {
557 		return equinoxContainer.getLogServices();
558 	}
559 
getExtensionInstaller()560 	public FrameworkExtensionInstaller getExtensionInstaller() {
561 		return extensionInstaller;
562 	}
563 
isReadOnly()564 	public boolean isReadOnly() {
565 		return osgiLocation.isReadOnly();
566 	}
567 
getContentConnection(Module module, String bundleLocation, final InputStream in)568 	public URLConnection getContentConnection(Module module, String bundleLocation, final InputStream in) throws IOException {
569 		List<StorageHookFactory<?, ?, ?>> storageHooks = getConfiguration().getHookRegistry().getStorageHookFactories();
570 		for (StorageHookFactory<?, ?, ?> storageHook : storageHooks) {
571 			URLConnection hookContent = storageHook.handleContentConnection(module, bundleLocation, in);
572 			if (hookContent != null) {
573 				return hookContent;
574 			}
575 		}
576 
577 		if (in != null) {
578 			return new URLConnection(null) {
579 				/**
580 				 * @throws IOException
581 				 */
582 				@Override
583 				public void connect() throws IOException {
584 					connected = true;
585 				}
586 
587 				/**
588 				 * @throws IOException
589 				 */
590 				@Override
591 				public InputStream getInputStream() throws IOException {
592 					return (in);
593 				}
594 			};
595 		}
596 		if (module == null) {
597 			if (bundleLocation == null) {
598 				throw new IllegalArgumentException("Module and location cannot be null"); //$NON-NLS-1$
599 			}
600 			return getContentConnection(bundleLocation);
601 		}
602 		return getContentConnection(getUpdateLocation(module));
603 	}
604 
605 	private String getUpdateLocation(final Module module) {
606 		if (System.getSecurityManager() == null)
607 			return getUpdateLocation0(module);
608 		return AccessController.doPrivileged(new PrivilegedAction<String>() {
609 			@Override
610 			public String run() {
611 				return getUpdateLocation0(module);
612 			}
613 		});
614 	}
615 
616 	String getUpdateLocation0(Module module) {
617 		ModuleRevision current = module.getCurrentRevision();
618 		Generation generation = (Generation) current.getRevisionInfo();
619 		String updateLocation = generation.getHeaders().get(Constants.BUNDLE_UPDATELOCATION);
620 		if (updateLocation == null) {
621 			updateLocation = module.getLocation();
622 		}
623 		if (updateLocation.startsWith(INITIAL_LOCATION)) {
624 			updateLocation = updateLocation.substring(INITIAL_LOCATION.length());
625 		}
626 		return updateLocation;
627 	}
628 
629 	private URLConnection getContentConnection(final String spec) throws IOException {
630 		if (System.getSecurityManager() == null) {
631 			return LocationHelper.getConnection(createURL(spec));
632 		}
633 		try {
634 			return AccessController.doPrivileged(new PrivilegedExceptionAction<URLConnection>() {
635 				@Override
636 				public URLConnection run() throws IOException {
637 					return LocationHelper.getConnection(createURL(spec));
638 				}
639 			});
640 		} catch (PrivilegedActionException e) {
641 			if (e.getException() instanceof IOException)
642 				throw (IOException) e.getException();
643 			throw (RuntimeException) e.getException();
644 		}
645 	}
646 
647 	URL createURL(String spec) throws MalformedURLException {
648 		if (spec.startsWith(URLStreamHandlerFactoryImpl.PROTOCOL_REFERENCE)) {
649 			return new URL(null, spec, new Handler(equinoxContainer.getConfiguration().getConfiguration(EquinoxLocations.PROP_INSTALL_AREA)));
650 		}
651 		return new URL(spec);
652 	}
653 
654 	public Generation install(Module origin, String bundleLocation, URLConnection content) throws BundleException {
655 		if (osgiLocation.isReadOnly()) {
656 			throw new BundleException("The framework storage area is read only.", BundleException.INVALID_OPERATION); //$NON-NLS-1$
657 		}
658 		URL sourceURL = content.getURL();
659 		InputStream in;
660 		try {
661 			in = content.getInputStream();
662 		} catch (Throwable e) {
663 			throw new BundleException("Error reading bundle content.", e); //$NON-NLS-1$
664 		}
665 
666 		// Check if the bundle already exists at this location
667 		// before doing the staging and generation creation.
668 		// This is important since some installers seem to continually
669 		// re-install bundles using the same location each startup
670 		Module existingLocation = moduleContainer.getModule(bundleLocation);
671 		if (existingLocation != null) {
672 			// NOTE this same logic is also in the ModuleContainer
673 			// This is necessary because the container does the location locking.
674 			// Another thread could win the location lock and install before this thread does.
675 			try {
676 				in.close();
677 			} catch (IOException e) {
678 				// ignore
679 			}
680 			if (origin != null) {
681 				// Check that the existing location is visible from the origin module
682 				Bundle bundle = origin.getBundle();
683 				BundleContext context = bundle == null ? null : bundle.getBundleContext();
684 				if (context != null && context.getBundle(existingLocation.getId()) == null) {
685 					Bundle b = existingLocation.getBundle();
686 					throw new BundleException(NLS.bind(Msg.ModuleContainer_NameCollisionWithLocation, new Object[] {b.getSymbolicName(), b.getVersion(), bundleLocation}), BundleException.REJECTED_BY_HOOK);
687 				}
688 			}
689 			return (Generation) existingLocation.getCurrentRevision().getRevisionInfo();
690 		}
691 
692 		boolean isReference = in instanceof ReferenceInputStream;
693 		File staged = stageContent(in, sourceURL);
694 		Generation generation = null;
695 		try {
696 			Long nextID = moduleDatabase.getAndIncrementNextId();
697 			BundleInfo info = new BundleInfo(this, nextID, bundleLocation, 0);
698 			generation = info.createGeneration();
699 
700 			File contentFile = getContentFile(staged, isReference, nextID, generation.getGenerationId());
701 			generation.setContent(contentFile, isReference);
702 			// Check that we can open the bundle file
703 			generation.getBundleFile().open();
704 			setStorageHooks(generation);
705 
706 			ModuleRevisionBuilder builder = getBuilder(generation);
707 			builder.setId(nextID);
708 
709 			Module m = moduleContainer.install(origin, bundleLocation, builder, generation);
710 			if (!nextID.equals(m.getId())) {
711 				// this revision is already installed. delete the generation
712 				generation.delete();
713 				return (Generation) m.getCurrentRevision().getRevisionInfo();
714 			}
715 			return generation;
716 		} catch (Throwable t) {
717 			if (!isReference) {
718 				try {
719 					delete(staged);
720 				} catch (IOException e) {
721 					// tried our best
722 				}
723 			}
724 			if (generation != null) {
725 				generation.delete();
726 				generation.getBundleInfo().delete();
727 			}
728 			if (t instanceof SecurityException) {
729 				// TODO hack from ModuleContainer
730 				// if the cause is a bundle exception then throw that
731 				if (t.getCause() instanceof BundleException) {
732 					throw (BundleException) t.getCause();
733 				}
734 				throw (SecurityException) t;
735 			}
736 			if (t instanceof BundleException) {
737 				throw (BundleException) t;
738 			}
739 			throw new BundleException("Error occurred installing a bundle.", t); //$NON-NLS-1$
740 		} finally {
741 			if (generation != null) {
742 				generation.getBundleInfo().unlockGeneration(generation);
743 			}
744 		}
745 	}
746 
747 	private void setStorageHooks(Generation generation) throws BundleException {
748 		if (generation.getBundleInfo().getBundleId() == 0) {
749 			return; // ignore system bundle
750 		}
751 		List<StorageHookFactory<?, ?, ?>> factories = new ArrayList<>(getConfiguration().getHookRegistry().getStorageHookFactories());
752 		List<StorageHook<?, ?>> hooks = new ArrayList<>(factories.size());
753 		for (Iterator<StorageHookFactory<?, ?, ?>> iFactories = factories.iterator(); iFactories.hasNext();) {
754 			@SuppressWarnings("unchecked")
755 			StorageHookFactory<Object, Object, StorageHook<Object, Object>> next = (StorageHookFactory<Object, Object, StorageHook<Object, Object>>) iFactories.next();
756 			StorageHook<Object, Object> hook = next.createStorageHookAndValidateFactoryClass(generation);
757 			if (hook != null) {
758 				hooks.add(hook);
759 			}
760 		}
761 		generation.setStorageHooks(Collections.unmodifiableList(hooks), true);
762 		for (StorageHook<?, ?> hook : hooks) {
763 			hook.initialize(generation.getHeaders());
764 		}
765 	}
766 
767 	public ModuleRevisionBuilder getBuilder(Generation generation) throws BundleException {
768 		return getBuilder(generation, null, null);
769 	}
770 
771 	public ModuleRevisionBuilder getBuilder(Generation generation, String extraCapabilities, String extraExports) throws BundleException {
772 		Dictionary<String, String> headers = generation.getHeaders();
773 		Map<String, String> mapHeaders;
774 		if (headers instanceof Map) {
775 			@SuppressWarnings("unchecked")
776 			Map<String, String> unchecked = (Map<String, String>) headers;
777 			mapHeaders = unchecked;
778 		} else {
779 			mapHeaders = new HashMap<>();
780 			for (Enumeration<String> eKeys = headers.keys(); eKeys.hasMoreElements();) {
781 				String key = eKeys.nextElement();
782 				mapHeaders.put(key, headers.get(key));
783 			}
784 		}
785 		if (generation.getBundleInfo().getBundleId() != 0) {
786 			ModuleRevisionBuilder builder = allowRestrictedProvides ? OSGiManifestBuilderFactory.createBuilder(mapHeaders, null, null, "") : OSGiManifestBuilderFactory.createBuilder(mapHeaders); //$NON-NLS-1$
787 			if ((builder.getTypes() & BundleRevision.TYPE_FRAGMENT) != 0) {
788 				for (ModuleRevisionBuilder.GenericInfo reqInfo : builder.getRequirements()) {
789 					if (HostNamespace.HOST_NAMESPACE.equals(reqInfo.getNamespace())) {
790 						if (HostNamespace.EXTENSION_BOOTCLASSPATH.equals(reqInfo.getDirectives().get(HostNamespace.REQUIREMENT_EXTENSION_DIRECTIVE))) {
791 							throw new BundleException("Boot classpath extensions are not supported.", BundleException.UNSUPPORTED_OPERATION, new UnsupportedOperationException()); //$NON-NLS-1$
792 						}
793 					}
794 				}
795 			}
796 			return builder;
797 		}
798 
799 		return OSGiManifestBuilderFactory.createBuilder(mapHeaders, Constants.SYSTEM_BUNDLE_SYMBOLICNAME, extraExports, extraCapabilities);
800 	}
801 
802 	private String getSystemExtraCapabilities() {
803 		EquinoxConfiguration equinoxConfig = equinoxContainer.getConfiguration();
804 		StringBuilder result = new StringBuilder();
805 
806 		String systemCapabilities = equinoxConfig.getConfiguration(Constants.FRAMEWORK_SYSTEMCAPABILITIES);
807 		if (systemCapabilities != null && systemCapabilities.trim().length() > 0) {
808 			result.append(systemCapabilities).append(", "); //$NON-NLS-1$
809 		}
810 
811 		String extraSystemCapabilities = equinoxConfig.getConfiguration(Constants.FRAMEWORK_SYSTEMCAPABILITIES_EXTRA);
812 		if (extraSystemCapabilities != null && extraSystemCapabilities.trim().length() > 0) {
813 			result.append(extraSystemCapabilities).append(", "); //$NON-NLS-1$
814 		}
815 
816 		result.append(EclipsePlatformNamespace.ECLIPSE_PLATFORM_NAMESPACE).append("; "); //$NON-NLS-1$
817 		result.append(EquinoxConfiguration.PROP_OSGI_OS).append("=").append(equinoxConfig.getOS()).append("; "); //$NON-NLS-1$ //$NON-NLS-2$
818 		result.append(EquinoxConfiguration.PROP_OSGI_WS).append("=").append(equinoxConfig.getWS()).append("; "); //$NON-NLS-1$ //$NON-NLS-2$
819 		result.append(EquinoxConfiguration.PROP_OSGI_ARCH).append("=").append(equinoxConfig.getOSArch()).append("; "); //$NON-NLS-1$ //$NON-NLS-2$
820 		result.append(EquinoxConfiguration.PROP_OSGI_NL).append("=").append(equinoxConfig.getNL()); //$NON-NLS-1$
821 
822 		String osName = equinoxConfig.getConfiguration(Constants.FRAMEWORK_OS_NAME);
823 		osName = osName == null ? null : osName.toLowerCase();
824 		String processor = equinoxConfig.getConfiguration(Constants.FRAMEWORK_PROCESSOR);
825 		processor = processor == null ? null : processor.toLowerCase();
826 		String osVersion = equinoxConfig.getConfiguration(Constants.FRAMEWORK_OS_VERSION);
827 		osVersion = osVersion == null ? null : osVersion.toLowerCase();
828 		String language = equinoxConfig.getConfiguration(Constants.FRAMEWORK_LANGUAGE);
829 		language = language == null ? null : language.toLowerCase();
830 
831 		result.append(", "); //$NON-NLS-1$
832 		result.append(NativeNamespace.NATIVE_NAMESPACE).append("; "); //$NON-NLS-1$
833 		if (osName != null) {
834 			osName = getAliasList(equinoxConfig.getAliasMapper().getOSNameAliases(osName));
835 			result.append(NativeNamespace.CAPABILITY_OSNAME_ATTRIBUTE).append(":List<String>=").append(osName).append("; "); //$NON-NLS-1$ //$NON-NLS-2$
836 		}
837 		if (processor != null) {
838 			processor = getAliasList(equinoxConfig.getAliasMapper().getProcessorAliases(processor));
839 			result.append(NativeNamespace.CAPABILITY_PROCESSOR_ATTRIBUTE).append(":List<String>=").append(processor).append("; "); //$NON-NLS-1$ //$NON-NLS-2$
840 		}
841 		result.append(NativeNamespace.CAPABILITY_OSVERSION_ATTRIBUTE).append(":Version").append("=\"").append(osVersion).append("\"; "); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
842 		result.append(NativeNamespace.CAPABILITY_LANGUAGE_ATTRIBUTE).append("=\"").append(language).append('\"'); //$NON-NLS-1$
843 		return result.toString();
844 	}
845 
846 	String getAliasList(Collection<String> aliases) {
847 		if (aliases.isEmpty()) {
848 			return null;
849 		}
850 		StringBuilder builder = new StringBuilder();
851 		builder.append('"');
852 		for (String alias : aliases) {
853 			builder.append(alias).append(',');
854 		}
855 		builder.setLength(builder.length() - 1);
856 		builder.append('"');
857 		return builder.toString();
858 	}
859 
860 	private String getSystemExtraPackages() {
861 		EquinoxConfiguration equinoxConfig = equinoxContainer.getConfiguration();
862 		StringBuilder result = new StringBuilder();
863 
864 		String systemPackages = equinoxConfig.getConfiguration(Constants.FRAMEWORK_SYSTEMPACKAGES);
865 		if (systemPackages != null) {
866 			result.append(systemPackages);
867 		}
868 
869 		String extraSystemPackages = equinoxConfig.getConfiguration(Constants.FRAMEWORK_SYSTEMPACKAGES_EXTRA);
870 		if (extraSystemPackages != null && extraSystemPackages.trim().length() > 0) {
871 			if (result.length() > 0) {
872 				result.append(", "); //$NON-NLS-1$
873 			}
874 			result.append(extraSystemPackages);
875 		}
876 
877 		return result.toString();
878 	}
879 
880 	private void refresh(Module module) throws BundleException {
881 		ModuleRevision current = module.getCurrentRevision();
882 		Generation currentGen = (Generation) current.getRevisionInfo();
883 		File content = currentGen.getContent();
884 		String spec = (currentGen.isReference() ? "reference:" : "") + content.toURI().toString(); //$NON-NLS-1$ //$NON-NLS-2$
885 		URLConnection contentConn;
886 		try {
887 			contentConn = getContentConnection(spec);
888 		} catch (IOException e) {
889 			throw new BundleException("Error reading bundle content.", e); //$NON-NLS-1$
890 		}
891 		update(module, contentConn);
892 	}
893 
894 	public Generation update(Module module, URLConnection content) throws BundleException {
895 		if (osgiLocation.isReadOnly()) {
896 			throw new BundleException("The framework storage area is read only.", BundleException.INVALID_OPERATION); //$NON-NLS-1$
897 		}
898 		URL sourceURL = content.getURL();
899 		InputStream in;
900 		try {
901 			in = content.getInputStream();
902 		} catch (Throwable e) {
903 			throw new BundleException("Error reading bundle content.", e); //$NON-NLS-1$
904 		}
905 		boolean isReference = in instanceof ReferenceInputStream;
906 		File staged = stageContent(in, sourceURL);
907 		ModuleRevision current = module.getCurrentRevision();
908 		Generation currentGen = (Generation) current.getRevisionInfo();
909 
910 		BundleInfo bundleInfo = currentGen.getBundleInfo();
911 		Generation newGen = bundleInfo.createGeneration();
912 
913 		try {
914 			File contentFile = getContentFile(staged, isReference, bundleInfo.getBundleId(), newGen.getGenerationId());
915 			newGen.setContent(contentFile, isReference);
916 			// Check that we can open the bundle file
917 			newGen.getBundleFile().open();
918 			setStorageHooks(newGen);
919 
920 			ModuleRevisionBuilder builder = getBuilder(newGen);
921 			moduleContainer.update(module, builder, newGen);
922 		} catch (Throwable t) {
923 			if (!isReference) {
924 				try {
925 					delete(staged);
926 				} catch (IOException e) {
927 					// tried our best
928 				}
929 			}
930 			newGen.delete();
931 			if (t instanceof SecurityException) {
932 				// TODO hack from ModuleContainer
933 				// if the cause is a bundle exception then throw that
934 				if (t.getCause() instanceof BundleException) {
935 					throw (BundleException) t.getCause();
936 				}
937 				throw (SecurityException) t;
938 			}
939 			if (t instanceof BundleException) {
940 				throw (BundleException) t;
941 			}
942 			throw new BundleException("Error occurred updating a bundle.", t); //$NON-NLS-1$
943 		} finally {
944 			bundleInfo.unlockGeneration(newGen);
945 		}
946 		return newGen;
947 	}
948 
949 	private File getContentFile(final File staged, final boolean isReference, final long bundleID, final long generationID) throws BundleException {
950 		if (System.getSecurityManager() == null)
951 			return getContentFile0(staged, isReference, bundleID, generationID);
952 		try {
953 			return AccessController.doPrivileged(new PrivilegedExceptionAction<File>() {
954 				@Override
955 				public File run() throws BundleException {
956 					return getContentFile0(staged, isReference, bundleID, generationID);
957 				}
958 			});
959 		} catch (PrivilegedActionException e) {
960 			if (e.getException() instanceof BundleException)
961 				throw (BundleException) e.getException();
962 			throw (RuntimeException) e.getException();
963 		}
964 	}
965 
966 	File getContentFile0(File staged, boolean isReference, long bundleID, long generationID) throws BundleException {
967 		File contentFile;
968 		if (!isReference) {
969 			File generationRoot = new File(childRoot, bundleID + "/" + generationID); //$NON-NLS-1$
970 			generationRoot.mkdirs();
971 			if (!generationRoot.isDirectory()) {
972 				throw new BundleException("Could not create generation directory: " + generationRoot.getAbsolutePath()); //$NON-NLS-1$
973 			}
974 			contentFile = new File(generationRoot, BUNDLE_FILE_NAME);
975 			try {
976 				StorageUtil.move(staged, contentFile, getConfiguration().getDebug().DEBUG_STORAGE);
977 			} catch (IOException e) {
978 				throw new BundleException("Error while renaming bundle file to final location: " + contentFile, //$NON-NLS-1$
979 						BundleException.READ_ERROR, e);
980 			}
981 		} else {
982 			contentFile = staged;
983 		}
984 		return contentFile;
985 	}
986 
987 	private static String getBundleFilePath(long bundleID, long generationID) {
988 		return bundleID + "/" + generationID + "/" + BUNDLE_FILE_NAME; //$NON-NLS-1$ //$NON-NLS-2$
989 	}
990 
991 	/**
992 	 * Gets a file from storage and conditionally checks the parent storage area
993 	 * if the file does not exist in the child configuration.
994 	 * Note, this method does not check for escaping of paths from the root storage area.
995 	 * @param path the path relative to the root of the storage area
996 	 * @param checkParent if true then check the parent storage (if any) when the file
997 	 * does not exist in the child storage area
998 	 * @return the file being requested. A {@code null} value is never returned.  The file
999 	 * returned may not exist.
1000 	 * @throws StorageException if there was an issue getting the file
1001 	 */
1002 	public File getFile(String path, boolean checkParent) throws StorageException {
1003 		return getFile(null, path, checkParent);
1004 	}
1005 
1006 	/**
1007 	 * Same as {@link #getFile(String, boolean)} except takes a base parameter which is
1008 	 * appended to the root storage area before looking for the path.  If base is not
1009 	 * null then additional checks are done to make sure the path does not escape out
1010 	 * of the base path.
1011 	 * @param base the additional base path to append to the root storage area.  May be
1012 	 * {@code null}, in which case no check is done for escaping out of the base path.
1013 	 * @param path the path relative to the root + base storage area.
1014 	 * @param checkParent if true then check the parent storage (if any) when the file
1015 	 * does not exist in the child storage area
1016 	 * @return the file being requested. A {@code null} value is never returned.  The file
1017 	 * returned may not exist.
1018 	 * @throws StorageException if there was an issue getting the file
1019 	 */
1020 	public File getFile(String base, String path, boolean checkParent) throws StorageException {
1021 		// first check the child location
1022 		File childPath = getFile(childRoot, base, path);
1023 		// now check the parent
1024 		if (checkParent && parentRoot != null) {
1025 			if (childPath.exists()) {
1026 				return childPath;
1027 			}
1028 			File parentPath = getFile(parentRoot, base, path);
1029 			if (parentPath.exists()) {
1030 				// only use the parent file only if it exists;
1031 				return parentPath;
1032 			}
1033 		}
1034 		// did not exist in both locations; use the child path
1035 		return childPath;
1036 	}
1037 
1038 	private static File getFile(File root, String base, String path) {
1039 		if (base == null) {
1040 			// return quick; no need to check for path traversal
1041 			return new File(root, path);
1042 		}
1043 
1044 		// if base is not null then move root to include the base
1045 		root = new File(root, base);
1046 		File result = new File(root, path);
1047 
1048 		// do the extra check to make sure the path did not escape the root path
1049 		try {
1050 			String resultCanonical = result.getCanonicalPath();
1051 			String rootCanonical = root.getCanonicalPath();
1052 			if (!resultCanonical.startsWith(rootCanonical + File.separator) && !resultCanonical.equals(rootCanonical)) {
1053 				throw new StorageException("Invalid path: " + path); //$NON-NLS-1$
1054 			}
1055 		} catch (IOException e) {
1056 			throw new StorageException("Invalid path: " + path, e); //$NON-NLS-1$
1057 		}
1058 		return result;
1059 	}
1060 
1061 	private File stageContent(final InputStream in, final URL sourceURL) throws BundleException {
1062 		if (System.getSecurityManager() == null)
1063 			return stageContent0(in, sourceURL);
1064 		try {
1065 			return AccessController.doPrivileged(new PrivilegedExceptionAction<File>() {
1066 				@Override
1067 				public File run() throws BundleException {
1068 					return stageContent0(in, sourceURL);
1069 				}
1070 			});
1071 		} catch (PrivilegedActionException e) {
1072 			if (e.getException() instanceof BundleException)
1073 				throw (BundleException) e.getException();
1074 			throw (RuntimeException) e.getException();
1075 		}
1076 	}
1077 
1078 	File stageContent0(InputStream in, URL sourceURL) throws BundleException {
1079 		File outFile = null;
1080 		try {
1081 			if (in instanceof ReferenceInputStream) {
1082 				return ((ReferenceInputStream) in).getReference();
1083 			}
1084 
1085 			outFile = File.createTempFile(BUNDLE_FILE_NAME, ".tmp", childRoot); //$NON-NLS-1$
1086 			String protocol = sourceURL == null ? null : sourceURL.getProtocol();
1087 
1088 			if ("file".equals(protocol)) { //$NON-NLS-1$
1089 				File inFile = new File(sourceURL.getPath());
1090 				inFile = LocationHelper.decodePath(inFile);
1091 				if (inFile.isDirectory()) {
1092 					// need to delete the outFile because it is not a directory
1093 					outFile.delete();
1094 					StorageUtil.copyDir(inFile, outFile);
1095 				} else {
1096 					StorageUtil.readFile(in, outFile);
1097 				}
1098 			} else {
1099 				StorageUtil.readFile(in, outFile);
1100 			}
1101 			return outFile;
1102 		} catch (IOException e) {
1103 			if (outFile != null) {
1104 				outFile.delete();
1105 			}
1106 			throw new BundleException(Msg.BUNDLE_READ_EXCEPTION, BundleException.READ_ERROR, e);
1107 		}
1108 	}
1109 
1110 	/**
1111 	 * Attempts to set the permissions of the file in a system dependent way.
1112 	 * @param file the file to set the permissions on
1113 	 */
1114 	public void setPermissions(File file) {
1115 		String commandProp = getConfiguration().getConfiguration(EquinoxConfiguration.PROP_SETPERMS_CMD);
1116 		if (commandProp == null)
1117 			commandProp = getConfiguration().getConfiguration(Constants.FRAMEWORK_EXECPERMISSION);
1118 		if (commandProp == null)
1119 			return;
1120 		String[] commandComponents = ManifestElement.getArrayFromList(commandProp, " "); //$NON-NLS-1$
1121 		List<String> command = new ArrayList<>(commandComponents.length + 1);
1122 		boolean foundFullPath = false;
1123 		for (String commandComponent : commandComponents) {
1124 			if ("[fullpath]".equals(commandComponent) || "${abspath}".equals(commandComponent)) { //$NON-NLS-1$ //$NON-NLS-2$
1125 				command.add(file.getAbsolutePath());
1126 				foundFullPath = true;
1127 			} else {
1128 				command.add(commandComponent);
1129 			}
1130 		}
1131 		if (!foundFullPath)
1132 			command.add(file.getAbsolutePath());
1133 		try {
1134 			Runtime.getRuntime().exec(command.toArray(new String[command.size()])).waitFor();
1135 		} catch (Exception e) {
1136 			e.printStackTrace();
1137 		}
1138 	}
1139 
1140 	public BundleFile createBundleFile(File content, Generation generation, boolean isDirectory, boolean isBase) {
1141 		BundleFile result;
1142 		try {
1143 			if (isDirectory) {
1144 				boolean strictPath = Boolean.parseBoolean(equinoxContainer.getConfiguration().getConfiguration(EquinoxConfiguration.PROPERTY_STRICT_BUNDLE_ENTRY_PATH, Boolean.FALSE.toString()));
1145 				result = new DirBundleFile(content, strictPath);
1146 			} else {
1147 				result = new ZipBundleFile(content, generation, mruList, getConfiguration().getDebug());
1148 			}
1149 		} catch (IOException e) {
1150 			throw new RuntimeException("Could not create bundle file.", e); //$NON-NLS-1$
1151 		}
1152 		return wrapBundleFile(result, generation, isBase);
1153 	}
1154 
1155 	public BundleFile createNestedBundleFile(String nestedDir, BundleFile bundleFile, Generation generation) {
1156 		return createNestedBundleFile(nestedDir, bundleFile, generation, Collections.<String> emptyList());
1157 	}
1158 
1159 	public BundleFile createNestedBundleFile(String nestedDir, BundleFile bundleFile, Generation generation, Collection<String> filterPrefixes) {
1160 		// here we assume the content is a path offset into the base bundle file;  create a NestedDirBundleFile
1161 		return wrapBundleFile(new NestedDirBundleFile(bundleFile, nestedDir, filterPrefixes), generation, false);
1162 	}
1163 
1164 	public BundleFile wrapBundleFile(BundleFile bundleFile, Generation generation, boolean isBase) {
1165 		// try creating a wrapper bundlefile out of it.
1166 		List<BundleFileWrapperFactoryHook> wrapperFactories = getConfiguration().getHookRegistry().getBundleFileWrapperFactoryHooks();
1167 		BundleFileWrapperChain wrapped = wrapperFactories.isEmpty() ? null : new BundleFileWrapperChain(bundleFile, null);
1168 		for (BundleFileWrapperFactoryHook wrapperFactory : wrapperFactories) {
1169 			BundleFileWrapper wrapperBundle = wrapperFactory.wrapBundleFile(bundleFile, generation, isBase);
1170 			if (wrapperBundle != null && wrapperBundle != bundleFile)
1171 				bundleFile = wrapped = new BundleFileWrapperChain(wrapperBundle, wrapped);
1172 		}
1173 
1174 		return bundleFile;
1175 	}
1176 
1177 	public void compact() {
1178 		if (!osgiLocation.isReadOnly()) {
1179 			compact(childRoot);
1180 		}
1181 	}
1182 
1183 	private void compact(File directory) {
1184 		if (getConfiguration().getDebug().DEBUG_STORAGE)
1185 			Debug.println("compact(" + directory.getPath() + ")"); //$NON-NLS-1$ //$NON-NLS-2$
1186 		String list[] = directory.list();
1187 		if (list == null)
1188 			return;
1189 
1190 		int len = list.length;
1191 		for (int i = 0; i < len; i++) {
1192 			if (BUNDLE_DATA_DIR.equals(list[i]))
1193 				continue; /* do not examine the bundles data dir. */
1194 			File target = new File(directory, list[i]);
1195 			// if the file is a directory
1196 			if (!target.isDirectory())
1197 				continue;
1198 			File delete = new File(target, DELETE_FLAG);
1199 			// and the directory is marked for delete
1200 			if (delete.exists()) {
1201 				// if rm fails to delete the directory and .delete was removed
1202 				if (!StorageUtil.rm(target, getConfiguration().getDebug().DEBUG_STORAGE) && !delete.exists()) {
1203 					try {
1204 						// recreate .delete
1205 						FileOutputStream out = new FileOutputStream(delete);
1206 						out.close();
1207 					} catch (IOException e) {
1208 						if (getConfiguration().getDebug().DEBUG_STORAGE)
1209 							Debug.println("Unable to write " + delete.getPath() + ": " + e.getMessage()); //$NON-NLS-1$ //$NON-NLS-2$
1210 					}
1211 				}
1212 			} else {
1213 				compact(target); /* descend into directory */
1214 			}
1215 		}
1216 	}
1217 
1218 	void delete(final File delete) throws IOException {
1219 		if (System.getSecurityManager() == null) {
1220 			delete0(delete);
1221 		} else {
1222 			try {
1223 				AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() {
1224 					@Override
1225 					public Void run() throws IOException {
1226 						delete0(delete);
1227 						return null;
1228 					}
1229 				});
1230 			} catch (PrivilegedActionException e) {
1231 				if (e.getException() instanceof IOException)
1232 					throw (IOException) e.getException();
1233 				throw (RuntimeException) e.getException();
1234 			}
1235 		}
1236 	}
1237 
1238 	void delete0(File delete) throws IOException {
1239 		if (!StorageUtil.rm(delete, getConfiguration().getDebug().DEBUG_STORAGE)) {
1240 			/* create .delete */
1241 			FileOutputStream out = new FileOutputStream(new File(delete, DELETE_FLAG));
1242 			out.close();
1243 		}
1244 	}
1245 
1246 	public void save() throws IOException {
1247 		if (isReadOnly()) {
1248 			return;
1249 		}
1250 		if (System.getSecurityManager() == null) {
1251 			save0();
1252 		} else {
1253 			try {
1254 				AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() {
1255 					@Override
1256 					public Void run() throws IOException {
1257 						save0();
1258 						return null;
1259 					}
1260 				});
1261 			} catch (PrivilegedActionException e) {
1262 				if (e.getException() instanceof IOException)
1263 					throw (IOException) e.getException();
1264 				throw (RuntimeException) e.getException();
1265 			}
1266 		}
1267 	}
1268 
1269 	void save0() throws IOException {
1270 		StorageManager childStorageManager = null;
1271 		ManagedOutputStream mos = null;
1272 		DataOutputStream out = null;
1273 		boolean success = false;
1274 		moduleDatabase.readLock();
1275 		try {
1276 			synchronized (this.saveMonitor) {
1277 				if (lastSavedTimestamp == moduleDatabase.getTimestamp())
1278 					return;
1279 				childStorageManager = getChildStorageManager();
1280 				mos = childStorageManager.getOutputStream(FRAMEWORK_INFO);
1281 				out = new DataOutputStream(new BufferedOutputStream(mos));
1282 				saveGenerations(out);
1283 				savePermissionData(out);
1284 				moduleDatabase.store(out, true);
1285 				lastSavedTimestamp = moduleDatabase.getTimestamp();
1286 				success = true;
1287 			}
1288 		} finally {
1289 			if (!success) {
1290 				if (mos != null) {
1291 					mos.abort();
1292 				}
1293 			}
1294 			if (out != null) {
1295 				try {
1296 					out.close();
1297 				} catch (IOException e) {
1298 					// tried our best
1299 				}
1300 			}
1301 			if (childStorageManager != null) {
1302 				childStorageManager.close();
1303 			}
1304 			moduleDatabase.readUnlock();
1305 		}
1306 	}
1307 
1308 	private void savePermissionData(DataOutputStream out) throws IOException {
1309 		permissionData.savePermissionData(out);
1310 	}
1311 
1312 	private void saveGenerations(DataOutputStream out) throws IOException {
1313 		List<Module> modules = moduleContainer.getModules();
1314 		List<Generation> generations = new ArrayList<>();
1315 		for (Module module : modules) {
1316 			ModuleRevision revision = module.getCurrentRevision();
1317 			if (revision != null) {
1318 				Generation generation = (Generation) revision.getRevisionInfo();
1319 				if (generation != null) {
1320 					generations.add(generation);
1321 				}
1322 			}
1323 		}
1324 		out.writeInt(VERSION);
1325 
1326 		out.writeUTF(runtimeVersion.toString());
1327 
1328 		Version curFrameworkVersion = findFrameworkVersion();
1329 		out.writeUTF(curFrameworkVersion == null ? Version.emptyVersion.toString() : curFrameworkVersion.toString());
1330 
1331 		saveLongString(out, getSystemExtraCapabilities());
1332 		saveLongString(out, getSystemExtraPackages());
1333 
1334 		out.writeInt(cachedHeaderKeys.size());
1335 		for (String headerKey : cachedHeaderKeys) {
1336 			out.writeUTF(headerKey);
1337 		}
1338 
1339 		out.writeInt(generations.size());
1340 		for (Generation generation : generations) {
1341 			BundleInfo bundleInfo = generation.getBundleInfo();
1342 			out.writeLong(bundleInfo.getBundleId());
1343 			out.writeUTF(bundleInfo.getLocation());
1344 			out.writeLong(bundleInfo.getNextGenerationId());
1345 			out.writeLong(generation.getGenerationId());
1346 			out.writeBoolean(generation.isDirectory());
1347 			out.writeBoolean(generation.isReference());
1348 			out.writeBoolean(generation.hasPackageInfo());
1349 			if (bundleInfo.getBundleId() == 0) {
1350 				// just write empty string for system bundle content in this case
1351 				out.writeUTF(""); //$NON-NLS-1$
1352 			} else {
1353 				if (generation.isReference()) {
1354 					// make reference installs relative to the install path
1355 					out.writeUTF(new FilePath(installPath).makeRelative(new FilePath(generation.getContent().getAbsolutePath())));
1356 				} else {
1357 					// make normal installs relative to the storage area
1358 					out.writeUTF(Storage.getBundleFilePath(bundleInfo.getBundleId(), generation.getGenerationId()));
1359 				}
1360 			}
1361 			out.writeLong(generation.getLastModified());
1362 
1363 			Dictionary<String, String> headers = generation.getHeaders();
1364 			for (String headerKey : cachedHeaderKeys) {
1365 				String value = headers.get(headerKey);
1366 				if (value != null) {
1367 					out.writeUTF(value);
1368 				} else {
1369 					out.writeUTF(NUL);
1370 				}
1371 			}
1372 
1373 			out.writeBoolean(generation.isMRJar());
1374 		}
1375 
1376 		saveStorageHookData(out, generations);
1377 	}
1378 
1379 	private void saveLongString(DataOutputStream out, String value) throws IOException {
1380 		if (value == null) {
1381 			out.writeInt(0);
1382 		} else {
1383 			// don't use out.writeUTF because it has a hard string limit
1384 			byte[] data = value.getBytes(StandardCharsets.UTF_8);
1385 			out.writeInt(data.length);
1386 			out.write(data);
1387 		}
1388 	}
1389 
1390 	private String readLongString(DataInputStream in) throws IOException {
1391 		int length = in.readInt();
1392 		byte[] data = new byte[length];
1393 		in.readFully(data);
1394 		return new String(data, StandardCharsets.UTF_8);
1395 	}
1396 
1397 	private void saveStorageHookData(DataOutputStream out, List<Generation> generations) throws IOException {
1398 		List<StorageHookFactory<?, ?, ?>> factories = getConfiguration().getHookRegistry().getStorageHookFactories();
1399 		out.writeInt(factories.size());
1400 		for (StorageHookFactory<?, ?, ?> factory : factories) {
1401 			out.writeUTF(factory.getKey());
1402 			out.writeInt(factory.getStorageVersion());
1403 
1404 			// create a temporary in memory stream so we can figure out the length
1405 			ByteArrayOutputStream tempBytes = new ByteArrayOutputStream();
1406 			DataOutputStream temp = new DataOutputStream(tempBytes);
1407 			try {
1408 				Object saveContext = factory.createSaveContext();
1409 				for (Generation generation : generations) {
1410 					if (generation.getBundleInfo().getBundleId() == 0) {
1411 						continue; // ignore system bundle
1412 					}
1413 					@SuppressWarnings({"rawtypes", "unchecked"})
1414 					StorageHook<Object, Object> hook = generation.getStorageHook((Class) factory.getClass());
1415 					if (hook != null) {
1416 						hook.save(saveContext, temp);
1417 					}
1418 				}
1419 			} finally {
1420 				temp.close();
1421 			}
1422 			out.writeInt(tempBytes.size());
1423 			out.write(tempBytes.toByteArray());
1424 		}
1425 	}
1426 
1427 	private Map<Long, Generation> loadGenerations(DataInputStream in, String[] cachedInfo) throws IOException {
1428 		if (in == null) {
1429 			return new HashMap<>(0);
1430 		}
1431 		int version = in.readInt();
1432 		if (version > VERSION || version < LOWEST_VERSION_SUPPORTED) {
1433 			throw new IllegalArgumentException("Found persistent version \"" + version + "\" expecting \"" + VERSION + "\""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
1434 		}
1435 		Version savedRuntimeVersion = (version >= MR_JAR_VERSION) ? Version.parseVersion(in.readUTF()) : null;
1436 		if (savedRuntimeVersion == null || !savedRuntimeVersion.equals(runtimeVersion)) {
1437 			refreshMRBundles.set(true);
1438 		}
1439 
1440 		cachedInfo[0] = (version >= CACHED_SYSTEM_CAPS_VERION) ? in.readUTF() : null;
1441 		cachedInfo[1] = (version >= CACHED_SYSTEM_CAPS_VERION) ? readLongString(in) : null;
1442 		cachedInfo[2] = (version >= CACHED_SYSTEM_CAPS_VERION) ? readLongString(in) : null;
1443 
1444 		int numCachedHeaders = in.readInt();
1445 		List<String> storedCachedHeaderKeys = new ArrayList<>(numCachedHeaders);
1446 		for (int i = 0; i < numCachedHeaders; i++) {
1447 			storedCachedHeaderKeys.add(ObjectPool.intern(in.readUTF()));
1448 		}
1449 
1450 		int numInfos = in.readInt();
1451 		Map<Long, Generation> result = new HashMap<>(numInfos);
1452 		List<Generation> generations = new ArrayList<>(numInfos);
1453 		for (int i = 0; i < numInfos; i++) {
1454 			long infoId = in.readLong();
1455 			String infoLocation = ObjectPool.intern(in.readUTF());
1456 			long nextGenId = in.readLong();
1457 			long generationId = in.readLong();
1458 			boolean isDirectory = in.readBoolean();
1459 			boolean isReference = in.readBoolean();
1460 			boolean hasPackageInfo = in.readBoolean();
1461 			String contentPath = in.readUTF();
1462 			long lastModified = in.readLong();
1463 
1464 			Map<String, String> cachedHeaders = new HashMap<>(storedCachedHeaderKeys.size());
1465 			for (String headerKey : storedCachedHeaderKeys) {
1466 				String value = in.readUTF();
1467 				if (NUL.equals(value)) {
1468 					value = null;
1469 				} else {
1470 					value = ObjectPool.intern(value);
1471 				}
1472 				cachedHeaders.put(headerKey, value);
1473 			}
1474 			boolean isMRJar = (version >= MR_JAR_VERSION) ? in.readBoolean() : false;
1475 
1476 			File content;
1477 			if (infoId == 0) {
1478 				content = getSystemContent();
1479 				isDirectory = content != null ? content.isDirectory() : false;
1480 				// Note that we do not do any checking for absolute paths with
1481 				// the system bundle.  We always take the content as discovered
1482 				// by getSystemContent()
1483 			} else {
1484 				content = new File(contentPath);
1485 				if (!content.isAbsolute()) {
1486 					// make sure it has the absolute location instead
1487 					if (isReference) {
1488 						// reference installs are relative to the installPath
1489 						content = new File(installPath, contentPath);
1490 					} else {
1491 						// normal installs are relative to the storage area
1492 						content = getFile(contentPath, true);
1493 					}
1494 				}
1495 			}
1496 
1497 			BundleInfo info = new BundleInfo(this, infoId, infoLocation, nextGenId);
1498 			Generation generation = info.restoreGeneration(generationId, content, isDirectory, isReference, hasPackageInfo, cachedHeaders, lastModified, isMRJar);
1499 			result.put(infoId, generation);
1500 			generations.add(generation);
1501 		}
1502 
1503 		loadStorageHookData(generations, in);
1504 		return result;
1505 	}
1506 
1507 	private void loadStorageHookData(List<Generation> generations, DataInputStream in) throws IOException {
1508 		List<StorageHookFactory<?, ?, ?>> factories = new ArrayList<>(getConfiguration().getHookRegistry().getStorageHookFactories());
1509 		Map<Generation, List<StorageHook<?, ?>>> hookMap = new HashMap<>();
1510 		int numFactories = in.readInt();
1511 		for (int i = 0; i < numFactories; i++) {
1512 			String factoryName = in.readUTF();
1513 			int version = in.readInt();
1514 			StorageHookFactory<Object, Object, StorageHook<Object, Object>> factory = null;
1515 			for (Iterator<StorageHookFactory<?, ?, ?>> iFactories = factories.iterator(); iFactories.hasNext();) {
1516 				@SuppressWarnings("unchecked")
1517 				StorageHookFactory<Object, Object, StorageHook<Object, Object>> next = (StorageHookFactory<Object, Object, StorageHook<Object, Object>>) iFactories.next();
1518 				if (next.getKey().equals(factoryName)) {
1519 					factory = next;
1520 					iFactories.remove();
1521 					break;
1522 				}
1523 			}
1524 			int dataSize = in.readInt();
1525 			byte[] bytes = new byte[dataSize];
1526 			in.readFully(bytes);
1527 			if (factory != null) {
1528 				DataInputStream temp = new DataInputStream(new ByteArrayInputStream(bytes));
1529 				try {
1530 					if (factory.isCompatibleWith(version)) {
1531 						Object loadContext = factory.createLoadContext(version);
1532 						for (Generation generation : generations) {
1533 							if (generation.getBundleInfo().getBundleId() == 0) {
1534 								continue; // ignore system bundle
1535 							}
1536 							StorageHook<Object, Object> hook = factory.createStorageHookAndValidateFactoryClass(generation);
1537 							if (hook != null) {
1538 								hook.load(loadContext, temp);
1539 								getHooks(hookMap, generation).add(hook);
1540 							}
1541 						}
1542 					} else {
1543 						// recover by reinitializing the hook
1544 						for (Generation generation : generations) {
1545 							if (generation.getBundleInfo().getBundleId() == 0) {
1546 								continue; // ignore system bundle
1547 							}
1548 							StorageHook<Object, Object> hook = factory.createStorageHookAndValidateFactoryClass(generation);
1549 							if (hook != null) {
1550 								hook.initialize(generation.getHeaders());
1551 								getHooks(hookMap, generation).add(hook);
1552 							}
1553 						}
1554 					}
1555 				} catch (BundleException e) {
1556 					throw new IOException(e);
1557 				} finally {
1558 					temp.close();
1559 				}
1560 			}
1561 		}
1562 		// now we need to recover for any hooks that are left
1563 		for (Iterator<StorageHookFactory<?, ?, ?>> iFactories = factories.iterator(); iFactories.hasNext();) {
1564 			@SuppressWarnings("unchecked")
1565 			StorageHookFactory<Object, Object, StorageHook<Object, Object>> next = (StorageHookFactory<Object, Object, StorageHook<Object, Object>>) iFactories.next();
1566 			// recover by reinitializing the hook
1567 			for (Generation generation : generations) {
1568 				if (generation.getBundleInfo().getBundleId() == 0) {
1569 					continue; // ignore system bundle
1570 				}
1571 				StorageHook<Object, Object> hook = next.createStorageHookAndValidateFactoryClass(generation);
1572 				if (hook != null) {
1573 					try {
1574 						hook.initialize(generation.getHeaders());
1575 						getHooks(hookMap, generation).add(hook);
1576 					} catch (BundleException e) {
1577 						throw new IOException(e);
1578 					}
1579 				}
1580 			}
1581 		}
1582 		// now set the hooks to the generations
1583 		for (Generation generation : generations) {
1584 			generation.setStorageHooks(Collections.unmodifiableList(getHooks(hookMap, generation)), false);
1585 		}
1586 	}
1587 
1588 	private static List<StorageHook<?, ?>> getHooks(Map<Generation, List<StorageHook<?, ?>>> hookMap, Generation generation) {
1589 		List<StorageHook<?, ?>> result = hookMap.get(generation);
1590 		if (result == null) {
1591 			result = new ArrayList<>();
1592 			hookMap.put(generation, result);
1593 		}
1594 		return result;
1595 	}
1596 
1597 	private File getSystemContent() {
1598 		String frameworkValue = equinoxContainer.getConfiguration().getConfiguration(EquinoxConfiguration.PROP_FRAMEWORK);
1599 		if (frameworkValue == null || !frameworkValue.startsWith("file:")) { //$NON-NLS-1$
1600 			return null;
1601 		}
1602 		// TODO assumes the location is a file URL
1603 		File result = new File(frameworkValue.substring(5)).getAbsoluteFile();
1604 		if (!result.exists()) {
1605 			throw new IllegalStateException("Configured framework location does not exist: " + result.getAbsolutePath()); //$NON-NLS-1$
1606 		}
1607 		return result;
1608 	}
1609 
1610 	@SuppressWarnings("deprecation")
1611 	private void loadVMProfile(Generation systemGeneration) {
1612 		EquinoxConfiguration equinoxConfig = equinoxContainer.getConfiguration();
1613 		Properties profileProps = findVMProfile(systemGeneration);
1614 		String systemExports = equinoxConfig.getConfiguration(Constants.FRAMEWORK_SYSTEMPACKAGES);
1615 		// set the system exports property using the vm profile; only if the property is not already set
1616 		if (systemExports == null) {
1617 			systemExports = profileProps.getProperty(Constants.FRAMEWORK_SYSTEMPACKAGES);
1618 			if (systemExports != null)
1619 				equinoxConfig.setConfiguration(Constants.FRAMEWORK_SYSTEMPACKAGES, systemExports);
1620 		}
1621 
1622 		// set the org.osgi.framework.bootdelegation property according to the java profile
1623 		String type = equinoxConfig.getConfiguration(EquinoxConfiguration.PROP_OSGI_JAVA_PROFILE_BOOTDELEGATION); // a null value means ignore
1624 		String profileBootDelegation = profileProps.getProperty(Constants.FRAMEWORK_BOOTDELEGATION);
1625 		if (EquinoxConfiguration.PROP_OSGI_BOOTDELEGATION_OVERRIDE.equals(type)) {
1626 			if (profileBootDelegation == null)
1627 				equinoxConfig.clearConfiguration(Constants.FRAMEWORK_BOOTDELEGATION); // override with a null value
1628 			else
1629 				equinoxConfig.setConfiguration(Constants.FRAMEWORK_BOOTDELEGATION, profileBootDelegation); // override with the profile value
1630 		} else if (EquinoxConfiguration.PROP_OSGI_BOOTDELEGATION_NONE.equals(type))
1631 			equinoxConfig.clearConfiguration(Constants.FRAMEWORK_BOOTDELEGATION); // remove the bootdelegation property in case it was set
1632 		// set the org.osgi.framework.executionenvironment property according to the java profile
1633 		if (equinoxConfig.getConfiguration(Constants.FRAMEWORK_EXECUTIONENVIRONMENT) == null) {
1634 			// get the ee from the java profile; if no ee is defined then try the java profile name
1635 			String ee = profileProps.getProperty(Constants.FRAMEWORK_EXECUTIONENVIRONMENT, profileProps.getProperty(EquinoxConfiguration.PROP_OSGI_JAVA_PROFILE_NAME));
1636 			if (ee != null)
1637 				equinoxConfig.setConfiguration(Constants.FRAMEWORK_EXECUTIONENVIRONMENT, ee);
1638 		}
1639 		// set the org.osgi.framework.system.capabilities property according to the java profile
1640 		if (equinoxConfig.getConfiguration(Constants.FRAMEWORK_SYSTEMCAPABILITIES) == null) {
1641 			String systemCapabilities = profileProps.getProperty(Constants.FRAMEWORK_SYSTEMCAPABILITIES);
1642 			if (systemCapabilities != null)
1643 				equinoxConfig.setConfiguration(Constants.FRAMEWORK_SYSTEMCAPABILITIES, systemCapabilities);
1644 		}
1645 	}
1646 
1647 	private Properties findVMProfile(Generation systemGeneration) {
1648 		Properties result = readConfiguredJavaProfile(systemGeneration);
1649 		String vmProfile = null;
1650 		try {
1651 			if (result != null) {
1652 				return result;
1653 			}
1654 
1655 			if (Version.valueOf("9").compareTo(runtimeVersion) <= 0) { //$NON-NLS-1$
1656 				result = calculateVMProfile(runtimeVersion);
1657 				if (result != null) {
1658 					return result;
1659 				}
1660 				// could not calculate; fall back to reading profile files
1661 			}
1662 
1663 			String embeddedProfileName = "-"; //$NON-NLS-1$
1664 			// If javaSE 1.8 then check for release file for profile name.
1665 			if (runtimeVersion != null && Version.valueOf("1.8").compareTo(runtimeVersion) <= 0) { //$NON-NLS-1$
1666 				String javaHome = System.getProperty("java.home"); //$NON-NLS-1$
1667 				if (javaHome != null) {
1668 					File release = new File(javaHome, "release"); //$NON-NLS-1$
1669 					if (release.exists()) {
1670 						Properties releaseProps = new Properties();
1671 						try (InputStream releaseStream = new FileInputStream(release)) {
1672 							releaseProps.load(releaseStream);
1673 							String releaseName = releaseProps.getProperty("JAVA_PROFILE"); //$NON-NLS-1$
1674 							if (releaseName != null) {
1675 								// make sure to remove extra quotes
1676 								releaseName = releaseName.replaceAll("^\\s*\"?|\"?\\s*$", ""); //$NON-NLS-1$ //$NON-NLS-2$
1677 								embeddedProfileName = "_" + releaseName + "-"; //$NON-NLS-1$ //$NON-NLS-2$
1678 							}
1679 						} catch (IOException e) {
1680 							// ignore
1681 						}
1682 					}
1683 				}
1684 			}
1685 
1686 			result = new Properties();
1687 			vmProfile = JAVASE + embeddedProfileName + javaSpecVersion;
1688 			InputStream profileIn = null;
1689 			if (vmProfile != null) {
1690 				// look for a profile in the system bundle based on the vm profile
1691 				String javaProfile = vmProfile + PROFILE_EXT;
1692 				profileIn = findInSystemBundle(systemGeneration, javaProfile);
1693 				if (profileIn == null)
1694 					profileIn = getNextBestProfile(systemGeneration, JAVASE, runtimeVersion, embeddedProfileName);
1695 			}
1696 			if (profileIn == null)
1697 				// the profile url is still null then use the min profile the framework can use
1698 				profileIn = findInSystemBundle(systemGeneration, "JavaSE-1.7.profile"); //$NON-NLS-1$
1699 			if (profileIn != null) {
1700 				try {
1701 					result.load(new BufferedInputStream(profileIn));
1702 				} catch (IOException e) {
1703 					// TODO consider logging ...
1704 				} finally {
1705 					try {
1706 						profileIn.close();
1707 					} catch (IOException ee) {
1708 						// do nothing
1709 					}
1710 				}
1711 			}
1712 		} finally {
1713 			// set the profile name if it does not provide one
1714 			if (result != null && result.getProperty(EquinoxConfiguration.PROP_OSGI_JAVA_PROFILE_NAME) == null) {
1715 				if (vmProfile != null) {
1716 					result.put(EquinoxConfiguration.PROP_OSGI_JAVA_PROFILE_NAME, vmProfile.replace('_', '/'));
1717 				} else {
1718 					// last resort; default to the absolute minimum profile name for the framework
1719 					result.put(EquinoxConfiguration.PROP_OSGI_JAVA_PROFILE_NAME, "JavaSE-1.7"); //$NON-NLS-1$
1720 				}
1721 			}
1722 		}
1723 		return result;
1724 	}
1725 
1726 	private Properties readConfiguredJavaProfile(Generation systemGeneration) {
1727 		// check for the java profile property for a url
1728 		String propJavaProfile = equinoxContainer.getConfiguration().getConfiguration(EquinoxConfiguration.PROP_OSGI_JAVA_PROFILE);
1729 		if (propJavaProfile != null) {
1730 			InputStream profileIn = null;
1731 			try {
1732 				// we assume a URL
1733 				profileIn = new URL(propJavaProfile).openStream();
1734 			} catch (IOException e) {
1735 				// try using a relative path in the system bundle
1736 				profileIn = findInSystemBundle(systemGeneration, propJavaProfile);
1737 			}
1738 			if (profileIn != null) {
1739 				Properties result = new Properties();
1740 				try {
1741 					result.load(new BufferedInputStream(profileIn));
1742 				} catch (IOException e) {
1743 					// consider logging
1744 				} finally {
1745 					try {
1746 						profileIn.close();
1747 					} catch (IOException e) {
1748 						// nothing to do
1749 					}
1750 				}
1751 				return result;
1752 			}
1753 		}
1754 		return null;
1755 	}
1756 
1757 	@SuppressWarnings("deprecation")
1758 	private Properties calculateVMProfile(Version javaVersion) {
1759 		String systemPackages = calculateVMPackages();
1760 		if (systemPackages == null) {
1761 			return null;
1762 		}
1763 		String executionEnvs = calculateVMExecutionEnvs(javaVersion);
1764 		String eeCapabilities = calculateEECapabilities(javaVersion);
1765 
1766 		Properties result = new Properties();
1767 		result.put(Constants.FRAMEWORK_SYSTEMPACKAGES, systemPackages);
1768 		result.put(Constants.FRAMEWORK_EXECUTIONENVIRONMENT, executionEnvs);
1769 		result.put(Constants.FRAMEWORK_SYSTEMCAPABILITIES, eeCapabilities);
1770 		return result;
1771 	}
1772 
1773 	private String calculateVMExecutionEnvs(Version javaVersion) {
1774 		StringBuilder result = new StringBuilder("OSGi/Minimum-1.0, OSGi/Minimum-1.1, OSGi/Minimum-1.2, JavaSE/compact1-1.8, JavaSE/compact2-1.8, JavaSE/compact3-1.8, JRE-1.1, J2SE-1.2, J2SE-1.3, J2SE-1.4, J2SE-1.5, JavaSE-1.6, JavaSE-1.7, JavaSE-1.8"); //$NON-NLS-1$
1775 		Version v = new Version(9, 0, 0);
1776 		while (v.compareTo(javaVersion) <= 0) {
1777 			result.append(',').append(' ').append(JAVASE).append('-').append(v.getMajor());
1778 			if (v.getMinor() > 0) {
1779 				result.append('.').append(v.getMinor());
1780 			}
1781 			if (v.getMajor() == javaVersion.getMajor()) {
1782 				v = new Version(v.getMajor(), v.getMinor() + 1, 0);
1783 			} else {
1784 				v = new Version(v.getMajor() + 1, 0, 0);
1785 			}
1786 		}
1787 		return result.toString();
1788 	}
1789 
1790 	private String calculateEECapabilities(Version javaVersion) {
1791 		Version v = new Version(9, 0, 0);
1792 		StringBuilder versionsBulder = new StringBuilder();
1793 		while (v.compareTo(javaVersion) <= 0) {
1794 			versionsBulder.append(',').append(' ').append(v.getMajor()).append('.').append(v.getMinor());
1795 			if (v.getMajor() == javaVersion.getMajor()) {
1796 				v = new Version(v.getMajor(), v.getMinor() + 1, 0);
1797 			} else {
1798 				v = new Version(v.getMajor() + 1, 0, 0);
1799 			}
1800 		}
1801 		String versionsList = versionsBulder.toString();
1802 
1803 		StringBuilder result = new StringBuilder("osgi.ee; osgi.ee=\"OSGi/Minimum\"; version:List<Version>=\"1.0, 1.1, 1.2\", osgi.ee; osgi.ee=\"JRE\"; version:List<Version>=\"1.0, 1.1\", osgi.ee; osgi.ee=\"JavaSE\"; version:List<Version>=\"1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8"); //$NON-NLS-1$
1804 		result.append(versionsList).append("\""); //$NON-NLS-1$
1805 		result.append(",osgi.ee; osgi.ee=\"JavaSE/compact1\"; version:List<Version>=\"1.8"); //$NON-NLS-1$
1806 		result.append(versionsList).append("\""); //$NON-NLS-1$
1807 		result.append(",osgi.ee; osgi.ee=\"JavaSE/compact2\"; version:List<Version>=\"1.8"); //$NON-NLS-1$
1808 		result.append(versionsList).append("\""); //$NON-NLS-1$
1809 		result.append(",osgi.ee; osgi.ee=\"JavaSE/compact3\"; version:List<Version>=\"1.8"); //$NON-NLS-1$
1810 		result.append(versionsList).append("\""); //$NON-NLS-1$
1811 
1812 		return result.toString();
1813 	}
1814 
1815 	@SuppressWarnings("unchecked")
1816 	private String calculateVMPackages() {
1817 		try {
1818 			List<String> packages = new ArrayList<>();
1819 			Class<?> moduleLayerClass = Class.forName("java.lang.ModuleLayer"); //$NON-NLS-1$
1820 			Method boot = moduleLayerClass.getMethod("boot"); //$NON-NLS-1$
1821 			Method modules = moduleLayerClass.getMethod("modules"); //$NON-NLS-1$
1822 			Class<?> moduleClass = Class.forName("java.lang.Module"); //$NON-NLS-1$
1823 			Method getDescriptor = moduleClass.getMethod("getDescriptor"); //$NON-NLS-1$
1824 			Class<?> moduleDescriptorClass = Class.forName("java.lang.module.ModuleDescriptor"); //$NON-NLS-1$
1825 			Method exports = moduleDescriptorClass.getMethod("exports"); //$NON-NLS-1$
1826 			Method isAutomatic = moduleDescriptorClass.getMethod("isAutomatic"); //$NON-NLS-1$
1827 			Method packagesMethod = moduleDescriptorClass.getMethod("packages"); //$NON-NLS-1$
1828 			Class<?> exportsClass = Class.forName("java.lang.module.ModuleDescriptor$Exports"); //$NON-NLS-1$
1829 			Method isQualified = exportsClass.getMethod("isQualified"); //$NON-NLS-1$
1830 			Method source = exportsClass.getMethod("source"); //$NON-NLS-1$
1831 
1832 			Object bootLayer = boot.invoke(null);
1833 			Set<?> bootModules = (Set<?>) modules.invoke(bootLayer);
1834 			for (Object m : bootModules) {
1835 				Object descriptor = getDescriptor.invoke(m);
1836 				if ((Boolean) isAutomatic.invoke(descriptor)) {
1837 					/*
1838 					 * Automatic modules are supposed to export all their packages.
1839 					 * However, java.lang.module.ModuleDescriptor::exports returns an empty set for them.
1840 					 * Add all their packages (as returned by java.lang.module.ModuleDescriptor::packages)
1841 					 * to the list of VM supplied packages.
1842 					 */
1843 					packages.addAll((Set<String>) packagesMethod.invoke(descriptor));
1844 				} else {
1845 					for (Object export : (Set<?>) exports.invoke(descriptor)) {
1846 						String pkg = (String) source.invoke(export);
1847 						if (!((Boolean) isQualified.invoke(export))) {
1848 							packages.add(pkg);
1849 						}
1850 					}
1851 				}
1852 			}
1853 			Collections.sort(packages);
1854 			StringBuilder result = new StringBuilder();
1855 			for (String pkg : packages) {
1856 				if (result.length() != 0) {
1857 					result.append(',').append(' ');
1858 				}
1859 				result.append(pkg);
1860 			}
1861 			return result.toString();
1862 		} catch (Exception e) {
1863 			equinoxContainer.getLogServices().log(EquinoxContainer.NAME, FrameworkLogEntry.ERROR, "Error determining system packages.", e); //$NON-NLS-1$
1864 			return null;
1865 		}
1866 	}
1867 
1868 	private InputStream getNextBestProfile(Generation systemGeneration, String javaEdition, Version javaVersion, String embeddedProfileName) {
1869 		if (javaVersion == null || javaEdition != JAVASE)
1870 			return null; // we cannot automatically choose the next best profile unless this is a JavaSE vm
1871 		InputStream bestProfile = findNextBestProfile(systemGeneration, javaEdition, javaVersion, embeddedProfileName);
1872 		if (bestProfile == null && !"-".equals(embeddedProfileName)) { //$NON-NLS-1$
1873 			// Just use the base javaEdition name without the profile name as backup
1874 			return getNextBestProfile(systemGeneration, javaEdition, javaVersion, "-"); //$NON-NLS-1$
1875 		}
1876 		return bestProfile;
1877 	}
1878 
1879 	private InputStream findNextBestProfile(Generation systemGeneration, String javaEdition, Version javaVersion, String embeddedProfileName) {
1880 		InputStream result = null;
1881 		int major = javaVersion.getMajor();
1882 		int minor = javaVersion.getMinor();
1883 		do {
1884 			// If minor is zero then it is not included in the name
1885 			String profileResourceName = javaEdition + embeddedProfileName + major + ((minor > 0) ? "." + minor : "") + PROFILE_EXT; //$NON-NLS-1$ //$NON-NLS-2$
1886 			result = findInSystemBundle(systemGeneration, profileResourceName);
1887 			if (minor > 0) {
1888 				minor -= 1;
1889 			} else if (major > 9) {
1890 				major -= 1;
1891 			} else if (major <= 9 && major > 1) {
1892 				minor = 8;
1893 				major = 1;
1894 			} else {
1895 				// we have reached the end of our search; return the existing result;
1896 				return result;
1897 			}
1898 		} while (result == null && minor >= 0);
1899 		return result;
1900 	}
1901 
1902 	private InputStream findInSystemBundle(Generation systemGeneration, String entry) {
1903 		BundleFile systemContent = systemGeneration.getBundleFile();
1904 		BundleEntry systemEntry = systemContent != null ? systemContent.getEntry(entry) : null;
1905 		InputStream result = null;
1906 		if (systemEntry != null) {
1907 			try {
1908 				result = systemEntry.getInputStream();
1909 			} catch (IOException e) {
1910 				// Do nothing
1911 			}
1912 		}
1913 		if (result == null) {
1914 			// Check the ClassLoader in case we're launched off the Java boot classpath
1915 			ClassLoader loader = getClass().getClassLoader();
1916 			result = loader == null ? ClassLoader.getSystemResourceAsStream(entry) : loader.getResourceAsStream(entry);
1917 		}
1918 		return result;
1919 	}
1920 
1921 	public static Enumeration<URL> findEntries(List<Generation> generations, String path, String filePattern, int options) {
1922 		List<BundleFile> bundleFiles = new ArrayList<>(generations.size());
1923 		for (Generation generation : generations)
1924 			bundleFiles.add(generation.getBundleFile());
1925 		// search all the bundle files
1926 		List<String> pathList = listEntryPaths(bundleFiles, path, filePattern, options);
1927 		// return null if no entries found
1928 		if (pathList.size() == 0)
1929 			return null;
1930 		// create an enumeration to enumerate the pathList
1931 		final String[] pathArray = pathList.toArray(new String[pathList.size()]);
1932 		final Generation[] generationArray = generations.toArray(new Generation[generations.size()]);
1933 		return new Enumeration<URL>() {
1934 			private int curPathIndex = 0;
1935 			private int curDataIndex = 0;
1936 			private URL nextElement = null;
1937 
1938 			@Override
1939 			public boolean hasMoreElements() {
1940 				if (nextElement != null)
1941 					return true;
1942 				getNextElement();
1943 				return nextElement != null;
1944 			}
1945 
1946 			@Override
1947 			public URL nextElement() {
1948 				if (!hasMoreElements())
1949 					throw new NoSuchElementException();
1950 				URL result = nextElement;
1951 				// force the next element search
1952 				getNextElement();
1953 				return result;
1954 			}
1955 
1956 			private void getNextElement() {
1957 				nextElement = null;
1958 				if (curPathIndex >= pathArray.length)
1959 					// reached the end of the pathArray; no more elements
1960 					return;
1961 				while (nextElement == null && curPathIndex < pathArray.length) {
1962 					String curPath = pathArray[curPathIndex];
1963 					// search the generation until we have searched them all
1964 					while (nextElement == null && curDataIndex < generationArray.length)
1965 						nextElement = generationArray[curDataIndex++].getEntry(curPath);
1966 					// we have searched all datas then advance to the next path
1967 					if (curDataIndex >= generationArray.length) {
1968 						curPathIndex++;
1969 						curDataIndex = 0;
1970 					}
1971 				}
1972 			}
1973 		};
1974 	}
1975 
1976 	/**
1977 	 * Returns the names of resources available from a list of bundle files.
1978 	 * No duplicate resource names are returned, each name is unique.
1979 	 * @param bundleFiles the list of bundle files to search in
1980 	 * @param path The path name in which to look.
1981 	 * @param filePattern The file name pattern for selecting resource names in
1982 	 *        the specified path.
1983 	 * @param options The options for listing resource names.
1984 	 * @return a list of resource names.  If no resources are found then
1985 	 * the empty list is returned.
1986 	 * @see BundleWiring#listResources(String, String, int)
1987 	 */
1988 	public static List<String> listEntryPaths(List<BundleFile> bundleFiles, String path, String filePattern, int options) {
1989 		// Use LinkedHashSet for optimized performance of contains() plus
1990 		// ordering guarantees.
1991 		LinkedHashSet<String> pathList = new LinkedHashSet<>();
1992 		Filter patternFilter = null;
1993 		Hashtable<String, String> patternProps = null;
1994 		if (filePattern != null) {
1995 			// Optimization: If the file pattern does not include a wildcard  or escape char then it must represent a single file.
1996 			// Avoid pattern matching and use BundleFile.getEntry() if recursion was not requested.
1997 			if ((options & BundleWiring.FINDENTRIES_RECURSE) == 0 && filePattern.indexOf('*') == -1 && filePattern.indexOf('\\') == -1) {
1998 				if (path.length() == 0)
1999 					path = filePattern;
2000 				else
2001 					path += path.charAt(path.length() - 1) == '/' ? filePattern : '/' + filePattern;
2002 				for (BundleFile bundleFile : bundleFiles) {
2003 					if (bundleFile.getEntry(path) != null && !pathList.contains(path))
2004 						pathList.add(path);
2005 				}
2006 				return new ArrayList<>(pathList);
2007 			}
2008 			// For when the file pattern includes a wildcard.
2009 			try {
2010 				// create a file pattern filter with 'filename' as the key
2011 				patternFilter = FilterImpl.newInstance("(filename=" + sanitizeFilterInput(filePattern) + ")"); //$NON-NLS-1$ //$NON-NLS-2$
2012 				// create a single hashtable to be shared during the recursive search
2013 				patternProps = new Hashtable<>(2);
2014 			} catch (InvalidSyntaxException e) {
2015 				// TODO something unexpected happened; log error and return nothing
2016 				//				Bundle b = context == null ? null : context.getBundle();
2017 				//				eventPublisher.publishFrameworkEvent(FrameworkEvent.ERROR, b, e);
2018 				return new ArrayList<>(pathList);
2019 			}
2020 		}
2021 		// find the entry paths for the datas
2022 		for (BundleFile bundleFile : bundleFiles) {
2023 			listEntryPaths(bundleFile, path, patternFilter, patternProps, options, pathList);
2024 		}
2025 		return new ArrayList<>(pathList);
2026 	}
2027 
2028 	public static String sanitizeFilterInput(String filePattern) throws InvalidSyntaxException {
2029 		StringBuilder buffer = null;
2030 		boolean foundEscape = false;
2031 		for (int i = 0; i < filePattern.length(); i++) {
2032 			char c = filePattern.charAt(i);
2033 			switch (c) {
2034 				case '\\' :
2035 					// we either used the escape found or found a new escape.
2036 					foundEscape = foundEscape ? false : true;
2037 					if (buffer != null)
2038 						buffer.append(c);
2039 					break;
2040 				case '(' :
2041 				case ')' :
2042 					if (!foundEscape) {
2043 						if (buffer == null) {
2044 							buffer = new StringBuilder(filePattern.length() + 16);
2045 							buffer.append(filePattern.substring(0, i));
2046 						}
2047 						// must escape with '\'
2048 						buffer.append('\\');
2049 					} else {
2050 						foundEscape = false; // used the escape found
2051 					}
2052 					if (buffer != null)
2053 						buffer.append(c);
2054 					break;
2055 				default :
2056 					// if we found an escape it has been used
2057 					foundEscape = false;
2058 					if (buffer != null)
2059 						buffer.append(c);
2060 					break;
2061 			}
2062 		}
2063 		if (foundEscape)
2064 			throw new InvalidSyntaxException("Trailing escape characters must be escaped.", filePattern); //$NON-NLS-1$
2065 		return buffer == null ? filePattern : buffer.toString();
2066 	}
2067 
2068 	// Use LinkedHashSet for optimized performance of contains() plus ordering
2069 	// guarantees.
2070 	private static LinkedHashSet<String> listEntryPaths(BundleFile bundleFile, String path, Filter patternFilter, Hashtable<String, String> patternProps, int options, LinkedHashSet<String> pathList) {
2071 		if (pathList == null)
2072 			pathList = new LinkedHashSet<>();
2073 		Enumeration<String> entryPaths;
2074 		if ((options & BundleWiring.FINDENTRIES_RECURSE) != 0)
2075 			entryPaths = bundleFile.getEntryPaths(path, true);
2076 		else
2077 			entryPaths = bundleFile.getEntryPaths(path);
2078 		if (entryPaths == null)
2079 			return pathList;
2080 		while (entryPaths.hasMoreElements()) {
2081 			String entry = entryPaths.nextElement();
2082 			int lastSlash = entry.lastIndexOf('/');
2083 			if (patternProps != null) {
2084 				int secondToLastSlash = entry.lastIndexOf('/', lastSlash - 1);
2085 				int fileStart;
2086 				int fileEnd = entry.length();
2087 				if (lastSlash < 0)
2088 					fileStart = 0;
2089 				else if (lastSlash != entry.length() - 1)
2090 					fileStart = lastSlash + 1;
2091 				else {
2092 					fileEnd = lastSlash; // leave the lastSlash out
2093 					if (secondToLastSlash < 0)
2094 						fileStart = 0;
2095 					else
2096 						fileStart = secondToLastSlash + 1;
2097 				}
2098 				String fileName = entry.substring(fileStart, fileEnd);
2099 				// set the filename to the current entry
2100 				patternProps.put("filename", fileName); //$NON-NLS-1$
2101 			}
2102 			// prevent duplicates and match on the patternFilter
2103 			if (!pathList.contains(entry) && (patternFilter == null || patternFilter.matchCase(patternProps)))
2104 				pathList.add(entry);
2105 		}
2106 		return pathList;
2107 	}
2108 
2109 	public String copyToTempLibrary(Generation generation, String absolutePath) {
2110 		File libTempDir = new File(childRoot, LIB_TEMP);
2111 		// we assume the absolutePath is a File path
2112 		File realLib = new File(absolutePath);
2113 		String libName = realLib.getName();
2114 		// find a temp dir for the bundle data and the library;
2115 		File bundleTempDir = null;
2116 		File libTempFile = null;
2117 		// We need a somewhat predictable temp dir for the libraries of a given bundle;
2118 		// This is not strictly necessary but it does help scenarios where one native library loads another native library without using java.
2119 		// On some OSes this causes issues because the second library is cannot be found.
2120 		// This has been worked around by the bundles loading the libraries in a particular order (and setting some LIB_PATH env).
2121 		// The one catch is that the libraries need to be in the same directory and they must use their original lib names.
2122 		//
2123 		// This bit of code attempts to do that by using the bundle ID as an ID for the temp dir along with an incrementing ID
2124 		// in cases where the temp dir may already exist.
2125 		Long bundleID = new Long(generation.getBundleInfo().getBundleId());
2126 		for (int i = 0; i < Integer.MAX_VALUE; i++) {
2127 			bundleTempDir = new File(libTempDir, bundleID.toString() + "_" + Integer.valueOf(i).toString()); //$NON-NLS-1$
2128 			libTempFile = new File(bundleTempDir, libName);
2129 			if (bundleTempDir.exists()) {
2130 				if (libTempFile.exists())
2131 					continue; // to to next temp file
2132 				break;
2133 			}
2134 			break;
2135 		}
2136 		if (!bundleTempDir.isDirectory()) {
2137 			bundleTempDir.mkdirs();
2138 			bundleTempDir.deleteOnExit();
2139 			// This is just a safeguard incase the VM is terminated unexpectantly, it also looks like deleteOnExit cannot really work because
2140 			// the VM likely will still have a lock on the lib file at the time of VM exit.
2141 			File deleteFlag = new File(libTempDir, DELETE_FLAG);
2142 			if (!deleteFlag.exists()) {
2143 				// need to create a delete flag to force removal the temp libraries
2144 				try {
2145 					FileOutputStream out = new FileOutputStream(deleteFlag);
2146 					out.close();
2147 				} catch (IOException e) {
2148 					// do nothing; that would mean we did not make the temp dir successfully
2149 				}
2150 			}
2151 		}
2152 		// copy the library file
2153 		try {
2154 			InputStream in = new FileInputStream(realLib);
2155 			StorageUtil.readFile(in, libTempFile);
2156 			// set permissions if needed
2157 			setPermissions(libTempFile);
2158 			libTempFile.deleteOnExit(); // this probably will not work because the VM will probably have the lib locked at exit
2159 			// return the temporary path
2160 			return libTempFile.getAbsolutePath();
2161 		} catch (IOException e) {
2162 			equinoxContainer.getLogServices().log(EquinoxContainer.NAME, FrameworkLogEntry.ERROR, e.getMessage(), e);
2163 			return null;
2164 		}
2165 	}
2166 
2167 	public SecurityAdmin getSecurityAdmin() {
2168 		return securityAdmin;
2169 	}
2170 
2171 	protected StorageManager getChildStorageManager() throws IOException {
2172 		String locking = getConfiguration().getConfiguration(LocationHelper.PROP_OSGI_LOCKING, LocationHelper.LOCKING_NIO);
2173 		StorageManager sManager = new StorageManager(childRoot, isReadOnly() ? LocationHelper.LOCKING_NONE : locking, isReadOnly());
2174 		try {
2175 			sManager.open(!isReadOnly());
2176 		} catch (IOException ex) {
2177 			if (getConfiguration().getDebug().DEBUG_STORAGE) {
2178 				Debug.println("Error reading framework.info: " + ex.getMessage()); //$NON-NLS-1$
2179 				Debug.printStackTrace(ex);
2180 			}
2181 			String message = NLS.bind(Msg.ECLIPSE_STARTUP_FILEMANAGER_OPEN_ERROR, ex.getMessage());
2182 			equinoxContainer.getLogServices().log(EquinoxContainer.NAME, FrameworkLogEntry.ERROR, message, ex);
2183 			getConfiguration().setProperty(EclipseStarter.PROP_EXITCODE, "15"); //$NON-NLS-1$
2184 			String errorDialog = "<title>" + Msg.ADAPTOR_STORAGE_INIT_FAILED_TITLE + "</title>" + NLS.bind(Msg.ADAPTOR_STORAGE_INIT_FAILED_MSG, childRoot) + "\n" + ex.getMessage(); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
2185 			getConfiguration().setProperty(EclipseStarter.PROP_EXITDATA, errorDialog);
2186 			throw ex;
2187 		}
2188 		return sManager;
2189 	}
2190 
2191 	private InputStream getInfoInputStream() throws IOException {
2192 		StorageManager storageManager = getChildStorageManager();
2193 		InputStream storageStream = null;
2194 		try {
2195 			storageStream = storageManager.getInputStream(FRAMEWORK_INFO);
2196 		} catch (IOException ex) {
2197 			if (getConfiguration().getDebug().DEBUG_STORAGE) {
2198 				Debug.println("Error reading framework.info: " + ex.getMessage()); //$NON-NLS-1$
2199 				Debug.printStackTrace(ex);
2200 			}
2201 		} finally {
2202 			storageManager.close();
2203 		}
2204 		if (storageStream == null && parentRoot != null) {
2205 			StorageManager parentStorageManager = null;
2206 			try {
2207 				parentStorageManager = new StorageManager(parentRoot, LocationHelper.LOCKING_NONE, true);
2208 				parentStorageManager.open(false);
2209 				storageStream = parentStorageManager.getInputStream(FRAMEWORK_INFO);
2210 			} catch (IOException e1) {
2211 				// That's ok we will regenerate the framework.info
2212 			} finally {
2213 				if (parentStorageManager != null) {
2214 					parentStorageManager.close();
2215 				}
2216 			}
2217 		}
2218 		return storageStream;
2219 	}
2220 }
2221