View Javadoc
1   /*
2    * Copyright (C) 2008-2009, Google Inc.
3    * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
4    * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org>
5    * and other copyright owners as documented in the project's IP log.
6    *
7    * This program and the accompanying materials are made available
8    * under the terms of the Eclipse Distribution License v1.0 which
9    * accompanies this distribution, is reproduced below, and is
10   * available at http://www.eclipse.org/org/documents/edl-v10.php
11   *
12   * All rights reserved.
13   *
14   * Redistribution and use in source and binary forms, with or
15   * without modification, are permitted provided that the following
16   * conditions are met:
17   *
18   * - Redistributions of source code must retain the above copyright
19   *   notice, this list of conditions and the following disclaimer.
20   *
21   * - Redistributions in binary form must reproduce the above
22   *   copyright notice, this list of conditions and the following
23   *   disclaimer in the documentation and/or other materials provided
24   *   with the distribution.
25   *
26   * - Neither the name of the Eclipse Foundation, Inc. nor the
27   *   names of its contributors may be used to endorse or promote
28   *   products derived from this software without specific prior
29   *   written permission.
30   *
31   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
32   * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
33   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
34   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
35   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
36   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
37   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
38   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
39   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
40   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
41   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
42   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
43   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
44   */
45  
46  package org.eclipse.jgit.internal.storage.file;
47  
48  import static org.eclipse.jgit.internal.storage.pack.PackExt.BITMAP_INDEX;
49  import static org.eclipse.jgit.internal.storage.pack.PackExt.INDEX;
50  import static org.eclipse.jgit.internal.storage.pack.PackExt.KEEP;
51  
52  import java.io.EOFException;
53  import java.io.File;
54  import java.io.FileNotFoundException;
55  import java.io.IOException;
56  import java.io.InterruptedIOException;
57  import java.io.RandomAccessFile;
58  import java.nio.MappedByteBuffer;
59  import java.nio.channels.FileChannel.MapMode;
60  import java.nio.file.AccessDeniedException;
61  import java.nio.file.NoSuchFileException;
62  import java.text.MessageFormat;
63  import java.util.Arrays;
64  import java.util.Collections;
65  import java.util.Comparator;
66  import java.util.Iterator;
67  import java.util.Set;
68  import java.util.concurrent.atomic.AtomicInteger;
69  import java.util.zip.CRC32;
70  import java.util.zip.DataFormatException;
71  import java.util.zip.Inflater;
72  
73  import org.eclipse.jgit.errors.CorruptObjectException;
74  import org.eclipse.jgit.errors.LargeObjectException;
75  import org.eclipse.jgit.errors.MissingObjectException;
76  import org.eclipse.jgit.errors.NoPackSignatureException;
77  import org.eclipse.jgit.errors.PackInvalidException;
78  import org.eclipse.jgit.errors.PackMismatchException;
79  import org.eclipse.jgit.errors.StoredObjectRepresentationNotAvailableException;
80  import org.eclipse.jgit.errors.UnpackException;
81  import org.eclipse.jgit.errors.UnsupportedPackIndexVersionException;
82  import org.eclipse.jgit.errors.UnsupportedPackVersionException;
83  import org.eclipse.jgit.internal.JGitText;
84  import org.eclipse.jgit.internal.storage.pack.BinaryDelta;
85  import org.eclipse.jgit.internal.storage.pack.ObjectToPack;
86  import org.eclipse.jgit.internal.storage.pack.PackExt;
87  import org.eclipse.jgit.internal.storage.pack.PackOutputStream;
88  import org.eclipse.jgit.lib.AbbreviatedObjectId;
89  import org.eclipse.jgit.lib.AnyObjectId;
90  import org.eclipse.jgit.lib.Constants;
91  import org.eclipse.jgit.lib.ObjectId;
92  import org.eclipse.jgit.lib.ObjectLoader;
93  import org.eclipse.jgit.util.LongList;
94  import org.eclipse.jgit.util.NB;
95  import org.eclipse.jgit.util.RawParseUtils;
96  import org.slf4j.Logger;
97  import org.slf4j.LoggerFactory;
98  
99  /**
100  * A Git version 2 pack file representation. A pack file contains Git objects in
101  * delta packed format yielding high compression of lots of object where some
102  * objects are similar.
103  */
104 public class PackFile implements Iterable<PackIndex.MutableEntry> {
105 	private final static Logger LOG = LoggerFactory.getLogger(PackFile.class);
106 	/** Sorts PackFiles to be most recently created to least recently created. */
107 	public static final Comparator<PackFile> SORT = (PackFile a,
108 			PackFile b) -> b.packLastModified - a.packLastModified;
109 
110 	private final File packFile;
111 
112 	private final int extensions;
113 
114 	private File keepFile;
115 
116 	private volatile String packName;
117 
118 	final int hash;
119 
120 	private RandomAccessFile fd;
121 
122 	/** Serializes reads performed against {@link #fd}. */
123 	private final Object readLock = new Object();
124 
125 	long length;
126 
127 	private int activeWindows;
128 
129 	private int activeCopyRawData;
130 
131 	int packLastModified;
132 
133 	private PackFileSnapshot fileSnapshot;
134 
135 	private volatile boolean invalid;
136 
137 	private volatile Exception invalidatingCause;
138 
139 	private boolean invalidBitmap;
140 
141 	private AtomicInteger transientErrorCount = new AtomicInteger();
142 
143 	private byte[] packChecksum;
144 
145 	private volatile PackIndex loadedIdx;
146 
147 	private PackReverseIndex reverseIdx;
148 
149 	private PackBitmapIndex bitmapIdx;
150 
151 	/**
152 	 * Objects we have tried to read, and discovered to be corrupt.
153 	 * <p>
154 	 * The list is allocated after the first corruption is found, and filled in
155 	 * as more entries are discovered. Typically this list is never used, as
156 	 * pack files do not usually contain corrupt objects.
157 	 */
158 	private volatile LongList corruptObjects;
159 
160 	/**
161 	 * Construct a reader for an existing, pre-indexed packfile.
162 	 *
163 	 * @param packFile
164 	 *            path of the <code>.pack</code> file holding the data.
165 	 * @param extensions
166 	 *            additional pack file extensions with the same base as the pack
167 	 */
168 	public PackFile(File packFile, int extensions) {
169 		this.packFile = packFile;
170 		this.fileSnapshot = PackFileSnapshot.save(packFile);
171 		this.packLastModified = (int) (fileSnapshot.lastModified() >> 10);
172 		this.extensions = extensions;
173 
174 		// Multiply by 31 here so we can more directly combine with another
175 		// value in WindowCache.hash(), without doing the multiply there.
176 		//
177 		hash = System.identityHashCode(this) * 31;
178 		length = Long.MAX_VALUE;
179 	}
180 
181 	private PackIndex idx() throws IOException {
182 		PackIndex idx = loadedIdx;
183 		if (idx == null) {
184 			synchronized (this) {
185 				idx = loadedIdx;
186 				if (idx == null) {
187 					if (invalid) {
188 						throw new PackInvalidException(packFile, invalidatingCause);
189 					}
190 					try {
191 						long start = System.currentTimeMillis();
192 						idx = PackIndex.open(extFile(INDEX));
193 						if (LOG.isDebugEnabled()) {
194 							LOG.debug(String.format(
195 									"Opening pack index %s, size %.3f MB took %d ms", //$NON-NLS-1$
196 									extFile(INDEX).getAbsolutePath(),
197 									Float.valueOf(extFile(INDEX).length()
198 											/ (1024f * 1024)),
199 									Long.valueOf(System.currentTimeMillis()
200 											- start)));
201 						}
202 
203 						if (packChecksum == null) {
204 							packChecksum = idx.packChecksum;
205 							fileSnapshot.setChecksum(
206 									ObjectId.fromRaw(packChecksum));
207 						} else if (!Arrays.equals(packChecksum,
208 								idx.packChecksum)) {
209 							throw new PackMismatchException(MessageFormat
210 									.format(JGitText.get().packChecksumMismatch,
211 											packFile.getPath(),
212 											ObjectId.fromRaw(packChecksum)
213 													.name(),
214 											ObjectId.fromRaw(idx.packChecksum)
215 													.name()));
216 						}
217 						loadedIdx = idx;
218 					} catch (InterruptedIOException e) {
219 						// don't invalidate the pack, we are interrupted from
220 						// another thread
221 						throw e;
222 					} catch (IOException e) {
223 						invalid = true;
224 						invalidatingCause = e;
225 						throw e;
226 					}
227 				}
228 			}
229 		}
230 		return idx;
231 	}
232 	/**
233 	 * Get the File object which locates this pack on disk.
234 	 *
235 	 * @return the File object which locates this pack on disk.
236 	 */
237 	public File getPackFile() {
238 		return packFile;
239 	}
240 
241 	/**
242 	 * Get the index for this pack file.
243 	 *
244 	 * @return the index for this pack file.
245 	 * @throws java.io.IOException
246 	 */
247 	public PackIndex getIndex() throws IOException {
248 		return idx();
249 	}
250 
251 	/**
252 	 * Get name extracted from {@code pack-*.pack} pattern.
253 	 *
254 	 * @return name extracted from {@code pack-*.pack} pattern.
255 	 */
256 	public String getPackName() {
257 		String name = packName;
258 		if (name == null) {
259 			name = getPackFile().getName();
260 			if (name.startsWith("pack-")) //$NON-NLS-1$
261 				name = name.substring("pack-".length()); //$NON-NLS-1$
262 			if (name.endsWith(".pack")) //$NON-NLS-1$
263 				name = name.substring(0, name.length() - ".pack".length()); //$NON-NLS-1$
264 			packName = name;
265 		}
266 		return name;
267 	}
268 
269 	/**
270 	 * Determine if an object is contained within the pack file.
271 	 * <p>
272 	 * For performance reasons only the index file is searched; the main pack
273 	 * content is ignored entirely.
274 	 * </p>
275 	 *
276 	 * @param id
277 	 *            the object to look for. Must not be null.
278 	 * @return true if the object is in this pack; false otherwise.
279 	 * @throws java.io.IOException
280 	 *             the index file cannot be loaded into memory.
281 	 */
282 	public boolean hasObject(AnyObjectId id) throws IOException {
283 		final long offset = idx().findOffset(id);
284 		return 0 < offset && !isCorrupt(offset);
285 	}
286 
287 	/**
288 	 * Determines whether a .keep file exists for this pack file.
289 	 *
290 	 * @return true if a .keep file exist.
291 	 */
292 	public boolean shouldBeKept() {
293 		if (keepFile == null)
294 			keepFile = extFile(KEEP);
295 		return keepFile.exists();
296 	}
297 
298 	/**
299 	 * Get an object from this pack.
300 	 *
301 	 * @param curs
302 	 *            temporary working space associated with the calling thread.
303 	 * @param id
304 	 *            the object to obtain from the pack. Must not be null.
305 	 * @return the object loader for the requested object if it is contained in
306 	 *         this pack; null if the object was not found.
307 	 * @throws IOException
308 	 *             the pack file or the index could not be read.
309 	 */
310 	ObjectLoader get(WindowCursor curs, AnyObjectId id)
311 			throws IOException {
312 		final long offset = idx().findOffset(id);
313 		return 0 < offset && !isCorrupt(offset) ? load(curs, offset) : null;
314 	}
315 
316 	void resolve(Set<ObjectId> matches, AbbreviatedObjectId id, int matchLimit)
317 			throws IOException {
318 		idx().resolve(matches, id, matchLimit);
319 	}
320 
321 	/**
322 	 * Close the resources utilized by this repository
323 	 */
324 	public void close() {
325 		WindowCache.purge(this);
326 		synchronized (this) {
327 			loadedIdx = null;
328 			reverseIdx = null;
329 		}
330 	}
331 
332 	/**
333 	 * {@inheritDoc}
334 	 * <p>
335 	 * Provide iterator over entries in associated pack index, that should also
336 	 * exist in this pack file. Objects returned by such iterator are mutable
337 	 * during iteration.
338 	 * <p>
339 	 * Iterator returns objects in SHA-1 lexicographical order.
340 	 * </p>
341 	 *
342 	 * @see PackIndex#iterator()
343 	 */
344 	@Override
345 	public Iterator<PackIndex.MutableEntry> iterator() {
346 		try {
347 			return idx().iterator();
348 		} catch (IOException e) {
349 			return Collections.<PackIndex.MutableEntry> emptyList().iterator();
350 		}
351 	}
352 
353 	/**
354 	 * Obtain the total number of objects available in this pack. This method
355 	 * relies on pack index, giving number of effectively available objects.
356 	 *
357 	 * @return number of objects in index of this pack, likewise in this pack
358 	 * @throws IOException
359 	 *             the index file cannot be loaded into memory.
360 	 */
361 	long getObjectCount() throws IOException {
362 		return idx().getObjectCount();
363 	}
364 
365 	/**
366 	 * Search for object id with the specified start offset in associated pack
367 	 * (reverse) index.
368 	 *
369 	 * @param offset
370 	 *            start offset of object to find
371 	 * @return object id for this offset, or null if no object was found
372 	 * @throws IOException
373 	 *             the index file cannot be loaded into memory.
374 	 */
375 	ObjectId findObjectForOffset(long offset) throws IOException {
376 		return getReverseIdx().findObject(offset);
377 	}
378 
379 	/**
380 	 * Return the @{@link FileSnapshot} associated to the underlying packfile
381 	 * that has been used when the object was created.
382 	 *
383 	 * @return the packfile @{@link FileSnapshot} that the object is loaded from.
384 	 */
385 	PackFileSnapshot getFileSnapshot() {
386 		return fileSnapshot;
387 	}
388 
389 	AnyObjectId getPackChecksum() {
390 		return ObjectId.fromRaw(packChecksum);
391 	}
392 
393 	private final byte[] decompress(final long position, final int sz,
394 			final WindowCursor curs) throws IOException, DataFormatException {
395 		byte[] dstbuf;
396 		try {
397 			dstbuf = new byte[sz];
398 		} catch (OutOfMemoryError noMemory) {
399 			// The size may be larger than our heap allows, return null to
400 			// let the caller know allocation isn't possible and it should
401 			// use the large object streaming approach instead.
402 			//
403 			// For example, this can occur when sz is 640 MB, and JRE
404 			// maximum heap size is only 256 MB. Even if the JRE has
405 			// 200 MB free, it cannot allocate a 640 MB byte array.
406 			return null;
407 		}
408 
409 		if (curs.inflate(this, position, dstbuf, false) != sz)
410 			throw new EOFException(MessageFormat.format(
411 					JGitText.get().shortCompressedStreamAt,
412 					Long.valueOf(position)));
413 		return dstbuf;
414 	}
415 
416 	void copyPackAsIs(PackOutputStream out, WindowCursor curs)
417 			throws IOException {
418 		// Pin the first window, this ensures the length is accurate.
419 		curs.pin(this, 0);
420 		curs.copyPackAsIs(this, length, out);
421 	}
422 
423 	final void copyAsIs(PackOutputStream out, LocalObjectToPack src,
424 			boolean validate, WindowCursor curs) throws IOException,
425 			StoredObjectRepresentationNotAvailableException {
426 		beginCopyAsIs(src);
427 		try {
428 			copyAsIs2(out, src, validate, curs);
429 		} finally {
430 			endCopyAsIs();
431 		}
432 	}
433 
434 	private void copyAsIs2(PackOutputStream out, LocalObjectToPack src,
435 			boolean validate, WindowCursor curs) throws IOException,
436 			StoredObjectRepresentationNotAvailableException {
437 		final CRC32 crc1 = validate ? new CRC32() : null;
438 		final CRC32 crc2 = validate ? new CRC32() : null;
439 		final byte[] buf = out.getCopyBuffer();
440 
441 		// Rip apart the header so we can discover the size.
442 		//
443 		readFully(src.offset, buf, 0, 20, curs);
444 		int c = buf[0] & 0xff;
445 		final int typeCode = (c >> 4) & 7;
446 		long inflatedLength = c & 15;
447 		int shift = 4;
448 		int headerCnt = 1;
449 		while ((c & 0x80) != 0) {
450 			c = buf[headerCnt++] & 0xff;
451 			inflatedLength += ((long) (c & 0x7f)) << shift;
452 			shift += 7;
453 		}
454 
455 		if (typeCode == Constants.OBJ_OFS_DELTA) {
456 			do {
457 				c = buf[headerCnt++] & 0xff;
458 			} while ((c & 128) != 0);
459 			if (validate) {
460 				assert(crc1 != null && crc2 != null);
461 				crc1.update(buf, 0, headerCnt);
462 				crc2.update(buf, 0, headerCnt);
463 			}
464 		} else if (typeCode == Constants.OBJ_REF_DELTA) {
465 			if (validate) {
466 				assert(crc1 != null && crc2 != null);
467 				crc1.update(buf, 0, headerCnt);
468 				crc2.update(buf, 0, headerCnt);
469 			}
470 
471 			readFully(src.offset + headerCnt, buf, 0, 20, curs);
472 			if (validate) {
473 				assert(crc1 != null && crc2 != null);
474 				crc1.update(buf, 0, 20);
475 				crc2.update(buf, 0, 20);
476 			}
477 			headerCnt += 20;
478 		} else if (validate) {
479 			assert(crc1 != null && crc2 != null);
480 			crc1.update(buf, 0, headerCnt);
481 			crc2.update(buf, 0, headerCnt);
482 		}
483 
484 		final long dataOffset = src.offset + headerCnt;
485 		final long dataLength = src.length;
486 		final long expectedCRC;
487 		final ByteArrayWindow quickCopy;
488 
489 		// Verify the object isn't corrupt before sending. If it is,
490 		// we report it missing instead.
491 		//
492 		try {
493 			quickCopy = curs.quickCopy(this, dataOffset, dataLength);
494 
495 			if (validate && idx().hasCRC32Support()) {
496 				assert(crc1 != null);
497 				// Index has the CRC32 code cached, validate the object.
498 				//
499 				expectedCRC = idx().findCRC32(src);
500 				if (quickCopy != null) {
501 					quickCopy.crc32(crc1, dataOffset, (int) dataLength);
502 				} else {
503 					long pos = dataOffset;
504 					long cnt = dataLength;
505 					while (cnt > 0) {
506 						final int n = (int) Math.min(cnt, buf.length);
507 						readFully(pos, buf, 0, n, curs);
508 						crc1.update(buf, 0, n);
509 						pos += n;
510 						cnt -= n;
511 					}
512 				}
513 				if (crc1.getValue() != expectedCRC) {
514 					setCorrupt(src.offset);
515 					throw new CorruptObjectException(MessageFormat.format(
516 							JGitText.get().objectAtHasBadZlibStream,
517 							Long.valueOf(src.offset), getPackFile()));
518 				}
519 			} else if (validate) {
520 				// We don't have a CRC32 code in the index, so compute it
521 				// now while inflating the raw data to get zlib to tell us
522 				// whether or not the data is safe.
523 				//
524 				Inflater inf = curs.inflater();
525 				byte[] tmp = new byte[1024];
526 				if (quickCopy != null) {
527 					quickCopy.check(inf, tmp, dataOffset, (int) dataLength);
528 				} else {
529 					assert(crc1 != null);
530 					long pos = dataOffset;
531 					long cnt = dataLength;
532 					while (cnt > 0) {
533 						final int n = (int) Math.min(cnt, buf.length);
534 						readFully(pos, buf, 0, n, curs);
535 						crc1.update(buf, 0, n);
536 						inf.setInput(buf, 0, n);
537 						while (inf.inflate(tmp, 0, tmp.length) > 0)
538 							continue;
539 						pos += n;
540 						cnt -= n;
541 					}
542 				}
543 				if (!inf.finished() || inf.getBytesRead() != dataLength) {
544 					setCorrupt(src.offset);
545 					throw new EOFException(MessageFormat.format(
546 							JGitText.get().shortCompressedStreamAt,
547 							Long.valueOf(src.offset)));
548 				}
549 				assert(crc1 != null);
550 				expectedCRC = crc1.getValue();
551 			} else {
552 				expectedCRC = -1;
553 			}
554 		} catch (DataFormatException dataFormat) {
555 			setCorrupt(src.offset);
556 
557 			CorruptObjectException corruptObject = new CorruptObjectException(
558 					MessageFormat.format(
559 							JGitText.get().objectAtHasBadZlibStream,
560 							Long.valueOf(src.offset), getPackFile()),
561 					dataFormat);
562 
563 			throw new StoredObjectRepresentationNotAvailableException(src,
564 					corruptObject);
565 
566 		} catch (IOException ioError) {
567 			throw new StoredObjectRepresentationNotAvailableException(src,
568 					ioError);
569 		}
570 
571 		if (quickCopy != null) {
572 			// The entire object fits into a single byte array window slice,
573 			// and we have it pinned.  Write this out without copying.
574 			//
575 			out.writeHeader(src, inflatedLength);
576 			quickCopy.write(out, dataOffset, (int) dataLength);
577 
578 		} else if (dataLength <= buf.length) {
579 			// Tiny optimization: Lots of objects are very small deltas or
580 			// deflated commits that are likely to fit in the copy buffer.
581 			//
582 			if (!validate) {
583 				long pos = dataOffset;
584 				long cnt = dataLength;
585 				while (cnt > 0) {
586 					final int n = (int) Math.min(cnt, buf.length);
587 					readFully(pos, buf, 0, n, curs);
588 					pos += n;
589 					cnt -= n;
590 				}
591 			}
592 			out.writeHeader(src, inflatedLength);
593 			out.write(buf, 0, (int) dataLength);
594 		} else {
595 			// Now we are committed to sending the object. As we spool it out,
596 			// check its CRC32 code to make sure there wasn't corruption between
597 			// the verification we did above, and us actually outputting it.
598 			//
599 			out.writeHeader(src, inflatedLength);
600 			long pos = dataOffset;
601 			long cnt = dataLength;
602 			while (cnt > 0) {
603 				final int n = (int) Math.min(cnt, buf.length);
604 				readFully(pos, buf, 0, n, curs);
605 				if (validate) {
606 					assert(crc2 != null);
607 					crc2.update(buf, 0, n);
608 				}
609 				out.write(buf, 0, n);
610 				pos += n;
611 				cnt -= n;
612 			}
613 			if (validate) {
614 				assert(crc2 != null);
615 				if (crc2.getValue() != expectedCRC) {
616 					throw new CorruptObjectException(MessageFormat.format(
617 							JGitText.get().objectAtHasBadZlibStream,
618 							Long.valueOf(src.offset), getPackFile()));
619 				}
620 			}
621 		}
622 	}
623 
624 	boolean invalid() {
625 		return invalid;
626 	}
627 
628 	void setInvalid() {
629 		invalid = true;
630 	}
631 
632 	int incrementTransientErrorCount() {
633 		return transientErrorCount.incrementAndGet();
634 	}
635 
636 	void resetTransientErrorCount() {
637 		transientErrorCount.set(0);
638 	}
639 
640 	private void readFully(final long position, final byte[] dstbuf,
641 			int dstoff, final int cnt, final WindowCursor curs)
642 			throws IOException {
643 		if (curs.copy(this, position, dstbuf, dstoff, cnt) != cnt)
644 			throw new EOFException();
645 	}
646 
647 	private synchronized void beginCopyAsIs(ObjectToPack otp)
648 			throws StoredObjectRepresentationNotAvailableException {
649 		if (++activeCopyRawData == 1 && activeWindows == 0) {
650 			try {
651 				doOpen();
652 			} catch (IOException thisPackNotValid) {
653 				throw new StoredObjectRepresentationNotAvailableException(otp,
654 						thisPackNotValid);
655 			}
656 		}
657 	}
658 
659 	private synchronized void endCopyAsIs() {
660 		if (--activeCopyRawData == 0 && activeWindows == 0)
661 			doClose();
662 	}
663 
664 	synchronized boolean beginWindowCache() throws IOException {
665 		if (++activeWindows == 1) {
666 			if (activeCopyRawData == 0)
667 				doOpen();
668 			return true;
669 		}
670 		return false;
671 	}
672 
673 	synchronized boolean endWindowCache() {
674 		final boolean r = --activeWindows == 0;
675 		if (r && activeCopyRawData == 0)
676 			doClose();
677 		return r;
678 	}
679 
680 	private void doOpen() throws IOException {
681 		if (invalid) {
682 			throw new PackInvalidException(packFile, invalidatingCause);
683 		}
684 		try {
685 			synchronized (readLock) {
686 				fd = new RandomAccessFile(packFile, "r"); //$NON-NLS-1$
687 				length = fd.length();
688 				onOpenPack();
689 			}
690 		} catch (InterruptedIOException e) {
691 			// don't invalidate the pack, we are interrupted from another thread
692 			openFail(false, e);
693 			throw e;
694 		} catch (FileNotFoundException fn) {
695 			// don't invalidate the pack if opening an existing file failed
696 			// since it may be related to a temporary lack of resources (e.g.
697 			// max open files)
698 			openFail(!packFile.exists(), fn);
699 			throw fn;
700 		} catch (EOFException | AccessDeniedException | NoSuchFileException
701 				| CorruptObjectException | NoPackSignatureException
702 				| PackMismatchException | UnpackException
703 				| UnsupportedPackIndexVersionException
704 				| UnsupportedPackVersionException pe) {
705 			// exceptions signaling permanent problems with a pack
706 			openFail(true, pe);
707 			throw pe;
708 		} catch (IOException | RuntimeException ge) {
709 			// generic exceptions could be transient so we should not mark the
710 			// pack invalid to avoid false MissingObjectExceptions
711 			openFail(false, ge);
712 			throw ge;
713 		}
714 	}
715 
716 	private void openFail(boolean invalidate, Exception cause) {
717 		activeWindows = 0;
718 		activeCopyRawData = 0;
719 		invalid = invalidate;
720 		invalidatingCause = cause;
721 		doClose();
722 	}
723 
724 	private void doClose() {
725 		synchronized (readLock) {
726 			if (fd != null) {
727 				try {
728 					fd.close();
729 				} catch (IOException err) {
730 					// Ignore a close event. We had it open only for reading.
731 					// There should not be errors related to network buffers
732 					// not flushed, etc.
733 				}
734 				fd = null;
735 			}
736 		}
737 	}
738 
739 	ByteArrayWindow read(long pos, int size) throws IOException {
740 		synchronized (readLock) {
741 			if (invalid || fd == null) {
742 				// Due to concurrency between a read and another packfile invalidation thread
743 				// one thread could come up to this point and then fail with NPE.
744 				// Detect the situation and throw a proper exception so that can be properly
745 				// managed by the main packfile search loop and the Git client won't receive
746 				// any failures.
747 				throw new PackInvalidException(packFile, invalidatingCause);
748 			}
749 			if (length < pos + size)
750 				size = (int) (length - pos);
751 			final byte[] buf = new byte[size];
752 			fd.seek(pos);
753 			fd.readFully(buf, 0, size);
754 			return new ByteArrayWindow(this, pos, buf);
755 		}
756 	}
757 
758 	ByteWindow mmap(long pos, int size) throws IOException {
759 		synchronized (readLock) {
760 			if (length < pos + size)
761 				size = (int) (length - pos);
762 
763 			MappedByteBuffer map;
764 			try {
765 				map = fd.getChannel().map(MapMode.READ_ONLY, pos, size);
766 			} catch (IOException ioe1) {
767 				// The most likely reason this failed is the JVM has run out
768 				// of virtual memory. We need to discard quickly, and try to
769 				// force the GC to finalize and release any existing mappings.
770 				//
771 				System.gc();
772 				System.runFinalization();
773 				map = fd.getChannel().map(MapMode.READ_ONLY, pos, size);
774 			}
775 
776 			if (map.hasArray())
777 				return new ByteArrayWindow(this, pos, map.array());
778 			return new ByteBufferWindow(this, pos, map);
779 		}
780 	}
781 
782 	private void onOpenPack() throws IOException {
783 		final PackIndex idx = idx();
784 		final byte[] buf = new byte[20];
785 
786 		fd.seek(0);
787 		fd.readFully(buf, 0, 12);
788 		if (RawParseUtils.match(buf, 0, Constants.PACK_SIGNATURE) != 4) {
789 			throw new NoPackSignatureException(JGitText.get().notAPACKFile);
790 		}
791 		final long vers = NB.decodeUInt32(buf, 4);
792 		final long packCnt = NB.decodeUInt32(buf, 8);
793 		if (vers != 2 && vers != 3) {
794 			throw new UnsupportedPackVersionException(vers);
795 		}
796 
797 		if (packCnt != idx.getObjectCount()) {
798 			throw new PackMismatchException(MessageFormat.format(
799 					JGitText.get().packObjectCountMismatch,
800 					Long.valueOf(packCnt), Long.valueOf(idx.getObjectCount()),
801 					getPackFile()));
802 		}
803 
804 		fd.seek(length - 20);
805 		fd.readFully(buf, 0, 20);
806 		if (!Arrays.equals(buf, packChecksum)) {
807 			throw new PackMismatchException(MessageFormat.format(
808 					JGitText.get().packChecksumMismatch,
809 					getPackFile(),
810 					ObjectId.fromRaw(buf).name(),
811 					ObjectId.fromRaw(idx.packChecksum).name()));
812 		}
813 	}
814 
815 	ObjectLoader load(WindowCursor curs, long pos)
816 			throws IOException, LargeObjectException {
817 		try {
818 			final byte[] ib = curs.tempId;
819 			Delta delta = null;
820 			byte[] data = null;
821 			int type = Constants.OBJ_BAD;
822 			boolean cached = false;
823 
824 			SEARCH: for (;;) {
825 				readFully(pos, ib, 0, 20, curs);
826 				int c = ib[0] & 0xff;
827 				final int typeCode = (c >> 4) & 7;
828 				long sz = c & 15;
829 				int shift = 4;
830 				int p = 1;
831 				while ((c & 0x80) != 0) {
832 					c = ib[p++] & 0xff;
833 					sz += ((long) (c & 0x7f)) << shift;
834 					shift += 7;
835 				}
836 
837 				switch (typeCode) {
838 				case Constants.OBJ_COMMIT:
839 				case Constants.OBJ_TREE:
840 				case Constants.OBJ_BLOB:
841 				case Constants.OBJ_TAG: {
842 					if (delta != null || sz < curs.getStreamFileThreshold())
843 						data = decompress(pos + p, (int) sz, curs);
844 
845 					if (delta != null) {
846 						type = typeCode;
847 						break SEARCH;
848 					}
849 
850 					if (data != null)
851 						return new ObjectLoader.SmallObject(typeCode, data);
852 					else
853 						return new LargePackedWholeObject(typeCode, sz, pos, p,
854 								this, curs.db);
855 				}
856 
857 				case Constants.OBJ_OFS_DELTA: {
858 					c = ib[p++] & 0xff;
859 					long base = c & 127;
860 					while ((c & 128) != 0) {
861 						base += 1;
862 						c = ib[p++] & 0xff;
863 						base <<= 7;
864 						base += (c & 127);
865 					}
866 					base = pos - base;
867 					delta = new Delta(delta, pos, (int) sz, p, base);
868 					if (sz != delta.deltaSize)
869 						break SEARCH;
870 
871 					DeltaBaseCache.Entry e = curs.getDeltaBaseCache().get(this, base);
872 					if (e != null) {
873 						type = e.type;
874 						data = e.data;
875 						cached = true;
876 						break SEARCH;
877 					}
878 					pos = base;
879 					continue SEARCH;
880 				}
881 
882 				case Constants.OBJ_REF_DELTA: {
883 					readFully(pos + p, ib, 0, 20, curs);
884 					long base = findDeltaBase(ObjectId.fromRaw(ib));
885 					delta = new Delta(delta, pos, (int) sz, p + 20, base);
886 					if (sz != delta.deltaSize)
887 						break SEARCH;
888 
889 					DeltaBaseCache.Entry e = curs.getDeltaBaseCache().get(this, base);
890 					if (e != null) {
891 						type = e.type;
892 						data = e.data;
893 						cached = true;
894 						break SEARCH;
895 					}
896 					pos = base;
897 					continue SEARCH;
898 				}
899 
900 				default:
901 					throw new IOException(MessageFormat.format(
902 							JGitText.get().unknownObjectType,
903 							Integer.valueOf(typeCode)));
904 				}
905 			}
906 
907 			// At this point there is at least one delta to apply to data.
908 			// (Whole objects with no deltas to apply return early above.)
909 
910 			if (data == null)
911 				throw new IOException(JGitText.get().inMemoryBufferLimitExceeded);
912 
913 			assert(delta != null);
914 			do {
915 				// Cache only the base immediately before desired object.
916 				if (cached)
917 					cached = false;
918 				else if (delta.next == null)
919 					curs.getDeltaBaseCache().store(this, delta.basePos, data, type);
920 
921 				pos = delta.deltaPos;
922 
923 				final byte[] cmds = decompress(pos + delta.hdrLen,
924 						delta.deltaSize, curs);
925 				if (cmds == null) {
926 					data = null; // Discard base in case of OutOfMemoryError
927 					throw new LargeObjectException.OutOfMemory(new OutOfMemoryError());
928 				}
929 
930 				final long sz = BinaryDelta.getResultSize(cmds);
931 				if (Integer.MAX_VALUE <= sz)
932 					throw new LargeObjectException.ExceedsByteArrayLimit();
933 
934 				final byte[] result;
935 				try {
936 					result = new byte[(int) sz];
937 				} catch (OutOfMemoryError tooBig) {
938 					data = null; // Discard base in case of OutOfMemoryError
939 					throw new LargeObjectException.OutOfMemory(tooBig);
940 				}
941 
942 				BinaryDelta.apply(data, cmds, result);
943 				data = result;
944 				delta = delta.next;
945 			} while (delta != null);
946 
947 			return new ObjectLoader.SmallObject(type, data);
948 
949 		} catch (DataFormatException dfe) {
950 			throw new CorruptObjectException(
951 					MessageFormat.format(
952 							JGitText.get().objectAtHasBadZlibStream,
953 							Long.valueOf(pos), getPackFile()),
954 					dfe);
955 		}
956 	}
957 
958 	private long findDeltaBase(ObjectId baseId) throws IOException,
959 			MissingObjectException {
960 		long ofs = idx().findOffset(baseId);
961 		if (ofs < 0)
962 			throw new MissingObjectException(baseId,
963 					JGitText.get().missingDeltaBase);
964 		return ofs;
965 	}
966 
967 	private static class Delta {
968 		/** Child that applies onto this object. */
969 		final Delta next;
970 
971 		/** Offset of the delta object. */
972 		final long deltaPos;
973 
974 		/** Size of the inflated delta stream. */
975 		final int deltaSize;
976 
977 		/** Total size of the delta's pack entry header (including base). */
978 		final int hdrLen;
979 
980 		/** Offset of the base object this delta applies onto. */
981 		final long basePos;
982 
983 		Delta(Delta next, long ofs, int sz, int hdrLen, long baseOffset) {
984 			this.next = next;
985 			this.deltaPos = ofs;
986 			this.deltaSize = sz;
987 			this.hdrLen = hdrLen;
988 			this.basePos = baseOffset;
989 		}
990 	}
991 
992 	byte[] getDeltaHeader(WindowCursor wc, long pos)
993 			throws IOException, DataFormatException {
994 		// The delta stream starts as two variable length integers. If we
995 		// assume they are 64 bits each, we need 16 bytes to encode them,
996 		// plus 2 extra bytes for the variable length overhead. So 18 is
997 		// the longest delta instruction header.
998 		//
999 		final byte[] hdr = new byte[18];
1000 		wc.inflate(this, pos, hdr, true /* headerOnly */);
1001 		return hdr;
1002 	}
1003 
1004 	int getObjectType(WindowCursor curs, long pos) throws IOException {
1005 		final byte[] ib = curs.tempId;
1006 		for (;;) {
1007 			readFully(pos, ib, 0, 20, curs);
1008 			int c = ib[0] & 0xff;
1009 			final int type = (c >> 4) & 7;
1010 
1011 			switch (type) {
1012 			case Constants.OBJ_COMMIT:
1013 			case Constants.OBJ_TREE:
1014 			case Constants.OBJ_BLOB:
1015 			case Constants.OBJ_TAG:
1016 				return type;
1017 
1018 			case Constants.OBJ_OFS_DELTA: {
1019 				int p = 1;
1020 				while ((c & 0x80) != 0)
1021 					c = ib[p++] & 0xff;
1022 				c = ib[p++] & 0xff;
1023 				long ofs = c & 127;
1024 				while ((c & 128) != 0) {
1025 					ofs += 1;
1026 					c = ib[p++] & 0xff;
1027 					ofs <<= 7;
1028 					ofs += (c & 127);
1029 				}
1030 				pos = pos - ofs;
1031 				continue;
1032 			}
1033 
1034 			case Constants.OBJ_REF_DELTA: {
1035 				int p = 1;
1036 				while ((c & 0x80) != 0)
1037 					c = ib[p++] & 0xff;
1038 				readFully(pos + p, ib, 0, 20, curs);
1039 				pos = findDeltaBase(ObjectId.fromRaw(ib));
1040 				continue;
1041 			}
1042 
1043 			default:
1044 				throw new IOException(
1045 						MessageFormat.format(JGitText.get().unknownObjectType,
1046 								Integer.valueOf(type)));
1047 			}
1048 		}
1049 	}
1050 
1051 	long getObjectSize(WindowCursor curs, AnyObjectId id)
1052 			throws IOException {
1053 		final long offset = idx().findOffset(id);
1054 		return 0 < offset ? getObjectSize(curs, offset) : -1;
1055 	}
1056 
1057 	long getObjectSize(WindowCursor curs, long pos)
1058 			throws IOException {
1059 		final byte[] ib = curs.tempId;
1060 		readFully(pos, ib, 0, 20, curs);
1061 		int c = ib[0] & 0xff;
1062 		final int type = (c >> 4) & 7;
1063 		long sz = c & 15;
1064 		int shift = 4;
1065 		int p = 1;
1066 		while ((c & 0x80) != 0) {
1067 			c = ib[p++] & 0xff;
1068 			sz += ((long) (c & 0x7f)) << shift;
1069 			shift += 7;
1070 		}
1071 
1072 		long deltaAt;
1073 		switch (type) {
1074 		case Constants.OBJ_COMMIT:
1075 		case Constants.OBJ_TREE:
1076 		case Constants.OBJ_BLOB:
1077 		case Constants.OBJ_TAG:
1078 			return sz;
1079 
1080 		case Constants.OBJ_OFS_DELTA:
1081 			c = ib[p++] & 0xff;
1082 			while ((c & 128) != 0)
1083 				c = ib[p++] & 0xff;
1084 			deltaAt = pos + p;
1085 			break;
1086 
1087 		case Constants.OBJ_REF_DELTA:
1088 			deltaAt = pos + p + 20;
1089 			break;
1090 
1091 		default:
1092 			throw new IOException(MessageFormat.format(
1093 					JGitText.get().unknownObjectType, Integer.valueOf(type)));
1094 		}
1095 
1096 		try {
1097 			return BinaryDelta.getResultSize(getDeltaHeader(curs, deltaAt));
1098 		} catch (DataFormatException e) {
1099 			throw new CorruptObjectException(MessageFormat.format(
1100 					JGitText.get().objectAtHasBadZlibStream, Long.valueOf(pos),
1101 					getPackFile()));
1102 		}
1103 	}
1104 
1105 	LocalObjectRepresentation representation(final WindowCursor curs,
1106 			final AnyObjectId objectId) throws IOException {
1107 		final long pos = idx().findOffset(objectId);
1108 		if (pos < 0)
1109 			return null;
1110 
1111 		final byte[] ib = curs.tempId;
1112 		readFully(pos, ib, 0, 20, curs);
1113 		int c = ib[0] & 0xff;
1114 		int p = 1;
1115 		final int typeCode = (c >> 4) & 7;
1116 		while ((c & 0x80) != 0)
1117 			c = ib[p++] & 0xff;
1118 
1119 		long len = (findEndOffset(pos) - pos);
1120 		switch (typeCode) {
1121 		case Constants.OBJ_COMMIT:
1122 		case Constants.OBJ_TREE:
1123 		case Constants.OBJ_BLOB:
1124 		case Constants.OBJ_TAG:
1125 			return LocalObjectRepresentation.newWhole(this, pos, len - p);
1126 
1127 		case Constants.OBJ_OFS_DELTA: {
1128 			c = ib[p++] & 0xff;
1129 			long ofs = c & 127;
1130 			while ((c & 128) != 0) {
1131 				ofs += 1;
1132 				c = ib[p++] & 0xff;
1133 				ofs <<= 7;
1134 				ofs += (c & 127);
1135 			}
1136 			ofs = pos - ofs;
1137 			return LocalObjectRepresentation.newDelta(this, pos, len - p, ofs);
1138 		}
1139 
1140 		case Constants.OBJ_REF_DELTA: {
1141 			len -= p;
1142 			len -= Constants.OBJECT_ID_LENGTH;
1143 			readFully(pos + p, ib, 0, 20, curs);
1144 			ObjectId id = ObjectId.fromRaw(ib);
1145 			return LocalObjectRepresentation.newDelta(this, pos, len, id);
1146 		}
1147 
1148 		default:
1149 			throw new IOException(
1150 					MessageFormat.format(JGitText.get().unknownObjectType,
1151 							Integer.valueOf(typeCode)));
1152 		}
1153 	}
1154 
1155 	private long findEndOffset(long startOffset)
1156 			throws IOException, CorruptObjectException {
1157 		final long maxOffset = length - 20;
1158 		return getReverseIdx().findNextOffset(startOffset, maxOffset);
1159 	}
1160 
1161 	synchronized PackBitmapIndex getBitmapIndex() throws IOException {
1162 		if (invalid || invalidBitmap)
1163 			return null;
1164 		if (bitmapIdx == null && hasExt(BITMAP_INDEX)) {
1165 			final PackBitmapIndex idx;
1166 			try {
1167 				idx = PackBitmapIndex.open(extFile(BITMAP_INDEX), idx(),
1168 						getReverseIdx());
1169 			} catch (FileNotFoundException e) {
1170 				// Once upon a time this bitmap file existed. Now it
1171 				// has been removed. Most likely an external gc  has
1172 				// removed this packfile and the bitmap
1173 				 invalidBitmap = true;
1174 				 return null;
1175 			}
1176 
1177 			// At this point, idx() will have set packChecksum.
1178 			if (Arrays.equals(packChecksum, idx.packChecksum))
1179 				bitmapIdx = idx;
1180 			else
1181 				invalidBitmap = true;
1182 		}
1183 		return bitmapIdx;
1184 	}
1185 
1186 	private synchronized PackReverseIndex getReverseIdx() throws IOException {
1187 		if (reverseIdx == null)
1188 			reverseIdx = new PackReverseIndex(idx());
1189 		return reverseIdx;
1190 	}
1191 
1192 	private boolean isCorrupt(long offset) {
1193 		LongList list = corruptObjects;
1194 		if (list == null)
1195 			return false;
1196 		synchronized (list) {
1197 			return list.contains(offset);
1198 		}
1199 	}
1200 
1201 	private void setCorrupt(long offset) {
1202 		LongList list = corruptObjects;
1203 		if (list == null) {
1204 			synchronized (readLock) {
1205 				list = corruptObjects;
1206 				if (list == null) {
1207 					list = new LongList();
1208 					corruptObjects = list;
1209 				}
1210 			}
1211 		}
1212 		synchronized (list) {
1213 			list.add(offset);
1214 		}
1215 	}
1216 
1217 	private File extFile(PackExt ext) {
1218 		String p = packFile.getName();
1219 		int dot = p.lastIndexOf('.');
1220 		String b = (dot < 0) ? p : p.substring(0, dot);
1221 		return new File(packFile.getParentFile(), b + '.' + ext.getExtension());
1222 	}
1223 
1224 	private boolean hasExt(PackExt ext) {
1225 		return (extensions & ext.getBit()) != 0;
1226 	}
1227 
1228 	@SuppressWarnings("nls")
1229 	@Override
1230 	public String toString() {
1231 		return "PackFile [packFileName=" + packFile.getName() + ", length="
1232 				+ packFile.length() + ", packChecksum="
1233 				+ ObjectId.fromRaw(packChecksum).name() + "]";
1234 	}
1235 }