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.BlockWriter.compare;
48  import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.FILE_BLOCK_TYPE;
49  import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.FILE_HEADER_LEN;
50  import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.INDEX_BLOCK_TYPE;
51  import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.LOG_BLOCK_TYPE;
52  import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.LOG_DATA;
53  import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.LOG_NONE;
54  import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.OBJ_BLOCK_TYPE;
55  import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.REF_BLOCK_TYPE;
56  import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.VALUE_1ID;
57  import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.VALUE_2ID;
58  import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.VALUE_NONE;
59  import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.VALUE_SYMREF;
60  import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.VALUE_TYPE_MASK;
61  import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.reverseUpdateIndex;
62  import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH;
63  import static org.eclipse.jgit.lib.Ref.Storage.NEW;
64  import static org.eclipse.jgit.lib.Ref.Storage.PACKED;
65  
66  import java.io.IOException;
67  import java.nio.ByteBuffer;
68  import java.util.Arrays;
69  import java.util.zip.DataFormatException;
70  import java.util.zip.Inflater;
71  
72  import org.eclipse.jgit.annotations.Nullable;
73  import org.eclipse.jgit.internal.JGitText;
74  import org.eclipse.jgit.internal.storage.io.BlockSource;
75  import org.eclipse.jgit.lib.CheckoutEntry;
76  import org.eclipse.jgit.lib.InflaterCache;
77  import org.eclipse.jgit.lib.ObjectId;
78  import org.eclipse.jgit.lib.ObjectIdRef;
79  import org.eclipse.jgit.lib.PersonIdent;
80  import org.eclipse.jgit.lib.Ref;
81  import org.eclipse.jgit.lib.ReflogEntry;
82  import org.eclipse.jgit.lib.SymbolicRef;
83  import org.eclipse.jgit.util.LongList;
84  import org.eclipse.jgit.util.NB;
85  import org.eclipse.jgit.util.RawParseUtils;
86  
87  /** Reads a single block for {@link ReftableReader}. */
88  class BlockReader {
89  	private byte blockType;
90  	private long endPosition;
91  	private boolean truncated;
92  
93  	private byte[] buf;
94  	private int bufLen;
95  	private int ptr;
96  
97  	private int keysStart;
98  	private int keysEnd;
99  
100 	private int restartCnt;
101 	private int restartTbl;
102 
103 	private byte[] nameBuf = new byte[256];
104 	private int nameLen;
105 	private int valueType;
106 
107 	byte type() {
108 		return blockType;
109 	}
110 
111 	boolean truncated() {
112 		return truncated;
113 	}
114 
115 	long endPosition() {
116 		return endPosition;
117 	}
118 
119 	boolean next() {
120 		return ptr < keysEnd;
121 	}
122 
123 	void parseKey() {
124 		int pfx = readVarint32();
125 		valueType = readVarint32();
126 		int sfx = valueType >>> 3;
127 		if (pfx + sfx > nameBuf.length) {
128 			int n = Math.max(pfx + sfx, nameBuf.length * 2);
129 			nameBuf = Arrays.copyOf(nameBuf, n);
130 		}
131 		System.arraycopy(buf, ptr, nameBuf, pfx, sfx);
132 		ptr += sfx;
133 		nameLen = pfx + sfx;
134 	}
135 
136 	String name() {
137 		int len = nameLen;
138 		if (blockType == LOG_BLOCK_TYPE) {
139 			len -= 9;
140 		}
141 		return RawParseUtils.decode(UTF_8, nameBuf, 0, len);
142 	}
143 
144 	boolean match(byte[] match, boolean matchIsPrefix) {
145 		int len = nameLen;
146 		if (blockType == LOG_BLOCK_TYPE) {
147 			len -= 9;
148 		}
149 		if (matchIsPrefix) {
150 			return len >= match.length
151 					&& compare(
152 							match, 0, match.length,
153 							nameBuf, 0, match.length) == 0;
154 		}
155 		return compare(match, 0, match.length, nameBuf, 0, len) == 0;
156 	}
157 
158 	long readPositionFromIndex() throws IOException {
159 		if (blockType != INDEX_BLOCK_TYPE) {
160 			throw invalidBlock();
161 		}
162 
163 		readVarint32(); // skip prefix length
164 		int n = readVarint32() >>> 3;
165 		ptr += n; // skip name
166 		return readVarint64();
167 	}
168 
169 	long readUpdateIndexDelta() {
170 		return readVarint64();
171 	}
172 
173 	Ref readRef(long minUpdateIndex) throws IOException {
174 		long updateIndex = minUpdateIndex + readUpdateIndexDelta();
175 		String name = RawParseUtils.decode(UTF_8, nameBuf, 0, nameLen);
176 		switch (valueType & VALUE_TYPE_MASK) {
177 		case VALUE_NONE: // delete
178 			return newRef(name, updateIndex);
179 
180 		case VALUE_1ID:
181 			return new ObjectIdRef.PeeledNonTag(PACKED, name, readValueId(),
182 					updateIndex);
183 
184 		case VALUE_2ID: { // annotated tag
185 			ObjectId id1 = readValueId();
186 			ObjectId id2 = readValueId();
187 			return new ObjectIdRef.PeeledTag(PACKED, name, id1, id2,
188 					updateIndex);
189 		}
190 
191 		case VALUE_SYMREF: {
192 			String val = readValueString();
193 			return new SymbolicRef(name, newRef(val, updateIndex), updateIndex);
194 		}
195 
196 		default:
197 			throw invalidBlock();
198 		}
199 	}
200 
201 	@Nullable
202 	LongList readBlockPositionList() {
203 		int n = valueType & VALUE_TYPE_MASK;
204 		if (n == 0) {
205 			n = readVarint32();
206 			if (n == 0) {
207 				return null;
208 			}
209 		}
210 
211 		LongList b = new LongList(n);
212 		b.add(readVarint64());
213 		for (int j = 1; j < n; j++) {
214 			long prior = b.get(j - 1);
215 			b.add(prior + readVarint64());
216 		}
217 		return b;
218 	}
219 
220 	long readLogUpdateIndex() {
221 		return reverseUpdateIndex(NB.decodeUInt64(nameBuf, nameLen - 8));
222 	}
223 
224 	@Nullable
225 	ReflogEntry readLogEntry() {
226 		if ((valueType & VALUE_TYPE_MASK) == LOG_NONE) {
227 			return null;
228 		}
229 
230 		ObjectId oldId = readValueId();
231 		ObjectId newId = readValueId();
232 		PersonIdent who = readPersonIdent();
233 		String msg = readValueString();
234 
235 		return new ReflogEntry() {
236 			@Override
237 			public ObjectId getOldId() {
238 				return oldId;
239 			}
240 
241 			@Override
242 			public ObjectId getNewId() {
243 				return newId;
244 			}
245 
246 			@Override
247 			public PersonIdent getWho() {
248 				return who;
249 			}
250 
251 			@Override
252 			public String getComment() {
253 				return msg;
254 			}
255 
256 			@Override
257 			public CheckoutEntry parseCheckout() {
258 				return null;
259 			}
260 		};
261 	}
262 
263 	private ObjectId readValueId() {
264 		ObjectId id = ObjectId.fromRaw(buf, ptr);
265 		ptr += OBJECT_ID_LENGTH;
266 		return id;
267 	}
268 
269 	private String readValueString() {
270 		int len = readVarint32();
271 		int end = ptr + len;
272 		String s = RawParseUtils.decode(UTF_8, buf, ptr, end);
273 		ptr = end;
274 		return s;
275 	}
276 
277 	private PersonIdent readPersonIdent() {
278 		String name = readValueString();
279 		String email = readValueString();
280 		long ms = readVarint64() * 1000;
281 		int tz = readInt16();
282 		return new PersonIdent(name, email, ms, tz);
283 	}
284 
285 	void readBlock(BlockSource src, long pos, int fileBlockSize)
286 			throws IOException {
287 		readBlockIntoBuf(src, pos, fileBlockSize);
288 		parseBlockStart(src, pos, fileBlockSize);
289 	}
290 
291 	private void readBlockIntoBuf(BlockSource src, long pos, int size)
292 			throws IOException {
293 		ByteBuffer b = src.read(pos, size);
294 		bufLen = b.position();
295 		if (bufLen <= 0) {
296 			throw invalidBlock();
297 		}
298 		if (b.hasArray() && b.arrayOffset() == 0) {
299 			buf = b.array();
300 		} else {
301 			buf = new byte[bufLen];
302 			b.flip();
303 			b.get(buf);
304 		}
305 		endPosition = pos + bufLen;
306 	}
307 
308 	private void parseBlockStart(BlockSource src, long pos, int fileBlockSize)
309 			throws IOException {
310 		ptr = 0;
311 		if (pos == 0) {
312 			if (bufLen == FILE_HEADER_LEN) {
313 				setupEmptyFileBlock();
314 				return;
315 			}
316 			ptr += FILE_HEADER_LEN; // first block begins with file header
317 		}
318 
319 		int typeAndSize = NB.decodeInt32(buf, ptr);
320 		ptr += 4;
321 
322 		blockType = (byte) (typeAndSize >>> 24);
323 		int blockLen = decodeBlockLen(typeAndSize);
324 		if (blockType == LOG_BLOCK_TYPE) {
325 			// Log blocks must be inflated after the header.
326 			long deflatedSize = inflateBuf(src, pos, blockLen, fileBlockSize);
327 			endPosition = pos + 4 + deflatedSize;
328 		}
329 		if (bufLen < blockLen) {
330 			if (blockType != INDEX_BLOCK_TYPE) {
331 				throw invalidBlock();
332 			}
333 			// Its OK during sequential scan for an index block to have been
334 			// partially read and be truncated in-memory. This happens when
335 			// the index block is larger than the file's blockSize. Caller
336 			// will break out of its scan loop once it sees the blockType.
337 			truncated = true;
338 		} else if (bufLen > blockLen) {
339 			bufLen = blockLen;
340 		}
341 
342 		if (blockType != FILE_BLOCK_TYPE) {
343 			restartCnt = NB.decodeUInt16(buf, bufLen - 2);
344 			restartTbl = bufLen - (restartCnt * 3 + 2);
345 			keysStart = ptr;
346 			keysEnd = restartTbl;
347 		} else {
348 			keysStart = ptr;
349 			keysEnd = ptr;
350 		}
351 	}
352 
353 	static int decodeBlockLen(int typeAndSize) {
354 		return typeAndSize & 0xffffff;
355 	}
356 
357 	private long inflateBuf(BlockSource src, long pos, int blockLen,
358 			int fileBlockSize) throws IOException {
359 		byte[] dst = new byte[blockLen];
360 		System.arraycopy(buf, 0, dst, 0, 4);
361 
362 		long deflatedSize = 0;
363 		Inflater inf = InflaterCache.get();
364 		try {
365 			inf.setInput(buf, ptr, bufLen - ptr);
366 			for (int o = 4;;) {
367 				int n = inf.inflate(dst, o, dst.length - o);
368 				o += n;
369 				if (inf.finished()) {
370 					deflatedSize = inf.getBytesRead();
371 					break;
372 				} else if (n <= 0 && inf.needsInput()) {
373 					long p = pos + 4 + inf.getBytesRead();
374 					readBlockIntoBuf(src, p, fileBlockSize);
375 					inf.setInput(buf, 0, bufLen);
376 				} else if (n <= 0) {
377 					throw invalidBlock();
378 				}
379 			}
380 		} catch (DataFormatException e) {
381 			throw invalidBlock(e);
382 		} finally {
383 			InflaterCache.release(inf);
384 		}
385 
386 		buf = dst;
387 		bufLen = dst.length;
388 		return deflatedSize;
389 	}
390 
391 	private void setupEmptyFileBlock() {
392 		// An empty reftable has only the file header in first block.
393 		blockType = FILE_BLOCK_TYPE;
394 		ptr = FILE_HEADER_LEN;
395 		restartCnt = 0;
396 		restartTbl = bufLen;
397 		keysStart = bufLen;
398 		keysEnd = bufLen;
399 	}
400 
401 	void verifyIndex() throws IOException {
402 		if (blockType != INDEX_BLOCK_TYPE || truncated) {
403 			throw invalidBlock();
404 		}
405 	}
406 
407 	/**
408 	 * Finds a key in the block and positions the current pointer on its record.
409 	 * <p>
410 	 * As a side-effect this method arranges for the current pointer to be near
411 	 * or exactly on {@code key}, allowing other methods to access data from
412 	 * that current record:
413 	 * <ul>
414 	 * <li>{@link #name()}
415 	 * <li>{@link #match(byte[], boolean)}
416 	 * <li>{@link #readRef(long)}
417 	 * <li>{@link #readLogUpdateIndex()}
418 	 * <li>{@link #readLogEntry()}
419 	 * <li>{@link #readBlockPositionList()}
420 	 * </ul>
421 	 *
422 	 * @param key
423 	 *            key to find.
424 	 * @return {@code <0} if the key occurs before the start of this block;
425 	 *         {@code 0} if the block is positioned on the key; {@code >0} if
426 	 *         the key occurs after the last key of this block.
427 	 */
428 	int seekKey(byte[] key) {
429 		int low = 0;
430 		int end = restartCnt;
431 		for (;;) {
432 			int mid = (low + end) >>> 1;
433 			int p = NB.decodeUInt24(buf, restartTbl + mid * 3);
434 			ptr = p + 1; // skip 0 prefix length
435 			int n = readVarint32() >>> 3;
436 			int cmp = compare(key, 0, key.length, buf, ptr, n);
437 			if (cmp < 0) {
438 				end = mid;
439 			} else if (cmp == 0) {
440 				ptr = p;
441 				return 0;
442 			} else /* if (cmp > 0) */ {
443 				low = mid + 1;
444 			}
445 			if (low >= end) {
446 				return scanToKey(key, p, low, cmp);
447 			}
448 		}
449 	}
450 
451 	/**
452 	 * Performs the linear search step within a restart interval.
453 	 * <p>
454 	 * Starts at a restart position whose key sorts before (or equal to)
455 	 * {@code key} and walks sequentially through the following prefix
456 	 * compressed records to find {@code key}.
457 	 *
458 	 * @param key
459 	 *            key the caller wants to find.
460 	 * @param rPtr
461 	 *            current record pointer from restart table binary search.
462 	 * @param rIdx
463 	 *            current restart table index.
464 	 * @param rCmp
465 	 *            result of compare from restart table binary search.
466 	 * @return {@code <0} if the key occurs before the start of this block;
467 	 *         {@code 0} if the block is positioned on the key; {@code >0} if
468 	 *         the key occurs after the last key of this block.
469 	 */
470 	private int scanToKey(byte[] key, int rPtr, int rIdx, int rCmp) {
471 		if (rCmp < 0) {
472 			if (rIdx == 0) {
473 				ptr = keysStart;
474 				return -1;
475 			}
476 			ptr = NB.decodeUInt24(buf, restartTbl + (rIdx - 1) * 3);
477 		} else {
478 			ptr = rPtr;
479 		}
480 
481 		int cmp;
482 		do {
483 			int savePtr = ptr;
484 			parseKey();
485 			cmp = compare(key, 0, key.length, nameBuf, 0, nameLen);
486 			if (cmp <= 0) {
487 				// cmp < 0, name should be in this block, but is not.
488 				// cmp = 0, block is positioned at name.
489 				ptr = savePtr;
490 				return cmp < 0 && savePtr == keysStart ? -1 : 0;
491 			}
492 			skipValue();
493 		} while (ptr < keysEnd);
494 		return cmp;
495 	}
496 
497 	void skipValue() {
498 		switch (blockType) {
499 		case REF_BLOCK_TYPE:
500 			readVarint64(); // update_index_delta
501 			switch (valueType & VALUE_TYPE_MASK) {
502 			case VALUE_NONE:
503 				return;
504 			case VALUE_1ID:
505 				ptr += OBJECT_ID_LENGTH;
506 				return;
507 			case VALUE_2ID:
508 				ptr += 2 * OBJECT_ID_LENGTH;
509 				return;
510 			case VALUE_SYMREF:
511 				skipString();
512 				return;
513 			}
514 			break;
515 
516 		case OBJ_BLOCK_TYPE: {
517 			int n = valueType & VALUE_TYPE_MASK;
518 			if (n == 0) {
519 				n = readVarint32();
520 			}
521 			while (n-- > 0) {
522 				readVarint32();
523 			}
524 			return;
525 		}
526 
527 		case INDEX_BLOCK_TYPE:
528 			readVarint32();
529 			return;
530 
531 		case LOG_BLOCK_TYPE:
532 			if ((valueType & VALUE_TYPE_MASK) == LOG_NONE) {
533 				return;
534 			} else if ((valueType & VALUE_TYPE_MASK) == LOG_DATA) {
535 				ptr += 2 * OBJECT_ID_LENGTH; // oldId, newId
536 				skipString(); // name
537 				skipString(); // email
538 				readVarint64(); // time
539 				ptr += 2; // tz
540 				skipString(); // msg
541 				return;
542 			}
543 		}
544 
545 		throw new IllegalStateException();
546 	}
547 
548 	private void skipString() {
549 		int n = readVarint32(); // string length
550 		ptr += n;
551 	}
552 
553 	private short readInt16() {
554 		return (short) NB.decodeUInt16(buf, ptr += 2);
555 	}
556 
557 	private int readVarint32() {
558 		byte c = buf[ptr++];
559 		int val = c & 0x7f;
560 		while ((c & 0x80) != 0) {
561 			c = buf[ptr++];
562 			val++;
563 			val <<= 7;
564 			val |= (c & 0x7f);
565 		}
566 		return val;
567 	}
568 
569 	private long readVarint64() {
570 		byte c = buf[ptr++];
571 		long val = c & 0x7f;
572 		while ((c & 0x80) != 0) {
573 			c = buf[ptr++];
574 			val++;
575 			val <<= 7;
576 			val |= (c & 0x7f);
577 		}
578 		return val;
579 	}
580 
581 	private static Ref newRef(String name, long updateIndex) {
582 		return new ObjectIdRef.Unpeeled(NEW, name, null, updateIndex);
583 	}
584 
585 	private static IOException invalidBlock() {
586 		return invalidBlock(null);
587 	}
588 
589 	private static IOException invalidBlock(Throwable cause) {
590 		return new IOException(JGitText.get().invalidReftableBlock, cause);
591 	}
592 }