1
2
3
4
5
6
7
8
9
10
11 package org.eclipse.jgit.internal.storage.reftable;
12
13 import static java.nio.charset.StandardCharsets.UTF_8;
14 import static org.eclipse.jgit.internal.storage.reftable.BlockReader.decodeBlockLen;
15 import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.FILE_BLOCK_TYPE;
16 import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.FILE_FOOTER_LEN;
17 import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.FILE_HEADER_LEN;
18 import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.INDEX_BLOCK_TYPE;
19 import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.LOG_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.VERSION_1;
22 import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.isFileHeaderMagic;
23 import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH;
24
25 import java.io.IOException;
26 import java.nio.ByteBuffer;
27 import java.text.MessageFormat;
28 import java.util.Arrays;
29 import java.util.zip.CRC32;
30
31 import org.eclipse.jgit.internal.JGitText;
32 import org.eclipse.jgit.internal.storage.io.BlockSource;
33 import org.eclipse.jgit.internal.storage.reftable.BlockWriter.LogEntry;
34 import org.eclipse.jgit.lib.AnyObjectId;
35 import org.eclipse.jgit.lib.ObjectId;
36 import org.eclipse.jgit.lib.Ref;
37 import org.eclipse.jgit.lib.ReflogEntry;
38 import org.eclipse.jgit.util.LongList;
39 import org.eclipse.jgit.util.LongMap;
40 import org.eclipse.jgit.util.NB;
41
42
43
44
45
46
47
48 public class ReftableReader extends Reftable implements AutoCloseable {
49 private final BlockSource src;
50
51 private int blockSize = -1;
52 private long minUpdateIndex;
53 private long maxUpdateIndex;
54
55 private long refEnd;
56 private long objPosition;
57 private long objEnd;
58 private long logPosition;
59 private long logEnd;
60 private int objIdLen;
61
62 private long refIndexPosition = -1;
63 private long objIndexPosition = -1;
64 private long logIndexPosition = -1;
65
66 private BlockReader refIndex;
67 private BlockReader objIndex;
68 private BlockReader logIndex;
69 private LongMap<BlockReader> indexCache;
70
71
72
73
74
75
76
77 public ReftableReader(BlockSource src) {
78 this.src = src;
79 }
80
81
82
83
84
85
86
87
88
89
90
91 public int blockSize() throws IOException {
92 if (blockSize == -1) {
93 readFileHeader();
94 }
95 return blockSize;
96 }
97
98 @Override
99 public boolean hasObjectMap() throws IOException {
100 if (objIndexPosition == -1) {
101 readFileFooter();
102 }
103
104
105 return (objPosition > 0 || refEnd == 24 || refIndexPosition == 0);
106 }
107
108
109
110
111 @Override
112 public long minUpdateIndex() throws IOException {
113 if (blockSize == -1) {
114 readFileHeader();
115 }
116 return minUpdateIndex;
117 }
118
119
120
121
122 @Override
123 public long maxUpdateIndex() throws IOException {
124 if (blockSize == -1) {
125 readFileHeader();
126 }
127 return maxUpdateIndex;
128 }
129
130
131 @Override
132 public RefCursor allRefs() throws IOException {
133 if (blockSize == -1) {
134 readFileHeader();
135 }
136
137 if (refEnd == 0) {
138 readFileFooter();
139 }
140 src.adviseSequentialRead(0, refEnd);
141
142 RefCursorImpl i = new RefCursorImpl(refEnd, null, false);
143 i.block = readBlock(0, refEnd);
144 return i;
145 }
146
147
148 @Override
149 public RefCursor seekRef(String refName) throws IOException {
150 initRefIndex();
151
152 byte[] key = refName.getBytes(UTF_8);
153 RefCursorImpl i = new RefCursorImpl(refEnd, key, false);
154 i.block = seek(REF_BLOCK_TYPE, key, refIndex, 0, refEnd);
155 return i;
156 }
157
158
159 @Override
160 public RefCursor seekRefsWithPrefix(String prefix) throws IOException {
161 initRefIndex();
162
163 byte[] key = prefix.getBytes(UTF_8);
164 RefCursorImpl i = new RefCursorImpl(refEnd, key, true);
165 i.block = seek(REF_BLOCK_TYPE, key, refIndex, 0, refEnd);
166 return i;
167 }
168
169
170 @Override
171 public RefCursor byObjectId(AnyObjectId id) throws IOException {
172 initObjIndex();
173 ObjCursorImpl i = new ObjCursorImpl(refEnd, id);
174 if (objIndex != null) {
175 i.initSeek();
176 } else {
177 i.initScan();
178 }
179 return i;
180 }
181
182
183 @Override
184 public LogCursor allLogs() throws IOException {
185 initLogIndex();
186 if (logPosition > 0) {
187 src.adviseSequentialRead(logPosition, logEnd);
188 LogCursorImpl i = new LogCursorImpl(logEnd, null);
189 i.block = readBlock(logPosition, logEnd);
190 return i;
191 }
192 return new EmptyLogCursor();
193 }
194
195
196 @Override
197 public LogCursor seekLog(String refName, long updateIndex)
198 throws IOException {
199 initLogIndex();
200 if (logPosition > 0) {
201 byte[] key = LogEntry.key(refName, updateIndex);
202 byte[] match = refName.getBytes(UTF_8);
203 LogCursorImpl i = new LogCursorImpl(logEnd, match);
204 i.block = seek(LOG_BLOCK_TYPE, key, logIndex, logPosition, logEnd);
205 return i;
206 }
207 return new EmptyLogCursor();
208 }
209
210 private BlockReader seek(byte blockType, byte[] key, BlockReader idx,
211 long startPos, long endPos) throws IOException {
212 if (idx != null) {
213
214 BlockReader block = idx;
215 do {
216 if (block.seekKey(key) > 0) {
217 return null;
218 }
219 long pos = block.readPositionFromIndex();
220 block = readBlock(pos, endPos);
221 } while (block.type() == INDEX_BLOCK_TYPE);
222 block.seekKey(key);
223 return block;
224 }
225 if (blockType == LOG_BLOCK_TYPE) {
226
227
228 BlockReader block = readBlock(startPos, endPos);
229
230 for (;;) {
231 if (block == null || block.type() != LOG_BLOCK_TYPE) {
232 return null;
233 }
234
235 int result = block.seekKey(key);
236 if (result <= 0) {
237
238
239
240
241 return block;
242 }
243
244 long pos = block.endPosition();
245 if (pos >= endPos) {
246 return null;
247 }
248 block = readBlock(pos, endPos);
249 }
250 }
251 return binarySearch(blockType, key, startPos, endPos);
252 }
253
254 private BlockReader binarySearch(byte blockType, byte[] key,
255 long startPos, long endPos) throws IOException {
256 if (blockSize == 0) {
257 BlockReader b = readBlock(startPos, endPos);
258 if (blockType != b.type()) {
259 return null;
260 }
261 b.seekKey(key);
262 return b;
263 }
264
265 int low = (int) (startPos / blockSize);
266 int end = blocksIn(startPos, endPos);
267 BlockReader block = null;
268 do {
269 int mid = (low + end) >>> 1;
270 block = readBlock(((long) mid) * blockSize, endPos);
271 if (blockType != block.type()) {
272 return null;
273 }
274 int cmp = block.seekKey(key);
275 if (cmp < 0) {
276 end = mid;
277 } else if (cmp == 0) {
278 break;
279 } else {
280 low = mid + 1;
281 }
282 } while (low < end);
283 return block;
284 }
285
286 private void readFileHeader() throws IOException {
287 readHeaderOrFooter(0, FILE_HEADER_LEN);
288 }
289
290 private void readFileFooter() throws IOException {
291 int ftrLen = FILE_FOOTER_LEN;
292 byte[] ftr = readHeaderOrFooter(src.size() - ftrLen, ftrLen);
293
294 CRC32 crc = new CRC32();
295 crc.update(ftr, 0, ftrLen - 4);
296 if (crc.getValue() != NB.decodeUInt32(ftr, ftrLen - 4)) {
297 throw new IOException(JGitText.get().invalidReftableCRC);
298 }
299
300 refIndexPosition = NB.decodeInt64(ftr, 24);
301 long p = NB.decodeInt64(ftr, 32);
302 objPosition = p >>> 5;
303 objIdLen = (int) (p & 0x1f);
304 objIndexPosition = NB.decodeInt64(ftr, 40);
305 logPosition = NB.decodeInt64(ftr, 48);
306 logIndexPosition = NB.decodeInt64(ftr, 56);
307
308 if (refIndexPosition > 0) {
309 refEnd = refIndexPosition;
310 } else if (objPosition > 0) {
311 refEnd = objPosition;
312 } else if (logPosition > 0) {
313 refEnd = logPosition;
314 } else {
315 refEnd = src.size() - ftrLen;
316 }
317
318 if (objPosition > 0) {
319 if (objIndexPosition > 0) {
320 objEnd = objIndexPosition;
321 } else if (logPosition > 0) {
322 objEnd = logPosition;
323 } else {
324 objEnd = src.size() - ftrLen;
325 }
326 }
327
328 if (logPosition > 0) {
329 if (logIndexPosition > 0) {
330 logEnd = logIndexPosition;
331 } else {
332 logEnd = src.size() - ftrLen;
333 }
334 }
335 }
336
337 private byte[] readHeaderOrFooter(long pos, int len) throws IOException {
338 ByteBuffer buf = src.read(pos, len);
339 if (buf.position() != len) {
340 throw new IOException(JGitText.get().shortReadOfBlock);
341 }
342
343 byte[] tmp = new byte[len];
344 buf.flip();
345 buf.get(tmp);
346 if (!isFileHeaderMagic(tmp, 0, len)) {
347 throw new IOException(JGitText.get().invalidReftableFile);
348 }
349
350 int v = NB.decodeInt32(tmp, 4);
351 int version = v >>> 24;
352 if (VERSION_1 != version) {
353 throw new IOException(MessageFormat.format(
354 JGitText.get().unsupportedReftableVersion,
355 Integer.valueOf(version)));
356 }
357 if (blockSize == -1) {
358 blockSize = v & 0xffffff;
359 }
360 minUpdateIndex = NB.decodeInt64(tmp, 8);
361 maxUpdateIndex = NB.decodeInt64(tmp, 16);
362 return tmp;
363 }
364
365 private void initRefIndex() throws IOException {
366 if (refIndexPosition < 0) {
367 readFileFooter();
368 }
369 if (refIndex == null && refIndexPosition > 0) {
370 refIndex = readIndex(refIndexPosition);
371 }
372 }
373
374 private void initObjIndex() throws IOException {
375 if (objIndexPosition < 0) {
376 readFileFooter();
377 }
378 if (objIndex == null && objIndexPosition > 0) {
379 objIndex = readIndex(objIndexPosition);
380 }
381 }
382
383 private void initLogIndex() throws IOException {
384 if (logIndexPosition < 0) {
385 readFileFooter();
386 }
387 if (logIndex == null && logIndexPosition > 0) {
388 logIndex = readIndex(logIndexPosition);
389 }
390 }
391
392 private BlockReader readIndex(long pos) throws IOException {
393 int sz = readBlockLen(pos);
394 BlockReader i = new BlockReader();
395 i.readBlock(src, pos, sz);
396 i.verifyIndex();
397 return i;
398 }
399
400 private int readBlockLen(long pos) throws IOException {
401 int sz = pos == 0 ? FILE_HEADER_LEN + 4 : 4;
402 ByteBuffer tmp = src.read(pos, sz);
403 if (tmp.position() < sz) {
404 throw new IOException(JGitText.get().invalidReftableFile);
405 }
406 byte[] buf;
407 if (tmp.hasArray() && tmp.arrayOffset() == 0) {
408 buf = tmp.array();
409 } else {
410 buf = new byte[sz];
411 tmp.flip();
412 tmp.get(buf);
413 }
414 if (pos == 0 && buf[FILE_HEADER_LEN] == FILE_BLOCK_TYPE) {
415 return FILE_HEADER_LEN;
416 }
417 int p = pos == 0 ? FILE_HEADER_LEN : 0;
418 return decodeBlockLen(NB.decodeInt32(buf, p));
419 }
420
421 private BlockReader readBlock(long pos, long end) throws IOException {
422 if (indexCache != null) {
423 BlockReader b = indexCache.get(pos);
424 if (b != null) {
425 return b;
426 }
427 }
428
429 int sz = blockSize;
430 if (sz == 0) {
431 sz = readBlockLen(pos);
432 } else if (pos + sz > end) {
433 sz = (int) (end - pos);
434 }
435
436 BlockReader b = new BlockReader();
437 b.readBlock(src, pos, sz);
438 if (b.type() == INDEX_BLOCK_TYPE) {
439 if (indexCache == null) {
440 indexCache = new LongMap<>();
441 }
442 indexCache.put(pos, b);
443 }
444 return b;
445 }
446
447 private int blocksIn(long pos, long end) {
448 int blocks = (int) ((end - pos) / blockSize);
449 return end % blockSize == 0 ? blocks : (blocks + 1);
450 }
451
452
453
454
455
456
457
458
459 public long size() throws IOException {
460 return src.size();
461 }
462
463
464 @Override
465 public void close() throws IOException {
466 src.close();
467 }
468
469 private class RefCursorImpl extends RefCursor {
470 private final long scanEnd;
471 private final byte[] match;
472 private final boolean prefix;
473
474 private Ref ref;
475 BlockReader block;
476
477 RefCursorImpl(long scanEnd, byte[] match, boolean prefix) {
478 this.scanEnd = scanEnd;
479 this.match = match;
480 this.prefix = prefix;
481 }
482
483 @Override
484 public boolean next() throws IOException {
485 for (;;) {
486 if (block == null || block.type() != REF_BLOCK_TYPE) {
487 return false;
488 } else if (!block.next()) {
489 long pos = block.endPosition();
490 if (pos >= scanEnd) {
491 return false;
492 }
493 block = readBlock(pos, scanEnd);
494 continue;
495 }
496
497 block.parseKey();
498 if (match != null && !block.match(match, prefix)) {
499 block.skipValue();
500 return false;
501 }
502
503 ref = block.readRef(minUpdateIndex);
504 if (!includeDeletes && wasDeleted()) {
505 continue;
506 }
507 return true;
508 }
509 }
510
511 @Override
512 public void seekPastPrefix(String prefixName) throws IOException {
513 initRefIndex();
514 byte[] key = prefixName.getBytes(UTF_8);
515 ByteBuffer byteBuffer = ByteBuffer.allocate(key.length + 1);
516 byteBuffer.put(key);
517
518
519
520
521 byteBuffer.put((byte) 0xFF);
522
523 block = seek(REF_BLOCK_TYPE, byteBuffer.array(), refIndex, 0, refEnd);
524 }
525
526 @Override
527 public Ref getRef() {
528 return ref;
529 }
530
531 @Override
532 public void close() {
533
534 }
535 }
536
537 private class LogCursorImpl extends LogCursor {
538 private final long scanEnd;
539 private final byte[] match;
540
541 private String refName;
542 private long updateIndex;
543 private ReflogEntry entry;
544 BlockReader block;
545
546
547
548
549
550
551
552
553
554 LogCursorImpl(long scanEnd, byte[] match) {
555 this.scanEnd = scanEnd;
556 this.match = match;
557 }
558
559 @Override
560 public boolean next() throws IOException {
561 for (;;) {
562 if (block == null || block.type() != LOG_BLOCK_TYPE) {
563 return false;
564 } else if (!block.next()) {
565 long pos = block.endPosition();
566 if (pos >= scanEnd) {
567 return false;
568 }
569 block = readBlock(pos, scanEnd);
570 continue;
571 }
572
573 block.parseKey();
574 if (match != null && !block.match(match, false)) {
575 block.skipValue();
576 return false;
577 }
578
579 refName = block.name();
580 updateIndex = block.readLogUpdateIndex();
581 entry = block.readLogEntry();
582 if (entry == null && !includeDeletes) {
583 continue;
584 }
585 return true;
586 }
587 }
588
589 @Override
590 public String getRefName() {
591 return refName;
592 }
593
594 @Override
595 public long getUpdateIndex() {
596 return updateIndex;
597 }
598
599 @Override
600 public ReflogEntry getReflogEntry() {
601 return entry;
602 }
603
604 @Override
605 public void close() {
606
607 }
608 }
609
610 static final LongList EMPTY_LONG_LIST = new LongList(0);
611
612 private class ObjCursorImpl extends RefCursor {
613 private final long scanEnd;
614 private final ObjectId match;
615
616 private Ref ref;
617 private int listIdx;
618
619 private LongList blockPos;
620 private BlockReader block;
621
622 ObjCursorImpl(long scanEnd, AnyObjectId id) {
623 this.scanEnd = scanEnd;
624 this.match = id.copy();
625 }
626
627 void initSeek() throws IOException {
628 byte[] rawId = new byte[OBJECT_ID_LENGTH];
629 match.copyRawTo(rawId, 0);
630 byte[] key = Arrays.copyOf(rawId, objIdLen);
631
632 BlockReader b = objIndex;
633 do {
634 if (b.seekKey(key) > 0) {
635 blockPos = EMPTY_LONG_LIST;
636 return;
637 }
638 long pos = b.readPositionFromIndex();
639 b = readBlock(pos, objEnd);
640 } while (b.type() == INDEX_BLOCK_TYPE);
641 b.seekKey(key);
642 while (b.next()) {
643 b.parseKey();
644 if (b.match(key, false)) {
645 blockPos = b.readBlockPositionList();
646 if (blockPos == null) {
647 initScan();
648 return;
649 }
650 break;
651 }
652 b.skipValue();
653 }
654 if (blockPos == null) {
655 blockPos = EMPTY_LONG_LIST;
656 }
657 if (blockPos.size() > 0) {
658 long pos = blockPos.get(listIdx++);
659 block = readBlock(pos, scanEnd);
660 }
661 }
662
663 void initScan() throws IOException {
664 block = readBlock(0, scanEnd);
665 }
666
667 @Override
668 public boolean next() throws IOException {
669 for (;;) {
670 if (block == null || block.type() != REF_BLOCK_TYPE) {
671 return false;
672 } else if (!block.next()) {
673 long pos;
674 if (blockPos != null) {
675 if (listIdx >= blockPos.size()) {
676 return false;
677 }
678 pos = blockPos.get(listIdx++);
679 } else {
680 pos = block.endPosition();
681 }
682 if (pos >= scanEnd) {
683 return false;
684 }
685 block = readBlock(pos, scanEnd);
686 continue;
687 }
688
689 block.parseKey();
690 ref = block.readRef(minUpdateIndex);
691 ObjectId id = ref.getObjectId();
692 if (id != null && match.equals(id)
693 && (includeDeletes || !wasDeleted())) {
694 return true;
695 }
696 }
697 }
698
699 @Override
700
701
702
703
704
705
706 public void seekPastPrefix(String prefixName) throws IOException {
707 throw new UnsupportedOperationException();
708 }
709
710 @Override
711 public Ref getRef() {
712 return ref;
713 }
714
715 @Override
716 public void close() {
717
718 }
719 }
720 }