View Javadoc
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  
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   * Reads a reftable formatted file.
44   * <p>
45   * {@code ReftableReader} is not thread-safe. Concurrent readers need their own
46   * instance to read from the same file.
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  	 * Initialize a new reftable reader.
73  	 *
74  	 * @param src
75  	 *            the file content to read.
76  	 */
77  	public ReftableReader(BlockSource src) {
78  		this.src = src;
79  	}
80  
81  	/**
82  	 * Get the block size in bytes chosen for this file by the writer.
83  	 *
84  	 * @return the block size in bytes chosen for this file by the writer. Most
85  	 *         reads from the
86  	 *         {@link org.eclipse.jgit.internal.storage.io.BlockSource} will be
87  	 *         aligned to the block size.
88  	 * @throws java.io.IOException
89  	 *             file cannot be read.
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 		// We have the map, we have no refs, or the table is small.
105 		return (objPosition > 0 || refEnd == 24 || refIndexPosition == 0);
106 	}
107 
108 	/**
109 	 * {@inheritDoc}
110 	 */
111 	@Override
112 	public long minUpdateIndex() throws IOException {
113 		if (blockSize == -1) {
114 			readFileHeader();
115 		}
116 		return minUpdateIndex;
117 	}
118 
119 	/**
120 	 * {@inheritDoc}
121 	 */
122 	@Override
123 	public long maxUpdateIndex() throws IOException {
124 		if (blockSize == -1) {
125 			readFileHeader();
126 		}
127 		return maxUpdateIndex;
128 	}
129 
130 	/** {@inheritDoc} */
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 	/** {@inheritDoc} */
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 	/** {@inheritDoc} */
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 	/** {@inheritDoc} */
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 	/** {@inheritDoc} */
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 	/** {@inheritDoc} */
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 BlockReadere/jgit/internal/storage/reftable/BlockReader.html#BlockReader">BlockReader seek(byte blockType, byte[] key, BlockReader idx,
211 			long startPos, long endPos) throws IOException {
212 		if (idx != null) {
213 			// Walk through a possibly multi-level index to a leaf block.
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 			// No index. Log blocks are irregularly sized, so we can't do binary
227 			// search between blocks. Scan over blocks instead.
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 					// == 0 : we found the key.
238 					// < 0 : the key is before this block. Either the ref name is there
239 					// but only at a newer updateIndex, or it is absent. We leave it to
240 					// logcursor to distinguish between both cases.
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 /* if (cmp > 0) */ {
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); // last block may omit padding.
434 		}
435 
436 		BlockReader b = new BlockReader();
437 		b.readBlock(src, pos, sz);
438 		if (b.type() == INDEX_BLOCK_TYPE && !b.truncated()) {
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 	 * Get size of the reftable, in bytes.
454 	 *
455 	 * @return size of the reftable, in bytes.
456 	 * @throws java.io.IOException
457 	 *             size cannot be obtained.
458 	 */
459 	public long size() throws IOException {
460 		return src.size();
461 	}
462 
463 	/** {@inheritDoc} */
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 Ref getRef() {
513 			return ref;
514 		}
515 
516 		@Override
517 		public void close() {
518 			// Do nothing.
519 		}
520 	}
521 
522 	private class LogCursorImpl extends LogCursor {
523 		private final long scanEnd;
524 		private final byte[] match;
525 
526 		private String refName;
527 		private long updateIndex;
528 		private ReflogEntry entry;
529 		BlockReader block;
530 
531 		/**
532 		 * Scans logs from this table until scanEnd position.
533 		 *
534 		 * @param scanEnd
535 		 *            end of the log data in the reftable.
536 		 * @param match
537 		 *            if non-null, limits the scan to precisely that refname.
538 		 */
539 		LogCursorImpl(long scanEnd, byte[] match) {
540 			this.scanEnd = scanEnd;
541 			this.match = match;
542 		}
543 
544 		@Override
545 		public boolean next() throws IOException {
546 			for (;;) {
547 				if (block == null || block.type() != LOG_BLOCK_TYPE) {
548 					return false;
549 				} else if (!block.next()) {
550 					long pos = block.endPosition();
551 					if (pos >= scanEnd) {
552 						return false;
553 					}
554 					block = readBlock(pos, scanEnd);
555 					continue;
556 				}
557 
558 				block.parseKey();
559 				if (match != null && !block.match(match, false)) {
560 					block.skipValue();
561 					return false;
562 				}
563 
564 				refName = block.name();
565 				updateIndex = block.readLogUpdateIndex();
566 				entry = block.readLogEntry();
567 				if (entry == null && !includeDeletes) {
568 					continue;
569 				}
570 				return true;
571 			}
572 		}
573 
574 		@Override
575 		public String getRefName() {
576 			return refName;
577 		}
578 
579 		@Override
580 		public long getUpdateIndex() {
581 			return updateIndex;
582 		}
583 
584 		@Override
585 		public ReflogEntry getReflogEntry() {
586 			return entry;
587 		}
588 
589 		@Override
590 		public void close() {
591 			// Do nothing.
592 		}
593 	}
594 
595 	static final LongListml#LongList">LongList EMPTY_LONG_LIST = new LongList(0);
596 
597 	private class ObjCursorImpl extends RefCursor {
598 		private final long scanEnd;
599 		private final ObjectId match;
600 
601 		private Ref ref;
602 		private int listIdx;
603 
604 		private LongList blockPos;
605 		private BlockReader block;
606 
607 		ObjCursorImpl(long scanEnd, AnyObjectId id) {
608 			this.scanEnd = scanEnd;
609 			this.match = id.copy();
610 		}
611 
612 		void initSeek() throws IOException {
613 			byte[] rawId = new byte[OBJECT_ID_LENGTH];
614 			match.copyRawTo(rawId, 0);
615 			byte[] key = Arrays.copyOf(rawId, objIdLen);
616 
617 			BlockReader b = objIndex;
618 			do {
619 				if (b.seekKey(key) > 0) {
620 					blockPos = EMPTY_LONG_LIST;
621 					return;
622 				}
623 				long pos = b.readPositionFromIndex();
624 				b = readBlock(pos, objEnd);
625 			} while (b.type() == INDEX_BLOCK_TYPE);
626 			b.seekKey(key);
627 			while (b.next()) {
628 				b.parseKey();
629 				if (b.match(key, false)) {
630 					blockPos = b.readBlockPositionList();
631 					if (blockPos == null) {
632 						initScan();
633 						return;
634 					}
635 					break;
636 				}
637 				b.skipValue();
638 			}
639 			if (blockPos == null) {
640 				blockPos = EMPTY_LONG_LIST;
641 			}
642 			if (blockPos.size() > 0) {
643 				long pos = blockPos.get(listIdx++);
644 				block = readBlock(pos, scanEnd);
645 			}
646 		}
647 
648 		void initScan() throws IOException {
649 			block = readBlock(0, scanEnd);
650 		}
651 
652 		@Override
653 		public boolean next() throws IOException {
654 			for (;;) {
655 				if (block == null || block.type() != REF_BLOCK_TYPE) {
656 					return false;
657 				} else if (!block.next()) {
658 					long pos;
659 					if (blockPos != null) {
660 						if (listIdx >= blockPos.size()) {
661 							return false;
662 						}
663 						pos = blockPos.get(listIdx++);
664 					} else {
665 						pos = block.endPosition();
666 					}
667 					if (pos >= scanEnd) {
668 						return false;
669 					}
670 					block = readBlock(pos, scanEnd);
671 					continue;
672 				}
673 
674 				block.parseKey();
675 				ref = block.readRef(minUpdateIndex);
676 				ObjectId id = ref.getObjectId();
677 				if (id != null && match.equals(id)
678 						&& (includeDeletes || !wasDeleted())) {
679 					return true;
680 				}
681 			}
682 		}
683 
684 		@Override
685 		public Ref getRef() {
686 			return ref;
687 		}
688 
689 		@Override
690 		public void close() {
691 			// Do nothing.
692 		}
693 	}
694 }