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