DfsPackFile.java

  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> 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. package org.eclipse.jgit.internal.storage.dfs;

  13. import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.UNREACHABLE_GARBAGE;
  14. import static org.eclipse.jgit.internal.storage.pack.PackExt.BITMAP_INDEX;
  15. import static org.eclipse.jgit.internal.storage.pack.PackExt.INDEX;
  16. import static org.eclipse.jgit.internal.storage.pack.PackExt.PACK;
  17. import static org.eclipse.jgit.internal.storage.pack.PackExt.REVERSE_INDEX;

  18. import java.io.BufferedInputStream;
  19. import java.io.EOFException;
  20. import java.io.IOException;
  21. import java.io.InputStream;
  22. import java.nio.ByteBuffer;
  23. import java.nio.channels.Channels;
  24. import java.text.MessageFormat;
  25. import java.util.Set;
  26. import java.util.concurrent.atomic.AtomicBoolean;
  27. import java.util.zip.CRC32;
  28. import java.util.zip.DataFormatException;
  29. import java.util.zip.Inflater;

  30. import org.eclipse.jgit.errors.CorruptObjectException;
  31. import org.eclipse.jgit.errors.LargeObjectException;
  32. import org.eclipse.jgit.errors.MissingObjectException;
  33. import org.eclipse.jgit.errors.PackInvalidException;
  34. import org.eclipse.jgit.errors.StoredObjectRepresentationNotAvailableException;
  35. import org.eclipse.jgit.internal.JGitText;
  36. import org.eclipse.jgit.internal.storage.file.PackBitmapIndex;
  37. import org.eclipse.jgit.internal.storage.file.PackIndex;
  38. import org.eclipse.jgit.internal.storage.file.PackReverseIndex;
  39. import org.eclipse.jgit.internal.storage.pack.BinaryDelta;
  40. import org.eclipse.jgit.internal.storage.pack.PackOutputStream;
  41. import org.eclipse.jgit.internal.storage.pack.StoredObjectRepresentation;
  42. import org.eclipse.jgit.lib.AbbreviatedObjectId;
  43. import org.eclipse.jgit.lib.AnyObjectId;
  44. import org.eclipse.jgit.lib.Constants;
  45. import org.eclipse.jgit.lib.ObjectId;
  46. import org.eclipse.jgit.lib.ObjectLoader;
  47. import org.eclipse.jgit.lib.Repository;
  48. import org.eclipse.jgit.util.LongList;

  49. /**
  50.  * A Git version 2 pack file representation. A pack file contains Git objects in
  51.  * delta packed format yielding high compression of lots of object where some
  52.  * objects are similar.
  53.  */
  54. public final class DfsPackFile extends BlockBasedFile {
  55.     private static final int REC_SIZE = Constants.OBJECT_ID_LENGTH + 8;
  56.     private static final long REF_POSITION = 0;

  57.     /** Index mapping {@link ObjectId} to position within the pack stream. */
  58.     private volatile PackIndex index;

  59.     /** Reverse version of {@link #index} mapping position to {@link ObjectId}. */
  60.     private volatile PackReverseIndex reverseIndex;

  61.     /** Index of compressed bitmap mapping entire object graph. */
  62.     private volatile PackBitmapIndex bitmapIndex;

  63.     /**
  64.      * Objects we have tried to read, and discovered to be corrupt.
  65.      * <p>
  66.      * The list is allocated after the first corruption is found, and filled in
  67.      * as more entries are discovered. Typically this list is never used, as
  68.      * pack files do not usually contain corrupt objects.
  69.      */
  70.     private volatile LongList corruptObjects;

  71.     /** Lock for {@link #corruptObjects}. */
  72.     private final Object corruptObjectsLock = new Object();

  73.     /**
  74.      * Construct a reader for an existing, packfile.
  75.      *
  76.      * @param cache
  77.      *            cache that owns the pack data.
  78.      * @param desc
  79.      *            description of the pack within the DFS.
  80.      */
  81.     DfsPackFile(DfsBlockCache cache, DfsPackDescription desc) {
  82.         super(cache, desc, PACK);

  83.         int bs = desc.getBlockSize(PACK);
  84.         if (bs > 0) {
  85.             setBlockSize(bs);
  86.         }

  87.         long sz = desc.getFileSize(PACK);
  88.         length = sz > 0 ? sz : -1;
  89.     }

  90.     /**
  91.      * Get description that was originally used to configure this pack file.
  92.      *
  93.      * @return description that was originally used to configure this pack file.
  94.      */
  95.     public DfsPackDescription getPackDescription() {
  96.         return desc;
  97.     }

  98.     /**
  99.      * Whether the pack index file is loaded and cached in memory.
  100.      *
  101.      * @return whether the pack index file is loaded and cached in memory.
  102.      */
  103.     public boolean isIndexLoaded() {
  104.         return index != null;
  105.     }

  106.     void setPackIndex(PackIndex idx) {
  107.         long objCnt = idx.getObjectCount();
  108.         int recSize = Constants.OBJECT_ID_LENGTH + 8;
  109.         long sz = objCnt * recSize;
  110.         cache.putRef(desc.getStreamKey(INDEX), sz, idx);
  111.         index = idx;
  112.     }

  113.     /**
  114.      * Get the PackIndex for this PackFile.
  115.      *
  116.      * @param ctx
  117.      *            reader context to support reading from the backing store if
  118.      *            the index is not already loaded in memory.
  119.      * @return the PackIndex.
  120.      * @throws java.io.IOException
  121.      *             the pack index is not available, or is corrupt.
  122.      */
  123.     public PackIndex getPackIndex(DfsReader ctx) throws IOException {
  124.         return idx(ctx);
  125.     }

  126.     private PackIndex idx(DfsReader ctx) throws IOException {
  127.         if (index != null) {
  128.             return index;
  129.         }

  130.         if (invalid) {
  131.             throw new PackInvalidException(getFileName(), invalidatingCause);
  132.         }

  133.         Repository.getGlobalListenerList()
  134.                 .dispatch(new BeforeDfsPackIndexLoadedEvent(this));
  135.         try {
  136.             DfsStreamKey idxKey = desc.getStreamKey(INDEX);
  137.             AtomicBoolean cacheHit = new AtomicBoolean(true);
  138.             DfsBlockCache.Ref<PackIndex> idxref = cache.getOrLoadRef(idxKey,
  139.                     REF_POSITION, () -> {
  140.                         cacheHit.set(false);
  141.                         return loadPackIndex(ctx, idxKey);
  142.                     });
  143.             if (cacheHit.get()) {
  144.                 ctx.stats.idxCacheHit++;
  145.             }
  146.             PackIndex idx = idxref.get();
  147.             if (index == null && idx != null) {
  148.                 index = idx;
  149.             }
  150.             return index;
  151.         } catch (IOException e) {
  152.             invalid = true;
  153.             invalidatingCause = e;
  154.             throw e;
  155.         }
  156.     }

  157.     final boolean isGarbage() {
  158.         return desc.getPackSource() == UNREACHABLE_GARBAGE;
  159.     }

  160.     /**
  161.      * Get the BitmapIndex for this PackFile.
  162.      *
  163.      * @param ctx
  164.      *            reader context to support reading from the backing store if
  165.      *            the index is not already loaded in memory.
  166.      * @return the BitmapIndex.
  167.      * @throws java.io.IOException
  168.      *             the bitmap index is not available, or is corrupt.
  169.      */
  170.     public PackBitmapIndex getBitmapIndex(DfsReader ctx) throws IOException {
  171.         if (invalid || isGarbage() || !desc.hasFileExt(BITMAP_INDEX)) {
  172.             return null;
  173.         }

  174.         if (bitmapIndex != null) {
  175.             return bitmapIndex;
  176.         }

  177.         DfsStreamKey bitmapKey = desc.getStreamKey(BITMAP_INDEX);
  178.         AtomicBoolean cacheHit = new AtomicBoolean(true);
  179.         DfsBlockCache.Ref<PackBitmapIndex> idxref = cache
  180.                 .getOrLoadRef(bitmapKey, REF_POSITION, () -> {
  181.                     cacheHit.set(false);
  182.                     return loadBitmapIndex(ctx, bitmapKey);
  183.                 });
  184.         if (cacheHit.get()) {
  185.             ctx.stats.bitmapCacheHit++;
  186.         }
  187.         PackBitmapIndex bmidx = idxref.get();
  188.         if (bitmapIndex == null && bmidx != null) {
  189.             bitmapIndex = bmidx;
  190.         }
  191.         return bitmapIndex;
  192.     }

  193.     PackReverseIndex getReverseIdx(DfsReader ctx) throws IOException {
  194.         if (reverseIndex != null) {
  195.             return reverseIndex;
  196.         }

  197.         PackIndex idx = idx(ctx);
  198.         DfsStreamKey revKey = desc.getStreamKey(REVERSE_INDEX);
  199.         AtomicBoolean cacheHit = new AtomicBoolean(true);
  200.         DfsBlockCache.Ref<PackReverseIndex> revref = cache.getOrLoadRef(revKey,
  201.                 REF_POSITION, () -> {
  202.                     cacheHit.set(false);
  203.                     return loadReverseIdx(ctx, revKey, idx);
  204.                 });
  205.         if (cacheHit.get()) {
  206.             ctx.stats.ridxCacheHit++;
  207.         }
  208.         PackReverseIndex revidx = revref.get();
  209.         if (reverseIndex == null && revidx != null) {
  210.             reverseIndex = revidx;
  211.         }
  212.         return reverseIndex;
  213.     }

  214.     /**
  215.      * Check if an object is stored within this pack.
  216.      *
  217.      * @param ctx
  218.      *            reader context to support reading from the backing store if
  219.      *            the index is not already loaded in memory.
  220.      * @param id
  221.      *            object to be located.
  222.      * @return true if the object exists in this pack; false if it does not.
  223.      * @throws java.io.IOException
  224.      *             the pack index is not available, or is corrupt.
  225.      */
  226.     public boolean hasObject(DfsReader ctx, AnyObjectId id) throws IOException {
  227.         final long offset = idx(ctx).findOffset(id);
  228.         return 0 < offset && !isCorrupt(offset);
  229.     }

  230.     /**
  231.      * Get an object from this pack.
  232.      *
  233.      * @param ctx
  234.      *            temporary working space associated with the calling thread.
  235.      * @param id
  236.      *            the object to obtain from the pack. Must not be null.
  237.      * @return the object loader for the requested object if it is contained in
  238.      *         this pack; null if the object was not found.
  239.      * @throws IOException
  240.      *             the pack file or the index could not be read.
  241.      */
  242.     ObjectLoader get(DfsReader ctx, AnyObjectId id)
  243.             throws IOException {
  244.         long offset = idx(ctx).findOffset(id);
  245.         return 0 < offset && !isCorrupt(offset) ? load(ctx, offset) : null;
  246.     }

  247.     long findOffset(DfsReader ctx, AnyObjectId id) throws IOException {
  248.         return idx(ctx).findOffset(id);
  249.     }

  250.     void resolve(DfsReader ctx, Set<ObjectId> matches, AbbreviatedObjectId id,
  251.             int matchLimit) throws IOException {
  252.         idx(ctx).resolve(matches, id, matchLimit);
  253.     }

  254.     /**
  255.      * Obtain the total number of objects available in this pack. This method
  256.      * relies on pack index, giving number of effectively available objects.
  257.      *
  258.      * @param ctx
  259.      *            current reader for the calling thread.
  260.      * @return number of objects in index of this pack, likewise in this pack
  261.      * @throws IOException
  262.      *             the index file cannot be loaded into memory.
  263.      */
  264.     long getObjectCount(DfsReader ctx) throws IOException {
  265.         return idx(ctx).getObjectCount();
  266.     }

  267.     private byte[] decompress(long position, int sz, DfsReader ctx)
  268.             throws IOException, DataFormatException {
  269.         byte[] dstbuf;
  270.         try {
  271.             dstbuf = new byte[sz];
  272.         } catch (OutOfMemoryError noMemory) {
  273.             // The size may be larger than our heap allows, return null to
  274.             // let the caller know allocation isn't possible and it should
  275.             // use the large object streaming approach instead.
  276.             //
  277.             // For example, this can occur when sz is 640 MB, and JRE
  278.             // maximum heap size is only 256 MB. Even if the JRE has
  279.             // 200 MB free, it cannot allocate a 640 MB byte array.
  280.             return null;
  281.         }

  282.         if (ctx.inflate(this, position, dstbuf, false) != sz) {
  283.             throw new EOFException(MessageFormat.format(
  284.                     JGitText.get().shortCompressedStreamAt,
  285.                     Long.valueOf(position)));
  286.         }
  287.         return dstbuf;
  288.     }

  289.     void copyPackAsIs(PackOutputStream out, DfsReader ctx) throws IOException {
  290.         // If the length hasn't been determined yet, pin to set it.
  291.         if (length == -1) {
  292.             ctx.pin(this, 0);
  293.             ctx.unpin();
  294.         }
  295.         try (ReadableChannel rc = ctx.db.openFile(desc, PACK)) {
  296.             int sz = ctx.getOptions().getStreamPackBufferSize();
  297.             if (sz > 0) {
  298.                 rc.setReadAheadBytes(sz);
  299.             }
  300.             if (cache.shouldCopyThroughCache(length)) {
  301.                 copyPackThroughCache(out, ctx, rc);
  302.             } else {
  303.                 copyPackBypassCache(out, rc);
  304.             }
  305.         }
  306.     }

  307.     private void copyPackThroughCache(PackOutputStream out, DfsReader ctx,
  308.             ReadableChannel rc) throws IOException {
  309.         long position = 12;
  310.         long remaining = length - (12 + 20);
  311.         while (0 < remaining) {
  312.             DfsBlock b = cache.getOrLoad(this, position, ctx, () -> rc);
  313.             int ptr = (int) (position - b.start);
  314.             if (b.size() <= ptr) {
  315.                 throw packfileIsTruncated();
  316.             }
  317.             int n = (int) Math.min(b.size() - ptr, remaining);
  318.             b.write(out, position, n);
  319.             position += n;
  320.             remaining -= n;
  321.         }
  322.     }

  323.     private long copyPackBypassCache(PackOutputStream out, ReadableChannel rc)
  324.             throws IOException {
  325.         ByteBuffer buf = newCopyBuffer(out, rc);
  326.         long position = 12;
  327.         long remaining = length - (12 + 20);
  328.         boolean packHeadSkipped = false;
  329.         while (0 < remaining) {
  330.             DfsBlock b = cache.get(key, alignToBlock(position));
  331.             if (b != null) {
  332.                 int ptr = (int) (position - b.start);
  333.                 if (b.size() <= ptr) {
  334.                     throw packfileIsTruncated();
  335.                 }
  336.                 int n = (int) Math.min(b.size() - ptr, remaining);
  337.                 b.write(out, position, n);
  338.                 position += n;
  339.                 remaining -= n;
  340.                 rc.position(position);
  341.                 packHeadSkipped = true;
  342.                 continue;
  343.             }

  344.             // Need to skip the 'PACK' header for the first read
  345.             int ptr = packHeadSkipped ? 0 : 12;
  346.             buf.position(0);
  347.             int bufLen = read(rc, buf);
  348.             if (bufLen <= ptr) {
  349.                 throw packfileIsTruncated();
  350.             }
  351.             int n = (int) Math.min(bufLen - ptr, remaining);
  352.             out.write(buf.array(), ptr, n);
  353.             position += n;
  354.             remaining -= n;
  355.             packHeadSkipped = true;
  356.         }
  357.         return position;
  358.     }

  359.     private ByteBuffer newCopyBuffer(PackOutputStream out, ReadableChannel rc) {
  360.         int bs = blockSize(rc);
  361.         byte[] copyBuf = out.getCopyBuffer();
  362.         if (bs > copyBuf.length) {
  363.             copyBuf = new byte[bs];
  364.         }
  365.         return ByteBuffer.wrap(copyBuf, 0, bs);
  366.     }

  367.     void copyAsIs(PackOutputStream out, DfsObjectToPack src,
  368.             boolean validate, DfsReader ctx) throws IOException,
  369.             StoredObjectRepresentationNotAvailableException {
  370.         final CRC32 crc1 = validate ? new CRC32() : null;
  371.         final CRC32 crc2 = validate ? new CRC32() : null;
  372.         final byte[] buf = out.getCopyBuffer();

  373.         // Rip apart the header so we can discover the size.
  374.         //
  375.         try {
  376.             readFully(src.offset, buf, 0, 20, ctx);
  377.         } catch (IOException ioError) {
  378.             throw new StoredObjectRepresentationNotAvailableException(ioError);
  379.         }
  380.         int c = buf[0] & 0xff;
  381.         final int typeCode = (c >> 4) & 7;
  382.         long inflatedLength = c & 15;
  383.         int shift = 4;
  384.         int headerCnt = 1;
  385.         while ((c & 0x80) != 0) {
  386.             c = buf[headerCnt++] & 0xff;
  387.             inflatedLength += ((long) (c & 0x7f)) << shift;
  388.             shift += 7;
  389.         }

  390.         if (typeCode == Constants.OBJ_OFS_DELTA) {
  391.             do {
  392.                 c = buf[headerCnt++] & 0xff;
  393.             } while ((c & 128) != 0);
  394.             if (validate) {
  395.                 assert(crc1 != null && crc2 != null);
  396.                 crc1.update(buf, 0, headerCnt);
  397.                 crc2.update(buf, 0, headerCnt);
  398.             }
  399.         } else if (typeCode == Constants.OBJ_REF_DELTA) {
  400.             if (validate) {
  401.                 assert(crc1 != null && crc2 != null);
  402.                 crc1.update(buf, 0, headerCnt);
  403.                 crc2.update(buf, 0, headerCnt);
  404.             }

  405.             readFully(src.offset + headerCnt, buf, 0, 20, ctx);
  406.             if (validate) {
  407.                 assert(crc1 != null && crc2 != null);
  408.                 crc1.update(buf, 0, 20);
  409.                 crc2.update(buf, 0, 20);
  410.             }
  411.             headerCnt += 20;
  412.         } else if (validate) {
  413.             assert(crc1 != null && crc2 != null);
  414.             crc1.update(buf, 0, headerCnt);
  415.             crc2.update(buf, 0, headerCnt);
  416.         }

  417.         final long dataOffset = src.offset + headerCnt;
  418.         final long dataLength = src.length;
  419.         final long expectedCRC;
  420.         final DfsBlock quickCopy;

  421.         // Verify the object isn't corrupt before sending. If it is,
  422.         // we report it missing instead.
  423.         //
  424.         try {
  425.             quickCopy = ctx.quickCopy(this, dataOffset, dataLength);

  426.             if (validate && idx(ctx).hasCRC32Support()) {
  427.                 assert(crc1 != null);
  428.                 // Index has the CRC32 code cached, validate the object.
  429.                 //
  430.                 expectedCRC = idx(ctx).findCRC32(src);
  431.                 if (quickCopy != null) {
  432.                     quickCopy.crc32(crc1, dataOffset, (int) dataLength);
  433.                 } else {
  434.                     long pos = dataOffset;
  435.                     long cnt = dataLength;
  436.                     while (cnt > 0) {
  437.                         final int n = (int) Math.min(cnt, buf.length);
  438.                         readFully(pos, buf, 0, n, ctx);
  439.                         crc1.update(buf, 0, n);
  440.                         pos += n;
  441.                         cnt -= n;
  442.                     }
  443.                 }
  444.                 if (crc1.getValue() != expectedCRC) {
  445.                     setCorrupt(src.offset);
  446.                     throw new CorruptObjectException(MessageFormat.format(
  447.                             JGitText.get().objectAtHasBadZlibStream,
  448.                             Long.valueOf(src.offset), getFileName()));
  449.                 }
  450.             } else if (validate) {
  451.                 assert(crc1 != null);
  452.                 // We don't have a CRC32 code in the index, so compute it
  453.                 // now while inflating the raw data to get zlib to tell us
  454.                 // whether or not the data is safe.
  455.                 //
  456.                 Inflater inf = ctx.inflater();
  457.                 byte[] tmp = new byte[1024];
  458.                 if (quickCopy != null) {
  459.                     quickCopy.check(inf, tmp, dataOffset, (int) dataLength);
  460.                 } else {
  461.                     long pos = dataOffset;
  462.                     long cnt = dataLength;
  463.                     while (cnt > 0) {
  464.                         final int n = (int) Math.min(cnt, buf.length);
  465.                         readFully(pos, buf, 0, n, ctx);
  466.                         crc1.update(buf, 0, n);
  467.                         inf.setInput(buf, 0, n);
  468.                         while (inf.inflate(tmp, 0, tmp.length) > 0) {
  469.                             continue;
  470.                         }
  471.                         pos += n;
  472.                         cnt -= n;
  473.                     }
  474.                 }
  475.                 if (!inf.finished() || inf.getBytesRead() != dataLength) {
  476.                     setCorrupt(src.offset);
  477.                     throw new EOFException(MessageFormat.format(
  478.                             JGitText.get().shortCompressedStreamAt,
  479.                             Long.valueOf(src.offset)));
  480.                 }
  481.                 expectedCRC = crc1.getValue();
  482.             } else {
  483.                 expectedCRC = -1;
  484.             }
  485.         } catch (DataFormatException dataFormat) {
  486.             setCorrupt(src.offset);

  487.             CorruptObjectException corruptObject = new CorruptObjectException(
  488.                     MessageFormat.format(
  489.                             JGitText.get().objectAtHasBadZlibStream,
  490.                             Long.valueOf(src.offset), getFileName()),
  491.                     dataFormat);

  492.             throw new StoredObjectRepresentationNotAvailableException(
  493.                     corruptObject);

  494.         } catch (IOException ioError) {
  495.             throw new StoredObjectRepresentationNotAvailableException(ioError);
  496.         }

  497.         if (quickCopy != null) {
  498.             // The entire object fits into a single byte array window slice,
  499.             // and we have it pinned.  Write this out without copying.
  500.             //
  501.             out.writeHeader(src, inflatedLength);
  502.             quickCopy.write(out, dataOffset, (int) dataLength);

  503.         } else if (dataLength <= buf.length) {
  504.             // Tiny optimization: Lots of objects are very small deltas or
  505.             // deflated commits that are likely to fit in the copy buffer.
  506.             //
  507.             if (!validate) {
  508.                 long pos = dataOffset;
  509.                 long cnt = dataLength;
  510.                 while (cnt > 0) {
  511.                     final int n = (int) Math.min(cnt, buf.length);
  512.                     readFully(pos, buf, 0, n, ctx);
  513.                     pos += n;
  514.                     cnt -= n;
  515.                 }
  516.             }
  517.             out.writeHeader(src, inflatedLength);
  518.             out.write(buf, 0, (int) dataLength);
  519.         } else {
  520.             // Now we are committed to sending the object. As we spool it out,
  521.             // check its CRC32 code to make sure there wasn't corruption between
  522.             // the verification we did above, and us actually outputting it.
  523.             //
  524.             out.writeHeader(src, inflatedLength);
  525.             long pos = dataOffset;
  526.             long cnt = dataLength;
  527.             while (cnt > 0) {
  528.                 final int n = (int) Math.min(cnt, buf.length);
  529.                 readFully(pos, buf, 0, n, ctx);
  530.                 if (validate) {
  531.                     assert(crc2 != null);
  532.                     crc2.update(buf, 0, n);
  533.                 }
  534.                 out.write(buf, 0, n);
  535.                 pos += n;
  536.                 cnt -= n;
  537.             }
  538.             if (validate) {
  539.                 assert(crc2 != null);
  540.                 if (crc2.getValue() != expectedCRC) {
  541.                     throw new CorruptObjectException(MessageFormat.format(
  542.                             JGitText.get().objectAtHasBadZlibStream,
  543.                             Long.valueOf(src.offset), getFileName()));
  544.                 }
  545.             }
  546.         }
  547.     }

  548.     private IOException packfileIsTruncated() {
  549.         invalid = true;
  550.         IOException exc = new IOException(MessageFormat.format(
  551.                 JGitText.get().packfileIsTruncated, getFileName()));
  552.         invalidatingCause = exc;
  553.         return exc;
  554.     }

  555.     private void readFully(long position, byte[] dstbuf, int dstoff, int cnt,
  556.             DfsReader ctx) throws IOException {
  557.         while (cnt > 0) {
  558.             int copied = ctx.copy(this, position, dstbuf, dstoff, cnt);
  559.             if (copied == 0) {
  560.                 throw new EOFException();
  561.             }
  562.             position += copied;
  563.             dstoff += copied;
  564.             cnt -= copied;
  565.         }
  566.     }

  567.     ObjectLoader load(DfsReader ctx, long pos)
  568.             throws IOException {
  569.         try {
  570.             final byte[] ib = ctx.tempId;
  571.             Delta delta = null;
  572.             byte[] data = null;
  573.             int type = Constants.OBJ_BAD;
  574.             boolean cached = false;

  575.             SEARCH: for (;;) {
  576.                 readFully(pos, ib, 0, 20, ctx);
  577.                 int c = ib[0] & 0xff;
  578.                 final int typeCode = (c >> 4) & 7;
  579.                 long sz = c & 15;
  580.                 int shift = 4;
  581.                 int p = 1;
  582.                 while ((c & 0x80) != 0) {
  583.                     c = ib[p++] & 0xff;
  584.                     sz += ((long) (c & 0x7f)) << shift;
  585.                     shift += 7;
  586.                 }

  587.                 switch (typeCode) {
  588.                 case Constants.OBJ_COMMIT:
  589.                 case Constants.OBJ_TREE:
  590.                 case Constants.OBJ_BLOB:
  591.                 case Constants.OBJ_TAG: {
  592.                     if (delta != null) {
  593.                         data = decompress(pos + p, (int) sz, ctx);
  594.                         type = typeCode;
  595.                         break SEARCH;
  596.                     }

  597.                     if (sz < ctx.getStreamFileThreshold()) {
  598.                         data = decompress(pos + p, (int) sz, ctx);
  599.                         if (data != null) {
  600.                             return new ObjectLoader.SmallObject(typeCode, data);
  601.                         }
  602.                     }
  603.                     return new LargePackedWholeObject(typeCode, sz, pos, p, this, ctx.db);
  604.                 }

  605.                 case Constants.OBJ_OFS_DELTA: {
  606.                     c = ib[p++] & 0xff;
  607.                     long base = c & 127;
  608.                     while ((c & 128) != 0) {
  609.                         base += 1;
  610.                         c = ib[p++] & 0xff;
  611.                         base <<= 7;
  612.                         base += (c & 127);
  613.                     }
  614.                     base = pos - base;
  615.                     delta = new Delta(delta, pos, (int) sz, p, base);
  616.                     if (sz != delta.deltaSize) {
  617.                         break SEARCH;
  618.                     }

  619.                     DeltaBaseCache.Entry e = ctx.getDeltaBaseCache().get(key, base);
  620.                     if (e != null) {
  621.                         type = e.type;
  622.                         data = e.data;
  623.                         cached = true;
  624.                         break SEARCH;
  625.                     }
  626.                     pos = base;
  627.                     continue SEARCH;
  628.                 }

  629.                 case Constants.OBJ_REF_DELTA: {
  630.                     readFully(pos + p, ib, 0, 20, ctx);
  631.                     long base = findDeltaBase(ctx, ObjectId.fromRaw(ib));
  632.                     delta = new Delta(delta, pos, (int) sz, p + 20, base);
  633.                     if (sz != delta.deltaSize) {
  634.                         break SEARCH;
  635.                     }

  636.                     DeltaBaseCache.Entry e = ctx.getDeltaBaseCache().get(key, base);
  637.                     if (e != null) {
  638.                         type = e.type;
  639.                         data = e.data;
  640.                         cached = true;
  641.                         break SEARCH;
  642.                     }
  643.                     pos = base;
  644.                     continue SEARCH;
  645.                 }

  646.                 default:
  647.                     throw new IOException(MessageFormat.format(
  648.                             JGitText.get().unknownObjectType, Integer.valueOf(typeCode)));
  649.                 }
  650.             }

  651.             // At this point there is at least one delta to apply to data.
  652.             // (Whole objects with no deltas to apply return early above.)

  653.             if (data == null)
  654.                 throw new LargeObjectException();

  655.             assert(delta != null);
  656.             do {
  657.                 // Cache only the base immediately before desired object.
  658.                 if (cached) {
  659.                     cached = false;
  660.                 } else if (delta.next == null) {
  661.                     ctx.getDeltaBaseCache().put(key, delta.basePos, type, data);
  662.                 }

  663.                 pos = delta.deltaPos;

  664.                 byte[] cmds = decompress(pos + delta.hdrLen, delta.deltaSize, ctx);
  665.                 if (cmds == null) {
  666.                     data = null; // Discard base in case of OutOfMemoryError
  667.                     throw new LargeObjectException();
  668.                 }

  669.                 final long sz = BinaryDelta.getResultSize(cmds);
  670.                 if (Integer.MAX_VALUE <= sz) {
  671.                     throw new LargeObjectException.ExceedsByteArrayLimit();
  672.                 }

  673.                 final byte[] result;
  674.                 try {
  675.                     result = new byte[(int) sz];
  676.                 } catch (OutOfMemoryError tooBig) {
  677.                     data = null; // Discard base in case of OutOfMemoryError
  678.                     cmds = null;
  679.                     throw new LargeObjectException.OutOfMemory(tooBig);
  680.                 }

  681.                 BinaryDelta.apply(data, cmds, result);
  682.                 data = result;
  683.                 delta = delta.next;
  684.             } while (delta != null);

  685.             return new ObjectLoader.SmallObject(type, data);

  686.         } catch (DataFormatException dfe) {
  687.             throw new CorruptObjectException(
  688.                     MessageFormat.format(
  689.                             JGitText.get().objectAtHasBadZlibStream, Long.valueOf(pos),
  690.                             getFileName()),
  691.                     dfe);
  692.         }
  693.     }

  694.     private long findDeltaBase(DfsReader ctx, ObjectId baseId)
  695.             throws IOException, MissingObjectException {
  696.         long ofs = idx(ctx).findOffset(baseId);
  697.         if (ofs < 0) {
  698.             throw new MissingObjectException(baseId,
  699.                     JGitText.get().missingDeltaBase);
  700.         }
  701.         return ofs;
  702.     }

  703.     private static class Delta {
  704.         /** Child that applies onto this object. */
  705.         final Delta next;

  706.         /** Offset of the delta object. */
  707.         final long deltaPos;

  708.         /** Size of the inflated delta stream. */
  709.         final int deltaSize;

  710.         /** Total size of the delta's pack entry header (including base). */
  711.         final int hdrLen;

  712.         /** Offset of the base object this delta applies onto. */
  713.         final long basePos;

  714.         Delta(Delta next, long ofs, int sz, int hdrLen, long baseOffset) {
  715.             this.next = next;
  716.             this.deltaPos = ofs;
  717.             this.deltaSize = sz;
  718.             this.hdrLen = hdrLen;
  719.             this.basePos = baseOffset;
  720.         }
  721.     }

  722.     byte[] getDeltaHeader(DfsReader wc, long pos)
  723.             throws IOException, DataFormatException {
  724.         // The delta stream starts as two variable length integers. If we
  725.         // assume they are 64 bits each, we need 16 bytes to encode them,
  726.         // plus 2 extra bytes for the variable length overhead. So 18 is
  727.         // the longest delta instruction header.
  728.         //
  729.         final byte[] hdr = new byte[32];
  730.         wc.inflate(this, pos, hdr, true /* header only */);
  731.         return hdr;
  732.     }

  733.     int getObjectType(DfsReader ctx, long pos) throws IOException {
  734.         final byte[] ib = ctx.tempId;
  735.         for (;;) {
  736.             readFully(pos, ib, 0, 20, ctx);
  737.             int c = ib[0] & 0xff;
  738.             final int type = (c >> 4) & 7;

  739.             switch (type) {
  740.             case Constants.OBJ_COMMIT:
  741.             case Constants.OBJ_TREE:
  742.             case Constants.OBJ_BLOB:
  743.             case Constants.OBJ_TAG:
  744.                 return type;

  745.             case Constants.OBJ_OFS_DELTA: {
  746.                 int p = 1;
  747.                 while ((c & 0x80) != 0) {
  748.                     c = ib[p++] & 0xff;
  749.                 }
  750.                 c = ib[p++] & 0xff;
  751.                 long ofs = c & 127;
  752.                 while ((c & 128) != 0) {
  753.                     ofs += 1;
  754.                     c = ib[p++] & 0xff;
  755.                     ofs <<= 7;
  756.                     ofs += (c & 127);
  757.                 }
  758.                 pos = pos - ofs;
  759.                 continue;
  760.             }

  761.             case Constants.OBJ_REF_DELTA: {
  762.                 int p = 1;
  763.                 while ((c & 0x80) != 0) {
  764.                     c = ib[p++] & 0xff;
  765.                 }
  766.                 readFully(pos + p, ib, 0, 20, ctx);
  767.                 pos = findDeltaBase(ctx, ObjectId.fromRaw(ib));
  768.                 continue;
  769.             }

  770.             default:
  771.                 throw new IOException(MessageFormat.format(
  772.                         JGitText.get().unknownObjectType, Integer.valueOf(type)));
  773.             }
  774.         }
  775.     }

  776.     long getObjectSize(DfsReader ctx, AnyObjectId id) throws IOException {
  777.         final long offset = idx(ctx).findOffset(id);
  778.         return 0 < offset ? getObjectSize(ctx, offset) : -1;
  779.     }

  780.     long getObjectSize(DfsReader ctx, long pos)
  781.             throws IOException {
  782.         final byte[] ib = ctx.tempId;
  783.         readFully(pos, ib, 0, 20, ctx);
  784.         int c = ib[0] & 0xff;
  785.         final int type = (c >> 4) & 7;
  786.         long sz = c & 15;
  787.         int shift = 4;
  788.         int p = 1;
  789.         while ((c & 0x80) != 0) {
  790.             c = ib[p++] & 0xff;
  791.             sz += ((long) (c & 0x7f)) << shift;
  792.             shift += 7;
  793.         }

  794.         long deltaAt;
  795.         switch (type) {
  796.         case Constants.OBJ_COMMIT:
  797.         case Constants.OBJ_TREE:
  798.         case Constants.OBJ_BLOB:
  799.         case Constants.OBJ_TAG:
  800.             return sz;

  801.         case Constants.OBJ_OFS_DELTA:
  802.             c = ib[p++] & 0xff;
  803.             while ((c & 128) != 0) {
  804.                 c = ib[p++] & 0xff;
  805.             }
  806.             deltaAt = pos + p;
  807.             break;

  808.         case Constants.OBJ_REF_DELTA:
  809.             deltaAt = pos + p + 20;
  810.             break;

  811.         default:
  812.             throw new IOException(MessageFormat.format(
  813.                     JGitText.get().unknownObjectType, Integer.valueOf(type)));
  814.         }

  815.         try {
  816.             return BinaryDelta.getResultSize(getDeltaHeader(ctx, deltaAt));
  817.         } catch (DataFormatException dfe) {
  818.             throw new CorruptObjectException(
  819.                     MessageFormat.format(
  820.                             JGitText.get().objectAtHasBadZlibStream, Long.valueOf(pos),
  821.                             getFileName()),
  822.                     dfe);
  823.         }
  824.     }

  825.     void representation(DfsObjectRepresentation r, final long pos,
  826.             DfsReader ctx, PackReverseIndex rev)
  827.             throws IOException {
  828.         r.offset = pos;
  829.         final byte[] ib = ctx.tempId;
  830.         readFully(pos, ib, 0, 20, ctx);
  831.         int c = ib[0] & 0xff;
  832.         int p = 1;
  833.         final int typeCode = (c >> 4) & 7;
  834.         while ((c & 0x80) != 0) {
  835.             c = ib[p++] & 0xff;
  836.         }

  837.         long len = rev.findNextOffset(pos, length - 20) - pos;
  838.         switch (typeCode) {
  839.         case Constants.OBJ_COMMIT:
  840.         case Constants.OBJ_TREE:
  841.         case Constants.OBJ_BLOB:
  842.         case Constants.OBJ_TAG:
  843.             r.format = StoredObjectRepresentation.PACK_WHOLE;
  844.             r.baseId = null;
  845.             r.length = len - p;
  846.             return;

  847.         case Constants.OBJ_OFS_DELTA: {
  848.             c = ib[p++] & 0xff;
  849.             long ofs = c & 127;
  850.             while ((c & 128) != 0) {
  851.                 ofs += 1;
  852.                 c = ib[p++] & 0xff;
  853.                 ofs <<= 7;
  854.                 ofs += (c & 127);
  855.             }
  856.             r.format = StoredObjectRepresentation.PACK_DELTA;
  857.             r.baseId = rev.findObject(pos - ofs);
  858.             r.length = len - p;
  859.             return;
  860.         }

  861.         case Constants.OBJ_REF_DELTA: {
  862.             readFully(pos + p, ib, 0, 20, ctx);
  863.             r.format = StoredObjectRepresentation.PACK_DELTA;
  864.             r.baseId = ObjectId.fromRaw(ib);
  865.             r.length = len - p - 20;
  866.             return;
  867.         }

  868.         default:
  869.             throw new IOException(MessageFormat.format(
  870.                     JGitText.get().unknownObjectType, Integer.valueOf(typeCode)));
  871.         }
  872.     }

  873.     boolean isCorrupt(long offset) {
  874.         LongList list = corruptObjects;
  875.         if (list == null) {
  876.             return false;
  877.         }
  878.         synchronized (list) {
  879.             return list.contains(offset);
  880.         }
  881.     }

  882.     private void setCorrupt(long offset) {
  883.         LongList list = corruptObjects;
  884.         if (list == null) {
  885.             synchronized (corruptObjectsLock) {
  886.                 list = corruptObjects;
  887.                 if (list == null) {
  888.                     list = new LongList();
  889.                     corruptObjects = list;
  890.                 }
  891.             }
  892.         }
  893.         synchronized (list) {
  894.             list.add(offset);
  895.         }
  896.     }

  897.     private DfsBlockCache.Ref<PackIndex> loadPackIndex(
  898.             DfsReader ctx, DfsStreamKey idxKey) throws IOException {
  899.         try {
  900.             ctx.stats.readIdx++;
  901.             long start = System.nanoTime();
  902.             try (ReadableChannel rc = ctx.db.openFile(desc, INDEX)) {
  903.                 InputStream in = Channels.newInputStream(rc);
  904.                 int wantSize = 8192;
  905.                 int bs = rc.blockSize();
  906.                 if (0 < bs && bs < wantSize) {
  907.                     bs = (wantSize / bs) * bs;
  908.                 } else if (bs <= 0) {
  909.                     bs = wantSize;
  910.                 }
  911.                 PackIndex idx = PackIndex.read(new BufferedInputStream(in, bs));
  912.                 ctx.stats.readIdxBytes += rc.position();
  913.                 index = idx;
  914.                 return new DfsBlockCache.Ref<>(
  915.                         idxKey,
  916.                         REF_POSITION,
  917.                         idx.getObjectCount() * REC_SIZE,
  918.                         idx);
  919.             } finally {
  920.                 ctx.stats.readIdxMicros += elapsedMicros(start);
  921.             }
  922.         } catch (EOFException e) {
  923.             throw new IOException(MessageFormat.format(
  924.                     DfsText.get().shortReadOfIndex,
  925.                     desc.getFileName(INDEX)), e);
  926.         } catch (IOException e) {
  927.             throw new IOException(MessageFormat.format(
  928.                     DfsText.get().cannotReadIndex,
  929.                     desc.getFileName(INDEX)), e);
  930.         }
  931.     }

  932.     private DfsBlockCache.Ref<PackReverseIndex> loadReverseIdx(
  933.             DfsReader ctx, DfsStreamKey revKey, PackIndex idx) {
  934.         ctx.stats.readReverseIdx++;
  935.         long start = System.nanoTime();
  936.         PackReverseIndex revidx = new PackReverseIndex(idx);
  937.         reverseIndex = revidx;
  938.         ctx.stats.readReverseIdxMicros += elapsedMicros(start);
  939.         return new DfsBlockCache.Ref<>(
  940.                 revKey,
  941.                 REF_POSITION,
  942.                 idx.getObjectCount() * 8,
  943.                 revidx);
  944.     }

  945.     private DfsBlockCache.Ref<PackBitmapIndex> loadBitmapIndex(DfsReader ctx,
  946.             DfsStreamKey bitmapKey) throws IOException {
  947.         ctx.stats.readBitmap++;
  948.         long start = System.nanoTime();
  949.         try (ReadableChannel rc = ctx.db.openFile(desc, BITMAP_INDEX)) {
  950.             long size;
  951.             PackBitmapIndex bmidx;
  952.             try {
  953.                 InputStream in = Channels.newInputStream(rc);
  954.                 int wantSize = 8192;
  955.                 int bs = rc.blockSize();
  956.                 if (0 < bs && bs < wantSize) {
  957.                     bs = (wantSize / bs) * bs;
  958.                 } else if (bs <= 0) {
  959.                     bs = wantSize;
  960.                 }
  961.                 in = new BufferedInputStream(in, bs);
  962.                 bmidx = PackBitmapIndex.read(in, () -> idx(ctx),
  963.                         () -> getReverseIdx(ctx),
  964.                         ctx.getOptions().shouldLoadRevIndexInParallel());
  965.             } finally {
  966.                 size = rc.position();
  967.                 ctx.stats.readBitmapIdxBytes += size;
  968.                 ctx.stats.readBitmapIdxMicros += elapsedMicros(start);
  969.             }
  970.             bitmapIndex = bmidx;
  971.             return new DfsBlockCache.Ref<>(
  972.                     bitmapKey, REF_POSITION, size, bmidx);
  973.         } catch (EOFException e) {
  974.             throw new IOException(MessageFormat.format(
  975.                     DfsText.get().shortReadOfIndex,
  976.                     desc.getFileName(BITMAP_INDEX)), e);
  977.         } catch (IOException e) {
  978.             throw new IOException(MessageFormat.format(
  979.                     DfsText.get().cannotReadIndex,
  980.                     desc.getFileName(BITMAP_INDEX)), e);
  981.         }
  982.     }
  983. }