View Javadoc
1   /*
2    * Copyright (C) 2017, Google Inc.
3    * and other copyright owners as documented in the project's IP log.
4    *
5    * This program and the accompanying materials are made available
6    * under the terms of the Eclipse Distribution License v1.0 which
7    * accompanies this distribution, is reproduced below, and is
8    * available at http://www.eclipse.org/org/documents/edl-v10.php
9    *
10   * All rights reserved.
11   *
12   * Redistribution and use in source and binary forms, with or
13   * without modification, are permitted provided that the following
14   * conditions are met:
15   *
16   * - Redistributions of source code must retain the above copyright
17   *   notice, this list of conditions and the following disclaimer.
18   *
19   * - Redistributions in binary form must reproduce the above
20   *   copyright notice, this list of conditions and the following
21   *   disclaimer in the documentation and/or other materials provided
22   *   with the distribution.
23   *
24   * - Neither the name of the Eclipse Foundation, Inc. nor the
25   *   names of its contributors may be used to endorse or promote
26   *   products derived from this software without specific prior
27   *   written permission.
28   *
29   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
30   * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
31   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
32   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
34   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
36   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
37   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
38   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
39   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
40   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
41   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
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   * Reads a reftable formatted file.
77   * <p>
78   * {@code ReftableReader} is not thread-safe. Concurrent readers need their own
79   * instance to read from the same file.
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 	 * Initialize a new reftable reader.
106 	 *
107 	 * @param src
108 	 *            the file content to read.
109 	 */
110 	public ReftableReader(BlockSource src) {
111 		this.src = src;
112 	}
113 
114 	/**
115 	 * Get the block size in bytes chosen for this file by the writer.
116 	 *
117 	 * @return the block size in bytes chosen for this file by the writer. Most
118 	 *         reads from the
119 	 *         {@link org.eclipse.jgit.internal.storage.io.BlockSource} will be
120 	 *         aligned to the block size.
121 	 * @throws java.io.IOException
122 	 *             file cannot be read.
123 	 */
124 	public int blockSize() throws IOException {
125 		if (blockSize == -1) {
126 			readFileHeader();
127 		}
128 		return blockSize;
129 	}
130 
131 	/**
132 	 * Get the minimum update index for log entries that appear in this
133 	 * reftable.
134 	 *
135 	 * @return the minimum update index for log entries that appear in this
136 	 *         reftable. This should be 1 higher than the prior reftable's
137 	 *         {@code maxUpdateIndex} if this table is used in a stack.
138 	 * @throws java.io.IOException
139 	 *             file cannot be read.
140 	 */
141 	public long minUpdateIndex() throws IOException {
142 		if (blockSize == -1) {
143 			readFileHeader();
144 		}
145 		return minUpdateIndex;
146 	}
147 
148 	/**
149 	 * Get the maximum update index for log entries that appear in this
150 	 * reftable.
151 	 *
152 	 * @return the maximum update index for log entries that appear in this
153 	 *         reftable. This should be 1 higher than the prior reftable's
154 	 *         {@code maxUpdateIndex} if this table is used in a stack.
155 	 * @throws java.io.IOException
156 	 *             file cannot be read.
157 	 */
158 	public long maxUpdateIndex() throws IOException {
159 		if (blockSize == -1) {
160 			readFileHeader();
161 		}
162 		return maxUpdateIndex;
163 	}
164 
165 	/** {@inheritDoc} */
166 	@Override
167 	public RefCursor allRefs() throws IOException {
168 		if (blockSize == -1) {
169 			readFileHeader();
170 		}
171 
172 		long end = refEnd > 0 ? refEnd : (src.size() - FILE_FOOTER_LEN);
173 		src.adviseSequentialRead(0, end);
174 
175 		RefCursorImpl i = new RefCursorImpl(end, null, false);
176 		i.block = readBlock(0, end);
177 		return i;
178 	}
179 
180 	/** {@inheritDoc} */
181 	@Override
182 	public RefCursor seekRef(String refName) throws IOException {
183 		initRefIndex();
184 
185 		byte[] key = refName.getBytes(UTF_8);
186 		RefCursorImpl i = new RefCursorImpl(refEnd, key, false);
187 		i.block = seek(REF_BLOCK_TYPE, key, refIndex, 0, refEnd);
188 		return i;
189 	}
190 
191 	/** {@inheritDoc} */
192 	@Override
193 	public RefCursor seekRefsWithPrefix(String prefix) throws IOException {
194 		initRefIndex();
195 
196 		byte[] key = prefix.getBytes(UTF_8);
197 		RefCursorImpl i = new RefCursorImpl(refEnd, key, true);
198 		i.block = seek(REF_BLOCK_TYPE, key, refIndex, 0, refEnd);
199 		return i;
200 	}
201 
202 	/** {@inheritDoc} */
203 	@Override
204 	public RefCursor byObjectId(AnyObjectId id) throws IOException {
205 		initObjIndex();
206 		ObjCursorImpl i = new ObjCursorImpl(refEnd, id);
207 		if (objIndex != null) {
208 			i.initSeek();
209 		} else {
210 			i.initScan();
211 		}
212 		return i;
213 	}
214 
215 	/** {@inheritDoc} */
216 	@Override
217 	public LogCursor allLogs() throws IOException {
218 		initLogIndex();
219 		if (logPosition > 0) {
220 			src.adviseSequentialRead(logPosition, logEnd);
221 			LogCursorImpl i = new LogCursorImpl(logEnd, null);
222 			i.block = readBlock(logPosition, logEnd);
223 			return i;
224 		}
225 		return new EmptyLogCursor();
226 	}
227 
228 	/** {@inheritDoc} */
229 	@Override
230 	public LogCursor seekLog(String refName, long updateIndex)
231 			throws IOException {
232 		initLogIndex();
233 		if (logPosition > 0) {
234 			byte[] key = LogEntry.key(refName, updateIndex);
235 			byte[] match = refName.getBytes(UTF_8);
236 			LogCursorImpl i = new LogCursorImpl(logEnd, match);
237 			i.block = seek(LOG_BLOCK_TYPE, key, logIndex, logPosition, logEnd);
238 			return i;
239 		}
240 		return new EmptyLogCursor();
241 	}
242 
243 	private BlockReadere/jgit/internal/storage/reftable/BlockReader.html#BlockReader">BlockReader seek(byte blockType, byte[] key, BlockReader idx,
244 			long startPos, long endPos) throws IOException {
245 		if (idx != null) {
246 			// Walk through a possibly multi-level index to a leaf block.
247 			BlockReader block = idx;
248 			do {
249 				if (block.seekKey(key) > 0) {
250 					return null;
251 				}
252 				long pos = block.readPositionFromIndex();
253 				block = readBlock(pos, endPos);
254 			} while (block.type() == INDEX_BLOCK_TYPE);
255 			block.seekKey(key);
256 			return block;
257 		}
258 		return binarySearch(blockType, key, startPos, endPos);
259 	}
260 
261 	private BlockReader binarySearch(byte blockType, byte[] key,
262 			long startPos, long endPos) throws IOException {
263 		if (blockSize == 0) {
264 			BlockReader b = readBlock(startPos, endPos);
265 			if (blockType != b.type()) {
266 				return null;
267 			}
268 			b.seekKey(key);
269 			return b;
270 		}
271 
272 		int low = (int) (startPos / blockSize);
273 		int end = blocksIn(startPos, endPos);
274 		BlockReader block = null;
275 		do {
276 			int mid = (low + end) >>> 1;
277 			block = readBlock(((long) mid) * blockSize, endPos);
278 			if (blockType != block.type()) {
279 				return null;
280 			}
281 			int cmp = block.seekKey(key);
282 			if (cmp < 0) {
283 				end = mid;
284 			} else if (cmp == 0) {
285 				break;
286 			} else /* if (cmp > 0) */ {
287 				low = mid + 1;
288 			}
289 		} while (low < end);
290 		return block;
291 	}
292 
293 	private void readFileHeader() throws IOException {
294 		readHeaderOrFooter(0, FILE_HEADER_LEN);
295 	}
296 
297 	private void readFileFooter() throws IOException {
298 		int ftrLen = FILE_FOOTER_LEN;
299 		byte[] ftr = readHeaderOrFooter(src.size() - ftrLen, ftrLen);
300 
301 		CRC32 crc = new CRC32();
302 		crc.update(ftr, 0, ftrLen - 4);
303 		if (crc.getValue() != NB.decodeUInt32(ftr, ftrLen - 4)) {
304 			throw new IOException(JGitText.get().invalidReftableCRC);
305 		}
306 
307 		refIndexPosition = NB.decodeInt64(ftr, 24);
308 		long p = NB.decodeInt64(ftr, 32);
309 		objPosition = p >>> 5;
310 		objIdLen = (int) (p & 0x1f);
311 		objIndexPosition = NB.decodeInt64(ftr, 40);
312 		logPosition = NB.decodeInt64(ftr, 48);
313 		logIndexPosition = NB.decodeInt64(ftr, 56);
314 
315 		if (refIndexPosition > 0) {
316 			refEnd = refIndexPosition;
317 		} else if (objPosition > 0) {
318 			refEnd = objPosition;
319 		} else if (logPosition > 0) {
320 			refEnd = logPosition;
321 		} else {
322 			refEnd = src.size() - ftrLen;
323 		}
324 
325 		if (objPosition > 0) {
326 			if (objIndexPosition > 0) {
327 				objEnd = objIndexPosition;
328 			} else if (logPosition > 0) {
329 				objEnd = logPosition;
330 			} else {
331 				objEnd = src.size() - ftrLen;
332 			}
333 		}
334 
335 		if (logPosition > 0) {
336 			if (logIndexPosition > 0) {
337 				logEnd = logIndexPosition;
338 			} else {
339 				logEnd = src.size() - ftrLen;
340 			}
341 		}
342 	}
343 
344 	private byte[] readHeaderOrFooter(long pos, int len) throws IOException {
345 		ByteBuffer buf = src.read(pos, len);
346 		if (buf.position() != len) {
347 			throw new IOException(JGitText.get().shortReadOfBlock);
348 		}
349 
350 		byte[] tmp = new byte[len];
351 		buf.flip();
352 		buf.get(tmp);
353 		if (!isFileHeaderMagic(tmp, 0, len)) {
354 			throw new IOException(JGitText.get().invalidReftableFile);
355 		}
356 
357 		int v = NB.decodeInt32(tmp, 4);
358 		int version = v >>> 24;
359 		if (VERSION_1 != version) {
360 			throw new IOException(MessageFormat.format(
361 					JGitText.get().unsupportedReftableVersion,
362 					Integer.valueOf(version)));
363 		}
364 		if (blockSize == -1) {
365 			blockSize = v & 0xffffff;
366 		}
367 		minUpdateIndex = NB.decodeInt64(tmp, 8);
368 		maxUpdateIndex = NB.decodeInt64(tmp, 16);
369 		return tmp;
370 	}
371 
372 	private void initRefIndex() throws IOException {
373 		if (refIndexPosition < 0) {
374 			readFileFooter();
375 		}
376 		if (refIndex == null && refIndexPosition > 0) {
377 			refIndex = readIndex(refIndexPosition);
378 		}
379 	}
380 
381 	private void initObjIndex() throws IOException {
382 		if (objIndexPosition < 0) {
383 			readFileFooter();
384 		}
385 		if (objIndex == null && objIndexPosition > 0) {
386 			objIndex = readIndex(objIndexPosition);
387 		}
388 	}
389 
390 	private void initLogIndex() throws IOException {
391 		if (logIndexPosition < 0) {
392 			readFileFooter();
393 		}
394 		if (logIndex == null && logIndexPosition > 0) {
395 			logIndex = readIndex(logIndexPosition);
396 		}
397 	}
398 
399 	private BlockReader readIndex(long pos) throws IOException {
400 		int sz = readBlockLen(pos);
401 		BlockReader i = new BlockReader();
402 		i.readBlock(src, pos, sz);
403 		i.verifyIndex();
404 		return i;
405 	}
406 
407 	private int readBlockLen(long pos) throws IOException {
408 		int sz = pos == 0 ? FILE_HEADER_LEN + 4 : 4;
409 		ByteBuffer tmp = src.read(pos, sz);
410 		if (tmp.position() < sz) {
411 			throw new IOException(JGitText.get().invalidReftableFile);
412 		}
413 		byte[] buf;
414 		if (tmp.hasArray() && tmp.arrayOffset() == 0) {
415 			buf = tmp.array();
416 		} else {
417 			buf = new byte[sz];
418 			tmp.flip();
419 			tmp.get(buf);
420 		}
421 		if (pos == 0 && buf[FILE_HEADER_LEN] == FILE_BLOCK_TYPE) {
422 			return FILE_HEADER_LEN;
423 		}
424 		int p = pos == 0 ? FILE_HEADER_LEN : 0;
425 		return decodeBlockLen(NB.decodeInt32(buf, p));
426 	}
427 
428 	private BlockReader readBlock(long pos, long end) throws IOException {
429 		if (indexCache != null) {
430 			BlockReader b = indexCache.get(pos);
431 			if (b != null) {
432 				return b;
433 			}
434 		}
435 
436 		int sz = blockSize;
437 		if (sz == 0) {
438 			sz = readBlockLen(pos);
439 		} else if (pos + sz > end) {
440 			sz = (int) (end - pos); // last block may omit padding.
441 		}
442 
443 		BlockReader b = new BlockReader();
444 		b.readBlock(src, pos, sz);
445 		if (b.type() == INDEX_BLOCK_TYPE && !b.truncated()) {
446 			if (indexCache == null) {
447 				indexCache = new LongMap<>();
448 			}
449 			indexCache.put(pos, b);
450 		}
451 		return b;
452 	}
453 
454 	private int blocksIn(long pos, long end) {
455 		int blocks = (int) ((end - pos) / blockSize);
456 		return end % blockSize == 0 ? blocks : (blocks + 1);
457 	}
458 
459 	/**
460 	 * Get size of the reftable, in bytes.
461 	 *
462 	 * @return size of the reftable, in bytes.
463 	 * @throws java.io.IOException
464 	 *             size cannot be obtained.
465 	 */
466 	public long size() throws IOException {
467 		return src.size();
468 	}
469 
470 	/** {@inheritDoc} */
471 	@Override
472 	public void close() throws IOException {
473 		src.close();
474 	}
475 
476 	private class RefCursorImpl extends RefCursor {
477 		private final long scanEnd;
478 		private final byte[] match;
479 		private final boolean prefix;
480 
481 		private Ref ref;
482 		BlockReader block;
483 
484 		RefCursorImpl(long scanEnd, byte[] match, boolean prefix) {
485 			this.scanEnd = scanEnd;
486 			this.match = match;
487 			this.prefix = prefix;
488 		}
489 
490 		@Override
491 		public boolean next() throws IOException {
492 			for (;;) {
493 				if (block == null || block.type() != REF_BLOCK_TYPE) {
494 					return false;
495 				} else if (!block.next()) {
496 					long pos = block.endPosition();
497 					if (pos >= scanEnd) {
498 						return false;
499 					}
500 					block = readBlock(pos, scanEnd);
501 					continue;
502 				}
503 
504 				block.parseKey();
505 				if (match != null && !block.match(match, prefix)) {
506 					block.skipValue();
507 					return false;
508 				}
509 
510 				ref = block.readRef(minUpdateIndex);
511 				if (!includeDeletes && wasDeleted()) {
512 					continue;
513 				}
514 				return true;
515 			}
516 		}
517 
518 		@Override
519 		public Ref getRef() {
520 			return ref;
521 		}
522 
523 		@Override
524 		public void close() {
525 			// Do nothing.
526 		}
527 	}
528 
529 	private class LogCursorImpl extends LogCursor {
530 		private final long scanEnd;
531 		private final byte[] match;
532 
533 		private String refName;
534 		private long updateIndex;
535 		private ReflogEntry entry;
536 		BlockReader block;
537 
538 		LogCursorImpl(long scanEnd, byte[] match) {
539 			this.scanEnd = scanEnd;
540 			this.match = match;
541 		}
542 
543 		@Override
544 		public boolean next() throws IOException {
545 			for (;;) {
546 				if (block == null || block.type() != LOG_BLOCK_TYPE) {
547 					return false;
548 				} else if (!block.next()) {
549 					long pos = block.endPosition();
550 					if (pos >= scanEnd) {
551 						return false;
552 					}
553 					block = readBlock(pos, scanEnd);
554 					continue;
555 				}
556 
557 				block.parseKey();
558 				if (match != null && !block.match(match, false)) {
559 					block.skipValue();
560 					return false;
561 				}
562 
563 				refName = block.name();
564 				updateIndex = block.readLogUpdateIndex();
565 				entry = block.readLogEntry();
566 				if (entry == null && !includeDeletes) {
567 					continue;
568 				}
569 				return true;
570 			}
571 		}
572 
573 		@Override
574 		public String getRefName() {
575 			return refName;
576 		}
577 
578 		@Override
579 		public long getUpdateIndex() {
580 			return updateIndex;
581 		}
582 
583 		@Override
584 		public ReflogEntry getReflogEntry() {
585 			return entry;
586 		}
587 
588 		@Override
589 		public void close() {
590 			// Do nothing.
591 		}
592 	}
593 
594 	static final LongListml#LongList">LongList EMPTY_LONG_LIST = new LongList(0);
595 
596 	private class ObjCursorImpl extends RefCursor {
597 		private final long scanEnd;
598 		private final ObjectId match;
599 
600 		private Ref ref;
601 		private int listIdx;
602 
603 		private LongList blockPos;
604 		private BlockReader block;
605 
606 		ObjCursorImpl(long scanEnd, AnyObjectId id) {
607 			this.scanEnd = scanEnd;
608 			this.match = id.copy();
609 		}
610 
611 		void initSeek() throws IOException {
612 			byte[] rawId = new byte[OBJECT_ID_LENGTH];
613 			match.copyRawTo(rawId, 0);
614 			byte[] key = Arrays.copyOf(rawId, objIdLen);
615 
616 			BlockReader b = objIndex;
617 			do {
618 				if (b.seekKey(key) > 0) {
619 					blockPos = EMPTY_LONG_LIST;
620 					return;
621 				}
622 				long pos = b.readPositionFromIndex();
623 				b = readBlock(pos, objEnd);
624 			} while (b.type() == INDEX_BLOCK_TYPE);
625 			b.seekKey(key);
626 			while (b.next()) {
627 				b.parseKey();
628 				if (b.match(key, false)) {
629 					blockPos = b.readBlockPositionList();
630 					if (blockPos == null) {
631 						initScan();
632 						return;
633 					}
634 					break;
635 				}
636 				b.skipValue();
637 			}
638 			if (blockPos == null) {
639 				blockPos = EMPTY_LONG_LIST;
640 			}
641 			if (blockPos.size() > 0) {
642 				long pos = blockPos.get(listIdx++);
643 				block = readBlock(pos, scanEnd);
644 			}
645 		}
646 
647 		void initScan() throws IOException {
648 			block = readBlock(0, scanEnd);
649 		}
650 
651 		@Override
652 		public boolean next() throws IOException {
653 			for (;;) {
654 				if (block == null || block.type() != REF_BLOCK_TYPE) {
655 					return false;
656 				} else if (!block.next()) {
657 					long pos;
658 					if (blockPos != null) {
659 						if (listIdx >= blockPos.size()) {
660 							return false;
661 						}
662 						pos = blockPos.get(listIdx++);
663 					} else {
664 						pos = block.endPosition();
665 					}
666 					if (pos >= scanEnd) {
667 						return false;
668 					}
669 					block = readBlock(pos, scanEnd);
670 					continue;
671 				}
672 
673 				block.parseKey();
674 				ref = block.readRef(minUpdateIndex);
675 				ObjectId id = ref.getObjectId();
676 				if (id != null && match.equals(id)
677 						&& (includeDeletes || !wasDeleted())) {
678 					return true;
679 				}
680 			}
681 		}
682 
683 		@Override
684 		public Ref getRef() {
685 			return ref;
686 		}
687 
688 		@Override
689 		public void close() {
690 			// Do nothing.
691 		}
692 	}
693 }