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 			throw new PackInvalidException(packFile, invalidatingCause);
654 		}
655 		try {
656 			synchronized (readLock) {
657 				fd = new RandomAccessFile(packFile, "r"); //$NON-NLS-1$
658 				length = fd.length();
659 				onOpenPack();
660 			}
661 		} catch (InterruptedIOException e) {
662 			// don't invalidate the pack, we are interrupted from another thread
663 			openFail(false, e);
664 			throw e;
665 		} catch (FileNotFoundException fn) {
666 			// don't invalidate the pack if opening an existing file failed
667 			// since it may be related to a temporary lack of resources (e.g.
668 			// max open files)
669 			openFail(!packFile.exists(), fn);
670 			throw fn;
671 		} catch (EOFException | AccessDeniedException | NoSuchFileException
672 				| CorruptObjectException | NoPackSignatureException
673 				| PackMismatchException | UnpackException
674 				| UnsupportedPackIndexVersionException
675 				| UnsupportedPackVersionException pe) {
676 			// exceptions signaling permanent problems with a pack
677 			openFail(true, pe);
678 			throw pe;
679 		} catch (IOException | RuntimeException ge) {
680 			// generic exceptions could be transient so we should not mark the
681 			// pack invalid to avoid false MissingObjectExceptions
682 			openFail(false, ge);
683 			throw ge;
684 		}
685 	}
686 
687 	private void openFail(boolean invalidate, Exception cause) {
688 		activeWindows = 0;
689 		activeCopyRawData = 0;
690 		invalid = invalidate;
691 		invalidatingCause = cause;
692 		doClose();
693 	}
694 
695 	private void doClose() {
696 		synchronized (readLock) {
697 			if (fd != null) {
698 				try {
699 					fd.close();
700 				} catch (IOException err) {
701 					// Ignore a close event. We had it open only for reading.
702 					// There should not be errors related to network buffers
703 					// not flushed, etc.
704 				}
705 				fd = null;
706 			}
707 		}
708 	}
709 
710 	ByteArrayWindow read(long pos, int size) throws IOException {
711 		synchronized (readLock) {
712 			if (invalid || fd == null) {
713 				// Due to concurrency between a read and another packfile invalidation thread
714 				// one thread could come up to this point and then fail with NPE.
715 				// Detect the situation and throw a proper exception so that can be properly
716 				// managed by the main packfile search loop and the Git client won't receive
717 				// any failures.
718 				throw new PackInvalidException(packFile, invalidatingCause);
719 			}
720 			if (length < pos + size)
721 				size = (int) (length - pos);
722 			final byte[] buf = new byte[size];
723 			fd.seek(pos);
724 			fd.readFully(buf, 0, size);
725 			return new ByteArrayWindow(this, pos, buf);
726 		}
727 	}
728 
729 	ByteWindow mmap(long pos, int size) throws IOException {
730 		synchronized (readLock) {
731 			if (length < pos + size)
732 				size = (int) (length - pos);
733 
734 			MappedByteBuffer map;
735 			try {
736 				map = fd.getChannel().map(MapMode.READ_ONLY, pos, size);
737 			} catch (IOException ioe1) {
738 				// The most likely reason this failed is the JVM has run out
739 				// of virtual memory. We need to discard quickly, and try to
740 				// force the GC to finalize and release any existing mappings.
741 				//
742 				System.gc();
743 				System.runFinalization();
744 				map = fd.getChannel().map(MapMode.READ_ONLY, pos, size);
745 			}
746 
747 			if (map.hasArray())
748 				return new ByteArrayWindow(this, pos, map.array());
749 			return new ByteBufferWindow(this, pos, map);
750 		}
751 	}
752 
753 	private void onOpenPack() throws IOException {
754 		final PackIndex idx = idx();
755 		final byte[] buf = new byte[20];
756 
757 		fd.seek(0);
758 		fd.readFully(buf, 0, 12);
759 		if (RawParseUtils.match(buf, 0, Constants.PACK_SIGNATURE) != 4) {
760 			throw new NoPackSignatureException(JGitText.get().notAPACKFile);
761 		}
762 		final long vers = NB.decodeUInt32(buf, 4);
763 		final long packCnt = NB.decodeUInt32(buf, 8);
764 		if (vers != 2 && vers != 3) {
765 			throw new UnsupportedPackVersionException(vers);
766 		}
767 
768 		if (packCnt != idx.getObjectCount()) {
769 			throw new PackMismatchException(MessageFormat.format(
770 					JGitText.get().packObjectCountMismatch,
771 					Long.valueOf(packCnt), Long.valueOf(idx.getObjectCount()),
772 					getPackFile()));
773 		}
774 
775 		fd.seek(length - 20);
776 		fd.readFully(buf, 0, 20);
777 		if (!Arrays.equals(buf, packChecksum)) {
778 			throw new PackMismatchException(MessageFormat.format(
779 					JGitText.get().packChecksumMismatch,
780 					getPackFile(),
781 					ObjectId.fromRaw(buf).name(),
782 					ObjectId.fromRaw(idx.packChecksum).name()));
783 		}
784 	}
785 
786 	ObjectLoader load(WindowCursor curs, long pos)
787 			throws IOException, LargeObjectException {
788 		try {
789 			final byte[] ib = curs.tempId;
790 			Delta delta = null;
791 			byte[] data = null;
792 			int type = Constants.OBJ_BAD;
793 			boolean cached = false;
794 
795 			SEARCH: for (;;) {
796 				readFully(pos, ib, 0, 20, curs);
797 				int c = ib[0] & 0xff;
798 				final int typeCode = (c >> 4) & 7;
799 				long sz = c & 15;
800 				int shift = 4;
801 				int p = 1;
802 				while ((c & 0x80) != 0) {
803 					c = ib[p++] & 0xff;
804 					sz += ((long) (c & 0x7f)) << shift;
805 					shift += 7;
806 				}
807 
808 				switch (typeCode) {
809 				case Constants.OBJ_COMMIT:
810 				case Constants.OBJ_TREE:
811 				case Constants.OBJ_BLOB:
812 				case Constants.OBJ_TAG: {
813 					if (delta != null || sz < curs.getStreamFileThreshold()) {
814 						data = decompress(pos + p, (int) sz, curs);
815 					}
816 
817 					if (delta != null) {
818 						type = typeCode;
819 						break SEARCH;
820 					}
821 
822 					if (data != null) {
823 						return new ObjectLoader.SmallObject(typeCode, data);
824 					}
825 					return new LargePackedWholeObject(typeCode, sz, pos, p,
826 							this, curs.db);
827 				}
828 
829 				case Constants.OBJ_OFS_DELTA: {
830 					c = ib[p++] & 0xff;
831 					long base = c & 127;
832 					while ((c & 128) != 0) {
833 						base += 1;
834 						c = ib[p++] & 0xff;
835 						base <<= 7;
836 						base += (c & 127);
837 					}
838 					base = pos - base;
839 					delta = new Delta(delta, pos, (int) sz, p, base);
840 					if (sz != delta.deltaSize)
841 						break SEARCH;
842 
843 					DeltaBaseCache.Entry e = curs.getDeltaBaseCache().get(this, base);
844 					if (e != null) {
845 						type = e.type;
846 						data = e.data;
847 						cached = true;
848 						break SEARCH;
849 					}
850 					pos = base;
851 					continue SEARCH;
852 				}
853 
854 				case Constants.OBJ_REF_DELTA: {
855 					readFully(pos + p, ib, 0, 20, curs);
856 					long base = findDeltaBase(ObjectId.fromRaw(ib));
857 					delta = new Delta(delta, pos, (int) sz, p + 20, base);
858 					if (sz != delta.deltaSize)
859 						break SEARCH;
860 
861 					DeltaBaseCache.Entry e = curs.getDeltaBaseCache().get(this, base);
862 					if (e != null) {
863 						type = e.type;
864 						data = e.data;
865 						cached = true;
866 						break SEARCH;
867 					}
868 					pos = base;
869 					continue SEARCH;
870 				}
871 
872 				default:
873 					throw new IOException(MessageFormat.format(
874 							JGitText.get().unknownObjectType,
875 							Integer.valueOf(typeCode)));
876 				}
877 			}
878 
879 			// At this point there is at least one delta to apply to data.
880 			// (Whole objects with no deltas to apply return early above.)
881 
882 			if (data == null)
883 				throw new IOException(JGitText.get().inMemoryBufferLimitExceeded);
884 
885 			assert(delta != null);
886 			do {
887 				// Cache only the base immediately before desired object.
888 				if (cached)
889 					cached = false;
890 				else if (delta.next == null)
891 					curs.getDeltaBaseCache().store(this, delta.basePos, data, type);
892 
893 				pos = delta.deltaPos;
894 
895 				final byte[] cmds = decompress(pos + delta.hdrLen,
896 						delta.deltaSize, curs);
897 				if (cmds == null) {
898 					data = null; // Discard base in case of OutOfMemoryError
899 					throw new LargeObjectException.OutOfMemory(new OutOfMemoryError());
900 				}
901 
902 				final long sz = BinaryDelta.getResultSize(cmds);
903 				if (Integer.MAX_VALUE <= sz)
904 					throw new LargeObjectException.ExceedsByteArrayLimit();
905 
906 				final byte[] result;
907 				try {
908 					result = new byte[(int) sz];
909 				} catch (OutOfMemoryError tooBig) {
910 					data = null; // Discard base in case of OutOfMemoryError
911 					throw new LargeObjectException.OutOfMemory(tooBig);
912 				}
913 
914 				BinaryDelta.apply(data, cmds, result);
915 				data = result;
916 				delta = delta.next;
917 			} while (delta != null);
918 
919 			return new ObjectLoader.SmallObject(type, data);
920 
921 		} catch (DataFormatException dfe) {
922 			throw new CorruptObjectException(
923 					MessageFormat.format(
924 							JGitText.get().objectAtHasBadZlibStream,
925 							Long.valueOf(pos), getPackFile()),
926 					dfe);
927 		}
928 	}
929 
930 	private long findDeltaBase(ObjectId baseId) throws IOException,
931 			MissingObjectException {
932 		long ofs = idx().findOffset(baseId);
933 		if (ofs < 0)
934 			throw new MissingObjectException(baseId,
935 					JGitText.get().missingDeltaBase);
936 		return ofs;
937 	}
938 
939 	private static class Delta {
940 		/** Child that applies onto this object. */
941 		final Delta next;
942 
943 		/** Offset of the delta object. */
944 		final long deltaPos;
945 
946 		/** Size of the inflated delta stream. */
947 		final int deltaSize;
948 
949 		/** Total size of the delta's pack entry header (including base). */
950 		final int hdrLen;
951 
952 		/** Offset of the base object this delta applies onto. */
953 		final long basePos;
954 
955 		Delta(Delta next, long ofs, int sz, int hdrLen, long baseOffset) {
956 			this.next = next;
957 			this.deltaPos = ofs;
958 			this.deltaSize = sz;
959 			this.hdrLen = hdrLen;
960 			this.basePos = baseOffset;
961 		}
962 	}
963 
964 	byte[] getDeltaHeader(WindowCursor wc, long pos)
965 			throws IOException, DataFormatException {
966 		// The delta stream starts as two variable length integers. If we
967 		// assume they are 64 bits each, we need 16 bytes to encode them,
968 		// plus 2 extra bytes for the variable length overhead. So 18 is
969 		// the longest delta instruction header.
970 		//
971 		final byte[] hdr = new byte[18];
972 		wc.inflate(this, pos, hdr, true /* headerOnly */);
973 		return hdr;
974 	}
975 
976 	int getObjectType(WindowCursor curs, long pos) throws IOException {
977 		final byte[] ib = curs.tempId;
978 		for (;;) {
979 			readFully(pos, ib, 0, 20, curs);
980 			int c = ib[0] & 0xff;
981 			final int type = (c >> 4) & 7;
982 
983 			switch (type) {
984 			case Constants.OBJ_COMMIT:
985 			case Constants.OBJ_TREE:
986 			case Constants.OBJ_BLOB:
987 			case Constants.OBJ_TAG:
988 				return type;
989 
990 			case Constants.OBJ_OFS_DELTA: {
991 				int p = 1;
992 				while ((c & 0x80) != 0)
993 					c = ib[p++] & 0xff;
994 				c = ib[p++] & 0xff;
995 				long ofs = c & 127;
996 				while ((c & 128) != 0) {
997 					ofs += 1;
998 					c = ib[p++] & 0xff;
999 					ofs <<= 7;
1000 					ofs += (c & 127);
1001 				}
1002 				pos = pos - ofs;
1003 				continue;
1004 			}
1005 
1006 			case Constants.OBJ_REF_DELTA: {
1007 				int p = 1;
1008 				while ((c & 0x80) != 0)
1009 					c = ib[p++] & 0xff;
1010 				readFully(pos + p, ib, 0, 20, curs);
1011 				pos = findDeltaBase(ObjectId.fromRaw(ib));
1012 				continue;
1013 			}
1014 
1015 			default:
1016 				throw new IOException(
1017 						MessageFormat.format(JGitText.get().unknownObjectType,
1018 								Integer.valueOf(type)));
1019 			}
1020 		}
1021 	}
1022 
1023 	long getObjectSize(WindowCursor curs, AnyObjectId id)
1024 			throws IOException {
1025 		final long offset = idx().findOffset(id);
1026 		return 0 < offset ? getObjectSize(curs, offset) : -1;
1027 	}
1028 
1029 	long getObjectSize(WindowCursor curs, long pos)
1030 			throws IOException {
1031 		final byte[] ib = curs.tempId;
1032 		readFully(pos, ib, 0, 20, curs);
1033 		int c = ib[0] & 0xff;
1034 		final int type = (c >> 4) & 7;
1035 		long sz = c & 15;
1036 		int shift = 4;
1037 		int p = 1;
1038 		while ((c & 0x80) != 0) {
1039 			c = ib[p++] & 0xff;
1040 			sz += ((long) (c & 0x7f)) << shift;
1041 			shift += 7;
1042 		}
1043 
1044 		long deltaAt;
1045 		switch (type) {
1046 		case Constants.OBJ_COMMIT:
1047 		case Constants.OBJ_TREE:
1048 		case Constants.OBJ_BLOB:
1049 		case Constants.OBJ_TAG:
1050 			return sz;
1051 
1052 		case Constants.OBJ_OFS_DELTA:
1053 			c = ib[p++] & 0xff;
1054 			while ((c & 128) != 0)
1055 				c = ib[p++] & 0xff;
1056 			deltaAt = pos + p;
1057 			break;
1058 
1059 		case Constants.OBJ_REF_DELTA:
1060 			deltaAt = pos + p + 20;
1061 			break;
1062 
1063 		default:
1064 			throw new IOException(MessageFormat.format(
1065 					JGitText.get().unknownObjectType, Integer.valueOf(type)));
1066 		}
1067 
1068 		try {
1069 			return BinaryDelta.getResultSize(getDeltaHeader(curs, deltaAt));
1070 		} catch (DataFormatException e) {
1071 			throw new CorruptObjectException(MessageFormat.format(
1072 					JGitText.get().objectAtHasBadZlibStream, Long.valueOf(pos),
1073 					getPackFile()), e);
1074 		}
1075 	}
1076 
1077 	LocalObjectRepresentation representation(final WindowCursor curs,
1078 			final AnyObjectId objectId) throws IOException {
1079 		final long pos = idx().findOffset(objectId);
1080 		if (pos < 0)
1081 			return null;
1082 
1083 		final byte[] ib = curs.tempId;
1084 		readFully(pos, ib, 0, 20, curs);
1085 		int c = ib[0] & 0xff;
1086 		int p = 1;
1087 		final int typeCode = (c >> 4) & 7;
1088 		while ((c & 0x80) != 0)
1089 			c = ib[p++] & 0xff;
1090 
1091 		long len = (findEndOffset(pos) - pos);
1092 		switch (typeCode) {
1093 		case Constants.OBJ_COMMIT:
1094 		case Constants.OBJ_TREE:
1095 		case Constants.OBJ_BLOB:
1096 		case Constants.OBJ_TAG:
1097 			return LocalObjectRepresentation.newWhole(this, pos, len - p);
1098 
1099 		case Constants.OBJ_OFS_DELTA: {
1100 			c = ib[p++] & 0xff;
1101 			long ofs = c & 127;
1102 			while ((c & 128) != 0) {
1103 				ofs += 1;
1104 				c = ib[p++] & 0xff;
1105 				ofs <<= 7;
1106 				ofs += (c & 127);
1107 			}
1108 			ofs = pos - ofs;
1109 			return LocalObjectRepresentation.newDelta(this, pos, len - p, ofs);
1110 		}
1111 
1112 		case Constants.OBJ_REF_DELTA: {
1113 			len -= p;
1114 			len -= Constants.OBJECT_ID_LENGTH;
1115 			readFully(pos + p, ib, 0, 20, curs);
1116 			ObjectId id = ObjectId.fromRaw(ib);
1117 			return LocalObjectRepresentation.newDelta(this, pos, len, id);
1118 		}
1119 
1120 		default:
1121 			throw new IOException(
1122 					MessageFormat.format(JGitText.get().unknownObjectType,
1123 							Integer.valueOf(typeCode)));
1124 		}
1125 	}
1126 
1127 	private long findEndOffset(long startOffset)
1128 			throws IOException, CorruptObjectException {
1129 		final long maxOffset = length - 20;
1130 		return getReverseIdx().findNextOffset(startOffset, maxOffset);
1131 	}
1132 
1133 	synchronized PackBitmapIndex getBitmapIndex() throws IOException {
1134 		if (invalid || invalidBitmap)
1135 			return null;
1136 		if (bitmapIdx == null && hasExt(BITMAP_INDEX)) {
1137 			final PackBitmapIndex idx;
1138 			try {
1139 				idx = PackBitmapIndex.open(extFile(BITMAP_INDEX), idx(),
1140 						getReverseIdx());
1141 			} catch (FileNotFoundException e) {
1142 				// Once upon a time this bitmap file existed. Now it
1143 				// has been removed. Most likely an external gc  has
1144 				// removed this packfile and the bitmap
1145 				 invalidBitmap = true;
1146 				 return null;
1147 			}
1148 
1149 			// At this point, idx() will have set packChecksum.
1150 			if (Arrays.equals(packChecksum, idx.packChecksum))
1151 				bitmapIdx = idx;
1152 			else
1153 				invalidBitmap = true;
1154 		}
1155 		return bitmapIdx;
1156 	}
1157 
1158 	private synchronized PackReverseIndex getReverseIdx() throws IOException {
1159 		if (reverseIdx == null)
1160 			reverseIdx = new PackReverseIndex(idx());
1161 		return reverseIdx;
1162 	}
1163 
1164 	private boolean isCorrupt(long offset) {
1165 		LongList list = corruptObjects;
1166 		if (list == null)
1167 			return false;
1168 		synchronized (list) {
1169 			return list.contains(offset);
1170 		}
1171 	}
1172 
1173 	private void setCorrupt(long offset) {
1174 		LongList list = corruptObjects;
1175 		if (list == null) {
1176 			synchronized (readLock) {
1177 				list = corruptObjects;
1178 				if (list == null) {
1179 					list = new LongList();
1180 					corruptObjects = list;
1181 				}
1182 			}
1183 		}
1184 		synchronized (list) {
1185 			list.add(offset);
1186 		}
1187 	}
1188 
1189 	private File extFile(PackExt ext) {
1190 		String p = packFile.getName();
1191 		int dot = p.lastIndexOf('.');
1192 		String b = (dot < 0) ? p : p.substring(0, dot);
1193 		return new File(packFile.getParentFile(), b + '.' + ext.getExtension());
1194 	}
1195 
1196 	private boolean hasExt(PackExt ext) {
1197 		return (extensions & ext.getBit()) != 0;
1198 	}
1199 
1200 	@SuppressWarnings("nls")
1201 	@Override
1202 	public String toString() {
1203 		return "PackFile [packFileName=" + packFile.getName() + ", length="
1204 				+ packFile.length() + ", packChecksum="
1205 				+ ObjectId.fromRaw(packChecksum).name() + "]";
1206 	}
1207 }