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 	 * @return the block size in bytes chosen for this file by the writer. Most
116 	 *         reads from the {@link BlockSource} will be aligned to the block
117 	 *         size.
118 	 * @throws IOException
119 	 *             file cannot be read.
120 	 */
121 	public int blockSize() throws IOException {
122 		if (blockSize == -1) {
123 			readFileHeader();
124 		}
125 		return blockSize;
126 	}
127 
128 	/**
129 	 * @return the minimum update index for log entries that appear in this
130 	 *         reftable. This should be 1 higher than the prior reftable's
131 	 *         {@code maxUpdateIndex} if this table is used in a stack.
132 	 * @throws IOException
133 	 *             file cannot be read.
134 	 */
135 	public long minUpdateIndex() throws IOException {
136 		if (blockSize == -1) {
137 			readFileHeader();
138 		}
139 		return minUpdateIndex;
140 	}
141 
142 	/**
143 	 * @return the maximum update index for log entries that appear in this
144 	 *         reftable. This should be 1 higher than the prior reftable's
145 	 *         {@code maxUpdateIndex} if this table is used in a stack.
146 	 * @throws IOException
147 	 *             file cannot be read.
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 			// Walk through a possibly multi-level index to a leaf block.
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 /* if (cmp > 0) */ {
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); // last block may omit padding.
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 	 * Get size of the reftable, in bytes.
438 	 *
439 	 * @return size of the reftable, in bytes.
440 	 * @throws IOException
441 	 *             size cannot be obtained.
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 			// Do nothing.
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 			// Do nothing.
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 			// Do nothing.
681 		}
682 	}
683 }