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 byte[] buf;
  58.     private int bufLen;
  59.     private int ptr;

  60.     private int keysStart;
  61.     private int keysEnd;

  62.     private int restartCnt;
  63.     private int restartTbl;

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

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

  70.     long endPosition() {
  71.         return endPosition;
  72.     }

  73.     boolean next() {
  74.         return ptr < keysEnd;
  75.     }

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

  88.     String name() {
  89.         int len = nameLen;
  90.         if (blockType == LOG_BLOCK_TYPE) {
  91.             len -= 9;
  92.         }
  93.         return RawParseUtils.decode(UTF_8, nameBuf, 0, len);
  94.     }

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

  110.     long readPositionFromIndex() throws IOException {
  111.         if (blockType != INDEX_BLOCK_TYPE) {
  112.             throw invalidBlock();
  113.         }

  114.         readVarint32(); // skip prefix length
  115.         int n = readVarint32() >>> 3;
  116.         ptr += n; // skip name
  117.         return readVarint64();
  118.     }

  119.     long readUpdateIndexDelta() {
  120.         return readVarint64();
  121.     }

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

  128.         case VALUE_1ID:
  129.             return new ObjectIdRef.PeeledNonTag(PACKED, name, readValueId(),
  130.                     updateIndex);

  131.         case VALUE_2ID: { // annotated tag
  132.             ObjectId id1 = readValueId();
  133.             ObjectId id2 = readValueId();
  134.             return new ObjectIdRef.PeeledTag(PACKED, name, id1, id2,
  135.                     updateIndex);
  136.         }

  137.         case VALUE_SYMREF: {
  138.             String val = readValueString();
  139.             return new SymbolicRef(name, newRef(val, updateIndex), updateIndex);
  140.         }

  141.         default:
  142.             throw invalidBlock();
  143.         }
  144.     }

  145.     @Nullable
  146.     LongList readBlockPositionList() {
  147.         int n = valueType & VALUE_TYPE_MASK;
  148.         if (n == 0) {
  149.             n = readVarint32();
  150.             if (n == 0) {
  151.                 return null;
  152.             }
  153.         }

  154.         LongList b = new LongList(n);
  155.         b.add(readVarint64());
  156.         for (int j = 1; j < n; j++) {
  157.             long prior = b.get(j - 1);
  158.             b.add(prior + readVarint64());
  159.         }
  160.         return b;
  161.     }

  162.     long readLogUpdateIndex() {
  163.         return reverseUpdateIndex(NB.decodeUInt64(nameBuf, nameLen - 8));
  164.     }

  165.     @Nullable
  166.     ReflogEntry readLogEntry() {
  167.         if ((valueType & VALUE_TYPE_MASK) == LOG_NONE) {
  168.             return null;
  169.         }

  170.         ObjectId oldId = readValueId();
  171.         ObjectId newId = readValueId();
  172.         PersonIdent who = readPersonIdent();
  173.         String msg = readValueString();

  174.         return new ReflogEntry() {
  175.             @Override
  176.             public ObjectId getOldId() {
  177.                 return oldId;
  178.             }

  179.             @Override
  180.             public ObjectId getNewId() {
  181.                 return newId;
  182.             }

  183.             @Override
  184.             public PersonIdent getWho() {
  185.                 return who;
  186.             }

  187.             @Override
  188.             public String getComment() {
  189.                 return msg;
  190.             }

  191.             @Override
  192.             public CheckoutEntry parseCheckout() {
  193.                 return null;
  194.             }
  195.         };
  196.     }

  197.     private ObjectId readValueId() {
  198.         ObjectId id = ObjectId.fromRaw(buf, ptr);
  199.         ptr += OBJECT_ID_LENGTH;
  200.         return id;
  201.     }

  202.     private String readValueString() {
  203.         int len = readVarint32();
  204.         int end = ptr + len;
  205.         String s = RawParseUtils.decode(UTF_8, buf, ptr, end);
  206.         ptr = end;
  207.         return s;
  208.     }

  209.     private PersonIdent readPersonIdent() {
  210.         String name = readValueString();
  211.         String email = readValueString();
  212.         long ms = readVarint64() * 1000;
  213.         int tz = readInt16();
  214.         return new PersonIdent(name, email, ms, tz);
  215.     }

  216.     void readBlock(BlockSource src, long pos, int fileBlockSize)
  217.             throws IOException {
  218.         readBlockIntoBuf(src, pos, fileBlockSize);
  219.         parseBlockStart(src, pos, fileBlockSize);
  220.     }

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

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

  247.         int typeAndSize = NB.decodeInt32(buf, ptr);
  248.         ptr += 4;

  249.         blockType = (byte) (typeAndSize >>> 24);
  250.         int blockLen = decodeBlockLen(typeAndSize);
  251.         if (blockType == LOG_BLOCK_TYPE) {
  252.             // Log blocks must be inflated after the header.
  253.             long deflatedSize = inflateBuf(src, pos, blockLen, fileBlockSize);
  254.             endPosition = pos + 4 + deflatedSize;
  255.         } else if (bufLen < blockLen) {
  256.             readBlockIntoBuf(src, pos, blockLen);
  257.         } else if (bufLen > blockLen) {
  258.             bufLen = blockLen;
  259.         }

  260.         if (blockType != FILE_BLOCK_TYPE) {
  261.             restartCnt = NB.decodeUInt16(buf, bufLen - 2);
  262.             restartTbl = bufLen - (restartCnt * 3 + 2);
  263.             keysStart = ptr;
  264.             keysEnd = restartTbl;
  265.         } else {
  266.             keysStart = ptr;
  267.             keysEnd = ptr;
  268.         }
  269.     }

  270.     static int decodeBlockLen(int typeAndSize) {
  271.         return typeAndSize & 0xffffff;
  272.     }

  273.     private long inflateBuf(BlockSource src, long pos, int blockLen,
  274.             int fileBlockSize) throws IOException {
  275.         byte[] dst = new byte[blockLen];
  276.         System.arraycopy(buf, 0, dst, 0, 4);

  277.         long deflatedSize = 0;
  278.         Inflater inf = InflaterCache.get();
  279.         try {
  280.             inf.setInput(buf, ptr, bufLen - ptr);
  281.             for (int o = 4;;) {
  282.                 int n = inf.inflate(dst, o, dst.length - o);
  283.                 o += n;
  284.                 if (inf.finished()) {
  285.                     deflatedSize = inf.getBytesRead();
  286.                     break;
  287.                 } else if (n <= 0 && inf.needsInput()) {
  288.                     long p = pos + 4 + inf.getBytesRead();
  289.                     readBlockIntoBuf(src, p, fileBlockSize);
  290.                     inf.setInput(buf, 0, bufLen);
  291.                 } else if (n <= 0) {
  292.                     throw invalidBlock();
  293.                 }
  294.             }
  295.         } catch (DataFormatException e) {
  296.             throw invalidBlock(e);
  297.         } finally {
  298.             InflaterCache.release(inf);
  299.         }

  300.         buf = dst;
  301.         bufLen = dst.length;
  302.         return deflatedSize;
  303.     }

  304.     private void setupEmptyFileBlock() {
  305.         // An empty reftable has only the file header in first block.
  306.         blockType = FILE_BLOCK_TYPE;
  307.         ptr = FILE_HEADER_LEN;
  308.         restartCnt = 0;
  309.         restartTbl = bufLen;
  310.         keysStart = bufLen;
  311.         keysEnd = bufLen;
  312.     }

  313.     void verifyIndex() throws IOException {
  314.         if (blockType != INDEX_BLOCK_TYPE) {
  315.             throw invalidBlock();
  316.         }
  317.     }

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

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

  390.         int cmp;
  391.         do {
  392.             int savePtr = ptr;
  393.             parseKey();
  394.             cmp = compare(key, 0, key.length, nameBuf, 0, nameLen);
  395.             if (cmp <= 0) {
  396.                 // cmp < 0, name should be in this block, but is not.
  397.                 // cmp = 0, block is positioned at name.
  398.                 ptr = savePtr;
  399.                 return cmp < 0 && savePtr == keysStart ? -1 : 0;
  400.             }
  401.             skipValue();
  402.         } while (ptr < keysEnd);
  403.         return cmp;
  404.     }

  405.     void skipValue() {
  406.         switch (blockType) {
  407.         case REF_BLOCK_TYPE:
  408.             readVarint64(); // update_index_delta
  409.             switch (valueType & VALUE_TYPE_MASK) {
  410.             case VALUE_NONE:
  411.                 return;
  412.             case VALUE_1ID:
  413.                 ptr += OBJECT_ID_LENGTH;
  414.                 return;
  415.             case VALUE_2ID:
  416.                 ptr += 2 * OBJECT_ID_LENGTH;
  417.                 return;
  418.             case VALUE_SYMREF:
  419.                 skipString();
  420.                 return;
  421.             }
  422.             break;

  423.         case OBJ_BLOCK_TYPE: {
  424.             int n = valueType & VALUE_TYPE_MASK;
  425.             if (n == 0) {
  426.                 n = readVarint32();
  427.             }
  428.             while (n-- > 0) {
  429.                 readVarint32();
  430.             }
  431.             return;
  432.         }

  433.         case INDEX_BLOCK_TYPE:
  434.             readVarint32();
  435.             return;

  436.         case LOG_BLOCK_TYPE:
  437.             if ((valueType & VALUE_TYPE_MASK) == LOG_NONE) {
  438.                 return;
  439.             } else if ((valueType & VALUE_TYPE_MASK) == LOG_DATA) {
  440.                 ptr += 2 * OBJECT_ID_LENGTH; // oldId, newId
  441.                 skipString(); // name
  442.                 skipString(); // email
  443.                 readVarint64(); // time
  444.                 ptr += 2; // tz
  445.                 skipString(); // msg
  446.                 return;
  447.             }
  448.         }

  449.         throw new IllegalStateException();
  450.     }

  451.     private void skipString() {
  452.         int n = readVarint32(); // string length
  453.         ptr += n;
  454.     }

  455.     private short readInt16() {
  456.         short result =(short) NB.decodeUInt16(buf, ptr);
  457.         ptr += 2;
  458.         return result;
  459.     }

  460.     private int readVarint32() {
  461.         byte c = buf[ptr++];
  462.         int val = c & 0x7f;
  463.         while ((c & 0x80) != 0) {
  464.             c = buf[ptr++];
  465.             val++;
  466.             val <<= 7;
  467.             val |= (c & 0x7f);
  468.         }
  469.         return val;
  470.     }

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

  482.     private static Ref newRef(String name, long updateIndex) {
  483.         return new ObjectIdRef.Unpeeled(NEW, name, null, updateIndex);
  484.     }

  485.     private static IOException invalidBlock() {
  486.         return invalidBlock(null);
  487.     }

  488.     private static IOException invalidBlock(Throwable cause) {
  489.         return new IOException(JGitText.get().invalidReftableBlock, cause);
  490.     }
  491. }