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