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