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