Skip to main content
aboutsummaryrefslogblamecommitdiffstats
blob: db62f0741a5f8c2546ca4841088f6c2ffcf849fe (plain) (tree)
1
2
3
4
5
6
7
8
9
                                                                                
                                                       


                                                                       
                                                           


                                         
  

                                                       
                                                  

                                                                                 
                                            
 
                    
                                 







                                                

                              
                                                                        
                                                 
                                                        
                                             
                                                            

                                              
                                                         
                                 




                                                    
 





                                                                               
                                                



                                                       


                       
                                         


                          
                                               
 

                                       
                                                                                                                                          

                                                              
                                                                                                   
                                   
                                             
                                   
                                       





                                               
                                    
                     
                                                        
                                         
                                                 

                                                                            








                                                                                                                                                               
                                 
                         


                                                                                        



                 


                                                                    
                                                                       


                                                  
                                                                         


                                     





















                                                                                                                    




                                             
                                                  


                                                  

         
           
                                                                                             




                                                                                                                              
                                                   


















                                                                                    

                                               
                                    
                 










                                                                                                            

         
                                                       
                                       
                                    
                                                                                 

         

                                                               
                                    
                 
                     








                                                                                 
                                                                            



                                                                                                                                 
                                                


                                                                                                     
                                                                                            









                                                                                                                                                                  
                                         
 


                                                      
                                                            

                                                                                                                                                                                                                                                     



                                                                                                                                                                                                                                                     
                         

                                          



                            

                                                
                                     



                                             
 
                                              
                                            
 




                                                       
 











                                                                                    
                         

                                          



                             

                                                  

                                    









                                                                                                                      
 



                                                                  

         
                 

                                                                                
                                    


























                                                                                                                         

                                 


                                                                                           
                 

         
                                                                                                               












                                                                                               
















                                                                                                                                                               
 



                                                
                                               
                         

                                          


                 

                                                                            

         
                                        














                                                                        
                                                 
                                           
                                                         


                 






                                                 
                                               
                                  
         
 






                                            

         









                                                                         
         



























































































                                                                                        
 
/*******************************************************************************
 * Copyright (c) 2005, 2018 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *     Rob Harrop - SpringSource Inc. (bug 253942)
 *******************************************************************************/

package org.eclipse.osgi.storage.bundlefile;

import java.io.File;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.eclipse.osgi.container.ModuleContainerAdaptor.ContainerEvent;
import org.eclipse.osgi.container.ModuleRevision;
import org.eclipse.osgi.framework.log.FrameworkLogEntry;
import org.eclipse.osgi.internal.debug.Debug;
import org.eclipse.osgi.internal.framework.EquinoxContainer;
import org.eclipse.osgi.internal.messages.Msg;
import org.eclipse.osgi.storage.BundleInfo;
import org.eclipse.osgi.storage.Storage.StorageException;
import org.eclipse.osgi.util.NLS;

/**
 * A BundleFile that uses a ZipFile as it base file.
 */
public class ZipBundleFile extends BundleFile {

	// A reentrant lock is used here (instead of intrinsic synchronization)
	// to allow the lock conditional held
	// see lockOpen() and getZipFile()
	private final ReentrantLock openLock = new ReentrantLock();
	private final Condition refCondition = openLock.newCondition();

	private final MRUBundleFileList mruList;

	private final BundleInfo.Generation generation;

	private final Debug debug;
	/**
	 * The zip file
	 */
	private volatile ZipFile zipFile;
	/**
	 * The closed flag
	 */
	private volatile boolean closed = true;

	private int referenceCount = 0;

	public ZipBundleFile(File basefile, BundleInfo.Generation generation, MRUBundleFileList mruList, Debug debug) throws IOException {
		super(basefile);
		if (!BundleFile.secureAction.exists(basefile))
			throw new IOException(NLS.bind(Msg.ADAPTER_FILEEXIST_EXCEPTION, basefile));
		this.debug = debug;
		this.generation = generation;
		this.closed = true;
		this.mruList = mruList;
	}

	/**
	 * Checks if the zip file is open
	 * @return true if the zip file is open
	 */
	private boolean lockOpen() {
		try {
			return getZipFile(true) != null;
		} catch (IOException e) {
			if (generation != null) {
				ModuleRevision r = generation.getRevision();
				if (r != null) {
					ContainerEvent eventType = ContainerEvent.ERROR;
					// If the revision has been removed from the list of revisions then it has been deleted
					// because the bundle has been uninstalled or updated
					if (!r.getRevisions().getModuleRevisions().contains(r)) {
						// instead of filling the log with errors about missing files from 
						// uninstalled/updated bundles just give it an info level
						eventType = ContainerEvent.INFO;
					}
					generation.getBundleInfo().getStorage().getAdaptor().publishContainerEvent(eventType, r.getRevisions().getModule(), e);
				}
			}
			// TODO not sure if throwing a runtime exception is better
			// throw new RuntimeException("Failed to open bundle file.", e);
			return false;
		}
	}

	/**
	 * Returns an open ZipFile for this bundle file.  If an open
	 * ZipFile does not exist then a new one is created and
	 * returned.
	 * @param keepLock true if the open zip lock should be retained
	 * @return an open ZipFile for this bundle
	 * @throws IOException
	 */
	private ZipFile getZipFile(boolean keepLock) throws IOException {
		openLock.lock();
		try {
			if (closed) {
				boolean needBackPressure = mruListAdd();
				if (needBackPressure) {
					// release lock before applying back pressure
					openLock.unlock();
					try {
						mruListApplyBackPressure();
					} finally {
						// get lock back after back pressure
						openLock.lock();
					}
				}
				// check close again after getting open lock again
				if (closed) {
					// always add again if back pressure was applied in case
					// the bundle file got removed while releasing the open lock
					if (needBackPressure) {
						mruListAdd();
					}
					// This can throw an IO exception resulting in closed remaining true on exit
					zipFile = BundleFile.secureAction.getZipFile(this.basefile);
					closed = false;
				}
			} else {
				mruListUse();
			}
			return zipFile;
		} finally {
			if (!keepLock || closed) {
				openLock.unlock();
			}
		}
	}

	/**
	* Returns a ZipEntry for the bundle file. Must be called while holding the open lock.
	* This method does not ensure that the ZipFile is opened. Callers may need to call getZipfile() prior to calling this 
	* method.
	* @param path the path to an entry
	* @return a ZipEntry or null if the entry does not exist
	*/
	private ZipEntry getZipEntry(String path) {
		if (path.length() > 0 && path.charAt(0) == '/')
			path = path.substring(1);
		ZipEntry entry = zipFile.getEntry(path);
		if (entry != null && entry.getSize() == 0 && !entry.isDirectory()) {
			// work around the directory bug see bug 83542
			ZipEntry dirEntry = zipFile.getEntry(path + '/');
			if (dirEntry != null)
				entry = dirEntry;
		}
		return entry;
	}

	/**
	 * Extracts a directory and all sub content to disk
	 * @param dirName the directory name to extract
	 * @return the File used to extract the content to.  A value
	 * of <code>null</code> is returned if the directory to extract does 
	 * not exist or if content extraction is not supported.
	 */
	File extractDirectory(String dirName) {
		if (!lockOpen()) {
			return null;
		}
		try {
			Enumeration<? extends ZipEntry> entries = zipFile.entries();
			while (entries.hasMoreElements()) {
				String entryPath = entries.nextElement().getName();
				if (entryPath.startsWith(dirName) && !entryPath.endsWith("/")) //$NON-NLS-1$
					getFile(entryPath, false);
			}
			return getExtractFile(dirName);
		} finally {
			openLock.unlock();
		}
	}

	private File getExtractFile(String entryName) {
		if (generation == null)
			return null;
		return generation.getExtractFile(".cp", entryName); //$NON-NLS-1$
	}

	public File getFile(String entry, boolean nativeCode) {
		if (!lockOpen()) {
			return null;
		}
		try {
			ZipEntry zipEntry = getZipEntry(entry);
			if (zipEntry == null)
				return null;

			try {
				File nested = getExtractFile(zipEntry.getName());
				if (nested != null) {
					if (nested.exists()) {
						/* the entry is already cached */
						if (debug.DEBUG_BUNDLE_FILE)
							Debug.println("File already present: " + nested.getPath()); //$NON-NLS-1$
						if (nested.isDirectory())
							// must ensure the complete directory is extracted (bug 182585)
							extractDirectory(zipEntry.getName());
					} else {
						if (zipEntry.getName().endsWith("/")) { //$NON-NLS-1$
							nested.mkdirs();
							if (!nested.isDirectory()) {
								if (debug.DEBUG_BUNDLE_FILE)
									Debug.println("Unable to create directory: " + nested.getPath()); //$NON-NLS-1$
								throw new IOException(NLS.bind(Msg.ADAPTOR_DIRECTORY_CREATE_EXCEPTION, nested.getAbsolutePath()));
							}
							extractDirectory(zipEntry.getName());
						} else {
							InputStream in = zipFile.getInputStream(zipEntry);
							if (in == null)
								return null;
							generation.storeContent(nested, in, nativeCode);
						}
					}

					return nested;
				}
			} catch (IOException e) {
				if (debug.DEBUG_BUNDLE_FILE)
					Debug.printStackTrace(e);
				generation.getBundleInfo().getStorage().getLogServices().log(EquinoxContainer.NAME, FrameworkLogEntry.ERROR, "Unable to extract content: " + generation.getRevision() + ": " + entry, e); //$NON-NLS-1$ //$NON-NLS-2$
			} catch (StorageException e) {
				if (debug.DEBUG_BUNDLE_FILE)
					Debug.printStackTrace(e);
				generation.getBundleInfo().getStorage().getLogServices().log(EquinoxContainer.NAME, FrameworkLogEntry.ERROR, "Unable to extract content: " + generation.getRevision() + ": " + entry, e); //$NON-NLS-1$ //$NON-NLS-2$
			}
		} finally {
			openLock.unlock();
		}
		return null;
	}

	public boolean containsDir(String dir) {
		if (!lockOpen()) {
			return false;
		}
		try {
			if (dir == null)
				return false;

			if (dir.length() == 0)
				return true;

			if (dir.charAt(0) == '/') {
				if (dir.length() == 1)
					return true;
				dir = dir.substring(1);
			}

			if (dir.length() > 0 && dir.charAt(dir.length() - 1) != '/')
				dir = dir + '/';

			Enumeration<? extends ZipEntry> entries = zipFile.entries();
			ZipEntry zipEntry;
			String entryPath;
			while (entries.hasMoreElements()) {
				zipEntry = entries.nextElement();
				entryPath = zipEntry.getName();
				if (entryPath.startsWith(dir)) {
					return true;
				}
			}
		} finally {
			openLock.unlock();
		}
		return false;
	}

	public BundleEntry getEntry(String path) {
		if (!lockOpen()) {
			return null;
		}
		try {
			ZipEntry zipEntry = getZipEntry(path);
			if (zipEntry == null) {
				if (path.length() == 0 || path.charAt(path.length() - 1) == '/') {
					// this is a directory request lets see if any entries exist in this directory
					if (containsDir(path))
						return new DirZipBundleEntry(this, path);
				}
				return null;
			}

			return new ZipBundleEntry(zipEntry, this);
		} finally {
			openLock.unlock();
		}
	}

	@Override
	public Enumeration<String> getEntryPaths(String path, boolean recurse) {
		if (!lockOpen()) {
			return null;
		}
		try {
			if (path == null)
				throw new NullPointerException();

			// Strip any leading '/' off of path.
			if (path.length() > 0 && path.charAt(0) == '/')
				path = path.substring(1);
			// Append a '/', if not already there, to path if not an empty string.
			if (path.length() > 0 && path.charAt(path.length() - 1) != '/')
				path = new StringBuilder(path).append("/").toString(); //$NON-NLS-1$

			LinkedHashSet<String> result = new LinkedHashSet<>();
			// Get all zip file entries and add the ones of interest.
			Enumeration<? extends ZipEntry> entries = zipFile.entries();
			while (entries.hasMoreElements()) {
				ZipEntry zipEntry = entries.nextElement();
				String entryPath = zipEntry.getName();
				// Is the entry of possible interest? Note that 
				// string.startsWith("") == true.
				if (entryPath.startsWith(path)) {
					// If we get here, we know that the entry is either (1) equal to
					// path, (2) a file under path, or (3) a subdirectory of path.
					if (path.length() < entryPath.length()) {
						// If we get here, we know that entry is not equal to path.
						getEntryPaths(path, entryPath.substring(path.length()), recurse, result);
					}
				}
			}
			return result.size() == 0 ? null : Collections.enumeration(result);
		} finally {
			openLock.unlock();
		}
	}

	private void getEntryPaths(String path, String entry, boolean recurse, LinkedHashSet<String> entries) {
		if (entry.length() == 0)
			return;
		int slash = entry.indexOf('/');
		if (slash == -1)
			entries.add(path + entry);
		else {
			path = path + entry.substring(0, slash + 1);
			entries.add(path);
			if (recurse)
				getEntryPaths(path, entry.substring(slash + 1), true, entries);
		}
	}

	public void close() throws IOException {
		openLock.lock();
		try {
			if (!closed) {
				if (referenceCount > 0 && isMruListClosing()) {
					// there are some opened streams to this BundleFile still;
					// wait for them all to close because this is being closed by the MRUBundleFileList
					try {
						refCondition.await(1000, TimeUnit.MICROSECONDS); // timeout after 1 second
					} catch (InterruptedException e) {
						// do nothing for now ...
					}
					if (referenceCount != 0 || closed)
						// either another thread closed the bundle file or we timed waiting for all the reference inputstreams to close
						// If the referenceCount did not reach zero then this bundle file will remain open until the
						// bundle file is closed explicitly (i.e. bundle is updated/uninstalled or framework is shutdown)
						return;

				}
				closed = true;
				zipFile.close();
				mruListRemove();
				zipFile = null;
			}
		} finally {
			openLock.unlock();
		}
	}

	private boolean isMruListClosing() {
		return this.mruList != null && this.mruList.isClosing(this);
	}

	private boolean isMruEnabled() {
		return this.mruList != null && this.mruList.isEnabled();
	}

	private void mruListRemove() {
		if (this.mruList != null) {
			this.mruList.remove(this);
		}
	}

	private void mruListUse() {
		if (this.mruList != null) {
			mruList.use(this);
		}
	}

	private void mruListApplyBackPressure() {
		if (this.mruList != null) {
			this.mruList.applyBackpressure();
		}
	}

	private boolean mruListAdd() {
		if (this.mruList != null) {
			return mruList.add(this);
		}
		return false;
	}

	public void open() throws IOException {
		getZipFile(false);
	}

	void incrementReference() {
		openLock.lock();
		try {
			referenceCount += 1;
		} finally {
			openLock.unlock();
		}
	}

	void decrementReference() {
		openLock.lock();
		try {
			referenceCount = Math.max(0, referenceCount - 1);
			// only notify if the referenceCount is zero.
			if (referenceCount == 0)
				refCondition.signal();
		} finally {
			openLock.unlock();
		}
	}

	InputStream getInputStream(ZipEntry entry) throws IOException {
		if (!lockOpen()) {
			throw new IOException("Failed to open zip file."); //$NON-NLS-1$
		}
		try {
			InputStream zipStream = zipFile.getInputStream(entry);
			if (isMruEnabled()) {
				zipStream = new ZipBundleEntryInputStream(zipStream);
			}
			return zipStream;
		} finally {
			openLock.unlock();
		}
	}

	private class ZipBundleEntryInputStream extends FilterInputStream {

		private boolean streamClosed = false;

		public ZipBundleEntryInputStream(InputStream stream) {
			super(stream);
			incrementReference();
		}

		public int available() throws IOException {
			try {
				return super.available();
			} catch (IOException e) {
				throw enrichExceptionWithBaseFile(e);
			}
		}

		public void close() throws IOException {
			try {
				super.close();
			} catch (IOException e) {
				throw enrichExceptionWithBaseFile(e);
			} finally {
				synchronized (this) {
					if (streamClosed)
						return;
					streamClosed = true;
				}
				decrementReference();
			}
		}

		public int read() throws IOException {
			try {
				return super.read();
			} catch (IOException e) {
				throw enrichExceptionWithBaseFile(e);
			}
		}

		public int read(byte[] var0, int var1, int var2) throws IOException {
			try {
				return super.read(var0, var1, var2);
			} catch (IOException e) {
				throw enrichExceptionWithBaseFile(e);
			}
		}

		public int read(byte[] var0) throws IOException {
			try {
				return super.read(var0);
			} catch (IOException e) {
				throw enrichExceptionWithBaseFile(e);
			}
		}

		public void reset() throws IOException {
			try {
				super.reset();
			} catch (IOException e) {
				throw enrichExceptionWithBaseFile(e);
			}
		}

		public long skip(long var0) throws IOException {
			try {
				return super.skip(var0);
			} catch (IOException e) {
				throw enrichExceptionWithBaseFile(e);
			}
		}

		private IOException enrichExceptionWithBaseFile(IOException e) {
			return new IOException(getBaseFile().toString(), e);
		}
	}
}

Back to the top