BlockReader.java

  1. /*
  2.  * Copyright (C) 2017, Google Inc. and others
  3.  *
  4.  * This program and the accompanying materials are made available under the
  5.  * terms of the Eclipse Distribution License v. 1.0 which is available at
  6.  * https://www.eclipse.org/org/documents/edl-v10.php.
  7.  *
  8.  * SPDX-License-Identifier: BSD-3-Clause
  9.  */

  10. package org.eclipse.jgit.internal.storage.reftable;

  11. import static java.nio.charset.StandardCharsets.UTF_8;
  12. import static org.eclipse.jgit.internal.storage.reftable.BlockWriter.compare;
  13. import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.FILE_BLOCK_TYPE;
  14. import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.FILE_HEADER_LEN;
  15. import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.INDEX_BLOCK_TYPE;
  16. import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.LOG_BLOCK_TYPE;
  17. import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.LOG_DATA;
  18. import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.LOG_NONE;
  19. import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.OBJ_BLOCK_TYPE;
  20. import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.REF_BLOCK_TYPE;
  21. import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.VALUE_1ID;
  22. import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.VALUE_2ID;
  23. import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.VALUE_NONE;
  24. import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.VALUE_SYMREF;
  25. import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.VALUE_TYPE_MASK;
  26. import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.reverseUpdateIndex;
  27. import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH;
  28. import static org.eclipse.jgit.lib.Ref.Storage.NEW;
  29. import static org.eclipse.jgit.lib.Ref.Storage.PACKED;

  30. import java.io.IOException;
  31. import java.nio.ByteBuffer;
  32. import java.util.Arrays;
  33. import java.util.zip.DataFormatException;
  34. import java.util.zip.Inflater;

  35. import org.eclipse.jgit.annotations.Nullable;
  36. import org.eclipse.jgit.internal.JGitText;
  37. import org.eclipse.jgit.internal.storage.io.BlockSource;
  38. import org.eclipse.jgit.lib.CheckoutEntry;
  39. import org.eclipse.jgit.lib.InflaterCache;
  40. import org.eclipse.jgit.lib.ObjectId;
  41. import org.eclipse.jgit.lib.ObjectIdRef;
  42. import org.eclipse.jgit.lib.PersonIdent;
  43. import org.eclipse.jgit.lib.Ref;
  44. import org.eclipse.jgit.lib.ReflogEntry;
  45. import org.eclipse.jgit.lib.SymbolicRef;
  46. import org.eclipse.jgit.util.LongList;
  47. import org.eclipse.jgit.util.NB;
  48. import org.eclipse.jgit.util.RawParseUtils;

  49. /**
  50.  * Reads a single block for {@link ReftableReader}. Instances are tied to a
  51.  * specific block in the file so are not reused for other blocks. Instances hold
  52.  * an offset into the block.
  53.  */
  54. class BlockReader {
  55.     private byte blockType;
  56.     private long endPosition;
  57.     private boolean truncated;

  58.     private byte[] buf;
  59.     private int bufLen;
  60.     private int ptr;

  61.     private int keysStart;
  62.     private int keysEnd;

  63.     private int restartCnt;
  64.     private int restartTbl;

  65.     private byte[] nameBuf = new byte[256];
  66.     private int nameLen;
  67.     private int valueType;

  68.     byte type() {
  69.         return blockType;
  70.     }

  71.     boolean truncated() {
  72.         return truncated;
  73.     }

  74.     long endPosition() {
  75.         return endPosition;
  76.     }

  77.     boolean next() {
  78.         return ptr < keysEnd;
  79.     }

  80.     void parseKey() {
  81.         int pfx = readVarint32();
  82.         valueType = readVarint32();
  83.         int sfx = valueType >>> 3;
  84.         if (pfx + sfx > nameBuf.length) {
  85.             int n = Math.max(pfx + sfx, nameBuf.length * 2);
  86.             nameBuf = Arrays.copyOf(nameBuf, n);
  87.         }
  88.         System.arraycopy(buf, ptr, nameBuf, pfx, sfx);
  89.         ptr += sfx;
  90.         nameLen = pfx + sfx;
  91.     }

  92.     String name() {
  93.         int len = nameLen;
  94.         if (blockType == LOG_BLOCK_TYPE) {
  95.             len -= 9;
  96.         }
  97.         return RawParseUtils.decode(UTF_8, nameBuf, 0, len);
  98.     }

  99.     // Matches the key against a name or a prefix. For reflogs, only the
  100.     // refname is matched, and the updateIndex suffix is ignored.
  101.     boolean match(byte[] match, boolean matchIsPrefix) {
  102.         int len = nameLen;
  103.         if (blockType == LOG_BLOCK_TYPE) {
  104.             len -= 9;
  105.         }
  106.         if (matchIsPrefix) {
  107.             return len >= match.length
  108.                     && compare(
  109.                             match, 0, match.length,
  110.                             nameBuf, 0, match.length) == 0;
  111.         }
  112.         return compare(match, 0, match.length, nameBuf, 0, len) == 0;
  113.     }

  114.     long readPositionFromIndex() throws IOException {
  115.         if (blockType != INDEX_BLOCK_TYPE) {
  116.             throw invalidBlock();
  117.         }

  118.         readVarint32(); // skip prefix length
  119.         int n = readVarint32() >>> 3;
  120.         ptr += n; // skip name
  121.         return readVarint64();
  122.     }

  123.     long readUpdateIndexDelta() {
  124.         return readVarint64();
  125.     }

  126.     Ref readRef(long minUpdateIndex) throws IOException {
  127.         long updateIndex = minUpdateIndex + readUpdateIndexDelta();
  128.         String name = RawParseUtils.decode(UTF_8, nameBuf, 0, nameLen);
  129.         switch (valueType & VALUE_TYPE_MASK) {
  130.         case VALUE_NONE: // delete
  131.             return newRef(name, updateIndex);

  132.         case VALUE_1ID:
  133.             return new ObjectIdRef.PeeledNonTag(PACKED, name, readValueId(),
  134.                     updateIndex);

  135.         case VALUE_2ID: { // annotated tag
  136.             ObjectId id1 = readValueId();
  137.             ObjectId id2 = readValueId();
  138.             return new ObjectIdRef.PeeledTag(PACKED, name, id1, id2,
  139.                     updateIndex);
  140.         }

  141.         case VALUE_SYMREF: {
  142.             String val = readValueString();
  143.             return new SymbolicRef(name, newRef(val, updateIndex), updateIndex);
  144.         }

  145.         default:
  146.             throw invalidBlock();
  147.         }
  148.     }

  149.     @Nullable
  150.     LongList readBlockPositionList() {
  151.         int n = valueType & VALUE_TYPE_MASK;
  152.         if (n == 0) {
  153.             n = readVarint32();
  154.             if (n == 0) {
  155.                 return null;
  156.             }
  157.         }

  158.         LongList b = new LongList(n);
  159.         b.add(readVarint64());
  160.         for (int j = 1; j < n; j++) {
  161.             long prior = b.get(j - 1);
  162.             b.add(prior + readVarint64());
  163.         }
  164.         return b;
  165.     }

  166.     long readLogUpdateIndex() {
  167.         return reverseUpdateIndex(NB.decodeUInt64(nameBuf, nameLen - 8));
  168.     }

  169.     @Nullable
  170.     ReflogEntry readLogEntry() {
  171.         if ((valueType & VALUE_TYPE_MASK) == LOG_NONE) {
  172.             return null;
  173.         }

  174.         ObjectId oldId = readValueId();
  175.         ObjectId newId = readValueId();
  176.         PersonIdent who = readPersonIdent();
  177.         String msg = readValueString();

  178.         return new ReflogEntry() {
  179.             @Override
  180.             public ObjectId getOldId() {
  181.                 return oldId;
  182.             }

  183.             @Override
  184.             public ObjectId getNewId() {
  185.                 return newId;
  186.             }

  187.             @Override
  188.             public PersonIdent getWho() {
  189.                 return who;
  190.             }

  191.             @Override
  192.             public String getComment() {
  193.                 return msg;
  194.             }

  195.             @Override
  196.             public CheckoutEntry parseCheckout() {
  197.                 return null;
  198.             }
  199.         };
  200.     }

  201.     private ObjectId readValueId() {
  202.         ObjectId id = ObjectId.fromRaw(buf, ptr);
  203.         ptr += OBJECT_ID_LENGTH;
  204.         return id;
  205.     }

  206.     private String readValueString() {
  207.         int len = readVarint32();
  208.         int end = ptr + len;
  209.         String s = RawParseUtils.decode(UTF_8, buf, ptr, end);
  210.         ptr = end;
  211.         return s;
  212.     }

  213.     private PersonIdent readPersonIdent() {
  214.         String name = readValueString();
  215.         String email = readValueString();
  216.         long ms = readVarint64() * 1000;
  217.         int tz = readInt16();
  218.         return new PersonIdent(name, email, ms, tz);
  219.     }

  220.     void readBlock(BlockSource src, long pos, int fileBlockSize)
  221.             throws IOException {
  222.         readBlockIntoBuf(src, pos, fileBlockSize);
  223.         parseBlockStart(src, pos, fileBlockSize);
  224.     }

  225.     private void readBlockIntoBuf(BlockSource src, long pos, int size)
  226.             throws IOException {
  227.         ByteBuffer b = src.read(pos, size);
  228.         bufLen = b.position();
  229.         if (bufLen <= 0) {
  230.             throw invalidBlock();
  231.         }
  232.         if (b.hasArray() && b.arrayOffset() == 0) {
  233.             buf = b.array();
  234.         } else {
  235.             buf = new byte[bufLen];
  236.             b.flip();
  237.             b.get(buf);
  238.         }
  239.         endPosition = pos + bufLen;
  240.     }

  241.     private void parseBlockStart(BlockSource src, long pos, int fileBlockSize)
  242.             throws IOException {
  243.         ptr = 0;
  244.         if (pos == 0) {
  245.             if (bufLen == FILE_HEADER_LEN) {
  246.                 setupEmptyFileBlock();
  247.                 return;
  248.             }
  249.             ptr += FILE_HEADER_LEN; // first block begins with file header
  250.         }

  251.         int typeAndSize = NB.decodeInt32(buf, ptr);
  252.         ptr += 4;

  253.         blockType = (byte) (typeAndSize >>> 24);
  254.         int blockLen = decodeBlockLen(typeAndSize);
  255.         if (blockType == LOG_BLOCK_TYPE) {
  256.             // Log blocks must be inflated after the header.
  257.             long deflatedSize = inflateBuf(src, pos, blockLen, fileBlockSize);
  258.             endPosition = pos + 4 + deflatedSize;
  259.         }
  260.         if (bufLen < blockLen) {
  261.             if (blockType != INDEX_BLOCK_TYPE) {
  262.                 throw invalidBlock();
  263.             }
  264.             // Its OK during sequential scan for an index block to have been
  265.             // partially read and be truncated in-memory. This happens when
  266.             // the index block is larger than the file's blockSize. Caller
  267.             // will break out of its scan loop once it sees the blockType.
  268.             truncated = true;
  269.         } else if (bufLen > blockLen) {
  270.             bufLen = blockLen;
  271.         }

  272.         if (blockType != FILE_BLOCK_TYPE) {
  273.             restartCnt = NB.decodeUInt16(buf, bufLen - 2);
  274.             restartTbl = bufLen - (restartCnt * 3 + 2);
  275.             keysStart = ptr;
  276.             keysEnd = restartTbl;
  277.         } else {
  278.             keysStart = ptr;
  279.             keysEnd = ptr;
  280.         }
  281.     }

  282.     static int decodeBlockLen(int typeAndSize) {
  283.         return typeAndSize & 0xffffff;
  284.     }

  285.     private long inflateBuf(BlockSource src, long pos, int blockLen,
  286.             int fileBlockSize) throws IOException {
  287.         byte[] dst = new byte[blockLen];
  288.         System.arraycopy(buf, 0, dst, 0, 4);

  289.         long deflatedSize = 0;
  290.         Inflater inf = InflaterCache.get();
  291.         try {
  292.             inf.setInput(buf, ptr, bufLen - ptr);
  293.             for (int o = 4;;) {
  294.                 int n = inf.inflate(dst, o, dst.length - o);
  295.                 o += n;
  296.                 if (inf.finished()) {
  297.                     deflatedSize = inf.getBytesRead();
  298.                     break;
  299.                 } else if (n <= 0 && inf.needsInput()) {
  300.                     long p = pos + 4 + inf.getBytesRead();
  301.                     readBlockIntoBuf(src, p, fileBlockSize);
  302.                     inf.setInput(buf, 0, bufLen);
  303.                 } else if (n <= 0) {
  304.                     throw invalidBlock();
  305.                 }
  306.             }
  307.         } catch (DataFormatException e) {
  308.             throw invalidBlock(e);
  309.         } finally {
  310.             InflaterCache.release(inf);
  311.         }

  312.         buf = dst;
  313.         bufLen = dst.length;
  314.         return deflatedSize;
  315.     }

  316.     private void setupEmptyFileBlock() {
  317.         // An empty reftable has only the file header in first block.
  318.         blockType = FILE_BLOCK_TYPE;
  319.         ptr = FILE_HEADER_LEN;
  320.         restartCnt = 0;
  321.         restartTbl = bufLen;
  322.         keysStart = bufLen;
  323.         keysEnd = bufLen;
  324.     }

  325.     void verifyIndex() throws IOException {
  326.         if (blockType != INDEX_BLOCK_TYPE || truncated) {
  327.             throw invalidBlock();
  328.         }
  329.     }

  330.     /**
  331.      * Finds a key in the block and positions the current pointer on its record.
  332.      * <p>
  333.      * As a side-effect this method arranges for the current pointer to be near
  334.      * or exactly on {@code key}, allowing other methods to access data from
  335.      * that current record:
  336.      * <ul>
  337.      * <li>{@link #name()}
  338.      * <li>{@link #match(byte[], boolean)}
  339.      * <li>{@link #readRef(long)}
  340.      * <li>{@link #readLogUpdateIndex()}
  341.      * <li>{@link #readLogEntry()}
  342.      * <li>{@link #readBlockPositionList()}
  343.      * </ul>
  344.      *
  345.      * @param key
  346.      *            key to find.
  347.      * @return {@code <0} if the key occurs before the start of this block;
  348.      *         {@code 0} if the block is positioned on the key; {@code >0} if
  349.      *         the key occurs after the last key of this block.
  350.      */
  351.     int seekKey(byte[] key) {
  352.         int low = 0;
  353.         int end = restartCnt;
  354.         for (;;) {
  355.             int mid = (low + end) >>> 1;
  356.             int p = NB.decodeUInt24(buf, restartTbl + mid * 3);
  357.             ptr = p + 1; // skip 0 prefix length
  358.             int n = readVarint32() >>> 3;
  359.             int cmp = compare(key, 0, key.length, buf, ptr, n);
  360.             if (cmp < 0) {
  361.                 end = mid;
  362.             } else if (cmp == 0) {
  363.                 ptr = p;
  364.                 return 0;
  365.             } else /* if (cmp > 0) */ {
  366.                 low = mid + 1;
  367.             }
  368.             if (low >= end) {
  369.                 return scanToKey(key, p, low, cmp);
  370.             }
  371.         }
  372.     }

  373.     /**
  374.      * Performs the linear search step within a restart interval.
  375.      * <p>
  376.      * Starts at a restart position whose key sorts before (or equal to)
  377.      * {@code key} and walks sequentially through the following prefix
  378.      * compressed records to find {@code key}.
  379.      *
  380.      * @param key
  381.      *            key the caller wants to find.
  382.      * @param rPtr
  383.      *            current record pointer from restart table binary search.
  384.      * @param rIdx
  385.      *            current restart table index.
  386.      * @param rCmp
  387.      *            result of compare from restart table binary search.
  388.      * @return {@code <0} if the key occurs before the start of this block;
  389.      *         {@code 0} if the block is positioned on the key; {@code >0} if
  390.      *         the key occurs after the last key of this block.
  391.      */
  392.     private int scanToKey(byte[] key, int rPtr, int rIdx, int rCmp) {
  393.         if (rCmp < 0) {
  394.             if (rIdx == 0) {
  395.                 ptr = keysStart;
  396.                 return -1;
  397.             }
  398.             ptr = NB.decodeUInt24(buf, restartTbl + (rIdx - 1) * 3);
  399.         } else {
  400.             ptr = rPtr;
  401.         }

  402.         int cmp;
  403.         do {
  404.             int savePtr = ptr;
  405.             parseKey();
  406.             cmp = compare(key, 0, key.length, nameBuf, 0, nameLen);
  407.             if (cmp <= 0) {
  408.                 // cmp < 0, name should be in this block, but is not.
  409.                 // cmp = 0, block is positioned at name.
  410.                 ptr = savePtr;
  411.                 return cmp < 0 && savePtr == keysStart ? -1 : 0;
  412.             }
  413.             skipValue();
  414.         } while (ptr < keysEnd);
  415.         return cmp;
  416.     }

  417.     void skipValue() {
  418.         switch (blockType) {
  419.         case REF_BLOCK_TYPE:
  420.             readVarint64(); // update_index_delta
  421.             switch (valueType & VALUE_TYPE_MASK) {
  422.             case VALUE_NONE:
  423.                 return;
  424.             case VALUE_1ID:
  425.                 ptr += OBJECT_ID_LENGTH;
  426.                 return;
  427.             case VALUE_2ID:
  428.                 ptr += 2 * OBJECT_ID_LENGTH;
  429.                 return;
  430.             case VALUE_SYMREF:
  431.                 skipString();
  432.                 return;
  433.             }
  434.             break;

  435.         case OBJ_BLOCK_TYPE: {
  436.             int n = valueType & VALUE_TYPE_MASK;
  437.             if (n == 0) {
  438.                 n = readVarint32();
  439.             }
  440.             while (n-- > 0) {
  441.                 readVarint32();
  442.             }
  443.             return;
  444.         }

  445.         case INDEX_BLOCK_TYPE:
  446.             readVarint32();
  447.             return;

  448.         case LOG_BLOCK_TYPE:
  449.             if ((valueType & VALUE_TYPE_MASK) == LOG_NONE) {
  450.                 return;
  451.             } else if ((valueType & VALUE_TYPE_MASK) == LOG_DATA) {
  452.                 ptr += 2 * OBJECT_ID_LENGTH; // oldId, newId
  453.                 skipString(); // name
  454.                 skipString(); // email
  455.                 readVarint64(); // time
  456.                 ptr += 2; // tz
  457.                 skipString(); // msg
  458.                 return;
  459.             }
  460.         }

  461.         throw new IllegalStateException();
  462.     }

  463.     private void skipString() {
  464.         int n = readVarint32(); // string length
  465.         ptr += n;
  466.     }

  467.     private short readInt16() {
  468.         short result =(short) NB.decodeUInt16(buf, ptr);
  469.         ptr += 2;
  470.         return result;
  471.     }

  472.     private int readVarint32() {
  473.         byte c = buf[ptr++];
  474.         int val = c & 0x7f;
  475.         while ((c & 0x80) != 0) {
  476.             c = buf[ptr++];
  477.             val++;
  478.             val <<= 7;
  479.             val |= (c & 0x7f);
  480.         }
  481.         return val;
  482.     }

  483.     private long readVarint64() {
  484.         byte c = buf[ptr++];
  485.         long val = c & 0x7f;
  486.         while ((c & 0x80) != 0) {
  487.             c = buf[ptr++];
  488.             val++;
  489.             val <<= 7;
  490.             val |= (c & 0x7f);
  491.         }
  492.         return val;
  493.     }

  494.     private static Ref newRef(String name, long updateIndex) {
  495.         return new ObjectIdRef.Unpeeled(NEW, name, null, updateIndex);
  496.     }

  497.     private static IOException invalidBlock() {
  498.         return invalidBlock(null);
  499.     }

  500.     private static IOException invalidBlock(Throwable cause) {
  501.         return new IOException(JGitText.get().invalidReftableBlock, cause);
  502.     }
  503. }