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 org.eclipse.jgit.lib.Constants.CHARSET;
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(CHARSET);
186 		boolean prefix = key[key.length - 1] == '/';
187 
188 		RefCursorImpl i = new RefCursorImpl(refEnd, key, prefix);
189 		i.block = seek(REF_BLOCK_TYPE, key, refIndex, 0, refEnd);
190 		return i;
191 	}
192 
193 	/** {@inheritDoc} */
194 	@Override
195 	public RefCursor byObjectId(AnyObjectId id) throws IOException {
196 		initObjIndex();
197 		ObjCursorImpl i = new ObjCursorImpl(refEnd, id);
198 		if (objIndex != null) {
199 			i.initSeek();
200 		} else {
201 			i.initScan();
202 		}
203 		return i;
204 	}
205 
206 	/** {@inheritDoc} */
207 	@Override
208 	public LogCursor allLogs() throws IOException {
209 		initLogIndex();
210 		if (logPosition > 0) {
211 			src.adviseSequentialRead(logPosition, logEnd);
212 			LogCursorImpl i = new LogCursorImpl(logEnd, null);
213 			i.block = readBlock(logPosition, logEnd);
214 			return i;
215 		}
216 		return new EmptyLogCursor();
217 	}
218 
219 	/** {@inheritDoc} */
220 	@Override
221 	public LogCursor seekLog(String refName, long updateIndex)
222 			throws IOException {
223 		initLogIndex();
224 		if (logPosition > 0) {
225 			byte[] key = LogEntry.key(refName, updateIndex);
226 			byte[] match = refName.getBytes(CHARSET);
227 			LogCursorImpl i = new LogCursorImpl(logEnd, match);
228 			i.block = seek(LOG_BLOCK_TYPE, key, logIndex, logPosition, logEnd);
229 			return i;
230 		}
231 		return new EmptyLogCursor();
232 	}
233 
234 	private BlockReader seek(byte blockType, byte[] key, BlockReader idx,
235 			long startPos, long endPos) throws IOException {
236 		if (idx != null) {
237 			// Walk through a possibly multi-level index to a leaf block.
238 			BlockReader block = idx;
239 			do {
240 				if (block.seekKey(key) > 0) {
241 					return null;
242 				}
243 				long pos = block.readPositionFromIndex();
244 				block = readBlock(pos, endPos);
245 			} while (block.type() == INDEX_BLOCK_TYPE);
246 			block.seekKey(key);
247 			return block;
248 		}
249 		return binarySearch(blockType, key, startPos, endPos);
250 	}
251 
252 	private BlockReader binarySearch(byte blockType, byte[] key,
253 			long startPos, long endPos) throws IOException {
254 		if (blockSize == 0) {
255 			BlockReader b = readBlock(startPos, endPos);
256 			if (blockType != b.type()) {
257 				return null;
258 			}
259 			b.seekKey(key);
260 			return b;
261 		}
262 
263 		int low = (int) (startPos / blockSize);
264 		int end = blocksIn(startPos, endPos);
265 		BlockReader block = null;
266 		do {
267 			int mid = (low + end) >>> 1;
268 			block = readBlock(((long) mid) * blockSize, endPos);
269 			if (blockType != block.type()) {
270 				return null;
271 			}
272 			int cmp = block.seekKey(key);
273 			if (cmp < 0) {
274 				end = mid;
275 			} else if (cmp == 0) {
276 				break;
277 			} else /* if (cmp > 0) */ {
278 				low = mid + 1;
279 			}
280 		} while (low < end);
281 		return block;
282 	}
283 
284 	private void readFileHeader() throws IOException {
285 		readHeaderOrFooter(0, FILE_HEADER_LEN);
286 	}
287 
288 	private void readFileFooter() throws IOException {
289 		int ftrLen = FILE_FOOTER_LEN;
290 		byte[] ftr = readHeaderOrFooter(src.size() - ftrLen, ftrLen);
291 
292 		CRC32 crc = new CRC32();
293 		crc.update(ftr, 0, ftrLen - 4);
294 		if (crc.getValue() != NB.decodeUInt32(ftr, ftrLen - 4)) {
295 			throw new IOException(JGitText.get().invalidReftableCRC);
296 		}
297 
298 		refIndexPosition = NB.decodeInt64(ftr, 24);
299 		long p = NB.decodeInt64(ftr, 32);
300 		objPosition = p >>> 5;
301 		objIdLen = (int) (p & 0x1f);
302 		objIndexPosition = NB.decodeInt64(ftr, 40);
303 		logPosition = NB.decodeInt64(ftr, 48);
304 		logIndexPosition = NB.decodeInt64(ftr, 56);
305 
306 		if (refIndexPosition > 0) {
307 			refEnd = refIndexPosition;
308 		} else if (objPosition > 0) {
309 			refEnd = objPosition;
310 		} else if (logPosition > 0) {
311 			refEnd = logPosition;
312 		} else {
313 			refEnd = src.size() - ftrLen;
314 		}
315 
316 		if (objPosition > 0) {
317 			if (objIndexPosition > 0) {
318 				objEnd = objIndexPosition;
319 			} else if (logPosition > 0) {
320 				objEnd = logPosition;
321 			} else {
322 				objEnd = src.size() - ftrLen;
323 			}
324 		}
325 
326 		if (logPosition > 0) {
327 			if (logIndexPosition > 0) {
328 				logEnd = logIndexPosition;
329 			} else {
330 				logEnd = src.size() - ftrLen;
331 			}
332 		}
333 	}
334 
335 	private byte[] readHeaderOrFooter(long pos, int len) throws IOException {
336 		ByteBuffer buf = src.read(pos, len);
337 		if (buf.position() != len) {
338 			throw new IOException(JGitText.get().shortReadOfBlock);
339 		}
340 
341 		byte[] tmp = new byte[len];
342 		buf.flip();
343 		buf.get(tmp);
344 		if (!isFileHeaderMagic(tmp, 0, len)) {
345 			throw new IOException(JGitText.get().invalidReftableFile);
346 		}
347 
348 		int v = NB.decodeInt32(tmp, 4);
349 		int version = v >>> 24;
350 		if (VERSION_1 != version) {
351 			throw new IOException(MessageFormat.format(
352 					JGitText.get().unsupportedReftableVersion,
353 					Integer.valueOf(version)));
354 		}
355 		if (blockSize == -1) {
356 			blockSize = v & 0xffffff;
357 		}
358 		minUpdateIndex = NB.decodeInt64(tmp, 8);
359 		maxUpdateIndex = NB.decodeInt64(tmp, 16);
360 		return tmp;
361 	}
362 
363 	private void initRefIndex() throws IOException {
364 		if (refIndexPosition < 0) {
365 			readFileFooter();
366 		}
367 		if (refIndex == null && refIndexPosition > 0) {
368 			refIndex = readIndex(refIndexPosition);
369 		}
370 	}
371 
372 	private void initObjIndex() throws IOException {
373 		if (objIndexPosition < 0) {
374 			readFileFooter();
375 		}
376 		if (objIndex == null && objIndexPosition > 0) {
377 			objIndex = readIndex(objIndexPosition);
378 		}
379 	}
380 
381 	private void initLogIndex() throws IOException {
382 		if (logIndexPosition < 0) {
383 			readFileFooter();
384 		}
385 		if (logIndex == null && logIndexPosition > 0) {
386 			logIndex = readIndex(logIndexPosition);
387 		}
388 	}
389 
390 	private BlockReader readIndex(long pos) throws IOException {
391 		int sz = readBlockLen(pos);
392 		BlockReader i = new BlockReader();
393 		i.readBlock(src, pos, sz);
394 		i.verifyIndex();
395 		return i;
396 	}
397 
398 	private int readBlockLen(long pos) throws IOException {
399 		int sz = pos == 0 ? FILE_HEADER_LEN + 4 : 4;
400 		ByteBuffer tmp = src.read(pos, sz);
401 		if (tmp.position() < sz) {
402 			throw new IOException(JGitText.get().invalidReftableFile);
403 		}
404 		byte[] buf;
405 		if (tmp.hasArray() && tmp.arrayOffset() == 0) {
406 			buf = tmp.array();
407 		} else {
408 			buf = new byte[sz];
409 			tmp.flip();
410 			tmp.get(buf);
411 		}
412 		if (pos == 0 && buf[FILE_HEADER_LEN] == FILE_BLOCK_TYPE) {
413 			return FILE_HEADER_LEN;
414 		}
415 		int p = pos == 0 ? FILE_HEADER_LEN : 0;
416 		return decodeBlockLen(NB.decodeInt32(buf, p));
417 	}
418 
419 	private BlockReader readBlock(long pos, long end) throws IOException {
420 		if (indexCache != null) {
421 			BlockReader b = indexCache.get(pos);
422 			if (b != null) {
423 				return b;
424 			}
425 		}
426 
427 		int sz = blockSize;
428 		if (sz == 0) {
429 			sz = readBlockLen(pos);
430 		} else if (pos + sz > end) {
431 			sz = (int) (end - pos); // last block may omit padding.
432 		}
433 
434 		BlockReader b = new BlockReader();
435 		b.readBlock(src, pos, sz);
436 		if (b.type() == INDEX_BLOCK_TYPE && !b.truncated()) {
437 			if (indexCache == null) {
438 				indexCache = new LongMap<>();
439 			}
440 			indexCache.put(pos, b);
441 		}
442 		return b;
443 	}
444 
445 	private int blocksIn(long pos, long end) {
446 		int blocks = (int) ((end - pos) / blockSize);
447 		return end % blockSize == 0 ? blocks : (blocks + 1);
448 	}
449 
450 	/**
451 	 * Get size of the reftable, in bytes.
452 	 *
453 	 * @return size of the reftable, in bytes.
454 	 * @throws java.io.IOException
455 	 *             size cannot be obtained.
456 	 */
457 	public long size() throws IOException {
458 		return src.size();
459 	}
460 
461 	/** {@inheritDoc} */
462 	@Override
463 	public void close() throws IOException {
464 		src.close();
465 	}
466 
467 	private class RefCursorImpl extends RefCursor {
468 		private final long scanEnd;
469 		private final byte[] match;
470 		private final boolean prefix;
471 
472 		private Ref ref;
473 		private long updateIndex;
474 		BlockReader block;
475 
476 		RefCursorImpl(long scanEnd, byte[] match, boolean prefix) {
477 			this.scanEnd = scanEnd;
478 			this.match = match;
479 			this.prefix = prefix;
480 		}
481 
482 		@Override
483 		public boolean next() throws IOException {
484 			for (;;) {
485 				if (block == null || block.type() != REF_BLOCK_TYPE) {
486 					return false;
487 				} else if (!block.next()) {
488 					long pos = block.endPosition();
489 					if (pos >= scanEnd) {
490 						return false;
491 					}
492 					block = readBlock(pos, scanEnd);
493 					continue;
494 				}
495 
496 				block.parseKey();
497 				if (match != null && !block.match(match, prefix)) {
498 					block.skipValue();
499 					return false;
500 				}
501 
502 				updateIndex = minUpdateIndex + block.readUpdateIndexDelta();
503 				ref = block.readRef();
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 long getUpdateIndex() {
518 			return updateIndex;
519 		}
520 
521 		@Override
522 		public void close() {
523 			// Do nothing.
524 		}
525 	}
526 
527 	private class LogCursorImpl extends LogCursor {
528 		private final long scanEnd;
529 		private final byte[] match;
530 
531 		private String refName;
532 		private long updateIndex;
533 		private ReflogEntry entry;
534 		BlockReader block;
535 
536 		LogCursorImpl(long scanEnd, byte[] match) {
537 			this.scanEnd = scanEnd;
538 			this.match = match;
539 		}
540 
541 		@Override
542 		public boolean next() throws IOException {
543 			for (;;) {
544 				if (block == null || block.type() != LOG_BLOCK_TYPE) {
545 					return false;
546 				} else if (!block.next()) {
547 					long pos = block.endPosition();
548 					if (pos >= scanEnd) {
549 						return false;
550 					}
551 					block = readBlock(pos, scanEnd);
552 					continue;
553 				}
554 
555 				block.parseKey();
556 				if (match != null && !block.match(match, false)) {
557 					block.skipValue();
558 					return false;
559 				}
560 
561 				refName = block.name();
562 				updateIndex = block.readLogUpdateIndex();
563 				entry = block.readLogEntry();
564 				if (entry == null && !includeDeletes) {
565 					continue;
566 				}
567 				return true;
568 			}
569 		}
570 
571 		@Override
572 		public String getRefName() {
573 			return refName;
574 		}
575 
576 		@Override
577 		public long getUpdateIndex() {
578 			return updateIndex;
579 		}
580 
581 		@Override
582 		public ReflogEntry getReflogEntry() {
583 			return entry;
584 		}
585 
586 		@Override
587 		public void close() {
588 			// Do nothing.
589 		}
590 	}
591 
592 	static final LongList EMPTY_LONG_LIST = new LongList(0);
593 
594 	private class ObjCursorImpl extends RefCursor {
595 		private final long scanEnd;
596 		private final ObjectId match;
597 
598 		private Ref ref;
599 		private long updateIndex;
600 		private int listIdx;
601 
602 		private LongList blockPos;
603 		private BlockReader block;
604 
605 		ObjCursorImpl(long scanEnd, AnyObjectId id) {
606 			this.scanEnd = scanEnd;
607 			this.match = id.copy();
608 		}
609 
610 		void initSeek() throws IOException {
611 			byte[] rawId = new byte[OBJECT_ID_LENGTH];
612 			match.copyRawTo(rawId, 0);
613 			byte[] key = Arrays.copyOf(rawId, objIdLen);
614 
615 			BlockReader b = objIndex;
616 			do {
617 				if (b.seekKey(key) > 0) {
618 					blockPos = EMPTY_LONG_LIST;
619 					return;
620 				}
621 				long pos = b.readPositionFromIndex();
622 				b = readBlock(pos, objEnd);
623 			} while (b.type() == INDEX_BLOCK_TYPE);
624 			b.seekKey(key);
625 			while (b.next()) {
626 				b.parseKey();
627 				if (b.match(key, false)) {
628 					blockPos = b.readBlockPositionList();
629 					if (blockPos == null) {
630 						initScan();
631 						return;
632 					}
633 					break;
634 				}
635 				b.skipValue();
636 			}
637 			if (blockPos == null) {
638 				blockPos = EMPTY_LONG_LIST;
639 			}
640 			if (blockPos.size() > 0) {
641 				long pos = blockPos.get(listIdx++);
642 				block = readBlock(pos, scanEnd);
643 			}
644 		}
645 
646 		void initScan() throws IOException {
647 			block = readBlock(0, scanEnd);
648 		}
649 
650 		@Override
651 		public boolean next() throws IOException {
652 			for (;;) {
653 				if (block == null || block.type() != REF_BLOCK_TYPE) {
654 					return false;
655 				} else if (!block.next()) {
656 					long pos;
657 					if (blockPos != null) {
658 						if (listIdx >= blockPos.size()) {
659 							return false;
660 						}
661 						pos = blockPos.get(listIdx++);
662 					} else {
663 						pos = block.endPosition();
664 					}
665 					if (pos >= scanEnd) {
666 						return false;
667 					}
668 					block = readBlock(pos, scanEnd);
669 					continue;
670 				}
671 
672 				block.parseKey();
673 				updateIndex = minUpdateIndex + block.readUpdateIndexDelta();
674 				ref = block.readRef();
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 long getUpdateIndex() {
690 			return updateIndex;
691 		}
692 
693 		@Override
694 		public void close() {
695 			// Do nothing.
696 		}
697 	}
698 }