View Javadoc
1   /*
2    * Copyright (C) 2008-2011, 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.dfs;
47  
48  import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.UNREACHABLE_GARBAGE;
49  import static org.eclipse.jgit.internal.storage.pack.PackExt.BITMAP_INDEX;
50  import static org.eclipse.jgit.internal.storage.pack.PackExt.INDEX;
51  import static org.eclipse.jgit.internal.storage.pack.PackExt.PACK;
52  
53  import java.io.BufferedInputStream;
54  import java.io.EOFException;
55  import java.io.IOException;
56  import java.io.InputStream;
57  import java.nio.ByteBuffer;
58  import java.nio.channels.Channels;
59  import java.text.MessageFormat;
60  import java.util.Set;
61  import java.util.zip.CRC32;
62  import java.util.zip.DataFormatException;
63  import java.util.zip.Inflater;
64  
65  import org.eclipse.jgit.errors.CorruptObjectException;
66  import org.eclipse.jgit.errors.LargeObjectException;
67  import org.eclipse.jgit.errors.MissingObjectException;
68  import org.eclipse.jgit.errors.PackInvalidException;
69  import org.eclipse.jgit.errors.StoredObjectRepresentationNotAvailableException;
70  import org.eclipse.jgit.internal.JGitText;
71  import org.eclipse.jgit.internal.storage.file.PackBitmapIndex;
72  import org.eclipse.jgit.internal.storage.file.PackIndex;
73  import org.eclipse.jgit.internal.storage.file.PackReverseIndex;
74  import org.eclipse.jgit.internal.storage.pack.BinaryDelta;
75  import org.eclipse.jgit.internal.storage.pack.PackOutputStream;
76  import org.eclipse.jgit.internal.storage.pack.StoredObjectRepresentation;
77  import org.eclipse.jgit.lib.AbbreviatedObjectId;
78  import org.eclipse.jgit.lib.AnyObjectId;
79  import org.eclipse.jgit.lib.Constants;
80  import org.eclipse.jgit.lib.ObjectId;
81  import org.eclipse.jgit.lib.ObjectLoader;
82  import org.eclipse.jgit.lib.Repository;
83  import org.eclipse.jgit.util.LongList;
84  
85  /**
86   * A Git version 2 pack file representation. A pack file contains Git objects in
87   * delta packed format yielding high compression of lots of object where some
88   * objects are similar.
89   */
90  public final class DfsPackFile extends BlockBasedFile {
91  	/**
92  	 * Lock for initialization of {@link #index} and {@link #corruptObjects}.
93  	 * <p>
94  	 * This lock ensures only one thread can perform the initialization work.
95  	 */
96  	private final Object initLock = new Object();
97  
98  	/** Index mapping {@link ObjectId} to position within the pack stream. */
99  	private volatile DfsBlockCache.Ref<PackIndex> index;
100 
101 	/** Reverse version of {@link #index} mapping position to {@link ObjectId}. */
102 	private volatile DfsBlockCache.Ref<PackReverseIndex> reverseIndex;
103 
104 	/** Index of compressed bitmap mapping entire object graph. */
105 	private volatile DfsBlockCache.Ref<PackBitmapIndex> bitmapIndex;
106 
107 	/**
108 	 * Objects we have tried to read, and discovered to be corrupt.
109 	 * <p>
110 	 * The list is allocated after the first corruption is found, and filled in
111 	 * as more entries are discovered. Typically this list is never used, as
112 	 * pack files do not usually contain corrupt objects.
113 	 */
114 	private volatile LongList corruptObjects;
115 
116 	/**
117 	 * Construct a reader for an existing, packfile.
118 	 *
119 	 * @param cache
120 	 *            cache that owns the pack data.
121 	 * @param desc
122 	 *            description of the pack within the DFS.
123 	 */
124 	DfsPackFile(DfsBlockCache cache, DfsPackDescription desc) {
125 		super(cache, desc, PACK);
126 
127 		int bs = desc.getBlockSize(PACK);
128 		if (bs > 0) {
129 			setBlockSize(bs);
130 		}
131 
132 		long sz = desc.getFileSize(PACK);
133 		length = sz > 0 ? sz : -1;
134 	}
135 
136 	/**
137 	 * Get description that was originally used to configure this pack file.
138 	 *
139 	 * @return description that was originally used to configure this pack file.
140 	 */
141 	public DfsPackDescription getPackDescription() {
142 		return desc;
143 	}
144 
145 	/**
146 	 * Whether the pack index file is loaded and cached in memory.
147 	 *
148 	 * @return whether the pack index file is loaded and cached in memory.
149 	 */
150 	public boolean isIndexLoaded() {
151 		DfsBlockCache.Ref<PackIndex> idxref = index;
152 		return idxref != null && idxref.has();
153 	}
154 
155 	void setPackIndex(PackIndex idx) {
156 		long objCnt = idx.getObjectCount();
157 		int recSize = Constants.OBJECT_ID_LENGTH + 8;
158 		long sz = objCnt * recSize;
159 		index = cache.putRef(desc.getStreamKey(INDEX), sz, idx);
160 	}
161 
162 	/**
163 	 * Get the PackIndex for this PackFile.
164 	 *
165 	 * @param ctx
166 	 *            reader context to support reading from the backing store if
167 	 *            the index is not already loaded in memory.
168 	 * @return the PackIndex.
169 	 * @throws java.io.IOException
170 	 *             the pack index is not available, or is corrupt.
171 	 */
172 	public PackIndex getPackIndex(DfsReader ctx) throws IOException {
173 		return idx(ctx);
174 	}
175 
176 	private PackIndex idx(DfsReader ctx) throws IOException {
177 		DfsBlockCache.Ref<PackIndex> idxref = index;
178 		if (idxref != null) {
179 			PackIndex idx = idxref.get();
180 			if (idx != null)
181 				return idx;
182 		}
183 
184 		if (invalid)
185 			throw new PackInvalidException(getFileName());
186 
187 		Repository.getGlobalListenerList()
188 				.dispatch(new BeforeDfsPackIndexLoadedEvent(this));
189 
190 		synchronized (initLock) {
191 			idxref = index;
192 			if (idxref != null) {
193 				PackIndex idx = idxref.get();
194 				if (idx != null)
195 					return idx;
196 			}
197 
198 			DfsStreamKey idxKey = desc.getStreamKey(INDEX);
199 			idxref = cache.getRef(idxKey);
200 			if (idxref != null) {
201 				PackIndex idx = idxref.get();
202 				if (idx != null) {
203 					index = idxref;
204 					return idx;
205 				}
206 			}
207 
208 			PackIndex idx;
209 			try {
210 				ctx.stats.readIdx++;
211 				long start = System.nanoTime();
212 				try (ReadableChannel rc = ctx.db.openFile(desc, INDEX)) {
213 					InputStream in = Channels.newInputStream(rc);
214 					int wantSize = 8192;
215 					int bs = rc.blockSize();
216 					if (0 < bs && bs < wantSize)
217 						bs = (wantSize / bs) * bs;
218 					else if (bs <= 0)
219 						bs = wantSize;
220 					idx = PackIndex.read(new BufferedInputStream(in, bs));
221 					ctx.stats.readIdxBytes += rc.position();
222 				} finally {
223 					ctx.stats.readIdxMicros += elapsedMicros(start);
224 				}
225 			} catch (EOFException e) {
226 				invalid = true;
227 				throw new IOException(MessageFormat.format(
228 						DfsText.get().shortReadOfIndex,
229 						desc.getFileName(INDEX)), e);
230 			} catch (IOException e) {
231 				invalid = true;
232 				throw new IOException(MessageFormat.format(
233 						DfsText.get().cannotReadIndex,
234 						desc.getFileName(INDEX)), e);
235 			}
236 
237 			setPackIndex(idx);
238 			return idx;
239 		}
240 	}
241 
242 	final boolean isGarbage() {
243 		return desc.getPackSource() == UNREACHABLE_GARBAGE;
244 	}
245 
246 	PackBitmapIndex getBitmapIndex(DfsReader ctx) throws IOException {
247 		if (invalid || isGarbage() || !desc.hasFileExt(BITMAP_INDEX))
248 			return null;
249 
250 		DfsBlockCache.Ref<PackBitmapIndex> idxref = bitmapIndex;
251 		if (idxref != null) {
252 			PackBitmapIndex idx = idxref.get();
253 			if (idx != null)
254 				return idx;
255 		}
256 
257 		synchronized (initLock) {
258 			idxref = bitmapIndex;
259 			if (idxref != null) {
260 				PackBitmapIndex idx = idxref.get();
261 				if (idx != null)
262 					return idx;
263 			}
264 
265 			DfsStreamKey bitmapKey = desc.getStreamKey(BITMAP_INDEX);
266 			idxref = cache.getRef(bitmapKey);
267 			if (idxref != null) {
268 				PackBitmapIndex idx = idxref.get();
269 				if (idx != null) {
270 					bitmapIndex = idxref;
271 					return idx;
272 				}
273 			}
274 
275 			long size;
276 			PackBitmapIndex idx;
277 			ctx.stats.readBitmap++;
278 			long start = System.nanoTime();
279 			try (ReadableChannel rc = ctx.db.openFile(desc, BITMAP_INDEX)) {
280 				try {
281 					InputStream in = Channels.newInputStream(rc);
282 					int wantSize = 8192;
283 					int bs = rc.blockSize();
284 					if (0 < bs && bs < wantSize)
285 						bs = (wantSize / bs) * bs;
286 					else if (bs <= 0)
287 						bs = wantSize;
288 					in = new BufferedInputStream(in, bs);
289 					idx = PackBitmapIndex.read(
290 							in, idx(ctx), getReverseIdx(ctx));
291 				} finally {
292 					size = rc.position();
293 					ctx.stats.readIdxBytes += size;
294 					ctx.stats.readIdxMicros += elapsedMicros(start);
295 				}
296 			} catch (EOFException e) {
297 				throw new IOException(MessageFormat.format(
298 						DfsText.get().shortReadOfIndex,
299 						desc.getFileName(BITMAP_INDEX)), e);
300 			} catch (IOException e) {
301 				throw new IOException(MessageFormat.format(
302 						DfsText.get().cannotReadIndex,
303 						desc.getFileName(BITMAP_INDEX)), e);
304 			}
305 
306 			bitmapIndex = cache.putRef(bitmapKey, size, idx);
307 			return idx;
308 		}
309 	}
310 
311 	PackReverseIndex getReverseIdx(DfsReader ctx) throws IOException {
312 		DfsBlockCache.Ref<PackReverseIndex> revref = reverseIndex;
313 		if (revref != null) {
314 			PackReverseIndex revidx = revref.get();
315 			if (revidx != null)
316 				return revidx;
317 		}
318 
319 		synchronized (initLock) {
320 			revref = reverseIndex;
321 			if (revref != null) {
322 				PackReverseIndex revidx = revref.get();
323 				if (revidx != null)
324 					return revidx;
325 			}
326 
327 			DfsStreamKey revKey =
328 					new DfsStreamKey.ForReverseIndex(desc.getStreamKey(INDEX));
329 			revref = cache.getRef(revKey);
330 			if (revref != null) {
331 				PackReverseIndex idx = revref.get();
332 				if (idx != null) {
333 					reverseIndex = revref;
334 					return idx;
335 				}
336 			}
337 
338 			PackIndex idx = idx(ctx);
339 			PackReverseIndex revidx = new PackReverseIndex(idx);
340 			long cnt = idx.getObjectCount();
341 			reverseIndex = cache.putRef(revKey, cnt * 8, revidx);
342 			return revidx;
343 		}
344 	}
345 
346 	/**
347 	 * Check if an object is stored within this pack.
348 	 *
349 	 * @param ctx
350 	 *            reader context to support reading from the backing store if
351 	 *            the index is not already loaded in memory.
352 	 * @param id
353 	 *            object to be located.
354 	 * @return true if the object exists in this pack; false if it does not.
355 	 * @throws java.io.IOException
356 	 *             the pack index is not available, or is corrupt.
357 	 */
358 	public boolean hasObject(DfsReader ctx, AnyObjectId id) throws IOException {
359 		final long offset = idx(ctx).findOffset(id);
360 		return 0 < offset && !isCorrupt(offset);
361 	}
362 
363 	/**
364 	 * Get an object from this pack.
365 	 *
366 	 * @param ctx
367 	 *            temporary working space associated with the calling thread.
368 	 * @param id
369 	 *            the object to obtain from the pack. Must not be null.
370 	 * @return the object loader for the requested object if it is contained in
371 	 *         this pack; null if the object was not found.
372 	 * @throws IOException
373 	 *             the pack file or the index could not be read.
374 	 */
375 	ObjectLoader get(DfsReader ctx, AnyObjectId id)
376 			throws IOException {
377 		long offset = idx(ctx).findOffset(id);
378 		return 0 < offset && !isCorrupt(offset) ? load(ctx, offset) : null;
379 	}
380 
381 	long findOffset(DfsReader ctx, AnyObjectId id) throws IOException {
382 		return idx(ctx).findOffset(id);
383 	}
384 
385 	void resolve(DfsReader ctx, Set<ObjectId> matches, AbbreviatedObjectId id,
386 			int matchLimit) throws IOException {
387 		idx(ctx).resolve(matches, id, matchLimit);
388 	}
389 
390 	/**
391 	 * Obtain the total number of objects available in this pack. This method
392 	 * relies on pack index, giving number of effectively available objects.
393 	 *
394 	 * @param ctx
395 	 *            current reader for the calling thread.
396 	 * @return number of objects in index of this pack, likewise in this pack
397 	 * @throws IOException
398 	 *             the index file cannot be loaded into memory.
399 	 */
400 	long getObjectCount(DfsReader ctx) throws IOException {
401 		return idx(ctx).getObjectCount();
402 	}
403 
404 	private byte[] decompress(long position, int sz, DfsReader ctx)
405 			throws IOException, DataFormatException {
406 		byte[] dstbuf;
407 		try {
408 			dstbuf = new byte[sz];
409 		} catch (OutOfMemoryError noMemory) {
410 			// The size may be larger than our heap allows, return null to
411 			// let the caller know allocation isn't possible and it should
412 			// use the large object streaming approach instead.
413 			//
414 			// For example, this can occur when sz is 640 MB, and JRE
415 			// maximum heap size is only 256 MB. Even if the JRE has
416 			// 200 MB free, it cannot allocate a 640 MB byte array.
417 			return null;
418 		}
419 
420 		if (ctx.inflate(this, position, dstbuf, false) != sz)
421 			throw new EOFException(MessageFormat.format(
422 					JGitText.get().shortCompressedStreamAt,
423 					Long.valueOf(position)));
424 		return dstbuf;
425 	}
426 
427 	void copyPackAsIs(PackOutputStream out, DfsReader ctx)
428 			throws IOException {
429 		// If the length hasn't been determined yet, pin to set it.
430 		if (length == -1) {
431 			ctx.pin(this, 0);
432 			ctx.unpin();
433 		}
434 		if (cache.shouldCopyThroughCache(length))
435 			copyPackThroughCache(out, ctx);
436 		else
437 			copyPackBypassCache(out, ctx);
438 	}
439 
440 	private void copyPackThroughCache(PackOutputStream out, DfsReader ctx)
441 			throws IOException {
442 		@SuppressWarnings("resource") // Explicitly closed in finally block
443 		ReadableChannel rc = null;
444 		try {
445 			long position = 12;
446 			long remaining = length - (12 + 20);
447 			while (0 < remaining) {
448 				DfsBlock b;
449 				if (rc != null) {
450 					b = cache.getOrLoad(this, position, ctx, rc);
451 				} else {
452 					b = cache.get(key, alignToBlock(position));
453 					if (b == null) {
454 						rc = ctx.db.openFile(desc, PACK);
455 						int sz = ctx.getOptions().getStreamPackBufferSize();
456 						if (sz > 0) {
457 							rc.setReadAheadBytes(sz);
458 						}
459 						b = cache.getOrLoad(this, position, ctx, rc);
460 					}
461 				}
462 
463 				int ptr = (int) (position - b.start);
464 				int n = (int) Math.min(b.size() - ptr, remaining);
465 				b.write(out, position, n);
466 				position += n;
467 				remaining -= n;
468 			}
469 		} finally {
470 			if (rc != null) {
471 				rc.close();
472 			}
473 		}
474 	}
475 
476 	private long copyPackBypassCache(PackOutputStream out, DfsReader ctx)
477 			throws IOException {
478 		try (ReadableChannel rc = ctx.db.openFile(desc, PACK)) {
479 			ByteBuffer buf = newCopyBuffer(out, rc);
480 			if (ctx.getOptions().getStreamPackBufferSize() > 0)
481 				rc.setReadAheadBytes(ctx.getOptions().getStreamPackBufferSize());
482 			long position = 12;
483 			long remaining = length - (12 + 20);
484 			boolean packHeadSkipped = false;
485 			while (0 < remaining) {
486 				DfsBlock b = cache.get(key, alignToBlock(position));
487 				if (b != null) {
488 					int ptr = (int) (position - b.start);
489 					int n = (int) Math.min(b.size() - ptr, remaining);
490 					b.write(out, position, n);
491 					position += n;
492 					remaining -= n;
493 					rc.position(position);
494 					packHeadSkipped = true;
495 					continue;
496 				}
497 
498 				buf.position(0);
499 				int n = read(rc, buf);
500 				if (n <= 0)
501 					throw packfileIsTruncated();
502 				else if (n > remaining)
503 					n = (int) remaining;
504 
505 				if (!packHeadSkipped) {
506 					// Need skip the 'PACK' header for the first read
507 					out.write(buf.array(), 12, n - 12);
508 					packHeadSkipped = true;
509 				} else {
510 					out.write(buf.array(), 0, n);
511 				}
512 				position += n;
513 				remaining -= n;
514 			}
515 			return position;
516 		}
517 	}
518 
519 	private ByteBuffer newCopyBuffer(PackOutputStream out, ReadableChannel rc) {
520 		int bs = blockSize(rc);
521 		byte[] copyBuf = out.getCopyBuffer();
522 		if (bs > copyBuf.length)
523 			copyBuf = new byte[bs];
524 		return ByteBuffer.wrap(copyBuf, 0, bs);
525 	}
526 
527 	void copyAsIs(PackOutputStream out, DfsObjectToPack src,
528 			boolean validate, DfsReader ctx) throws IOException,
529 			StoredObjectRepresentationNotAvailableException {
530 		final CRC32 crc1 = validate ? new CRC32() : null;
531 		final CRC32 crc2 = validate ? new CRC32() : null;
532 		final byte[] buf = out.getCopyBuffer();
533 
534 		// Rip apart the header so we can discover the size.
535 		//
536 		try {
537 			readFully(src.offset, buf, 0, 20, ctx);
538 		} catch (IOException ioError) {
539 			throw new StoredObjectRepresentationNotAvailableException(src,
540 					ioError);
541 		}
542 		int c = buf[0] & 0xff;
543 		final int typeCode = (c >> 4) & 7;
544 		long inflatedLength = c & 15;
545 		int shift = 4;
546 		int headerCnt = 1;
547 		while ((c & 0x80) != 0) {
548 			c = buf[headerCnt++] & 0xff;
549 			inflatedLength += ((long) (c & 0x7f)) << shift;
550 			shift += 7;
551 		}
552 
553 		if (typeCode == Constants.OBJ_OFS_DELTA) {
554 			do {
555 				c = buf[headerCnt++] & 0xff;
556 			} while ((c & 128) != 0);
557 			if (validate) {
558 				assert(crc1 != null && crc2 != null);
559 				crc1.update(buf, 0, headerCnt);
560 				crc2.update(buf, 0, headerCnt);
561 			}
562 		} else if (typeCode == Constants.OBJ_REF_DELTA) {
563 			if (validate) {
564 				assert(crc1 != null && crc2 != null);
565 				crc1.update(buf, 0, headerCnt);
566 				crc2.update(buf, 0, headerCnt);
567 			}
568 
569 			readFully(src.offset + headerCnt, buf, 0, 20, ctx);
570 			if (validate) {
571 				assert(crc1 != null && crc2 != null);
572 				crc1.update(buf, 0, 20);
573 				crc2.update(buf, 0, 20);
574 			}
575 			headerCnt += 20;
576 		} else if (validate) {
577 			assert(crc1 != null && crc2 != null);
578 			crc1.update(buf, 0, headerCnt);
579 			crc2.update(buf, 0, headerCnt);
580 		}
581 
582 		final long dataOffset = src.offset + headerCnt;
583 		final long dataLength = src.length;
584 		final long expectedCRC;
585 		final DfsBlock quickCopy;
586 
587 		// Verify the object isn't corrupt before sending. If it is,
588 		// we report it missing instead.
589 		//
590 		try {
591 			quickCopy = ctx.quickCopy(this, dataOffset, dataLength);
592 
593 			if (validate && idx(ctx).hasCRC32Support()) {
594 				assert(crc1 != null);
595 				// Index has the CRC32 code cached, validate the object.
596 				//
597 				expectedCRC = idx(ctx).findCRC32(src);
598 				if (quickCopy != null) {
599 					quickCopy.crc32(crc1, dataOffset, (int) dataLength);
600 				} else {
601 					long pos = dataOffset;
602 					long cnt = dataLength;
603 					while (cnt > 0) {
604 						final int n = (int) Math.min(cnt, buf.length);
605 						readFully(pos, buf, 0, n, ctx);
606 						crc1.update(buf, 0, n);
607 						pos += n;
608 						cnt -= n;
609 					}
610 				}
611 				if (crc1.getValue() != expectedCRC) {
612 					setCorrupt(src.offset);
613 					throw new CorruptObjectException(MessageFormat.format(
614 							JGitText.get().objectAtHasBadZlibStream,
615 							Long.valueOf(src.offset), getFileName()));
616 				}
617 			} else if (validate) {
618 				assert(crc1 != null);
619 				// We don't have a CRC32 code in the index, so compute it
620 				// now while inflating the raw data to get zlib to tell us
621 				// whether or not the data is safe.
622 				//
623 				Inflater inf = ctx.inflater();
624 				byte[] tmp = new byte[1024];
625 				if (quickCopy != null) {
626 					quickCopy.check(inf, tmp, dataOffset, (int) dataLength);
627 				} else {
628 					long pos = dataOffset;
629 					long cnt = dataLength;
630 					while (cnt > 0) {
631 						final int n = (int) Math.min(cnt, buf.length);
632 						readFully(pos, buf, 0, n, ctx);
633 						crc1.update(buf, 0, n);
634 						inf.setInput(buf, 0, n);
635 						while (inf.inflate(tmp, 0, tmp.length) > 0)
636 							continue;
637 						pos += n;
638 						cnt -= n;
639 					}
640 				}
641 				if (!inf.finished() || inf.getBytesRead() != dataLength) {
642 					setCorrupt(src.offset);
643 					throw new EOFException(MessageFormat.format(
644 							JGitText.get().shortCompressedStreamAt,
645 							Long.valueOf(src.offset)));
646 				}
647 				expectedCRC = crc1.getValue();
648 			} else {
649 				expectedCRC = -1;
650 			}
651 		} catch (DataFormatException dataFormat) {
652 			setCorrupt(src.offset);
653 
654 			CorruptObjectException corruptObject = new CorruptObjectException(
655 					MessageFormat.format(
656 							JGitText.get().objectAtHasBadZlibStream,
657 							Long.valueOf(src.offset), getFileName()),
658 					dataFormat);
659 
660 			throw new StoredObjectRepresentationNotAvailableException(src,
661 					corruptObject);
662 
663 		} catch (IOException ioError) {
664 			throw new StoredObjectRepresentationNotAvailableException(src,
665 					ioError);
666 		}
667 
668 		if (quickCopy != null) {
669 			// The entire object fits into a single byte array window slice,
670 			// and we have it pinned.  Write this out without copying.
671 			//
672 			out.writeHeader(src, inflatedLength);
673 			quickCopy.write(out, dataOffset, (int) dataLength);
674 
675 		} else if (dataLength <= buf.length) {
676 			// Tiny optimization: Lots of objects are very small deltas or
677 			// deflated commits that are likely to fit in the copy buffer.
678 			//
679 			if (!validate) {
680 				long pos = dataOffset;
681 				long cnt = dataLength;
682 				while (cnt > 0) {
683 					final int n = (int) Math.min(cnt, buf.length);
684 					readFully(pos, buf, 0, n, ctx);
685 					pos += n;
686 					cnt -= n;
687 				}
688 			}
689 			out.writeHeader(src, inflatedLength);
690 			out.write(buf, 0, (int) dataLength);
691 		} else {
692 			// Now we are committed to sending the object. As we spool it out,
693 			// check its CRC32 code to make sure there wasn't corruption between
694 			// the verification we did above, and us actually outputting it.
695 			//
696 			out.writeHeader(src, inflatedLength);
697 			long pos = dataOffset;
698 			long cnt = dataLength;
699 			while (cnt > 0) {
700 				final int n = (int) Math.min(cnt, buf.length);
701 				readFully(pos, buf, 0, n, ctx);
702 				if (validate) {
703 					assert(crc2 != null);
704 					crc2.update(buf, 0, n);
705 				}
706 				out.write(buf, 0, n);
707 				pos += n;
708 				cnt -= n;
709 			}
710 			if (validate) {
711 				assert(crc2 != null);
712 				if (crc2.getValue() != expectedCRC) {
713 					throw new CorruptObjectException(MessageFormat.format(
714 							JGitText.get().objectAtHasBadZlibStream,
715 							Long.valueOf(src.offset), getFileName()));
716 				}
717 			}
718 		}
719 	}
720 
721 	private IOException packfileIsTruncated() {
722 		invalid = true;
723 		return new IOException(MessageFormat.format(
724 				JGitText.get().packfileIsTruncated, getFileName()));
725 	}
726 
727 	private void readFully(long position, byte[] dstbuf, int dstoff, int cnt,
728 			DfsReader ctx) throws IOException {
729 		if (ctx.copy(this, position, dstbuf, dstoff, cnt) != cnt)
730 			throw new EOFException();
731 	}
732 
733 	ObjectLoader load(DfsReader ctx, long pos)
734 			throws IOException {
735 		try {
736 			final byte[] ib = ctx.tempId;
737 			Delta delta = null;
738 			byte[] data = null;
739 			int type = Constants.OBJ_BAD;
740 			boolean cached = false;
741 
742 			SEARCH: for (;;) {
743 				readFully(pos, ib, 0, 20, ctx);
744 				int c = ib[0] & 0xff;
745 				final int typeCode = (c >> 4) & 7;
746 				long sz = c & 15;
747 				int shift = 4;
748 				int p = 1;
749 				while ((c & 0x80) != 0) {
750 					c = ib[p++] & 0xff;
751 					sz += ((long) (c & 0x7f)) << shift;
752 					shift += 7;
753 				}
754 
755 				switch (typeCode) {
756 				case Constants.OBJ_COMMIT:
757 				case Constants.OBJ_TREE:
758 				case Constants.OBJ_BLOB:
759 				case Constants.OBJ_TAG: {
760 					if (delta != null) {
761 						data = decompress(pos + p, (int) sz, ctx);
762 						type = typeCode;
763 						break SEARCH;
764 					}
765 
766 					if (sz < ctx.getStreamFileThreshold()) {
767 						data = decompress(pos + p, (int) sz, ctx);
768 						if (data != null)
769 							return new ObjectLoader.SmallObject(typeCode, data);
770 					}
771 					return new LargePackedWholeObject(typeCode, sz, pos, p, this, ctx.db);
772 				}
773 
774 				case Constants.OBJ_OFS_DELTA: {
775 					c = ib[p++] & 0xff;
776 					long base = c & 127;
777 					while ((c & 128) != 0) {
778 						base += 1;
779 						c = ib[p++] & 0xff;
780 						base <<= 7;
781 						base += (c & 127);
782 					}
783 					base = pos - base;
784 					delta = new Delta(delta, pos, (int) sz, p, base);
785 					if (sz != delta.deltaSize)
786 						break SEARCH;
787 
788 					DeltaBaseCache.Entry e = ctx.getDeltaBaseCache().get(key, base);
789 					if (e != null) {
790 						type = e.type;
791 						data = e.data;
792 						cached = true;
793 						break SEARCH;
794 					}
795 					pos = base;
796 					continue SEARCH;
797 				}
798 
799 				case Constants.OBJ_REF_DELTA: {
800 					readFully(pos + p, ib, 0, 20, ctx);
801 					long base = findDeltaBase(ctx, ObjectId.fromRaw(ib));
802 					delta = new Delta(delta, pos, (int) sz, p + 20, base);
803 					if (sz != delta.deltaSize)
804 						break SEARCH;
805 
806 					DeltaBaseCache.Entry e = ctx.getDeltaBaseCache().get(key, base);
807 					if (e != null) {
808 						type = e.type;
809 						data = e.data;
810 						cached = true;
811 						break SEARCH;
812 					}
813 					pos = base;
814 					continue SEARCH;
815 				}
816 
817 				default:
818 					throw new IOException(MessageFormat.format(
819 							JGitText.get().unknownObjectType, Integer.valueOf(typeCode)));
820 				}
821 			}
822 
823 			// At this point there is at least one delta to apply to data.
824 			// (Whole objects with no deltas to apply return early above.)
825 
826 			if (data == null)
827 				throw new LargeObjectException();
828 
829 			assert(delta != null);
830 			do {
831 				// Cache only the base immediately before desired object.
832 				if (cached)
833 					cached = false;
834 				else if (delta.next == null)
835 					ctx.getDeltaBaseCache().put(key, delta.basePos, type, data);
836 
837 				pos = delta.deltaPos;
838 
839 				byte[] cmds = decompress(pos + delta.hdrLen, delta.deltaSize, ctx);
840 				if (cmds == null) {
841 					data = null; // Discard base in case of OutOfMemoryError
842 					throw new LargeObjectException();
843 				}
844 
845 				final long sz = BinaryDelta.getResultSize(cmds);
846 				if (Integer.MAX_VALUE <= sz)
847 					throw new LargeObjectException.ExceedsByteArrayLimit();
848 
849 				final byte[] result;
850 				try {
851 					result = new byte[(int) sz];
852 				} catch (OutOfMemoryError tooBig) {
853 					data = null; // Discard base in case of OutOfMemoryError
854 					cmds = null;
855 					throw new LargeObjectException.OutOfMemory(tooBig);
856 				}
857 
858 				BinaryDelta.apply(data, cmds, result);
859 				data = result;
860 				delta = delta.next;
861 			} while (delta != null);
862 
863 			return new ObjectLoader.SmallObject(type, data);
864 
865 		} catch (DataFormatException dfe) {
866 			throw new CorruptObjectException(
867 					MessageFormat.format(
868 							JGitText.get().objectAtHasBadZlibStream, Long.valueOf(pos),
869 							getFileName()),
870 					dfe);
871 		}
872 	}
873 
874 	private long findDeltaBase(DfsReader ctx, ObjectId baseId)
875 			throws IOException, MissingObjectException {
876 		long ofs = idx(ctx).findOffset(baseId);
877 		if (ofs < 0)
878 			throw new MissingObjectException(baseId,
879 					JGitText.get().missingDeltaBase);
880 		return ofs;
881 	}
882 
883 	private static class Delta {
884 		/** Child that applies onto this object. */
885 		final Delta next;
886 
887 		/** Offset of the delta object. */
888 		final long deltaPos;
889 
890 		/** Size of the inflated delta stream. */
891 		final int deltaSize;
892 
893 		/** Total size of the delta's pack entry header (including base). */
894 		final int hdrLen;
895 
896 		/** Offset of the base object this delta applies onto. */
897 		final long basePos;
898 
899 		Delta(Delta next, long ofs, int sz, int hdrLen, long baseOffset) {
900 			this.next = next;
901 			this.deltaPos = ofs;
902 			this.deltaSize = sz;
903 			this.hdrLen = hdrLen;
904 			this.basePos = baseOffset;
905 		}
906 	}
907 
908 	byte[] getDeltaHeader(DfsReader wc, long pos)
909 			throws IOException, DataFormatException {
910 		// The delta stream starts as two variable length integers. If we
911 		// assume they are 64 bits each, we need 16 bytes to encode them,
912 		// plus 2 extra bytes for the variable length overhead. So 18 is
913 		// the longest delta instruction header.
914 		//
915 		final byte[] hdr = new byte[32];
916 		wc.inflate(this, pos, hdr, true /* header only */);
917 		return hdr;
918 	}
919 
920 	int getObjectType(DfsReader ctx, long pos) throws IOException {
921 		final byte[] ib = ctx.tempId;
922 		for (;;) {
923 			readFully(pos, ib, 0, 20, ctx);
924 			int c = ib[0] & 0xff;
925 			final int type = (c >> 4) & 7;
926 
927 			switch (type) {
928 			case Constants.OBJ_COMMIT:
929 			case Constants.OBJ_TREE:
930 			case Constants.OBJ_BLOB:
931 			case Constants.OBJ_TAG:
932 				return type;
933 
934 			case Constants.OBJ_OFS_DELTA: {
935 				int p = 1;
936 				while ((c & 0x80) != 0)
937 					c = ib[p++] & 0xff;
938 				c = ib[p++] & 0xff;
939 				long ofs = c & 127;
940 				while ((c & 128) != 0) {
941 					ofs += 1;
942 					c = ib[p++] & 0xff;
943 					ofs <<= 7;
944 					ofs += (c & 127);
945 				}
946 				pos = pos - ofs;
947 				continue;
948 			}
949 
950 			case Constants.OBJ_REF_DELTA: {
951 				int p = 1;
952 				while ((c & 0x80) != 0)
953 					c = ib[p++] & 0xff;
954 				readFully(pos + p, ib, 0, 20, ctx);
955 				pos = findDeltaBase(ctx, ObjectId.fromRaw(ib));
956 				continue;
957 			}
958 
959 			default:
960 				throw new IOException(MessageFormat.format(
961 						JGitText.get().unknownObjectType, Integer.valueOf(type)));
962 			}
963 		}
964 	}
965 
966 	long getObjectSize(DfsReader ctx, AnyObjectId id) throws IOException {
967 		final long offset = idx(ctx).findOffset(id);
968 		return 0 < offset ? getObjectSize(ctx, offset) : -1;
969 	}
970 
971 	long getObjectSize(DfsReader ctx, long pos)
972 			throws IOException {
973 		final byte[] ib = ctx.tempId;
974 		readFully(pos, ib, 0, 20, ctx);
975 		int c = ib[0] & 0xff;
976 		final int type = (c >> 4) & 7;
977 		long sz = c & 15;
978 		int shift = 4;
979 		int p = 1;
980 		while ((c & 0x80) != 0) {
981 			c = ib[p++] & 0xff;
982 			sz += ((long) (c & 0x7f)) << shift;
983 			shift += 7;
984 		}
985 
986 		long deltaAt;
987 		switch (type) {
988 		case Constants.OBJ_COMMIT:
989 		case Constants.OBJ_TREE:
990 		case Constants.OBJ_BLOB:
991 		case Constants.OBJ_TAG:
992 			return sz;
993 
994 		case Constants.OBJ_OFS_DELTA:
995 			c = ib[p++] & 0xff;
996 			while ((c & 128) != 0)
997 				c = ib[p++] & 0xff;
998 			deltaAt = pos + p;
999 			break;
1000 
1001 		case Constants.OBJ_REF_DELTA:
1002 			deltaAt = pos + p + 20;
1003 			break;
1004 
1005 		default:
1006 			throw new IOException(MessageFormat.format(
1007 					JGitText.get().unknownObjectType, Integer.valueOf(type)));
1008 		}
1009 
1010 		try {
1011 			return BinaryDelta.getResultSize(getDeltaHeader(ctx, deltaAt));
1012 		} catch (DataFormatException dfe) {
1013 			throw new CorruptObjectException(
1014 					MessageFormat.format(
1015 							JGitText.get().objectAtHasBadZlibStream, Long.valueOf(pos),
1016 							getFileName()),
1017 					dfe);
1018 		}
1019 	}
1020 
1021 	void representation(DfsObjectRepresentation r, final long pos,
1022 			DfsReader ctx, PackReverseIndex rev)
1023 			throws IOException {
1024 		r.offset = pos;
1025 		final byte[] ib = ctx.tempId;
1026 		readFully(pos, ib, 0, 20, ctx);
1027 		int c = ib[0] & 0xff;
1028 		int p = 1;
1029 		final int typeCode = (c >> 4) & 7;
1030 		while ((c & 0x80) != 0)
1031 			c = ib[p++] & 0xff;
1032 
1033 		long len = rev.findNextOffset(pos, length - 20) - pos;
1034 		switch (typeCode) {
1035 		case Constants.OBJ_COMMIT:
1036 		case Constants.OBJ_TREE:
1037 		case Constants.OBJ_BLOB:
1038 		case Constants.OBJ_TAG:
1039 			r.format = StoredObjectRepresentation.PACK_WHOLE;
1040 			r.baseId = null;
1041 			r.length = len - p;
1042 			return;
1043 
1044 		case Constants.OBJ_OFS_DELTA: {
1045 			c = ib[p++] & 0xff;
1046 			long ofs = c & 127;
1047 			while ((c & 128) != 0) {
1048 				ofs += 1;
1049 				c = ib[p++] & 0xff;
1050 				ofs <<= 7;
1051 				ofs += (c & 127);
1052 			}
1053 			r.format = StoredObjectRepresentation.PACK_DELTA;
1054 			r.baseId = rev.findObject(pos - ofs);
1055 			r.length = len - p;
1056 			return;
1057 		}
1058 
1059 		case Constants.OBJ_REF_DELTA: {
1060 			readFully(pos + p, ib, 0, 20, ctx);
1061 			r.format = StoredObjectRepresentation.PACK_DELTA;
1062 			r.baseId = ObjectId.fromRaw(ib);
1063 			r.length = len - p - 20;
1064 			return;
1065 		}
1066 
1067 		default:
1068 			throw new IOException(MessageFormat.format(
1069 					JGitText.get().unknownObjectType, Integer.valueOf(typeCode)));
1070 		}
1071 	}
1072 
1073 	boolean isCorrupt(long offset) {
1074 		LongList list = corruptObjects;
1075 		if (list == null)
1076 			return false;
1077 		synchronized (list) {
1078 			return list.contains(offset);
1079 		}
1080 	}
1081 
1082 	private void setCorrupt(long offset) {
1083 		LongList list = corruptObjects;
1084 		if (list == null) {
1085 			synchronized (initLock) {
1086 				list = corruptObjects;
1087 				if (list == null) {
1088 					list = new LongList();
1089 					corruptObjects = list;
1090 				}
1091 			}
1092 		}
1093 		synchronized (list) {
1094 			list.add(offset);
1095 		}
1096 	}
1097 }