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.

ReftableReader.java 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694
  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.BlockReader.decodeBlockLen;
  13. import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.FILE_BLOCK_TYPE;
  14. import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.FILE_FOOTER_LEN;
  15. import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.FILE_HEADER_LEN;
  16. import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.INDEX_BLOCK_TYPE;
  17. import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.LOG_BLOCK_TYPE;
  18. import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.REF_BLOCK_TYPE;
  19. import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.VERSION_1;
  20. import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.isFileHeaderMagic;
  21. import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH;
  22. import java.io.IOException;
  23. import java.nio.ByteBuffer;
  24. import java.text.MessageFormat;
  25. import java.util.Arrays;
  26. import java.util.zip.CRC32;
  27. import org.eclipse.jgit.internal.JGitText;
  28. import org.eclipse.jgit.internal.storage.io.BlockSource;
  29. import org.eclipse.jgit.internal.storage.reftable.BlockWriter.LogEntry;
  30. import org.eclipse.jgit.lib.AnyObjectId;
  31. import org.eclipse.jgit.lib.ObjectId;
  32. import org.eclipse.jgit.lib.Ref;
  33. import org.eclipse.jgit.lib.ReflogEntry;
  34. import org.eclipse.jgit.util.LongList;
  35. import org.eclipse.jgit.util.LongMap;
  36. import org.eclipse.jgit.util.NB;
  37. /**
  38. * Reads a reftable formatted file.
  39. * <p>
  40. * {@code ReftableReader} is not thread-safe. Concurrent readers need their own
  41. * instance to read from the same file.
  42. */
  43. public class ReftableReader extends Reftable implements AutoCloseable {
  44. private final BlockSource src;
  45. private int blockSize = -1;
  46. private long minUpdateIndex;
  47. private long maxUpdateIndex;
  48. private long refEnd;
  49. private long objPosition;
  50. private long objEnd;
  51. private long logPosition;
  52. private long logEnd;
  53. private int objIdLen;
  54. private long refIndexPosition = -1;
  55. private long objIndexPosition = -1;
  56. private long logIndexPosition = -1;
  57. private BlockReader refIndex;
  58. private BlockReader objIndex;
  59. private BlockReader logIndex;
  60. private LongMap<BlockReader> indexCache;
  61. /**
  62. * Initialize a new reftable reader.
  63. *
  64. * @param src
  65. * the file content to read.
  66. */
  67. public ReftableReader(BlockSource src) {
  68. this.src = src;
  69. }
  70. /**
  71. * Get the block size in bytes chosen for this file by the writer.
  72. *
  73. * @return the block size in bytes chosen for this file by the writer. Most
  74. * reads from the
  75. * {@link org.eclipse.jgit.internal.storage.io.BlockSource} will be
  76. * aligned to the block size.
  77. * @throws java.io.IOException
  78. * file cannot be read.
  79. */
  80. public int blockSize() throws IOException {
  81. if (blockSize == -1) {
  82. readFileHeader();
  83. }
  84. return blockSize;
  85. }
  86. @Override
  87. public boolean hasObjectMap() throws IOException {
  88. if (objIndexPosition == -1) {
  89. readFileFooter();
  90. }
  91. // We have the map, we have no refs, or the table is small.
  92. return (objPosition > 0 || refEnd == 24 || refIndexPosition == 0);
  93. }
  94. /**
  95. * {@inheritDoc}
  96. */
  97. @Override
  98. public long minUpdateIndex() throws IOException {
  99. if (blockSize == -1) {
  100. readFileHeader();
  101. }
  102. return minUpdateIndex;
  103. }
  104. /**
  105. * {@inheritDoc}
  106. */
  107. @Override
  108. public long maxUpdateIndex() throws IOException {
  109. if (blockSize == -1) {
  110. readFileHeader();
  111. }
  112. return maxUpdateIndex;
  113. }
  114. /** {@inheritDoc} */
  115. @Override
  116. public RefCursor allRefs() throws IOException {
  117. if (blockSize == -1) {
  118. readFileHeader();
  119. }
  120. if (refEnd == 0) {
  121. readFileFooter();
  122. }
  123. src.adviseSequentialRead(0, refEnd);
  124. RefCursorImpl i = new RefCursorImpl(refEnd, null, false);
  125. i.block = readBlock(0, refEnd);
  126. return i;
  127. }
  128. /** {@inheritDoc} */
  129. @Override
  130. public RefCursor seekRef(String refName) throws IOException {
  131. initRefIndex();
  132. byte[] key = refName.getBytes(UTF_8);
  133. RefCursorImpl i = new RefCursorImpl(refEnd, key, false);
  134. i.block = seek(REF_BLOCK_TYPE, key, refIndex, 0, refEnd);
  135. return i;
  136. }
  137. /** {@inheritDoc} */
  138. @Override
  139. public RefCursor seekRefsWithPrefix(String prefix) throws IOException {
  140. initRefIndex();
  141. byte[] key = prefix.getBytes(UTF_8);
  142. RefCursorImpl i = new RefCursorImpl(refEnd, key, true);
  143. i.block = seek(REF_BLOCK_TYPE, key, refIndex, 0, refEnd);
  144. return i;
  145. }
  146. /** {@inheritDoc} */
  147. @Override
  148. public RefCursor byObjectId(AnyObjectId id) throws IOException {
  149. initObjIndex();
  150. ObjCursorImpl i = new ObjCursorImpl(refEnd, id);
  151. if (objIndex != null) {
  152. i.initSeek();
  153. } else {
  154. i.initScan();
  155. }
  156. return i;
  157. }
  158. /** {@inheritDoc} */
  159. @Override
  160. public LogCursor allLogs() throws IOException {
  161. initLogIndex();
  162. if (logPosition > 0) {
  163. src.adviseSequentialRead(logPosition, logEnd);
  164. LogCursorImpl i = new LogCursorImpl(logEnd, null);
  165. i.block = readBlock(logPosition, logEnd);
  166. return i;
  167. }
  168. return new EmptyLogCursor();
  169. }
  170. /** {@inheritDoc} */
  171. @Override
  172. public LogCursor seekLog(String refName, long updateIndex)
  173. throws IOException {
  174. initLogIndex();
  175. if (logPosition > 0) {
  176. byte[] key = LogEntry.key(refName, updateIndex);
  177. byte[] match = refName.getBytes(UTF_8);
  178. LogCursorImpl i = new LogCursorImpl(logEnd, match);
  179. i.block = seek(LOG_BLOCK_TYPE, key, logIndex, logPosition, logEnd);
  180. return i;
  181. }
  182. return new EmptyLogCursor();
  183. }
  184. private BlockReader seek(byte blockType, byte[] key, BlockReader idx,
  185. long startPos, long endPos) throws IOException {
  186. if (idx != null) {
  187. // Walk through a possibly multi-level index to a leaf block.
  188. BlockReader block = idx;
  189. do {
  190. if (block.seekKey(key) > 0) {
  191. return null;
  192. }
  193. long pos = block.readPositionFromIndex();
  194. block = readBlock(pos, endPos);
  195. } while (block.type() == INDEX_BLOCK_TYPE);
  196. block.seekKey(key);
  197. return block;
  198. }
  199. if (blockType == LOG_BLOCK_TYPE) {
  200. // No index. Log blocks are irregularly sized, so we can't do binary
  201. // search between blocks. Scan over blocks instead.
  202. BlockReader block = readBlock(startPos, endPos);
  203. for (;;) {
  204. if (block == null || block.type() != LOG_BLOCK_TYPE) {
  205. return null;
  206. }
  207. int result = block.seekKey(key);
  208. if (result <= 0) {
  209. // == 0 : we found the key.
  210. // < 0 : the key is before this block. Either the ref name is there
  211. // but only at a newer updateIndex, or it is absent. We leave it to
  212. // logcursor to distinguish between both cases.
  213. return block;
  214. }
  215. long pos = block.endPosition();
  216. if (pos >= endPos) {
  217. return null;
  218. }
  219. block = readBlock(pos, endPos);
  220. }
  221. }
  222. return binarySearch(blockType, key, startPos, endPos);
  223. }
  224. private BlockReader binarySearch(byte blockType, byte[] key,
  225. long startPos, long endPos) throws IOException {
  226. if (blockSize == 0) {
  227. BlockReader b = readBlock(startPos, endPos);
  228. if (blockType != b.type()) {
  229. return null;
  230. }
  231. b.seekKey(key);
  232. return b;
  233. }
  234. int low = (int) (startPos / blockSize);
  235. int end = blocksIn(startPos, endPos);
  236. BlockReader block = null;
  237. do {
  238. int mid = (low + end) >>> 1;
  239. block = readBlock(((long) mid) * blockSize, endPos);
  240. if (blockType != block.type()) {
  241. return null;
  242. }
  243. int cmp = block.seekKey(key);
  244. if (cmp < 0) {
  245. end = mid;
  246. } else if (cmp == 0) {
  247. break;
  248. } else /* if (cmp > 0) */ {
  249. low = mid + 1;
  250. }
  251. } while (low < end);
  252. return block;
  253. }
  254. private void readFileHeader() throws IOException {
  255. readHeaderOrFooter(0, FILE_HEADER_LEN);
  256. }
  257. private void readFileFooter() throws IOException {
  258. int ftrLen = FILE_FOOTER_LEN;
  259. byte[] ftr = readHeaderOrFooter(src.size() - ftrLen, ftrLen);
  260. CRC32 crc = new CRC32();
  261. crc.update(ftr, 0, ftrLen - 4);
  262. if (crc.getValue() != NB.decodeUInt32(ftr, ftrLen - 4)) {
  263. throw new IOException(JGitText.get().invalidReftableCRC);
  264. }
  265. refIndexPosition = NB.decodeInt64(ftr, 24);
  266. long p = NB.decodeInt64(ftr, 32);
  267. objPosition = p >>> 5;
  268. objIdLen = (int) (p & 0x1f);
  269. objIndexPosition = NB.decodeInt64(ftr, 40);
  270. logPosition = NB.decodeInt64(ftr, 48);
  271. logIndexPosition = NB.decodeInt64(ftr, 56);
  272. if (refIndexPosition > 0) {
  273. refEnd = refIndexPosition;
  274. } else if (objPosition > 0) {
  275. refEnd = objPosition;
  276. } else if (logPosition > 0) {
  277. refEnd = logPosition;
  278. } else {
  279. refEnd = src.size() - ftrLen;
  280. }
  281. if (objPosition > 0) {
  282. if (objIndexPosition > 0) {
  283. objEnd = objIndexPosition;
  284. } else if (logPosition > 0) {
  285. objEnd = logPosition;
  286. } else {
  287. objEnd = src.size() - ftrLen;
  288. }
  289. }
  290. if (logPosition > 0) {
  291. if (logIndexPosition > 0) {
  292. logEnd = logIndexPosition;
  293. } else {
  294. logEnd = src.size() - ftrLen;
  295. }
  296. }
  297. }
  298. private byte[] readHeaderOrFooter(long pos, int len) throws IOException {
  299. ByteBuffer buf = src.read(pos, len);
  300. if (buf.position() != len) {
  301. throw new IOException(JGitText.get().shortReadOfBlock);
  302. }
  303. byte[] tmp = new byte[len];
  304. buf.flip();
  305. buf.get(tmp);
  306. if (!isFileHeaderMagic(tmp, 0, len)) {
  307. throw new IOException(JGitText.get().invalidReftableFile);
  308. }
  309. int v = NB.decodeInt32(tmp, 4);
  310. int version = v >>> 24;
  311. if (VERSION_1 != version) {
  312. throw new IOException(MessageFormat.format(
  313. JGitText.get().unsupportedReftableVersion,
  314. Integer.valueOf(version)));
  315. }
  316. if (blockSize == -1) {
  317. blockSize = v & 0xffffff;
  318. }
  319. minUpdateIndex = NB.decodeInt64(tmp, 8);
  320. maxUpdateIndex = NB.decodeInt64(tmp, 16);
  321. return tmp;
  322. }
  323. private void initRefIndex() throws IOException {
  324. if (refIndexPosition < 0) {
  325. readFileFooter();
  326. }
  327. if (refIndex == null && refIndexPosition > 0) {
  328. refIndex = readIndex(refIndexPosition);
  329. }
  330. }
  331. private void initObjIndex() throws IOException {
  332. if (objIndexPosition < 0) {
  333. readFileFooter();
  334. }
  335. if (objIndex == null && objIndexPosition > 0) {
  336. objIndex = readIndex(objIndexPosition);
  337. }
  338. }
  339. private void initLogIndex() throws IOException {
  340. if (logIndexPosition < 0) {
  341. readFileFooter();
  342. }
  343. if (logIndex == null && logIndexPosition > 0) {
  344. logIndex = readIndex(logIndexPosition);
  345. }
  346. }
  347. private BlockReader readIndex(long pos) throws IOException {
  348. int sz = readBlockLen(pos);
  349. BlockReader i = new BlockReader();
  350. i.readBlock(src, pos, sz);
  351. i.verifyIndex();
  352. return i;
  353. }
  354. private int readBlockLen(long pos) throws IOException {
  355. int sz = pos == 0 ? FILE_HEADER_LEN + 4 : 4;
  356. ByteBuffer tmp = src.read(pos, sz);
  357. if (tmp.position() < sz) {
  358. throw new IOException(JGitText.get().invalidReftableFile);
  359. }
  360. byte[] buf;
  361. if (tmp.hasArray() && tmp.arrayOffset() == 0) {
  362. buf = tmp.array();
  363. } else {
  364. buf = new byte[sz];
  365. tmp.flip();
  366. tmp.get(buf);
  367. }
  368. if (pos == 0 && buf[FILE_HEADER_LEN] == FILE_BLOCK_TYPE) {
  369. return FILE_HEADER_LEN;
  370. }
  371. int p = pos == 0 ? FILE_HEADER_LEN : 0;
  372. return decodeBlockLen(NB.decodeInt32(buf, p));
  373. }
  374. private BlockReader readBlock(long pos, long end) throws IOException {
  375. if (indexCache != null) {
  376. BlockReader b = indexCache.get(pos);
  377. if (b != null) {
  378. return b;
  379. }
  380. }
  381. int sz = blockSize;
  382. if (sz == 0) {
  383. sz = readBlockLen(pos);
  384. } else if (pos + sz > end) {
  385. sz = (int) (end - pos); // last block may omit padding.
  386. }
  387. BlockReader b = new BlockReader();
  388. b.readBlock(src, pos, sz);
  389. if (b.type() == INDEX_BLOCK_TYPE && !b.truncated()) {
  390. if (indexCache == null) {
  391. indexCache = new LongMap<>();
  392. }
  393. indexCache.put(pos, b);
  394. }
  395. return b;
  396. }
  397. private int blocksIn(long pos, long end) {
  398. int blocks = (int) ((end - pos) / blockSize);
  399. return end % blockSize == 0 ? blocks : (blocks + 1);
  400. }
  401. /**
  402. * Get size of the reftable, in bytes.
  403. *
  404. * @return size of the reftable, in bytes.
  405. * @throws java.io.IOException
  406. * size cannot be obtained.
  407. */
  408. public long size() throws IOException {
  409. return src.size();
  410. }
  411. /** {@inheritDoc} */
  412. @Override
  413. public void close() throws IOException {
  414. src.close();
  415. }
  416. private class RefCursorImpl extends RefCursor {
  417. private final long scanEnd;
  418. private final byte[] match;
  419. private final boolean prefix;
  420. private Ref ref;
  421. BlockReader block;
  422. RefCursorImpl(long scanEnd, byte[] match, boolean prefix) {
  423. this.scanEnd = scanEnd;
  424. this.match = match;
  425. this.prefix = prefix;
  426. }
  427. @Override
  428. public boolean next() throws IOException {
  429. for (;;) {
  430. if (block == null || block.type() != REF_BLOCK_TYPE) {
  431. return false;
  432. } else if (!block.next()) {
  433. long pos = block.endPosition();
  434. if (pos >= scanEnd) {
  435. return false;
  436. }
  437. block = readBlock(pos, scanEnd);
  438. continue;
  439. }
  440. block.parseKey();
  441. if (match != null && !block.match(match, prefix)) {
  442. block.skipValue();
  443. return false;
  444. }
  445. ref = block.readRef(minUpdateIndex);
  446. if (!includeDeletes && wasDeleted()) {
  447. continue;
  448. }
  449. return true;
  450. }
  451. }
  452. @Override
  453. public Ref getRef() {
  454. return ref;
  455. }
  456. @Override
  457. public void close() {
  458. // Do nothing.
  459. }
  460. }
  461. private class LogCursorImpl extends LogCursor {
  462. private final long scanEnd;
  463. private final byte[] match;
  464. private String refName;
  465. private long updateIndex;
  466. private ReflogEntry entry;
  467. BlockReader block;
  468. /**
  469. * Scans logs from this table until scanEnd position.
  470. *
  471. * @param scanEnd
  472. * end of the log data in the reftable.
  473. * @param match
  474. * if non-null, limits the scan to precisely that refname.
  475. */
  476. LogCursorImpl(long scanEnd, byte[] match) {
  477. this.scanEnd = scanEnd;
  478. this.match = match;
  479. }
  480. @Override
  481. public boolean next() throws IOException {
  482. for (;;) {
  483. if (block == null || block.type() != LOG_BLOCK_TYPE) {
  484. return false;
  485. } else if (!block.next()) {
  486. long pos = block.endPosition();
  487. if (pos >= scanEnd) {
  488. return false;
  489. }
  490. block = readBlock(pos, scanEnd);
  491. continue;
  492. }
  493. block.parseKey();
  494. if (match != null && !block.match(match, false)) {
  495. block.skipValue();
  496. return false;
  497. }
  498. refName = block.name();
  499. updateIndex = block.readLogUpdateIndex();
  500. entry = block.readLogEntry();
  501. if (entry == null && !includeDeletes) {
  502. continue;
  503. }
  504. return true;
  505. }
  506. }
  507. @Override
  508. public String getRefName() {
  509. return refName;
  510. }
  511. @Override
  512. public long getUpdateIndex() {
  513. return updateIndex;
  514. }
  515. @Override
  516. public ReflogEntry getReflogEntry() {
  517. return entry;
  518. }
  519. @Override
  520. public void close() {
  521. // Do nothing.
  522. }
  523. }
  524. static final LongList EMPTY_LONG_LIST = new LongList(0);
  525. private class ObjCursorImpl extends RefCursor {
  526. private final long scanEnd;
  527. private final ObjectId match;
  528. private Ref ref;
  529. private int listIdx;
  530. private LongList blockPos;
  531. private BlockReader block;
  532. ObjCursorImpl(long scanEnd, AnyObjectId id) {
  533. this.scanEnd = scanEnd;
  534. this.match = id.copy();
  535. }
  536. void initSeek() throws IOException {
  537. byte[] rawId = new byte[OBJECT_ID_LENGTH];
  538. match.copyRawTo(rawId, 0);
  539. byte[] key = Arrays.copyOf(rawId, objIdLen);
  540. BlockReader b = objIndex;
  541. do {
  542. if (b.seekKey(key) > 0) {
  543. blockPos = EMPTY_LONG_LIST;
  544. return;
  545. }
  546. long pos = b.readPositionFromIndex();
  547. b = readBlock(pos, objEnd);
  548. } while (b.type() == INDEX_BLOCK_TYPE);
  549. b.seekKey(key);
  550. while (b.next()) {
  551. b.parseKey();
  552. if (b.match(key, false)) {
  553. blockPos = b.readBlockPositionList();
  554. if (blockPos == null) {
  555. initScan();
  556. return;
  557. }
  558. break;
  559. }
  560. b.skipValue();
  561. }
  562. if (blockPos == null) {
  563. blockPos = EMPTY_LONG_LIST;
  564. }
  565. if (blockPos.size() > 0) {
  566. long pos = blockPos.get(listIdx++);
  567. block = readBlock(pos, scanEnd);
  568. }
  569. }
  570. void initScan() throws IOException {
  571. block = readBlock(0, scanEnd);
  572. }
  573. @Override
  574. public boolean next() throws IOException {
  575. for (;;) {
  576. if (block == null || block.type() != REF_BLOCK_TYPE) {
  577. return false;
  578. } else if (!block.next()) {
  579. long pos;
  580. if (blockPos != null) {
  581. if (listIdx >= blockPos.size()) {
  582. return false;
  583. }
  584. pos = blockPos.get(listIdx++);
  585. } else {
  586. pos = block.endPosition();
  587. }
  588. if (pos >= scanEnd) {
  589. return false;
  590. }
  591. block = readBlock(pos, scanEnd);
  592. continue;
  593. }
  594. block.parseKey();
  595. ref = block.readRef(minUpdateIndex);
  596. ObjectId id = ref.getObjectId();
  597. if (id != null && match.equals(id)
  598. && (includeDeletes || !wasDeleted())) {
  599. return true;
  600. }
  601. }
  602. }
  603. @Override
  604. public Ref getRef() {
  605. return ref;
  606. }
  607. @Override
  608. public void close() {
  609. // Do nothing.
  610. }
  611. }
  612. }