FsckPackParser.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.fsck;

  11. import java.io.IOException;
  12. import java.nio.ByteBuffer;
  13. import java.nio.channels.Channels;
  14. import java.text.MessageFormat;
  15. import java.util.Arrays;
  16. import java.util.HashSet;
  17. import java.util.Set;
  18. import java.util.zip.CRC32;

  19. import org.eclipse.jgit.errors.CorruptObjectException;
  20. import org.eclipse.jgit.errors.CorruptPackIndexException;
  21. import org.eclipse.jgit.errors.CorruptPackIndexException.ErrorType;
  22. import org.eclipse.jgit.errors.MissingObjectException;
  23. import org.eclipse.jgit.internal.JGitText;
  24. import org.eclipse.jgit.internal.fsck.FsckError.CorruptObject;
  25. import org.eclipse.jgit.internal.storage.dfs.ReadableChannel;
  26. import org.eclipse.jgit.internal.storage.file.PackIndex;
  27. import org.eclipse.jgit.internal.storage.file.PackIndex.MutableEntry;
  28. import org.eclipse.jgit.lib.AnyObjectId;
  29. import org.eclipse.jgit.lib.ObjectDatabase;
  30. import org.eclipse.jgit.lib.ObjectIdOwnerMap;
  31. import org.eclipse.jgit.transport.PackParser;
  32. import org.eclipse.jgit.transport.PackedObjectInfo;

  33. /**
  34.  * A read-only pack parser for object validity checking.
  35.  */
  36. public class FsckPackParser extends PackParser {
  37.     private final CRC32 crc;

  38.     private final ReadableChannel channel;

  39.     private final Set<CorruptObject> corruptObjects = new HashSet<>();

  40.     private long expectedObjectCount = -1L;

  41.     private long offset;

  42.     private int blockSize;

  43.     /**
  44.      * Constructor for FsckPackParser
  45.      *
  46.      * @param db
  47.      *            the object database which stores repository's data.
  48.      * @param channel
  49.      *            readable channel of the pack file.
  50.      */
  51.     public FsckPackParser(ObjectDatabase db, ReadableChannel channel) {
  52.         super(db, Channels.newInputStream(channel));
  53.         this.channel = channel;
  54.         setCheckObjectCollisions(false);
  55.         this.crc = new CRC32();
  56.         this.blockSize = channel.blockSize() > 0 ? channel.blockSize() : 65536;
  57.     }

  58.     /** {@inheritDoc} */
  59.     @Override
  60.     protected void onPackHeader(long objCnt) throws IOException {
  61.         if (expectedObjectCount >= 0) {
  62.             // Some DFS pack files don't contain the correct object count, e.g.
  63.             // INSERT/RECEIVE packs don't always contain the correct object
  64.             // count in their headers. Overwrite the expected object count
  65.             // after parsing the pack header.
  66.             setExpectedObjectCount(expectedObjectCount);
  67.         }
  68.     }

  69.     /** {@inheritDoc} */
  70.     @Override
  71.     protected void onBeginWholeObject(long streamPosition, int type,
  72.             long inflatedSize) throws IOException {
  73.         crc.reset();
  74.     }

  75.     /** {@inheritDoc} */
  76.     @Override
  77.     protected void onObjectHeader(Source src, byte[] raw, int pos, int len)
  78.             throws IOException {
  79.         crc.update(raw, pos, len);
  80.     }

  81.     /** {@inheritDoc} */
  82.     @Override
  83.     protected void onObjectData(Source src, byte[] raw, int pos, int len)
  84.             throws IOException {
  85.         crc.update(raw, pos, len);
  86.     }

  87.     /** {@inheritDoc} */
  88.     @Override
  89.     protected void onEndWholeObject(PackedObjectInfo info) throws IOException {
  90.         info.setCRC((int) crc.getValue());
  91.     }

  92.     /** {@inheritDoc} */
  93.     @Override
  94.     protected void onBeginOfsDelta(long deltaStreamPosition,
  95.             long baseStreamPosition, long inflatedSize) throws IOException {
  96.         crc.reset();
  97.     }

  98.     /** {@inheritDoc} */
  99.     @Override
  100.     protected void onBeginRefDelta(long deltaStreamPosition, AnyObjectId baseId,
  101.             long inflatedSize) throws IOException {
  102.         crc.reset();
  103.     }

  104.     /** {@inheritDoc} */
  105.     @Override
  106.     protected UnresolvedDelta onEndDelta() throws IOException {
  107.         UnresolvedDelta delta = new UnresolvedDelta();
  108.         delta.setCRC((int) crc.getValue());
  109.         return delta;
  110.     }

  111.     /** {@inheritDoc} */
  112.     @Override
  113.     protected void onInflatedObjectData(PackedObjectInfo obj, int typeCode,
  114.             byte[] data) throws IOException {
  115.         // FsckPackParser ignores this event.
  116.     }

  117.     /** {@inheritDoc} */
  118.     @Override
  119.     protected void verifySafeObject(final AnyObjectId id, final int type,
  120.             final byte[] data) {
  121.         try {
  122.             super.verifySafeObject(id, type, data);
  123.         } catch (CorruptObjectException e) {
  124.             corruptObjects.add(
  125.                     new CorruptObject(id.toObjectId(), type, e.getErrorType()));
  126.         }
  127.     }

  128.     /** {@inheritDoc} */
  129.     @Override
  130.     protected void onPackFooter(byte[] hash) throws IOException {
  131.         // Do nothing.
  132.     }

  133.     /** {@inheritDoc} */
  134.     @Override
  135.     protected boolean onAppendBase(int typeCode, byte[] data,
  136.             PackedObjectInfo info) throws IOException {
  137.         // Do nothing.
  138.         return false;
  139.     }

  140.     /** {@inheritDoc} */
  141.     @Override
  142.     protected void onEndThinPack() throws IOException {
  143.         // Do nothing.
  144.     }

  145.     /** {@inheritDoc} */
  146.     @Override
  147.     protected ObjectTypeAndSize seekDatabase(PackedObjectInfo obj,
  148.             ObjectTypeAndSize info) throws IOException {
  149.         crc.reset();
  150.         offset = obj.getOffset();
  151.         return readObjectHeader(info);
  152.     }

  153.     /** {@inheritDoc} */
  154.     @Override
  155.     protected ObjectTypeAndSize seekDatabase(UnresolvedDelta delta,
  156.             ObjectTypeAndSize info) throws IOException {
  157.         crc.reset();
  158.         offset = delta.getOffset();
  159.         return readObjectHeader(info);
  160.     }

  161.     /** {@inheritDoc} */
  162.     @Override
  163.     protected int readDatabase(byte[] dst, int pos, int cnt)
  164.             throws IOException {
  165.         // read from input instead of database.
  166.         int n = read(offset, dst, pos, cnt);
  167.         if (n > 0) {
  168.             offset += n;
  169.         }
  170.         return n;
  171.     }

  172.     int read(long channelPosition, byte[] dst, int pos, int cnt)
  173.             throws IOException {
  174.         long block = channelPosition / blockSize;
  175.         byte[] bytes = readFromChannel(block);
  176.         if (bytes == null) {
  177.             return -1;
  178.         }
  179.         int offs = (int) (channelPosition - block * blockSize);
  180.         int bytesToCopy = Math.min(cnt, bytes.length - offs);
  181.         if (bytesToCopy < 1) {
  182.             return -1;
  183.         }
  184.         System.arraycopy(bytes, offs, dst, pos, bytesToCopy);
  185.         return bytesToCopy;
  186.     }

  187.     private byte[] readFromChannel(long block) throws IOException {
  188.         channel.position(block * blockSize);
  189.         ByteBuffer buf = ByteBuffer.allocate(blockSize);
  190.         int totalBytesRead = 0;
  191.         while (totalBytesRead < blockSize) {
  192.             int bytesRead = channel.read(buf);
  193.             if (bytesRead == -1) {
  194.                 if (totalBytesRead == 0) {
  195.                     return null;
  196.                 }
  197.                 return Arrays.copyOf(buf.array(), totalBytesRead);
  198.             }
  199.             totalBytesRead += bytesRead;
  200.         }
  201.         return buf.array();
  202.     }

  203.     /** {@inheritDoc} */
  204.     @Override
  205.     protected boolean checkCRC(int oldCRC) {
  206.         return oldCRC == (int) crc.getValue();
  207.     }

  208.     /** {@inheritDoc} */
  209.     @Override
  210.     protected void onStoreStream(byte[] raw, int pos, int len)
  211.             throws IOException {
  212.         // Do nothing.
  213.     }

  214.     /**
  215.      * Get corrupt objects reported by
  216.      * {@link org.eclipse.jgit.lib.ObjectChecker}
  217.      *
  218.      * @return corrupt objects that are reported by
  219.      *         {@link org.eclipse.jgit.lib.ObjectChecker}.
  220.      */
  221.     public Set<CorruptObject> getCorruptObjects() {
  222.         return corruptObjects;
  223.     }

  224.     /**
  225.      * Verify the existing index file with all objects from the pack.
  226.      *
  227.      * @param idx
  228.      *            index file associate with the pack
  229.      * @throws org.eclipse.jgit.errors.CorruptPackIndexException
  230.      *             when the index file is corrupt.
  231.      */
  232.     public void verifyIndex(PackIndex idx)
  233.             throws CorruptPackIndexException {
  234.         ObjectIdOwnerMap<ObjFromPack> inPack = new ObjectIdOwnerMap<>();
  235.         for (int i = 0; i < getObjectCount(); i++) {
  236.             PackedObjectInfo entry = getObject(i);
  237.             inPack.add(new ObjFromPack(entry));

  238.             long offs = idx.findOffset(entry);
  239.             if (offs == -1) {
  240.                 throw new CorruptPackIndexException(
  241.                         MessageFormat.format(JGitText.get().missingObject,
  242.                                 Integer.valueOf(entry.getType()),
  243.                                 entry.getName()),
  244.                         ErrorType.MISSING_OBJ);
  245.             } else if (offs != entry.getOffset()) {
  246.                 throw new CorruptPackIndexException(MessageFormat
  247.                         .format(JGitText.get().mismatchOffset, entry.getName()),
  248.                         ErrorType.MISMATCH_OFFSET);
  249.             }

  250.             try {
  251.                 if (idx.hasCRC32Support()
  252.                         && (int) idx.findCRC32(entry) != entry.getCRC()) {
  253.                     throw new CorruptPackIndexException(
  254.                             MessageFormat.format(JGitText.get().mismatchCRC,
  255.                                     entry.getName()),
  256.                             ErrorType.MISMATCH_CRC);
  257.                 }
  258.             } catch (MissingObjectException e) {
  259.                 CorruptPackIndexException cpe = new CorruptPackIndexException(
  260.                         MessageFormat.format(JGitText.get().missingCRC,
  261.                                 entry.getName()),
  262.                         ErrorType.MISSING_CRC);
  263.                 cpe.initCause(e);
  264.                 throw cpe;
  265.             }
  266.         }

  267.         for (MutableEntry entry : idx) {
  268.             if (!inPack.contains(entry.toObjectId())) {
  269.                 throw new CorruptPackIndexException(MessageFormat.format(
  270.                         JGitText.get().unknownObjectInIndex, entry.name()),
  271.                         ErrorType.UNKNOWN_OBJ);
  272.             }
  273.         }
  274.     }

  275.     /**
  276.      * Set the object count for overwriting the expected object count from pack
  277.      * header.
  278.      *
  279.      * @param objectCount
  280.      *            the actual expected object count.
  281.      */
  282.     public void overwriteObjectCount(long objectCount) {
  283.         this.expectedObjectCount = objectCount;
  284.     }

  285.     static class ObjFromPack extends ObjectIdOwnerMap.Entry {
  286.         ObjFromPack(AnyObjectId id) {
  287.             super(id);
  288.         }
  289.     }
  290. }