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