You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

FsckPackParser.java 8.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  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. }