FsckPackParser.java

  1. /*
  2.  * Copyright (C) 2017, Google Inc.
  3.  * and other copyright owners as documented in the project's IP log.
  4.  *
  5.  * This program and the accompanying materials are made available
  6.  * under the terms of the Eclipse Distribution License v1.0 which
  7.  * accompanies this distribution, is reproduced below, and is
  8.  * available at http://www.eclipse.org/org/documents/edl-v10.php
  9.  *
  10.  * All rights reserved.
  11.  *
  12.  * Redistribution and use in source and binary forms, with or
  13.  * without modification, are permitted provided that the following
  14.  * conditions are met:
  15.  *
  16.  * - Redistributions of source code must retain the above copyright
  17.  *   notice, this list of conditions and the following disclaimer.
  18.  *
  19.  * - Redistributions in binary form must reproduce the above
  20.  *   copyright notice, this list of conditions and the following
  21.  *   disclaimer in the documentation and/or other materials provided
  22.  *   with the distribution.
  23.  *
  24.  * - Neither the name of the Eclipse Foundation, Inc. nor the
  25.  *   names of its contributors may be used to endorse or promote
  26.  *   products derived from this software without specific prior
  27.  *   written permission.
  28.  *
  29.  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
  30.  * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
  31.  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  32.  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  33.  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  34.  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  35.  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  36.  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  37.  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  38.  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  39.  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  40.  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  41.  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  42.  */

  43. package org.eclipse.jgit.internal.fsck;

  44. import java.io.IOException;
  45. import java.nio.ByteBuffer;
  46. import java.nio.channels.Channels;
  47. import java.text.MessageFormat;
  48. import java.util.Arrays;
  49. import java.util.HashSet;
  50. import java.util.Set;
  51. import java.util.zip.CRC32;

  52. import org.eclipse.jgit.errors.CorruptObjectException;
  53. import org.eclipse.jgit.errors.CorruptPackIndexException;
  54. import org.eclipse.jgit.errors.CorruptPackIndexException.ErrorType;
  55. import org.eclipse.jgit.errors.MissingObjectException;
  56. import org.eclipse.jgit.internal.JGitText;
  57. import org.eclipse.jgit.internal.fsck.FsckError.CorruptObject;
  58. import org.eclipse.jgit.internal.storage.dfs.ReadableChannel;
  59. import org.eclipse.jgit.internal.storage.file.PackIndex;
  60. import org.eclipse.jgit.internal.storage.file.PackIndex.MutableEntry;
  61. import org.eclipse.jgit.lib.AnyObjectId;
  62. import org.eclipse.jgit.lib.ObjectDatabase;
  63. import org.eclipse.jgit.lib.ObjectIdOwnerMap;
  64. import org.eclipse.jgit.transport.PackParser;
  65. import org.eclipse.jgit.transport.PackedObjectInfo;

  66. /**
  67.  * A read-only pack parser for object validity checking.
  68.  */
  69. public class FsckPackParser extends PackParser {
  70.     private final CRC32 crc;

  71.     private final ReadableChannel channel;

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

  73.     private long expectedObjectCount = -1L;

  74.     private long offset;

  75.     private int blockSize;

  76.     /**
  77.      * Constructor for FsckPackParser
  78.      *
  79.      * @param db
  80.      *            the object database which stores repository's data.
  81.      * @param channel
  82.      *            readable channel of the pack file.
  83.      */
  84.     public FsckPackParser(ObjectDatabase db, ReadableChannel channel) {
  85.         super(db, Channels.newInputStream(channel));
  86.         this.channel = channel;
  87.         setCheckObjectCollisions(false);
  88.         this.crc = new CRC32();
  89.         this.blockSize = channel.blockSize() > 0 ? channel.blockSize() : 65536;
  90.     }

  91.     /** {@inheritDoc} */
  92.     @Override
  93.     protected void onPackHeader(long objCnt) throws IOException {
  94.         if (expectedObjectCount >= 0) {
  95.             // Some DFS pack files don't contain the correct object count, e.g.
  96.             // INSERT/RECEIVE packs don't always contain the correct object
  97.             // count in their headers. Overwrite the expected object count
  98.             // after parsing the pack header.
  99.             setExpectedObjectCount(expectedObjectCount);
  100.         }
  101.     }

  102.     /** {@inheritDoc} */
  103.     @Override
  104.     protected void onBeginWholeObject(long streamPosition, int type,
  105.             long inflatedSize) throws IOException {
  106.         crc.reset();
  107.     }

  108.     /** {@inheritDoc} */
  109.     @Override
  110.     protected void onObjectHeader(Source src, byte[] raw, int pos, int len)
  111.             throws IOException {
  112.         crc.update(raw, pos, len);
  113.     }

  114.     /** {@inheritDoc} */
  115.     @Override
  116.     protected void onObjectData(Source src, byte[] raw, int pos, int len)
  117.             throws IOException {
  118.         crc.update(raw, pos, len);
  119.     }

  120.     /** {@inheritDoc} */
  121.     @Override
  122.     protected void onEndWholeObject(PackedObjectInfo info) throws IOException {
  123.         info.setCRC((int) crc.getValue());
  124.     }

  125.     /** {@inheritDoc} */
  126.     @Override
  127.     protected void onBeginOfsDelta(long deltaStreamPosition,
  128.             long baseStreamPosition, long inflatedSize) throws IOException {
  129.         crc.reset();
  130.     }

  131.     /** {@inheritDoc} */
  132.     @Override
  133.     protected void onBeginRefDelta(long deltaStreamPosition, AnyObjectId baseId,
  134.             long inflatedSize) throws IOException {
  135.         crc.reset();
  136.     }

  137.     /** {@inheritDoc} */
  138.     @Override
  139.     protected UnresolvedDelta onEndDelta() throws IOException {
  140.         UnresolvedDelta delta = new UnresolvedDelta();
  141.         delta.setCRC((int) crc.getValue());
  142.         return delta;
  143.     }

  144.     /** {@inheritDoc} */
  145.     @Override
  146.     protected void onInflatedObjectData(PackedObjectInfo obj, int typeCode,
  147.             byte[] data) throws IOException {
  148.         // FsckPackParser ignores this event.
  149.     }

  150.     /** {@inheritDoc} */
  151.     @Override
  152.     protected void verifySafeObject(final AnyObjectId id, final int type,
  153.             final byte[] data) {
  154.         try {
  155.             super.verifySafeObject(id, type, data);
  156.         } catch (CorruptObjectException e) {
  157.             corruptObjects.add(
  158.                     new CorruptObject(id.toObjectId(), type, e.getErrorType()));
  159.         }
  160.     }

  161.     /** {@inheritDoc} */
  162.     @Override
  163.     protected void onPackFooter(byte[] hash) throws IOException {
  164.         // Do nothing.
  165.     }

  166.     /** {@inheritDoc} */
  167.     @Override
  168.     protected boolean onAppendBase(int typeCode, byte[] data,
  169.             PackedObjectInfo info) throws IOException {
  170.         // Do nothing.
  171.         return false;
  172.     }

  173.     /** {@inheritDoc} */
  174.     @Override
  175.     protected void onEndThinPack() throws IOException {
  176.         // Do nothing.
  177.     }

  178.     /** {@inheritDoc} */
  179.     @Override
  180.     protected ObjectTypeAndSize seekDatabase(PackedObjectInfo obj,
  181.             ObjectTypeAndSize info) throws IOException {
  182.         crc.reset();
  183.         offset = obj.getOffset();
  184.         return readObjectHeader(info);
  185.     }

  186.     /** {@inheritDoc} */
  187.     @Override
  188.     protected ObjectTypeAndSize seekDatabase(UnresolvedDelta delta,
  189.             ObjectTypeAndSize info) throws IOException {
  190.         crc.reset();
  191.         offset = delta.getOffset();
  192.         return readObjectHeader(info);
  193.     }

  194.     /** {@inheritDoc} */
  195.     @Override
  196.     protected int readDatabase(byte[] dst, int pos, int cnt)
  197.             throws IOException {
  198.         // read from input instead of database.
  199.         int n = read(offset, dst, pos, cnt);
  200.         if (n > 0) {
  201.             offset += n;
  202.         }
  203.         return n;
  204.     }

  205.     int read(long channelPosition, byte[] dst, int pos, int cnt)
  206.             throws IOException {
  207.         long block = channelPosition / blockSize;
  208.         byte[] bytes = readFromChannel(block);
  209.         if (bytes == null) {
  210.             return -1;
  211.         }
  212.         int offs = (int) (channelPosition - block * blockSize);
  213.         int bytesToCopy = Math.min(cnt, bytes.length - offs);
  214.         if (bytesToCopy < 1) {
  215.             return -1;
  216.         }
  217.         System.arraycopy(bytes, offs, dst, pos, bytesToCopy);
  218.         return bytesToCopy;
  219.     }

  220.     private byte[] readFromChannel(long block) throws IOException {
  221.         channel.position(block * blockSize);
  222.         ByteBuffer buf = ByteBuffer.allocate(blockSize);
  223.         int totalBytesRead = 0;
  224.         while (totalBytesRead < blockSize) {
  225.             int bytesRead = channel.read(buf);
  226.             if (bytesRead == -1) {
  227.                 if (totalBytesRead == 0) {
  228.                     return null;
  229.                 }
  230.                 return Arrays.copyOf(buf.array(), totalBytesRead);
  231.             }
  232.             totalBytesRead += bytesRead;
  233.         }
  234.         return buf.array();
  235.     }

  236.     /** {@inheritDoc} */
  237.     @Override
  238.     protected boolean checkCRC(int oldCRC) {
  239.         return oldCRC == (int) crc.getValue();
  240.     }

  241.     /** {@inheritDoc} */
  242.     @Override
  243.     protected void onStoreStream(byte[] raw, int pos, int len)
  244.             throws IOException {
  245.         // Do nothing.
  246.     }

  247.     /**
  248.      * Get corrupt objects reported by
  249.      * {@link org.eclipse.jgit.lib.ObjectChecker}
  250.      *
  251.      * @return corrupt objects that are reported by
  252.      *         {@link org.eclipse.jgit.lib.ObjectChecker}.
  253.      */
  254.     public Set<CorruptObject> getCorruptObjects() {
  255.         return corruptObjects;
  256.     }

  257.     /**
  258.      * Verify the existing index file with all objects from the pack.
  259.      *
  260.      * @param idx
  261.      *            index file associate with the pack
  262.      * @throws org.eclipse.jgit.errors.CorruptPackIndexException
  263.      *             when the index file is corrupt.
  264.      */
  265.     public void verifyIndex(PackIndex idx)
  266.             throws CorruptPackIndexException {
  267.         ObjectIdOwnerMap<ObjFromPack> inPack = new ObjectIdOwnerMap<>();
  268.         for (int i = 0; i < getObjectCount(); i++) {
  269.             PackedObjectInfo entry = getObject(i);
  270.             inPack.add(new ObjFromPack(entry));

  271.             long offs = idx.findOffset(entry);
  272.             if (offs == -1) {
  273.                 throw new CorruptPackIndexException(
  274.                         MessageFormat.format(JGitText.get().missingObject,
  275.                                 Integer.valueOf(entry.getType()),
  276.                                 entry.getName()),
  277.                         ErrorType.MISSING_OBJ);
  278.             } else if (offs != entry.getOffset()) {
  279.                 throw new CorruptPackIndexException(MessageFormat
  280.                         .format(JGitText.get().mismatchOffset, entry.getName()),
  281.                         ErrorType.MISMATCH_OFFSET);
  282.             }

  283.             try {
  284.                 if (idx.hasCRC32Support()
  285.                         && (int) idx.findCRC32(entry) != entry.getCRC()) {
  286.                     throw new CorruptPackIndexException(
  287.                             MessageFormat.format(JGitText.get().mismatchCRC,
  288.                                     entry.getName()),
  289.                             ErrorType.MISMATCH_CRC);
  290.                 }
  291.             } catch (MissingObjectException e) {
  292.                 throw new CorruptPackIndexException(MessageFormat
  293.                         .format(JGitText.get().missingCRC, entry.getName()),
  294.                         ErrorType.MISSING_CRC);
  295.             }
  296.         }

  297.         for (MutableEntry entry : idx) {
  298.             if (!inPack.contains(entry.toObjectId())) {
  299.                 throw new CorruptPackIndexException(MessageFormat.format(
  300.                         JGitText.get().unknownObjectInIndex, entry.name()),
  301.                         ErrorType.UNKNOWN_OBJ);
  302.             }
  303.         }
  304.     }

  305.     /**
  306.      * Set the object count for overwriting the expected object count from pack
  307.      * header.
  308.      *
  309.      * @param objectCount
  310.      *            the actual expected object count.
  311.      */
  312.     public void overwriteObjectCount(long objectCount) {
  313.         this.expectedObjectCount = objectCount;
  314.     }

  315.     static class ObjFromPack extends ObjectIdOwnerMap.Entry {
  316.         ObjFromPack(AnyObjectId id) {
  317.             super(id);
  318.         }
  319.     }
  320. }