View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.hadoop.hbase.io.hfile;
19  
20  import java.io.ByteArrayOutputStream;
21  import java.io.DataInputStream;
22  import java.io.DataOutput;
23  import java.io.DataOutputStream;
24  import java.io.IOException;
25  import java.io.InputStream;
26  import java.nio.ByteBuffer;
27  import java.util.concurrent.locks.Lock;
28  import java.util.concurrent.locks.ReentrantLock;
29  
30  import org.apache.commons.logging.Log;
31  import org.apache.commons.logging.LogFactory;
32  import org.apache.hadoop.fs.FSDataInputStream;
33  import org.apache.hadoop.fs.FSDataOutputStream;
34  import org.apache.hadoop.fs.Path;
35  import org.apache.hadoop.hbase.Cell;
36  import org.apache.hadoop.hbase.HConstants;
37  import org.apache.hadoop.hbase.classification.InterfaceAudience;
38  import org.apache.hadoop.hbase.fs.HFileSystem;
39  import org.apache.hadoop.hbase.io.ByteBufferInputStream;
40  import org.apache.hadoop.hbase.io.FSDataInputStreamWrapper;
41  import org.apache.hadoop.hbase.io.encoding.DataBlockEncoding;
42  import org.apache.hadoop.hbase.io.encoding.HFileBlockDecodingContext;
43  import org.apache.hadoop.hbase.io.encoding.HFileBlockDefaultDecodingContext;
44  import org.apache.hadoop.hbase.io.encoding.HFileBlockDefaultEncodingContext;
45  import org.apache.hadoop.hbase.io.encoding.HFileBlockEncodingContext;
46  import org.apache.hadoop.hbase.util.ByteBufferUtils;
47  import org.apache.hadoop.hbase.util.Bytes;
48  import org.apache.hadoop.hbase.util.ChecksumType;
49  import org.apache.hadoop.hbase.util.ClassSize;
50  import org.apache.hadoop.io.IOUtils;
51  
52  import com.google.common.annotations.VisibleForTesting;
53  import com.google.common.base.Preconditions;
54  
55  /**
56   * Reads {@link HFile} version 2 blocks to HFiles and via {@link Cacheable} Interface to caches.
57   * Version 2 was introduced in hbase-0.92.0. No longer has support for version 1 blocks since
58   * hbase-1.3.0.
59   *
60   * <p>Version 1 was the original file block. Version 2 was introduced when we changed the hbase file
61   * format to support multi-level block indexes and compound bloom filters (HBASE-3857).
62   *
63   * <h3>HFileBlock: Version 2</h3>
64   * In version 2, a block is structured as follows:
65   * <ul>
66   * <li><b>Header:</b> See Writer#putHeader() for where header is written; header total size is
67   * HFILEBLOCK_HEADER_SIZE
68   * <ul>
69   * <li>0. blockType: Magic record identifying the {@link BlockType} (8 bytes):
70   * e.g. <code>DATABLK*</code>
71   * <li>1. onDiskSizeWithoutHeader: Compressed -- a.k.a 'on disk' -- block size, excluding header,
72   * but including tailing checksum bytes (4 bytes)
73   * <li>2. uncompressedSizeWithoutHeader: Uncompressed block size, excluding header, and excluding
74   * checksum bytes (4 bytes)
75   * <li>3. prevBlockOffset: The offset of the previous block of the same type (8 bytes). This is
76   * used to navigate to the previous block without having to go to the block index
77   * <li>4: For minorVersions &gt;=1, the ordinal describing checksum type (1 byte)
78   * <li>5: For minorVersions &gt;=1, the number of data bytes/checksum chunk (4 bytes)
79   * <li>6: onDiskDataSizeWithHeader: For minorVersions &gt;=1, the size of data 'on disk', including
80   * header, excluding checksums (4 bytes)
81   * </ul>
82   * </li>
83   * <li><b>Raw/Compressed/Encrypted/Encoded data:</b> The compression
84   * algorithm is the same for all the blocks in an {@link HFile}. If compression is NONE, this is
85   * just raw, serialized Cells.
86   * <li><b>Tail:</b> For minorVersions &gt;=1, a series of 4 byte checksums, one each for
87   * the number of bytes specified by bytesPerChecksum.
88   * </ul>
89   * <h3>Caching</h3>
90   * Caches cache whole blocks with trailing checksums if any. We then tag on some metadata, the
91   * content of BLOCK_METADATA_SPACE which will be flag on if we are doing 'hbase'
92   * checksums and then the offset into the file which is needed when we re-make a cache key
93   * when we return the block to the cache as 'done'. See {@link Cacheable#serialize(ByteBuffer)} and
94   * {@link Cacheable#getDeserializer()}.
95   *
96   * <p>TODO: Should we cache the checksums? Down in Writer#getBlockForCaching(CacheConfig) where
97   * we make a block to cache-on-write, there is an attempt at turning off checksums. This is not the
98   * only place we get blocks to cache. We also will cache the raw return from an hdfs read. In this
99   * case, the checksums may be present. If the cache is backed by something that doesn't do ECC,
100  * say an SSD, we might want to preserve checksums. For now this is open question.
101  * <p>TODO: Over in BucketCache, we save a block allocation by doing a custom serialization.
102  * Be sure to change it if serialization changes in here. Could we add a method here that takes an
103  * IOEngine and that then serializes to it rather than expose our internals over in BucketCache?
104  * IOEngine is in the bucket subpackage. Pull it up? Then this class knows about bucketcache. Ugh.
105  */
106 @InterfaceAudience.Private
107 @edu.umd.cs.findbugs.annotations.SuppressWarnings(value="HE_EQUALS_USE_HASHCODE",
108   justification="Fix!!! Fine for now bug FIXXXXXXX!!!!")
109 public class HFileBlock implements Cacheable {
110   private static final Log LOG = LogFactory.getLog(HFileBlock.class);
111 
112   /** Type of block. Header field 0. */
113   private BlockType blockType;
114 
115   /**
116    * Size on disk excluding header, including checksum. Header field 1.
117    * @see Writer#putHeader(byte[], int, int, int, int)
118    */
119   private int onDiskSizeWithoutHeader;
120 
121   /**
122    * Size of pure data. Does not include header or checksums. Header field 2.
123    * @see Writer#putHeader(byte[], int, int, int, int)
124    */
125   private int uncompressedSizeWithoutHeader;
126 
127   /**
128    * The offset of the previous block on disk. Header field 3.
129    * @see Writer#putHeader(byte[], int, int, int, int)
130    */
131   private long prevBlockOffset;
132 
133   /**
134    * Size on disk of header + data. Excludes checksum. Header field 6,
135    * OR calculated from {@link #onDiskSizeWithoutHeader} when using HDFS checksum.
136    * @see Writer#putHeader(byte[], int, int, int, int)
137    */
138   private int onDiskDataSizeWithHeader;
139 
140 
141   /**
142    * The in-memory representation of the hfile block. Can be on or offheap. Can be backed by
143    * a single ByteBuffer or by many. Make no assumptions.
144    *
145    * <p>Be careful reading from this <code>buf</code>. Duplicate and work on the duplicate or if
146    * not, be sure to reset position and limit else trouble down the road.
147    *
148    * <p>TODO: Make this read-only once made.
149    */
150   private ByteBuffer buf;
151 
152   /** Meta data that holds meta information on the hfileblock.
153    */
154   private HFileContext fileContext;
155 
156   /**
157    * The offset of this block in the file. Populated by the reader for
158    * convenience of access. This offset is not part of the block header.
159    */
160   private long offset = UNSET;
161 
162   /**
163    * The on-disk size of the next block, including the header and checksums if present, obtained by
164    * peeking into the first {@link HConstants#HFILEBLOCK_HEADER_SIZE} bytes of the next block's
165    * header, or UNSET if unknown.
166    *
167    * Blocks try to carry the size of the next block to read in this data member. They will even have
168    * this value when served from cache. Could save a seek in the case where we are iterating through
169    * a file and some of the blocks come from cache. If from cache, then having this info to hand
170    * will save us doing a seek to read the header so we can read the body of a block.
171    * TODO: see how effective this is at saving seeks.
172    */
173   private int nextBlockOnDiskSize = UNSET;
174 
175   /**
176    * On a checksum failure, do these many succeeding read requests using hdfs checksums before
177    * auto-reenabling hbase checksum verification.
178    */
179   static final int CHECKSUM_VERIFICATION_NUM_IO_THRESHOLD = 3;
180 
181   private static int UNSET = -1;
182   public static final boolean FILL_HEADER = true;
183   public static final boolean DONT_FILL_HEADER = false;
184 
185   public static final int BYTE_BUFFER_HEAP_SIZE = (int) ClassSize.estimateBase(
186       ByteBuffer.wrap(new byte[0], 0, 0).getClass(), false);
187 
188   /**
189    * Space for metadata on a block that gets stored along with the block when we cache it.
190    * There are a few bytes stuck on the end of the HFileBlock that we pull in from HDFS (note,
191    * when we read from HDFS, we pull in an HFileBlock AND the header of the next block if one).
192    * 8 bytes are offset of this block (long) in the file. Offset is important because
193    * used when we remake the CacheKey when we return the block to cache when done. There is also
194    * a flag on whether checksumming is being done by hbase or not. See class comment for note on
195    * uncertain state of checksumming of blocks that come out of cache (should we or should we not?).
196    * Finally there 4 bytes to hold the length of the next block which can save a seek on occasion.
197    * <p>This EXTRA came in with original commit of the bucketcache, HBASE-7404. Was formerly
198    * known as EXTRA_SERIALIZATION_SPACE.
199    */
200   static final int BLOCK_METADATA_SPACE = Bytes.SIZEOF_BYTE + Bytes.SIZEOF_LONG + Bytes.SIZEOF_INT;
201 
202   /**
203    * Each checksum value is an integer that can be stored in 4 bytes.
204    */
205   static final int CHECKSUM_SIZE = Bytes.SIZEOF_INT;
206 
207   static final byte[] DUMMY_HEADER_NO_CHECKSUM =
208       new byte[HConstants.HFILEBLOCK_HEADER_SIZE_NO_CHECKSUM];
209 
210   /**
211    * Used deserializing blocks from Cache.
212    *
213    * <code>
214    * ++++++++++++++
215    * + HFileBlock +
216    * ++++++++++++++
217    * + Checksums  + <= Optional
218    * ++++++++++++++
219    * + Metadata!  +
220    * ++++++++++++++
221    * </code>
222    * @see #serialize(ByteBuffer)
223    */
224   static final CacheableDeserializer<Cacheable> BLOCK_DESERIALIZER =
225       new CacheableDeserializer<Cacheable>() {
226         public HFileBlock deserialize(ByteBuffer buf, boolean reuse) throws IOException{
227           // The buf has the file block followed by block metadata.
228           // Set limit to just before the BLOCK_METADATA_SPACE then rewind.
229           buf.limit(buf.limit() - BLOCK_METADATA_SPACE).rewind();
230           // Get a new buffer to pass the HFileBlock for it to 'own'.
231           ByteBuffer newByteBuff;
232           if (reuse) {
233             newByteBuff = buf.slice();
234           } else {
235             int len = buf.limit();
236             newByteBuff = ByteBuffer.allocate(len);
237             ByteBufferUtils.copyFromBufferToBuffer(newByteBuff, buf, buf.position(), 0, len);
238           }
239           // Read out the BLOCK_METADATA_SPACE content and shove into our HFileBlock.
240           buf.position(buf.limit());
241           buf.limit(buf.limit() + HFileBlock.BLOCK_METADATA_SPACE);
242           boolean usesChecksum = buf.get() == (byte)1;
243           long offset = buf.getLong();
244           int nextBlockOnDiskSize = buf.getInt();
245           HFileBlock hFileBlock =
246               new HFileBlock(newByteBuff, usesChecksum, offset, nextBlockOnDiskSize, null);
247           return hFileBlock;
248         }
249 
250         @Override
251         public int getDeserialiserIdentifier() {
252           return DESERIALIZER_IDENTIFIER;
253         }
254 
255         @Override
256         public HFileBlock deserialize(ByteBuffer b) throws IOException {
257           return deserialize(b, false);
258         }
259       };
260   private static final int DESERIALIZER_IDENTIFIER;
261   static {
262     DESERIALIZER_IDENTIFIER =
263         CacheableDeserializerIdManager.registerDeserializer(BLOCK_DESERIALIZER);
264   }
265 
266   // Todo: encapsulate Header related logic in this inner class.
267   static class Header {
268     // Format of header is:
269     // 8 bytes - block magic
270     // 4 bytes int - onDiskSizeWithoutHeader
271     // 4 bytes int - uncompressedSizeWithoutHeader
272     // 8 bytes long - prevBlockOffset
273     // The following 3 are only present if header contains checksum information
274     // 1 byte - checksum type
275     // 4 byte int - bytes per checksum
276     // 4 byte int - onDiskDataSizeWithHeader
277     static int BLOCK_MAGIC_INDEX = 0;
278     static int ON_DISK_SIZE_WITHOUT_HEADER_INDEX = 8;
279     static int UNCOMPRESSED_SIZE_WITHOUT_HEADER_INDEX = 12;
280     static int PREV_BLOCK_OFFSET_INDEX = 16;
281     static int CHECKSUM_TYPE_INDEX = 24;
282     static int BYTES_PER_CHECKSUM_INDEX = 25;
283     static int ON_DISK_DATA_SIZE_WITH_HEADER_INDEX = 29;
284   }
285 
286   /**
287    * Copy constructor. Creates a shallow copy of {@code that}'s buffer.
288    */
289   private HFileBlock(HFileBlock that) {
290     this.blockType = that.blockType;
291     this.onDiskSizeWithoutHeader = that.onDiskSizeWithoutHeader;
292     this.uncompressedSizeWithoutHeader = that.uncompressedSizeWithoutHeader;
293     this.prevBlockOffset = that.prevBlockOffset;
294     this.buf = that.buf.duplicate();
295     this.offset = that.offset;
296     this.onDiskDataSizeWithHeader = that.onDiskDataSizeWithHeader;
297     this.fileContext = that.fileContext;
298     this.nextBlockOnDiskSize = that.nextBlockOnDiskSize;
299   }
300 
301   /**
302    * Creates a new {@link HFile} block from the given fields. This constructor
303    * is used when the block data has already been read and uncompressed,
304    * and is sitting in a byte buffer and we want to stuff the block into cache.
305    * See {@link Writer#getBlockForCaching(CacheConfig)}.
306    *
307    * <p>TODO: The caller presumes no checksumming
308    * required of this block instance since going into cache; checksum already verified on
309    * underlying block data pulled in from filesystem. Is that correct? What if cache is SSD?
310    *
311    * @param blockType the type of this block, see {@link BlockType}
312    * @param onDiskSizeWithoutHeader see {@link #onDiskSizeWithoutHeader}
313    * @param uncompressedSizeWithoutHeader see {@link #uncompressedSizeWithoutHeader}
314    * @param prevBlockOffset see {@link #prevBlockOffset}
315    * @param b block header ({@link HConstants#HFILEBLOCK_HEADER_SIZE} bytes) followed by
316    *          uncompressed data.
317    * @param fillHeader when true, write the first 4 header fields into passed buffer.
318    * @param offset the file offset the block was read from
319    * @param onDiskDataSizeWithHeader see {@link #onDiskDataSizeWithHeader}
320    * @param fileContext HFile meta data
321    */
322   HFileBlock(BlockType blockType, int onDiskSizeWithoutHeader, int uncompressedSizeWithoutHeader,
323       long prevBlockOffset, ByteBuffer b, boolean fillHeader, long offset,
324       final int nextBlockOnDiskSize, int onDiskDataSizeWithHeader, HFileContext fileContext) {
325     init(blockType, onDiskSizeWithoutHeader, uncompressedSizeWithoutHeader,
326         prevBlockOffset, offset, onDiskDataSizeWithHeader, nextBlockOnDiskSize, fileContext);
327     this.buf = b;
328     if (fillHeader) {
329       overwriteHeader();
330     }
331     this.buf.rewind();
332   }
333 
334   /**
335    * Creates a block from an existing buffer starting with a header. Rewinds
336    * and takes ownership of the buffer. By definition of rewind, ignores the
337    * buffer position, but if you slice the buffer beforehand, it will rewind
338    * to that point.
339    * @param buf Has header, content, and trailing checksums if present.
340    */
341   HFileBlock(ByteBuffer buf, boolean usesHBaseChecksum, final long offset,
342       final int nextBlockOnDiskSize, HFileContext fileContext) throws IOException {
343     buf.rewind();
344     final BlockType blockType = BlockType.read(buf);
345     final int onDiskSizeWithoutHeader = buf.getInt(Header.ON_DISK_SIZE_WITHOUT_HEADER_INDEX);
346     final int uncompressedSizeWithoutHeader =
347         buf.getInt(Header.UNCOMPRESSED_SIZE_WITHOUT_HEADER_INDEX);
348     final long prevBlockOffset = buf.getLong(Header.PREV_BLOCK_OFFSET_INDEX);
349     // This constructor is called when we deserialize a block from cache and when we read a block in
350     // from the fs. fileCache is null when deserialized from cache so need to make up one.
351     HFileContextBuilder fileContextBuilder = fileContext != null?
352         new HFileContextBuilder(fileContext): new HFileContextBuilder();
353     fileContextBuilder.withHBaseCheckSum(usesHBaseChecksum);
354     int onDiskDataSizeWithHeader;
355     if (usesHBaseChecksum) {
356       byte checksumType = buf.get(Header.CHECKSUM_TYPE_INDEX);
357       int bytesPerChecksum = buf.getInt(Header.BYTES_PER_CHECKSUM_INDEX);
358       onDiskDataSizeWithHeader = buf.getInt(Header.ON_DISK_DATA_SIZE_WITH_HEADER_INDEX);
359       // Use the checksum type and bytes per checksum from header, not from filecontext.
360       fileContextBuilder.withChecksumType(ChecksumType.codeToType(checksumType));
361       fileContextBuilder.withBytesPerCheckSum(bytesPerChecksum);
362     } else {
363       fileContextBuilder.withChecksumType(ChecksumType.NULL);
364       fileContextBuilder.withBytesPerCheckSum(0);
365       // Need to fix onDiskDataSizeWithHeader; there are not checksums after-block-data
366       onDiskDataSizeWithHeader = onDiskSizeWithoutHeader + headerSize(usesHBaseChecksum);
367     }
368     fileContext = fileContextBuilder.build();
369     assert usesHBaseChecksum == fileContext.isUseHBaseChecksum();
370     init(blockType, onDiskSizeWithoutHeader, uncompressedSizeWithoutHeader,
371         prevBlockOffset, offset, onDiskDataSizeWithHeader, nextBlockOnDiskSize, fileContext);
372     this.offset = offset;
373     this.buf = buf;
374     this.buf.rewind();
375  }
376 
377   /**
378    * Called from constructors.
379    */
380   private void init(BlockType blockType, int onDiskSizeWithoutHeader,
381       int uncompressedSizeWithoutHeader, long prevBlockOffset,
382       long offset, int onDiskDataSizeWithHeader, final int nextBlockOnDiskSize,
383       HFileContext fileContext) {
384     this.blockType = blockType;
385     this.onDiskSizeWithoutHeader = onDiskSizeWithoutHeader;
386     this.uncompressedSizeWithoutHeader = uncompressedSizeWithoutHeader;
387     this.prevBlockOffset = prevBlockOffset;
388     this.offset = offset;
389     this.onDiskDataSizeWithHeader = onDiskDataSizeWithHeader;
390     this.nextBlockOnDiskSize = nextBlockOnDiskSize;
391     this.fileContext = fileContext;
392   }
393 
394   /**
395    * Parse total ondisk size including header and checksum.
396    * @param headerBuf Header ByteBuffer. Presumed exact size of header.
397    * @param verifyChecksum true if checksum verification is in use.
398    * @return Size of the block with header included.
399    */
400   private static int getOnDiskSizeWithHeader(final ByteBuffer headerBuf, boolean verifyChecksum) {
401     return headerBuf.getInt(Header.ON_DISK_SIZE_WITHOUT_HEADER_INDEX) +
402       headerSize(verifyChecksum);
403   }
404 
405   /**
406    * @return the on-disk size of the next block (including the header size and any checksums if
407    * present) read by peeking into the next block's header; use as a hint when doing
408    * a read of the next block when scanning or running over a file.
409    */
410   public int getNextBlockOnDiskSize() {
411     return nextBlockOnDiskSize;
412   }
413 
414   public BlockType getBlockType() {
415     return blockType;
416   }
417 
418   /** @return get data block encoding id that was used to encode this block */
419   public short getDataBlockEncodingId() {
420     if (blockType != BlockType.ENCODED_DATA) {
421       throw new IllegalArgumentException("Querying encoder ID of a block " +
422           "of type other than " + BlockType.ENCODED_DATA + ": " + blockType);
423     }
424     return buf.getShort(headerSize());
425   }
426 
427   /**
428    * @return the on-disk size of header + data part + checksum.
429    */
430   public int getOnDiskSizeWithHeader() {
431     return onDiskSizeWithoutHeader + headerSize();
432   }
433 
434   /**
435    * @return the on-disk size of the data part + checksum (header excluded).
436    */
437   int getOnDiskSizeWithoutHeader() {
438     return onDiskSizeWithoutHeader;
439   }
440 
441   /**
442    * @return the uncompressed size of data part (header and checksum excluded).
443    */
444   public int getUncompressedSizeWithoutHeader() {
445     return uncompressedSizeWithoutHeader;
446   }
447 
448   /**
449    * @return the offset of the previous block of the same type in the file, or
450    *         -1 if unknown
451    */
452   long getPrevBlockOffset() {
453     return prevBlockOffset;
454   }
455 
456   /**
457    * Rewinds {@code buf} and writes first 4 header fields. {@code buf} position
458    * is modified as side-effect.
459    */
460   private void overwriteHeader() {
461     buf.rewind();
462     blockType.write(buf);
463     buf.putInt(onDiskSizeWithoutHeader);
464     buf.putInt(uncompressedSizeWithoutHeader);
465     buf.putLong(prevBlockOffset);
466     if (this.fileContext.isUseHBaseChecksum()) {
467       buf.put(fileContext.getChecksumType().getCode());
468       buf.putInt(fileContext.getBytesPerChecksum());
469       buf.putInt(onDiskDataSizeWithHeader);
470     }
471   }
472 
473   /**
474    * Returns a buffer that does not include the header or checksum.
475    *
476    * @return the buffer with header skipped and checksum omitted.
477    */
478   public ByteBuffer getBufferWithoutHeader() {
479     ByteBuffer dup = getBufferReadOnly();
480     // Now set it up so Buffer spans content only -- no header or no checksums.
481     dup.position(headerSize()).limit(buf.limit() - totalChecksumBytes());
482     return dup.slice();
483   }
484 
485   /**
486    * Returns a read-only duplicate of the buffer this block stores internally ready to be read.
487    * Clients must not modify the buffer object though they may set position and limit on the
488    * returned buffer since we pass back a duplicate. This method has to be public because it is used
489    * used in {@link org.apache.hadoop.hbase.util.CompoundBloomFilter}
490    * to avoid object creation on every Bloom
491    * filter lookup, but has to be used with caution. Buffer holds header, block content,
492    * and any follow-on checksums if present.
493    *
494    * @return the buffer of this block for read-only operations
495    */
496   public ByteBuffer getBufferReadOnly() {
497     ByteBuffer dup = this.buf.duplicate();
498     return dup;
499   }
500 
501   private void sanityCheckAssertion(long valueFromBuf, long valueFromField,
502       String fieldName) throws IOException {
503     if (valueFromBuf != valueFromField) {
504       throw new AssertionError(fieldName + " in the buffer (" + valueFromBuf
505           + ") is different from that in the field (" + valueFromField + ")");
506     }
507   }
508 
509   private void sanityCheckAssertion(BlockType valueFromBuf, BlockType valueFromField)
510       throws IOException {
511     if (valueFromBuf != valueFromField) {
512       throw new IOException("Block type stored in the buffer: " +
513         valueFromBuf + ", block type field: " + valueFromField);
514     }
515   }
516 
517   /**
518    * Checks if the block is internally consistent, i.e. the first
519    * {@link HConstants#HFILEBLOCK_HEADER_SIZE} bytes of the buffer contain a
520    * valid header consistent with the fields. Assumes a packed block structure.
521    * This function is primary for testing and debugging, and is not
522    * thread-safe, because it alters the internal buffer pointer.
523    * Used by tests only.
524    */
525   @VisibleForTesting
526   void sanityCheck() throws IOException {
527     // Duplicate so no side-effects
528     ByteBuffer dup = this.buf.duplicate();
529     dup.rewind();
530     sanityCheckAssertion(BlockType.read(dup), blockType);
531     sanityCheckAssertion(dup.getInt(), onDiskSizeWithoutHeader, "onDiskSizeWithoutHeader");
532     sanityCheckAssertion(dup.getInt(), uncompressedSizeWithoutHeader,
533         "uncompressedSizeWithoutHeader");
534     sanityCheckAssertion(dup.getLong(), prevBlockOffset, "prevBlockOffset");
535     if (this.fileContext.isUseHBaseChecksum()) {
536       sanityCheckAssertion(dup.get(), this.fileContext.getChecksumType().getCode(), "checksumType");
537       sanityCheckAssertion(dup.getInt(), this.fileContext.getBytesPerChecksum(),
538           "bytesPerChecksum");
539       sanityCheckAssertion(dup.getInt(), onDiskDataSizeWithHeader, "onDiskDataSizeWithHeader");
540     }
541 
542     int cksumBytes = totalChecksumBytes();
543     int expectedBufLimit = onDiskDataSizeWithHeader + cksumBytes;
544     if (dup.limit() != expectedBufLimit) {
545       throw new AssertionError("Expected limit " + expectedBufLimit + ", got " + dup.limit());
546     }
547 
548     // We might optionally allocate HFILEBLOCK_HEADER_SIZE more bytes to read the next
549     // block's header, so there are two sensible values for buffer capacity.
550     int hdrSize = headerSize();
551     if (dup.capacity() != expectedBufLimit && dup.capacity() != expectedBufLimit + hdrSize) {
552       throw new AssertionError("Invalid buffer capacity: " + dup.capacity() +
553           ", expected " + expectedBufLimit + " or " + (expectedBufLimit + hdrSize));
554     }
555   }
556 
557   @Override
558   public String toString() {
559     StringBuilder sb = new StringBuilder()
560       .append("[")
561       .append("blockType=").append(blockType)
562       .append(", fileOffset=").append(offset)
563       .append(", headerSize=").append(headerSize())
564       .append(", onDiskSizeWithoutHeader=").append(onDiskSizeWithoutHeader)
565       .append(", uncompressedSizeWithoutHeader=").append(uncompressedSizeWithoutHeader)
566       .append(", prevBlockOffset=").append(prevBlockOffset)
567       .append(", isUseHBaseChecksum=").append(fileContext.isUseHBaseChecksum());
568     if (fileContext.isUseHBaseChecksum()) {
569       sb.append(", checksumType=").append(ChecksumType.codeToType(this.buf.get(24)))
570         .append(", bytesPerChecksum=").append(this.buf.getInt(24 + 1))
571         .append(", onDiskDataSizeWithHeader=").append(onDiskDataSizeWithHeader);
572     } else {
573       sb.append(", onDiskDataSizeWithHeader=").append(onDiskDataSizeWithHeader)
574         .append("(").append(onDiskSizeWithoutHeader)
575         .append("+").append(HConstants.HFILEBLOCK_HEADER_SIZE_NO_CHECKSUM).append(")");
576     }
577     String dataBegin = null;
578     if (buf.hasArray()) {
579       dataBegin = Bytes.toStringBinary(buf.array(), buf.arrayOffset() + headerSize(),
580           Math.min(32, buf.limit() - buf.arrayOffset() - headerSize()));
581     } else {
582       ByteBuffer bufWithoutHeader = getBufferWithoutHeader();
583       byte[] dataBeginBytes = new byte[Math.min(32,
584           bufWithoutHeader.limit() - bufWithoutHeader.position())];
585       bufWithoutHeader.get(dataBeginBytes);
586       dataBegin = Bytes.toStringBinary(dataBeginBytes);
587     }
588     sb.append(", getOnDiskSizeWithHeader=").append(getOnDiskSizeWithHeader())
589       .append(", totalChecksumBytes=").append(totalChecksumBytes())
590       .append(", isUnpacked=").append(isUnpacked())
591       .append(", buf=[").append(buf).append("]")
592       .append(", dataBeginsWith=").append(dataBegin)
593       .append(", fileContext=").append(fileContext)
594       .append("]");
595     return sb.toString();
596   }
597 
598   /**
599    * Retrieves the decompressed/decrypted view of this block. An encoded block remains in its
600    * encoded structure. Internal structures are shared between instances where applicable.
601    */
602   HFileBlock unpack(HFileContext fileContext, FSReader reader) throws IOException {
603     if (!fileContext.isCompressedOrEncrypted()) {
604       // TODO: cannot use our own fileContext here because HFileBlock(ByteBuffer, boolean),
605       // which is used for block serialization to L2 cache, does not preserve encoding and
606       // encryption details.
607       return this;
608     }
609 
610     HFileBlock unpacked = new HFileBlock(this);
611     unpacked.allocateBuffer(); // allocates space for the decompressed block
612 
613     HFileBlockDecodingContext ctx = blockType == BlockType.ENCODED_DATA ?
614       reader.getBlockDecodingContext() : reader.getDefaultBlockDecodingContext();
615 
616     ByteBuffer dup = this.buf.duplicate();
617     dup.position(this.headerSize());
618     dup = dup.slice();
619     ctx.prepareDecoding(unpacked.getOnDiskSizeWithoutHeader(),
620       unpacked.getUncompressedSizeWithoutHeader(), unpacked.getBufferWithoutHeader(),
621       dup);
622     return unpacked;
623   }
624 
625   /**
626    * Always allocates a new buffer of the correct size. Copies header bytes
627    * from the existing buffer. Does not change header fields.
628    * Reserve room to keep checksum bytes too.
629    */
630   private void allocateBuffer() {
631     int cksumBytes = totalChecksumBytes();
632     int headerSize = headerSize();
633     int capacityNeeded = headerSize + uncompressedSizeWithoutHeader + cksumBytes;
634 
635     // TODO we need consider allocating offheap here?
636     ByteBuffer newBuf = ByteBuffer.allocate(capacityNeeded);
637 
638     // Copy header bytes into newBuf.
639     // newBuf is HBB so no issue in calling array()
640     ByteBuffer dup = buf.duplicate();
641     dup.position(0);
642     dup.get(newBuf.array(), newBuf.arrayOffset(), headerSize);
643 
644     buf = newBuf;
645     // set limit to exclude next block's header
646     buf.limit(headerSize + uncompressedSizeWithoutHeader + cksumBytes);
647   }
648 
649   /**
650    * Return true when this block's buffer has been unpacked, false otherwise. Note this is a
651    * calculated heuristic, not tracked attribute of the block.
652    */
653   public boolean isUnpacked() {
654     final int cksumBytes = totalChecksumBytes();
655     final int headerSize = headerSize();
656     final int expectedCapacity = headerSize + uncompressedSizeWithoutHeader + cksumBytes;
657     final int bufCapacity = buf.capacity();
658     return bufCapacity == expectedCapacity || bufCapacity == expectedCapacity + headerSize;
659   }
660 
661   /** An additional sanity-check in case no compression or encryption is being used. */
662   public void sanityCheckUncompressedSize() throws IOException {
663     if (onDiskSizeWithoutHeader != uncompressedSizeWithoutHeader + totalChecksumBytes()) {
664       throw new IOException("Using no compression but "
665           + "onDiskSizeWithoutHeader=" + onDiskSizeWithoutHeader + ", "
666           + "uncompressedSizeWithoutHeader=" + uncompressedSizeWithoutHeader
667           + ", numChecksumbytes=" + totalChecksumBytes());
668     }
669   }
670 
671   /**
672    * Cannot be {@link #UNSET}. Must be a legitimate value. Used re-making the {@link CacheKey} when
673    * block is returned to the cache.
674    * @return the offset of this block in the file it was read from
675    */
676   long getOffset() {
677     if (offset < 0) {
678       throw new IllegalStateException("HFile block offset not initialized properly");
679     }
680     return offset;
681   }
682 
683   /**
684    * @return a byte stream reading the data + checksum of this block
685    */
686   public DataInputStream getByteStream() {
687     ByteBuffer dup = this.buf.duplicate();
688     dup.position(this.headerSize());
689     return new DataInputStream(new ByteBufferInputStream(dup));
690   }
691 
692   @Override
693   public long heapSize() {
694     long size = ClassSize.align(
695         ClassSize.OBJECT +
696         // Block type, byte buffer and meta references
697         3 * ClassSize.REFERENCE +
698         // On-disk size, uncompressed size, and next block's on-disk size
699         // bytePerChecksum and onDiskDataSize
700         4 * Bytes.SIZEOF_INT +
701         // This and previous block offset
702         2 * Bytes.SIZEOF_LONG +
703         // Heap size of the meta object. meta will be always not null.
704         fileContext.heapSize()
705     );
706 
707     if (buf != null) {
708       // Deep overhead of the byte buffer. Needs to be aligned separately.
709       size += ClassSize.align(buf.capacity() + BYTE_BUFFER_HEAP_SIZE);
710     }
711 
712     return ClassSize.align(size);
713   }
714 
715   /**
716    * Read from an input stream at least <code>necessaryLen</code> and if possible,
717    * <code>extraLen</code> also if available. Analogous to
718    * {@link IOUtils#readFully(InputStream, byte[], int, int)}, but specifies a
719    * number of "extra" bytes to also optionally read.
720    *
721    * @param in the input stream to read from
722    * @param buf the buffer to read into
723    * @param bufOffset the destination offset in the buffer
724    * @param necessaryLen the number of bytes that are absolutely necessary to read
725    * @param extraLen the number of extra bytes that would be nice to read
726    * @return true if succeeded reading the extra bytes
727    * @throws IOException if failed to read the necessary bytes
728    */
729   static boolean readWithExtra(InputStream in, byte[] buf,
730       int bufOffset, int necessaryLen, int extraLen) throws IOException {
731     int bytesRemaining = necessaryLen + extraLen;
732     while (bytesRemaining > 0) {
733       int ret = in.read(buf, bufOffset, bytesRemaining);
734       if (ret == -1 && bytesRemaining <= extraLen) {
735         // We could not read the "extra data", but that is OK.
736         break;
737       }
738       if (ret < 0) {
739         throw new IOException("Premature EOF from inputStream (read "
740             + "returned " + ret + ", was trying to read " + necessaryLen
741             + " necessary bytes and " + extraLen + " extra bytes, "
742             + "successfully read "
743             + (necessaryLen + extraLen - bytesRemaining));
744       }
745       bufOffset += ret;
746       bytesRemaining -= ret;
747     }
748     return bytesRemaining <= 0;
749   }
750 
751   /**
752    * Read from an input stream at least <code>necessaryLen</code> and if possible,
753    * <code>extraLen</code> also if available. Analogous to
754    * {@link IOUtils#readFully(InputStream, byte[], int, int)}, but uses
755    * positional read and specifies a number of "extra" bytes that would be
756    * desirable but not absolutely necessary to read.
757    *
758    * @param in the input stream to read from
759    * @param position the position within the stream from which to start reading
760    * @param buf the buffer to read into
761    * @param bufOffset the destination offset in the buffer
762    * @param necessaryLen the number of bytes that are absolutely necessary to
763    *     read
764    * @param extraLen the number of extra bytes that would be nice to read
765    * @return true if and only if extraLen is > 0 and reading those extra bytes
766    *     was successful
767    * @throws IOException if failed to read the necessary bytes
768    */
769   @VisibleForTesting
770   static boolean positionalReadWithExtra(FSDataInputStream in,
771       long position, byte[] buf, int bufOffset, int necessaryLen, int extraLen)
772       throws IOException {
773     int bytesRemaining = necessaryLen + extraLen;
774     int bytesRead = 0;
775     while (bytesRead < necessaryLen) {
776       int ret = in.read(position, buf, bufOffset, bytesRemaining);
777       if (ret < 0) {
778         throw new IOException("Premature EOF from inputStream (positional read "
779             + "returned " + ret + ", was trying to read " + necessaryLen
780             + " necessary bytes and " + extraLen + " extra bytes, "
781             + "successfully read " + bytesRead);
782       }
783       position += ret;
784       bufOffset += ret;
785       bytesRemaining -= ret;
786       bytesRead += ret;
787     }
788     return bytesRead != necessaryLen && bytesRemaining <= 0;
789   }
790 
791   /**
792    * Unified version 2 {@link HFile} block writer. The intended usage pattern
793    * is as follows:
794    * <ol>
795    * <li>Construct an {@link HFileBlock.Writer}, providing a compression algorithm.
796    * <li>Call {@link Writer#startWriting} and get a data stream to write to.
797    * <li>Write your data into the stream.
798    * <li>Call Writer#writeHeaderAndData(FSDataOutputStream) as many times as you need to.
799    * store the serialized block into an external stream.
800    * <li>Repeat to write more blocks.
801    * </ol>
802    * <p>
803    */
804   public static class Writer {
805     private enum State {
806       INIT,
807       WRITING,
808       BLOCK_READY
809     };
810 
811     /** Writer state. Used to ensure the correct usage protocol. */
812     private State state = State.INIT;
813 
814     /** Data block encoder used for data blocks */
815     private final HFileDataBlockEncoder dataBlockEncoder;
816 
817     private HFileBlockEncodingContext dataBlockEncodingCtx;
818 
819     /** block encoding context for non-data blocks*/
820     private HFileBlockDefaultEncodingContext defaultBlockEncodingCtx;
821 
822     /**
823      * The stream we use to accumulate data into a block in an uncompressed format.
824      * We reset this stream at the end of each block and reuse it. The
825      * header is written as the first {@link HConstants#HFILEBLOCK_HEADER_SIZE} bytes into this
826      * stream.
827      */
828     private ByteArrayOutputStream baosInMemory;
829 
830     /**
831      * Current block type. Set in {@link #startWriting(BlockType)}. Could be
832      * changed in {@link #finishBlock()} from {@link BlockType#DATA}
833      * to {@link BlockType#ENCODED_DATA}.
834      */
835     private BlockType blockType;
836 
837     /**
838      * A stream that we write uncompressed bytes to, which compresses them and
839      * writes them to {@link #baosInMemory}.
840      */
841     private DataOutputStream userDataStream;
842 
843     // Size of actual data being written. Not considering the block encoding/compression. This
844     // includes the header size also.
845     private int unencodedDataSizeWritten;
846 
847     /**
848      * Bytes to be written to the file system, including the header. Compressed
849      * if compression is turned on. It also includes the checksum data that
850      * immediately follows the block data. (header + data + checksums)
851      */
852     private byte[] onDiskBlockBytesWithHeader;
853 
854     /**
855      * The size of the checksum data on disk. It is used only if data is
856      * not compressed. If data is compressed, then the checksums are already
857      * part of onDiskBytesWithHeader. If data is uncompressed, then this
858      * variable stores the checksum data for this block.
859      */
860     private byte[] onDiskChecksum;
861 
862     /**
863      * Valid in the READY state. Contains the header and the uncompressed (but
864      * potentially encoded, if this is a data block) bytes, so the length is
865      * {@link #uncompressedSizeWithoutHeader} +
866      * {@link org.apache.hadoop.hbase.HConstants#HFILEBLOCK_HEADER_SIZE}.
867      * Does not store checksums.
868      */
869     private byte[] uncompressedBlockBytesWithHeader;
870 
871     /**
872      * Current block's start offset in the {@link HFile}. Set in
873      * {@link #writeHeaderAndData(FSDataOutputStream)}.
874      */
875     private long startOffset;
876 
877     /**
878      * Offset of previous block by block type. Updated when the next block is
879      * started.
880      */
881     private long[] prevOffsetByType;
882 
883     /** The offset of the previous block of the same type */
884     private long prevOffset;
885     /** Meta data that holds information about the hfileblock**/
886     private HFileContext fileContext;
887 
888     /**
889      * @param dataBlockEncoder data block encoding algorithm to use
890      */
891     public Writer(HFileDataBlockEncoder dataBlockEncoder, HFileContext fileContext) {
892       if (fileContext.getBytesPerChecksum() < HConstants.HFILEBLOCK_HEADER_SIZE) {
893         throw new RuntimeException("Unsupported value of bytesPerChecksum. " +
894             " Minimum is " + HConstants.HFILEBLOCK_HEADER_SIZE + " but the configured value is " +
895             fileContext.getBytesPerChecksum());
896       }
897       this.dataBlockEncoder = dataBlockEncoder != null?
898           dataBlockEncoder: NoOpDataBlockEncoder.INSTANCE;
899       this.dataBlockEncodingCtx = this.dataBlockEncoder.
900           newDataBlockEncodingContext(HConstants.HFILEBLOCK_DUMMY_HEADER, fileContext);
901       // TODO: This should be lazily instantiated since we usually do NOT need this default encoder
902       this.defaultBlockEncodingCtx = new HFileBlockDefaultEncodingContext(null,
903           HConstants.HFILEBLOCK_DUMMY_HEADER, fileContext);
904       // TODO: Set BAOS initial size. Use fileContext.getBlocksize() and add for header/checksum
905       baosInMemory = new ByteArrayOutputStream();
906       prevOffsetByType = new long[BlockType.values().length];
907       for (int i = 0; i < prevOffsetByType.length; ++i) {
908         prevOffsetByType[i] = UNSET;
909       }
910       // TODO: Why fileContext saved away when we have dataBlockEncoder and/or
911       // defaultDataBlockEncoder?
912       this.fileContext = fileContext;
913     }
914 
915     /**
916      * Starts writing into the block. The previous block's data is discarded.
917      *
918      * @return the stream the user can write their data into
919      * @throws IOException
920      */
921     DataOutputStream startWriting(BlockType newBlockType)
922         throws IOException {
923       if (state == State.BLOCK_READY && startOffset != -1) {
924         // We had a previous block that was written to a stream at a specific
925         // offset. Save that offset as the last offset of a block of that type.
926         prevOffsetByType[blockType.getId()] = startOffset;
927       }
928 
929       startOffset = -1;
930       blockType = newBlockType;
931 
932       baosInMemory.reset();
933       baosInMemory.write(HConstants.HFILEBLOCK_DUMMY_HEADER);
934 
935       state = State.WRITING;
936 
937       // We will compress it later in finishBlock()
938       userDataStream = new DataOutputStream(baosInMemory);
939       if (newBlockType == BlockType.DATA) {
940         this.dataBlockEncoder.startBlockEncoding(dataBlockEncodingCtx, userDataStream);
941       }
942       this.unencodedDataSizeWritten = 0;
943       return userDataStream;
944     }
945 
946     /**
947      * Writes the Cell to this block
948      * @param cell
949      * @throws IOException
950      */
951     void write(Cell cell) throws IOException{
952       expectState(State.WRITING);
953       this.unencodedDataSizeWritten +=
954           this.dataBlockEncoder.encode(cell, dataBlockEncodingCtx, this.userDataStream);
955     }
956 
957     /**
958      * Returns the stream for the user to write to. The block writer takes care
959      * of handling compression and buffering for caching on write. Can only be
960      * called in the "writing" state.
961      *
962      * @return the data output stream for the user to write to
963      */
964     DataOutputStream getUserDataStream() {
965       expectState(State.WRITING);
966       return userDataStream;
967     }
968 
969     /**
970      * Transitions the block writer from the "writing" state to the "block
971      * ready" state.  Does nothing if a block is already finished.
972      */
973     void ensureBlockReady() throws IOException {
974       Preconditions.checkState(state != State.INIT,
975           "Unexpected state: " + state);
976 
977       if (state == State.BLOCK_READY) {
978         return;
979       }
980 
981       // This will set state to BLOCK_READY.
982       finishBlock();
983     }
984 
985     /**
986      * Finish up writing of the block.
987      * Flushes the compressing stream (if using compression), fills out the header,
988      * does any compression/encryption of bytes to flush out to disk, and manages
989      * the cache on write content, if applicable. Sets block write state to "block ready".
990      */
991     private void finishBlock() throws IOException {
992       if (blockType == BlockType.DATA) {
993         BufferGrabbingByteArrayOutputStream baosInMemoryCopy =
994             new BufferGrabbingByteArrayOutputStream();
995         baosInMemory.writeTo(baosInMemoryCopy);
996         this.dataBlockEncoder.endBlockEncoding(dataBlockEncodingCtx, userDataStream,
997             baosInMemoryCopy.buf, blockType);
998         blockType = dataBlockEncodingCtx.getBlockType();
999       }
1000       userDataStream.flush();
1001       // This does an array copy, so it is safe to cache this byte array when cache-on-write.
1002       // Header is still the empty, 'dummy' header that is yet to be filled out.
1003       uncompressedBlockBytesWithHeader = baosInMemory.toByteArray();
1004       prevOffset = prevOffsetByType[blockType.getId()];
1005 
1006       // We need to set state before we can package the block up for cache-on-write. In a way, the
1007       // block is ready, but not yet encoded or compressed.
1008       state = State.BLOCK_READY;
1009       if (blockType == BlockType.DATA || blockType == BlockType.ENCODED_DATA) {
1010         onDiskBlockBytesWithHeader = dataBlockEncodingCtx.
1011             compressAndEncrypt(uncompressedBlockBytesWithHeader);
1012       } else {
1013         onDiskBlockBytesWithHeader = defaultBlockEncodingCtx.
1014             compressAndEncrypt(uncompressedBlockBytesWithHeader);
1015       }
1016       // Calculate how many bytes we need for checksum on the tail of the block.
1017       int numBytes = (int) ChecksumUtil.numBytes(
1018           onDiskBlockBytesWithHeader.length,
1019           fileContext.getBytesPerChecksum());
1020 
1021       // Put the header for the on disk bytes; header currently is unfilled-out
1022       putHeader(onDiskBlockBytesWithHeader, 0,
1023           onDiskBlockBytesWithHeader.length + numBytes,
1024           uncompressedBlockBytesWithHeader.length, onDiskBlockBytesWithHeader.length);
1025       // Set the header for the uncompressed bytes (for cache-on-write) -- IFF different from
1026       // onDiskBlockBytesWithHeader array.
1027       if (onDiskBlockBytesWithHeader != uncompressedBlockBytesWithHeader) {
1028         putHeader(uncompressedBlockBytesWithHeader, 0,
1029           onDiskBlockBytesWithHeader.length + numBytes,
1030           uncompressedBlockBytesWithHeader.length, onDiskBlockBytesWithHeader.length);
1031       }
1032       onDiskChecksum = new byte[numBytes];
1033       ChecksumUtil.generateChecksums(
1034           onDiskBlockBytesWithHeader, 0, onDiskBlockBytesWithHeader.length,
1035           onDiskChecksum, 0, fileContext.getChecksumType(), fileContext.getBytesPerChecksum());
1036     }
1037 
1038     public static class BufferGrabbingByteArrayOutputStream extends ByteArrayOutputStream {
1039       private byte[] buf;
1040 
1041       @Override
1042       public void write(byte[] b, int off, int len) {
1043         this.buf = b;
1044       }
1045 
1046       public byte[] getBuffer() {
1047         return this.buf;
1048       }
1049     }
1050 
1051     /**
1052      * Put the header into the given byte array at the given offset.
1053      * @param onDiskSize size of the block on disk header + data + checksum
1054      * @param uncompressedSize size of the block after decompression (but
1055      *          before optional data block decoding) including header
1056      * @param onDiskDataSize size of the block on disk with header
1057      *        and data but not including the checksums
1058      */
1059     private void putHeader(byte[] dest, int offset, int onDiskSize,
1060         int uncompressedSize, int onDiskDataSize) {
1061       offset = blockType.put(dest, offset);
1062       offset = Bytes.putInt(dest, offset, onDiskSize - HConstants.HFILEBLOCK_HEADER_SIZE);
1063       offset = Bytes.putInt(dest, offset, uncompressedSize - HConstants.HFILEBLOCK_HEADER_SIZE);
1064       offset = Bytes.putLong(dest, offset, prevOffset);
1065       offset = Bytes.putByte(dest, offset, fileContext.getChecksumType().getCode());
1066       offset = Bytes.putInt(dest, offset, fileContext.getBytesPerChecksum());
1067       Bytes.putInt(dest, offset, onDiskDataSize);
1068     }
1069 
1070     /**
1071      * Similar to {@link #writeHeaderAndData(FSDataOutputStream)}, but records
1072      * the offset of this block so that it can be referenced in the next block
1073      * of the same type.
1074      *
1075      * @param out
1076      * @throws IOException
1077      */
1078     void writeHeaderAndData(FSDataOutputStream out) throws IOException {
1079       long offset = out.getPos();
1080       if (startOffset != UNSET && offset != startOffset) {
1081         throw new IOException("A " + blockType + " block written to a "
1082             + "stream twice, first at offset " + startOffset + ", then at "
1083             + offset);
1084       }
1085       startOffset = offset;
1086 
1087       finishBlockAndWriteHeaderAndData((DataOutputStream) out);
1088     }
1089 
1090     /**
1091      * Writes the header and the compressed data of this block (or uncompressed
1092      * data when not using compression) into the given stream. Can be called in
1093      * the "writing" state or in the "block ready" state. If called in the
1094      * "writing" state, transitions the writer to the "block ready" state.
1095      *
1096      * @param out the output stream to write the
1097      * @throws IOException
1098      */
1099     protected void finishBlockAndWriteHeaderAndData(DataOutputStream out)
1100       throws IOException {
1101       ensureBlockReady();
1102       out.write(onDiskBlockBytesWithHeader);
1103       out.write(onDiskChecksum);
1104     }
1105 
1106     /**
1107      * Returns the header or the compressed data (or uncompressed data when not
1108      * using compression) as a byte array. Can be called in the "writing" state
1109      * or in the "block ready" state. If called in the "writing" state,
1110      * transitions the writer to the "block ready" state. This returns
1111      * the header + data + checksums stored on disk.
1112      *
1113      * @return header and data as they would be stored on disk in a byte array
1114      * @throws IOException
1115      */
1116     byte[] getHeaderAndDataForTest() throws IOException {
1117       ensureBlockReady();
1118       // This is not very optimal, because we are doing an extra copy.
1119       // But this method is used only by unit tests.
1120       byte[] output =
1121           new byte[onDiskBlockBytesWithHeader.length
1122               + onDiskChecksum.length];
1123       System.arraycopy(onDiskBlockBytesWithHeader, 0, output, 0,
1124           onDiskBlockBytesWithHeader.length);
1125       System.arraycopy(onDiskChecksum, 0, output,
1126           onDiskBlockBytesWithHeader.length, onDiskChecksum.length);
1127       return output;
1128     }
1129 
1130     /**
1131      * Releases resources used by this writer.
1132      */
1133     void release() {
1134       if (dataBlockEncodingCtx != null) {
1135         dataBlockEncodingCtx.close();
1136         dataBlockEncodingCtx = null;
1137       }
1138       if (defaultBlockEncodingCtx != null) {
1139         defaultBlockEncodingCtx.close();
1140         defaultBlockEncodingCtx = null;
1141       }
1142     }
1143 
1144     /**
1145      * Returns the on-disk size of the data portion of the block. This is the
1146      * compressed size if compression is enabled. Can only be called in the
1147      * "block ready" state. Header is not compressed, and its size is not
1148      * included in the return value.
1149      *
1150      * @return the on-disk size of the block, not including the header.
1151      */
1152     int getOnDiskSizeWithoutHeader() {
1153       expectState(State.BLOCK_READY);
1154       return onDiskBlockBytesWithHeader.length +
1155           onDiskChecksum.length - HConstants.HFILEBLOCK_HEADER_SIZE;
1156     }
1157 
1158     /**
1159      * Returns the on-disk size of the block. Can only be called in the
1160      * "block ready" state.
1161      *
1162      * @return the on-disk size of the block ready to be written, including the
1163      *         header size, the data and the checksum data.
1164      */
1165     int getOnDiskSizeWithHeader() {
1166       expectState(State.BLOCK_READY);
1167       return onDiskBlockBytesWithHeader.length + onDiskChecksum.length;
1168     }
1169 
1170     /**
1171      * The uncompressed size of the block data. Does not include header size.
1172      */
1173     int getUncompressedSizeWithoutHeader() {
1174       expectState(State.BLOCK_READY);
1175       return uncompressedBlockBytesWithHeader.length - HConstants.HFILEBLOCK_HEADER_SIZE;
1176     }
1177 
1178     /**
1179      * The uncompressed size of the block data, including header size.
1180      */
1181     int getUncompressedSizeWithHeader() {
1182       expectState(State.BLOCK_READY);
1183       return uncompressedBlockBytesWithHeader.length;
1184     }
1185 
1186     /** @return true if a block is being written  */
1187     boolean isWriting() {
1188       return state == State.WRITING;
1189     }
1190 
1191     /**
1192      * Returns the number of bytes written into the current block so far, or
1193      * zero if not writing the block at the moment. Note that this will return
1194      * zero in the "block ready" state as well.
1195      *
1196      * @return the number of bytes written
1197      */
1198     int blockSizeWritten() {
1199       if (state != State.WRITING) return 0;
1200       return this.unencodedDataSizeWritten;
1201     }
1202 
1203     /**
1204      * Returns the header followed by the uncompressed data, even if using
1205      * compression. This is needed for storing uncompressed blocks in the block
1206      * cache. Can be called in the "writing" state or the "block ready" state.
1207      * Returns only the header and data, does not include checksum data.
1208      *
1209      * @return uncompressed block bytes for caching on write
1210      */
1211     ByteBuffer getUncompressedBufferWithHeader() {
1212       expectState(State.BLOCK_READY);
1213       return ByteBuffer.wrap(uncompressedBlockBytesWithHeader);
1214     }
1215 
1216     /**
1217      * Returns the header followed by the on-disk (compressed/encoded/encrypted) data. This is
1218      * needed for storing packed blocks in the block cache. Expects calling semantics identical to
1219      * {@link #getUncompressedBufferWithHeader()}. Returns only the header and data,
1220      * Does not include checksum data.
1221      *
1222      * @return packed block bytes for caching on write
1223      */
1224     ByteBuffer getOnDiskBufferWithHeader() {
1225       expectState(State.BLOCK_READY);
1226       return ByteBuffer.wrap(onDiskBlockBytesWithHeader);
1227     }
1228 
1229     private void expectState(State expectedState) {
1230       if (state != expectedState) {
1231         throw new IllegalStateException("Expected state: " + expectedState +
1232             ", actual state: " + state);
1233       }
1234     }
1235 
1236     /**
1237      * Takes the given {@link BlockWritable} instance, creates a new block of
1238      * its appropriate type, writes the writable into this block, and flushes
1239      * the block into the output stream. The writer is instructed not to buffer
1240      * uncompressed bytes for cache-on-write.
1241      *
1242      * @param bw the block-writable object to write as a block
1243      * @param out the file system output stream
1244      * @throws IOException
1245      */
1246     void writeBlock(BlockWritable bw, FSDataOutputStream out)
1247         throws IOException {
1248       bw.writeToBlock(startWriting(bw.getBlockType()));
1249       writeHeaderAndData(out);
1250     }
1251 
1252     /**
1253      * Creates a new HFileBlock. Checksums have already been validated, so
1254      * the byte buffer passed into the constructor of this newly created
1255      * block does not have checksum data even though the header minor
1256      * version is MINOR_VERSION_WITH_CHECKSUM. This is indicated by setting a
1257      * 0 value in bytesPerChecksum.
1258      *
1259      * <p>TODO: Should there be an option where a cache can ask that hbase preserve block
1260      * checksums for checking after a block comes out of the cache? Otehrwise, cache is responsible
1261      * for blocks being wholesome (ECC memory or if file-backed, it does checksumming).
1262      */
1263     HFileBlock getBlockForCaching(CacheConfig cacheConf) {
1264       HFileContext newContext = new HFileContextBuilder()
1265                                 .withBlockSize(fileContext.getBlocksize())
1266                                 .withBytesPerCheckSum(0)
1267                                 .withChecksumType(ChecksumType.NULL) // no checksums in cached data
1268                                 .withCompression(fileContext.getCompression())
1269                                 .withDataBlockEncoding(fileContext.getDataBlockEncoding())
1270                                 .withHBaseCheckSum(fileContext.isUseHBaseChecksum())
1271                                 .withCompressTags(fileContext.isCompressTags())
1272                                 .withIncludesMvcc(fileContext.isIncludesMvcc())
1273                                 .withIncludesTags(fileContext.isIncludesTags())
1274                                 .build();
1275        return new HFileBlock(blockType, getOnDiskSizeWithoutHeader(),
1276           getUncompressedSizeWithoutHeader(), prevOffset,
1277           cacheConf.shouldCacheCompressed(blockType.getCategory())?
1278             getOnDiskBufferWithHeader() :
1279             getUncompressedBufferWithHeader(),
1280           FILL_HEADER, startOffset, UNSET,
1281           onDiskBlockBytesWithHeader.length + onDiskChecksum.length, newContext);
1282     }
1283   }
1284 
1285   /** Something that can be written into a block. */
1286   interface BlockWritable {
1287 
1288     /** The type of block this data should use. */
1289     BlockType getBlockType();
1290 
1291     /**
1292      * Writes the block to the provided stream. Must not write any magic
1293      * records.
1294      *
1295      * @param out a stream to write uncompressed data into
1296      */
1297     void writeToBlock(DataOutput out) throws IOException;
1298   }
1299 
1300   // Block readers and writers
1301 
1302   /** An interface allowing to iterate {@link HFileBlock}s. */
1303   interface BlockIterator {
1304 
1305     /**
1306      * Get the next block, or null if there are no more blocks to iterate.
1307      */
1308     HFileBlock nextBlock() throws IOException;
1309 
1310     /**
1311      * Similar to {@link #nextBlock()} but checks block type, throws an
1312      * exception if incorrect, and returns the HFile block
1313      */
1314     HFileBlock nextBlockWithBlockType(BlockType blockType) throws IOException;
1315   }
1316 
1317   /** A full-fledged reader with iteration ability. */
1318   interface FSReader {
1319 
1320     /**
1321      * Reads the block at the given offset in the file with the given on-disk
1322      * size and uncompressed size.
1323      *
1324      * @param offset
1325      * @param onDiskSize the on-disk size of the entire block, including all
1326      *          applicable headers, or -1 if unknown
1327      * @return the newly read block
1328      */
1329     HFileBlock readBlockData(long offset, long onDiskSize, boolean pread) throws IOException;
1330 
1331     /**
1332      * Creates a block iterator over the given portion of the {@link HFile}.
1333      * The iterator returns blocks starting with offset such that offset &lt;=
1334      * startOffset &lt; endOffset. Returned blocks are always unpacked.
1335      *
1336      * @param startOffset the offset of the block to start iteration with
1337      * @param endOffset the offset to end iteration at (exclusive)
1338      * @return an iterator of blocks between the two given offsets
1339      */
1340     BlockIterator blockRange(long startOffset, long endOffset);
1341 
1342     /** Closes the backing streams */
1343     void closeStreams() throws IOException;
1344 
1345     /** Get a decoder for {@link BlockType#ENCODED_DATA} blocks from this file. */
1346     HFileBlockDecodingContext getBlockDecodingContext();
1347 
1348     /** Get the default decoder for blocks from this file. */
1349     HFileBlockDecodingContext getDefaultBlockDecodingContext();
1350   }
1351 
1352   /**
1353    * A common implementation of some methods of {@link FSReader} and some
1354    * tools for implementing HFile format version-specific block readers.
1355    */
1356   private abstract static class AbstractFSReader implements FSReader {
1357     /** Compression algorithm used by the {@link HFile} */
1358 
1359     /** The size of the file we are reading from, or -1 if unknown. */
1360     protected long fileSize;
1361 
1362     /** The size of the header */
1363     protected final int hdrSize;
1364 
1365     /** The filesystem used to access data */
1366     protected HFileSystem hfs;
1367 
1368     private final Lock streamLock = new ReentrantLock();
1369 
1370     /** The default buffer size for our buffered streams */
1371     public static final int DEFAULT_BUFFER_SIZE = 1 << 20;
1372 
1373     protected HFileContext fileContext;
1374     // Cache the fileName
1375     protected String pathName;
1376 
1377     public AbstractFSReader(long fileSize, HFileSystem hfs, Path path, HFileContext fileContext)
1378         throws IOException {
1379       this.fileSize = fileSize;
1380       this.hfs = hfs;
1381       if (path != null) {
1382         this.pathName = path.toString();
1383       }
1384       this.fileContext = fileContext;
1385       this.hdrSize = headerSize(fileContext.isUseHBaseChecksum());
1386     }
1387 
1388     @Override
1389     public BlockIterator blockRange(final long startOffset,
1390         final long endOffset) {
1391       final FSReader owner = this; // handle for inner class
1392       return new BlockIterator() {
1393         private long offset = startOffset;
1394 
1395         @Override
1396         public HFileBlock nextBlock() throws IOException {
1397           if (offset >= endOffset)
1398             return null;
1399           HFileBlock b = readBlockData(offset, -1, false);
1400           offset += b.getOnDiskSizeWithHeader();
1401           return b.unpack(fileContext, owner);
1402         }
1403 
1404         @Override
1405         public HFileBlock nextBlockWithBlockType(BlockType blockType)
1406             throws IOException {
1407           HFileBlock blk = nextBlock();
1408           if (blk.getBlockType() != blockType) {
1409             throw new IOException("Expected block of type " + blockType
1410                 + " but found " + blk.getBlockType());
1411           }
1412           return blk;
1413         }
1414       };
1415     }
1416 
1417     /**
1418      * Does a positional read or a seek and read into the given buffer. Returns
1419      * the on-disk size of the next block, or -1 if it could not be read/determined; e.g. EOF.
1420      *
1421      * @param dest destination buffer
1422      * @param destOffset offset into the destination buffer at where to put the bytes we read
1423      * @param size size of read
1424      * @param peekIntoNextBlock whether to read the next block's on-disk size
1425      * @param fileOffset position in the stream to read at
1426      * @param pread whether we should do a positional read
1427      * @param istream The input source of data
1428      * @return the on-disk size of the next block with header size included, or
1429      *         -1 if it could not be determined; if not -1, the <code>dest</code> INCLUDES the
1430      *         next header
1431      * @throws IOException
1432      */
1433     protected int readAtOffset(FSDataInputStream istream, byte [] dest, int destOffset, int size,
1434         boolean peekIntoNextBlock, long fileOffset, boolean pread) throws IOException {
1435       if (peekIntoNextBlock && destOffset + size + hdrSize > dest.length) {
1436         // We are asked to read the next block's header as well, but there is
1437         // not enough room in the array.
1438         throw new IOException("Attempted to read " + size + " bytes and " +
1439             hdrSize + " bytes of next header into a " + dest.length +
1440             "-byte array at offset " + destOffset);
1441       }
1442 
1443       if (!pread && streamLock.tryLock()) {
1444         // Seek + read. Better for scanning.
1445         try {
1446           HFileUtil.seekOnMultipleSources(istream, fileOffset);
1447 
1448           long realOffset = istream.getPos();
1449           if (realOffset != fileOffset) {
1450             throw new IOException("Tried to seek to " + fileOffset + " to "
1451                 + "read " + size + " bytes, but pos=" + realOffset
1452                 + " after seek");
1453           }
1454 
1455           if (!peekIntoNextBlock) {
1456             IOUtils.readFully(istream, dest, destOffset, size);
1457             return -1;
1458           }
1459 
1460           // Try to read the next block header.
1461           if (!readWithExtra(istream, dest, destOffset, size, hdrSize)) {
1462             return -1;
1463           }
1464         } finally {
1465           streamLock.unlock();
1466         }
1467       } else {
1468         // Positional read. Better for random reads; or when the streamLock is already locked.
1469         int extraSize = peekIntoNextBlock ? hdrSize : 0;
1470         if (!positionalReadWithExtra(istream, fileOffset, dest, destOffset, size, extraSize)) {
1471           return -1;
1472         }
1473       }
1474 
1475       assert peekIntoNextBlock;
1476       return Bytes.toInt(dest, destOffset + size + BlockType.MAGIC_LENGTH) + hdrSize;
1477     }
1478 
1479   }
1480 
1481   /**
1482    * We always prefetch the header of the next block, so that we know its
1483    * on-disk size in advance and can read it in one operation.
1484    */
1485   private static class PrefetchedHeader {
1486     long offset = -1;
1487     byte[] header = new byte[HConstants.HFILEBLOCK_HEADER_SIZE];
1488     final ByteBuffer buf = ByteBuffer.wrap(header, 0, HConstants.HFILEBLOCK_HEADER_SIZE);
1489     @Override
1490     public String toString() {
1491       return "offset=" + this.offset + ", header=" + Bytes.toStringBinary(header);
1492     }
1493   }
1494 
1495   /**
1496    * Reads version 2 blocks from the filesystem.
1497    */
1498   static class FSReaderImpl extends AbstractFSReader {
1499     /** The file system stream of the underlying {@link HFile} that
1500      * does or doesn't do checksum validations in the filesystem */
1501     protected FSDataInputStreamWrapper streamWrapper;
1502 
1503     private HFileBlockDecodingContext encodedBlockDecodingCtx;
1504 
1505     /** Default context used when BlockType != {@link BlockType#ENCODED_DATA}. */
1506     private final HFileBlockDefaultDecodingContext defaultDecodingCtx;
1507 
1508     /**
1509      * When we read a block, we overread and pull in the next blocks header too. We will save it
1510      * here. If moving serially through the file, we will trip over this caching of the next blocks
1511      * header so we won't have to do explicit seek to find next blocks lengths, etc.
1512      */
1513     private ThreadLocal<PrefetchedHeader> prefetchedHeaderForThread =
1514         new ThreadLocal<PrefetchedHeader>() {
1515           @Override
1516           public PrefetchedHeader initialValue() {
1517             return new PrefetchedHeader();
1518           }
1519         };
1520 
1521     public FSReaderImpl(FSDataInputStreamWrapper stream, long fileSize, HFileSystem hfs, Path path,
1522         HFileContext fileContext) throws IOException {
1523       super(fileSize, hfs, path, fileContext);
1524       this.streamWrapper = stream;
1525       // Older versions of HBase didn't support checksum.
1526       this.streamWrapper.prepareForBlockReader(!fileContext.isUseHBaseChecksum());
1527       defaultDecodingCtx = new HFileBlockDefaultDecodingContext(fileContext);
1528       encodedBlockDecodingCtx = defaultDecodingCtx;
1529     }
1530 
1531     /**
1532      * A constructor that reads files with the latest minor version.
1533      * This is used by unit tests only.
1534      */
1535     FSReaderImpl(FSDataInputStream istream, long fileSize, HFileContext fileContext)
1536     throws IOException {
1537       this(new FSDataInputStreamWrapper(istream), fileSize, null, null, fileContext);
1538     }
1539 
1540     /**
1541      * Reads a version 2 block (version 1 blocks not supported and not expected). Tries to do as
1542      * little memory allocation as possible, using the provided on-disk size.
1543      *
1544      * @param offset the offset in the stream to read at
1545      * @param onDiskSizeWithHeaderL the on-disk size of the block, including
1546      *          the header, or -1 if unknown
1547      * @param pread whether to use a positional read
1548      */
1549     @Override
1550     public HFileBlock readBlockData(long offset, long onDiskSizeWithHeaderL, boolean pread)
1551     throws IOException {
1552       // Get a copy of the current state of whether to validate
1553       // hbase checksums or not for this read call. This is not
1554       // thread-safe but the one constaint is that if we decide
1555       // to skip hbase checksum verification then we are
1556       // guaranteed to use hdfs checksum verification.
1557       boolean doVerificationThruHBaseChecksum = streamWrapper.shouldUseHBaseChecksum();
1558       FSDataInputStream is = streamWrapper.getStream(doVerificationThruHBaseChecksum);
1559 
1560       HFileBlock blk = readBlockDataInternal(is, offset,
1561                          onDiskSizeWithHeaderL, pread,
1562                          doVerificationThruHBaseChecksum);
1563       if (blk == null) {
1564         HFile.LOG.warn("HBase checksum verification failed for file " +
1565                        pathName + " at offset " +
1566                        offset + " filesize " + fileSize +
1567                        ". Retrying read with HDFS checksums turned on...");
1568 
1569         if (!doVerificationThruHBaseChecksum) {
1570           String msg = "HBase checksum verification failed for file " +
1571                        pathName + " at offset " +
1572                        offset + " filesize " + fileSize +
1573                        " but this cannot happen because doVerify is " +
1574                        doVerificationThruHBaseChecksum;
1575           HFile.LOG.warn(msg);
1576           throw new IOException(msg); // cannot happen case here
1577         }
1578         HFile.CHECKSUM_FAILURES.increment(); // update metrics
1579 
1580         // If we have a checksum failure, we fall back into a mode where
1581         // the next few reads use HDFS level checksums. We aim to make the
1582         // next CHECKSUM_VERIFICATION_NUM_IO_THRESHOLD reads avoid
1583         // hbase checksum verification, but since this value is set without
1584         // holding any locks, it can so happen that we might actually do
1585         // a few more than precisely this number.
1586         is = this.streamWrapper.fallbackToFsChecksum(CHECKSUM_VERIFICATION_NUM_IO_THRESHOLD);
1587         doVerificationThruHBaseChecksum = false;
1588         blk = readBlockDataInternal(is, offset, onDiskSizeWithHeaderL, pread,
1589                                     doVerificationThruHBaseChecksum);
1590         if (blk != null) {
1591           HFile.LOG.warn("HDFS checksum verification succeeded for file " +
1592                          pathName + " at offset " +
1593                          offset + " filesize " + fileSize);
1594         }
1595       }
1596       if (blk == null && !doVerificationThruHBaseChecksum) {
1597         String msg = "readBlockData failed, possibly due to " +
1598                      "checksum verification failed for file " + pathName +
1599                      " at offset " + offset + " filesize " + fileSize;
1600         HFile.LOG.warn(msg);
1601         throw new IOException(msg);
1602       }
1603 
1604       // If there is a checksum mismatch earlier, then retry with
1605       // HBase checksums switched off and use HDFS checksum verification.
1606       // This triggers HDFS to detect and fix corrupt replicas. The
1607       // next checksumOffCount read requests will use HDFS checksums.
1608       // The decrementing of this.checksumOffCount is not thread-safe,
1609       // but it is harmless because eventually checksumOffCount will be
1610       // a negative number.
1611       streamWrapper.checksumOk();
1612       return blk;
1613     }
1614 
1615      /**
1616      * @return Check <code>onDiskSizeWithHeaderL</code> size is healthy and then return it as an int
1617      * @throws IOException
1618      */
1619     private static int checkAndGetSizeAsInt(final long onDiskSizeWithHeaderL, final int hdrSize)
1620     throws IOException {
1621       if ((onDiskSizeWithHeaderL < hdrSize && onDiskSizeWithHeaderL != -1)
1622           || onDiskSizeWithHeaderL >= Integer.MAX_VALUE) {
1623         throw new IOException("Invalid onDisksize=" + onDiskSizeWithHeaderL
1624             + ": expected to be at least " + hdrSize
1625             + " and at most " + Integer.MAX_VALUE + ", or -1");
1626       }
1627       return (int)onDiskSizeWithHeaderL;
1628     }
1629 
1630     /**
1631      * Check threadlocal cache for this block's header; we usually read it on the tail of reading
1632      * the previous block to save a seek. Otherwise, we have to do a seek to read the header before
1633      * we can pull in the block.
1634      * @return The cached block header or null if not found.
1635      * @see #cacheNextBlockHeader(long, byte[], int, int)
1636      */
1637     private ByteBuffer getCachedHeader(final long offset) {
1638       PrefetchedHeader prefetchedHeader = prefetchedHeaderForThread.get();
1639       // PrefetchedHeader prefetchedHeader = prefetchedHeaderForThread.get();
1640       return prefetchedHeader != null && prefetchedHeader.offset == offset?
1641           prefetchedHeader.buf: null;
1642     }
1643 
1644     /**
1645      * Save away the next blocks header in thread local.
1646      * @see #getCachedHeader(long)
1647      */
1648     private void cacheNextBlockHeader(final long nextBlockOffset,
1649         final byte [] header, final int headerOffset, final int headerLength) {
1650       PrefetchedHeader prefetchedHeader = prefetchedHeaderForThread.get();
1651       prefetchedHeader.offset = nextBlockOffset;
1652       System.arraycopy(header, headerOffset, prefetchedHeader.header, 0, headerLength);
1653     }
1654 
1655     /**
1656      * Verify the passed in onDiskSizeWithHeader aligns with what is in the header else something
1657      * is not right.
1658      * @throws IOException
1659      */
1660     private void verifyOnDiskSizeMatchesHeader(final int passedIn, final ByteBuffer headerBuf,
1661         final long offset, boolean verifyChecksum)
1662     throws IOException {
1663       // Assert size provided aligns with what is in the header
1664       int fromHeader = getOnDiskSizeWithHeader(headerBuf, verifyChecksum);
1665       if (passedIn != fromHeader) {
1666         throw new IOException("Passed in onDiskSizeWithHeader=" + passedIn + " != " + fromHeader +
1667             ", offset=" + offset + ", fileContext=" + this.fileContext);
1668       }
1669     }
1670 
1671     /**
1672      * Reads a version 2 block.
1673      *
1674      * @param offset the offset in the stream to read at
1675      * @param onDiskSizeWithHeaderL the on-disk size of the block, including
1676      *          the header and checksums if present or -1 if unknown
1677      * @param pread whether to use a positional read
1678      * @param verifyChecksum Whether to use HBase checksums.
1679      *        If HBase checksum is switched off, then use HDFS checksum.
1680      * @return the HFileBlock or null if there is a HBase checksum mismatch
1681      */
1682     protected HFileBlock readBlockDataInternal(FSDataInputStream is, long offset,
1683         long onDiskSizeWithHeaderL, boolean pread, boolean verifyChecksum) throws IOException {
1684       if (offset < 0) {
1685         throw new IOException("Invalid offset=" + offset + " trying to read "
1686             + "block (onDiskSize=" + onDiskSizeWithHeaderL + ")");
1687       }
1688       int onDiskSizeWithHeader = checkAndGetSizeAsInt(onDiskSizeWithHeaderL, hdrSize);
1689       ByteBuffer headerBuf = getCachedHeader(offset);
1690       if (LOG.isTraceEnabled()) {
1691         LOG.trace("Reading " + this.fileContext.getHFileName() + " at offset=" + offset +
1692           ", pread=" + pread + ", verifyChecksum=" + verifyChecksum + ", cachedHeader=" +
1693           headerBuf + ", onDiskSizeWithHeader=" + onDiskSizeWithHeader);
1694       }
1695       if (onDiskSizeWithHeader <= 0) {
1696         // We were not passed the block size. Need to get it from the header. If header was not in
1697         // cache, need to seek to pull it in. This latter might happen when we are doing the first
1698         // read in a series of reads or a random read, and we don't have access to the block index.
1699         // This is costly and should happen very rarely.
1700         if (headerBuf == null) {
1701           headerBuf = ByteBuffer.allocate(hdrSize);
1702           readAtOffset(is, headerBuf.array(), headerBuf.arrayOffset(), hdrSize, false,
1703               offset, pread);
1704         }
1705         onDiskSizeWithHeader = getOnDiskSizeWithHeader(headerBuf,
1706           this.fileContext.isUseHBaseChecksum());
1707       }
1708 
1709       int preReadHeaderSize = headerBuf == null? 0 : hdrSize;
1710       // Allocate enough space to fit the next block's header too; saves a seek next time through.
1711       // onDiskBlock is whole block + header + checksums then extra hdrSize to read next header;
1712       // onDiskSizeWithHeader is header, body, and any checksums if present.
1713       // TODO: Make this ByteBuffer-based. Will make it easier to go to HDFS with BBPool (offheap).
1714       byte[] onDiskBlock = new byte[onDiskSizeWithHeader + hdrSize];
1715       int nextBlockOnDiskSize = readAtOffset(is, onDiskBlock, preReadHeaderSize,
1716           onDiskSizeWithHeader - preReadHeaderSize, true, offset + preReadHeaderSize, pread);
1717       if (headerBuf != null) {
1718         // The header has been read when reading the previous block OR in a distinct header-only
1719         // read. Copy to this block's header.
1720         System.arraycopy(headerBuf.array(), headerBuf.arrayOffset(), onDiskBlock, 0, hdrSize);
1721       } else {
1722         headerBuf = ByteBuffer.wrap(onDiskBlock, 0, hdrSize);
1723       }
1724 
1725       // Do a few checks before we go instantiate HFileBlock.
1726       assert onDiskSizeWithHeader > this.hdrSize;
1727       verifyOnDiskSizeMatchesHeader(onDiskSizeWithHeader, headerBuf, offset,
1728         this.fileContext.isUseHBaseChecksum());
1729       ByteBuffer onDiskBlockByteBuffer = ByteBuffer.wrap(onDiskBlock, 0, onDiskSizeWithHeader);
1730       // Verify checksum of the data before using it for building HFileBlock.
1731       if (verifyChecksum &&
1732           !validateChecksum(offset, onDiskBlockByteBuffer, hdrSize)) {
1733         return null;
1734       }
1735       // The onDiskBlock will become the headerAndDataBuffer for this block.
1736       // If nextBlockOnDiskSizeWithHeader is not zero, the onDiskBlock already
1737       // contains the header of next block, so no need to set next block's header in it.
1738       HFileBlock hFileBlock =
1739           new HFileBlock(onDiskBlockByteBuffer, this.fileContext.isUseHBaseChecksum(), offset,
1740               nextBlockOnDiskSize, fileContext);
1741       // Run check on uncompressed sizings.
1742       if (!fileContext.isCompressedOrEncrypted()) {
1743         hFileBlock.sanityCheckUncompressed();
1744       }
1745       if (LOG.isTraceEnabled()) {
1746         LOG.trace("Read " + hFileBlock);
1747       }
1748       // Cache next block header if we read it for the next time through here.
1749       if (nextBlockOnDiskSize != -1) {
1750         cacheNextBlockHeader(offset + hFileBlock.getOnDiskSizeWithHeader(),
1751             onDiskBlock, onDiskSizeWithHeader, hdrSize);
1752       }
1753       return hFileBlock;
1754     }
1755 
1756     void setIncludesMemstoreTS(boolean includesMemstoreTS) {
1757       this.fileContext.setIncludesMvcc(includesMemstoreTS);
1758     }
1759 
1760     void setDataBlockEncoder(HFileDataBlockEncoder encoder) {
1761       encodedBlockDecodingCtx = encoder.newDataBlockDecodingContext(this.fileContext);
1762     }
1763 
1764     @Override
1765     public HFileBlockDecodingContext getBlockDecodingContext() {
1766       return this.encodedBlockDecodingCtx;
1767     }
1768 
1769     @Override
1770     public HFileBlockDecodingContext getDefaultBlockDecodingContext() {
1771       return this.defaultDecodingCtx;
1772     }
1773 
1774     /**
1775      * Generates the checksum for the header as well as the data and then validates it.
1776      * If the block doesn't uses checksum, returns false.
1777      * @return True if checksum matches, else false.
1778      */
1779     protected boolean validateChecksum(long offset, ByteBuffer data, int hdrSize)
1780         throws IOException {
1781       // If this is an older version of the block that does not have checksums, then return false
1782       // indicating that checksum verification did not succeed. Actually, this method should never
1783       // be called when the minorVersion is 0, thus this is a defensive check for a cannot-happen
1784       // case. Since this is a cannot-happen case, it is better to return false to indicate a
1785       // checksum validation failure.
1786       if (!fileContext.isUseHBaseChecksum()) {
1787         return false;
1788       }
1789       return ChecksumUtil.validateChecksum(data, pathName, offset, hdrSize);
1790     }
1791 
1792     @Override
1793     public void closeStreams() throws IOException {
1794       streamWrapper.close();
1795     }
1796 
1797     @Override
1798     public String toString() {
1799       return "hfs=" + hfs + ", path=" + pathName + ", fileContext=" + fileContext;
1800     }
1801   }
1802 
1803   /** An additional sanity-check in case no compression or encryption is being used. */
1804   void sanityCheckUncompressed() throws IOException {
1805     if (onDiskSizeWithoutHeader != uncompressedSizeWithoutHeader +
1806         totalChecksumBytes()) {
1807       throw new IOException("Using no compression but "
1808           + "onDiskSizeWithoutHeader=" + onDiskSizeWithoutHeader + ", "
1809           + "uncompressedSizeWithoutHeader=" + uncompressedSizeWithoutHeader
1810           + ", numChecksumbytes=" + totalChecksumBytes());
1811     }
1812   }
1813 
1814   // Cacheable implementation
1815   @Override
1816   public int getSerializedLength() {
1817     if (buf != null) {
1818       // Include extra bytes for block metadata.
1819       return this.buf.limit() + BLOCK_METADATA_SPACE;
1820     }
1821     return 0;
1822   }
1823 
1824   // Cacheable implementation
1825   @Override
1826   public void serialize(ByteBuffer destination) {
1827     // BE CAREFUL!! There is a custom version of this serialization over in BucketCache#doDrain.
1828     // Make sure any changes in here are reflected over there.
1829     ByteBufferUtils.copyFromBufferToBuffer(destination, this.buf, 0,
1830         getSerializedLength() - BLOCK_METADATA_SPACE);
1831     destination = addMetaData(destination);
1832 
1833     // Make it ready for reading. flip sets position to zero and limit to current position which
1834     // is what we want if we do not want to serialize the block plus checksums if present plus
1835     // metadata.
1836     destination.flip();
1837   }
1838 
1839   /**
1840    * For use by bucketcache. This exposes internals.
1841    */
1842   public ByteBuffer getMetaData() {
1843     ByteBuffer bb = ByteBuffer.allocate(BLOCK_METADATA_SPACE);
1844     bb = addMetaData(bb);
1845     bb.flip();
1846     return bb;
1847   }
1848 
1849   /**
1850    * Adds metadata at current position (position is moved forward). Does not flip or reset.
1851    * @return The passed <code>destination</code> with metadata added.
1852    */
1853   private ByteBuffer addMetaData(final ByteBuffer destination) {
1854     destination.put(this.fileContext.isUseHBaseChecksum() ? (byte) 1 : (byte) 0);
1855     destination.putLong(this.offset);
1856     destination.putInt(this.nextBlockOnDiskSize);
1857     return destination;
1858   }
1859 
1860   // Cacheable implementation
1861   @Override
1862   public CacheableDeserializer<Cacheable> getDeserializer() {
1863     return HFileBlock.BLOCK_DESERIALIZER;
1864   }
1865 
1866   @Override
1867   public boolean equals(Object comparison) {
1868     if (this == comparison) {
1869       return true;
1870     }
1871     if (comparison == null) {
1872       return false;
1873     }
1874     if (comparison.getClass() != this.getClass()) {
1875       return false;
1876     }
1877 
1878     HFileBlock castedComparison = (HFileBlock) comparison;
1879 
1880     if (castedComparison.blockType != this.blockType) {
1881       return false;
1882     }
1883     if (castedComparison.nextBlockOnDiskSize != this.nextBlockOnDiskSize) {
1884       return false;
1885     }
1886     // Offset is important. Needed when we have to remake cachekey when block is returned to cache.
1887     if (castedComparison.offset != this.offset) {
1888       return false;
1889     }
1890     if (castedComparison.onDiskSizeWithoutHeader != this.onDiskSizeWithoutHeader) {
1891       return false;
1892     }
1893     if (castedComparison.prevBlockOffset != this.prevBlockOffset) {
1894       return false;
1895     }
1896     if (castedComparison.uncompressedSizeWithoutHeader != this.uncompressedSizeWithoutHeader) {
1897       return false;
1898     }
1899     if (ByteBufferUtils.compareTo(this.buf, 0, this.buf.limit(), castedComparison.buf, 0,
1900         castedComparison.buf.limit()) != 0) {
1901       return false;
1902     }
1903     return true;
1904   }
1905 
1906   public DataBlockEncoding getDataBlockEncoding() {
1907     if (blockType == BlockType.ENCODED_DATA) {
1908       return DataBlockEncoding.getEncodingById(getDataBlockEncodingId());
1909     }
1910     return DataBlockEncoding.NONE;
1911   }
1912 
1913   byte getChecksumType() {
1914     return this.fileContext.getChecksumType().getCode();
1915   }
1916 
1917   int getBytesPerChecksum() {
1918     return this.fileContext.getBytesPerChecksum();
1919   }
1920 
1921   /** @return the size of data on disk + header. Excludes checksum. */
1922   int getOnDiskDataSizeWithHeader() {
1923     return this.onDiskDataSizeWithHeader;
1924   }
1925 
1926   /**
1927    * Calculate the number of bytes required to store all the checksums
1928    * for this block. Each checksum value is a 4 byte integer.
1929    */
1930   int totalChecksumBytes() {
1931     // If the hfile block has minorVersion 0, then there are no checksum
1932     // data to validate. Similarly, a zero value in this.bytesPerChecksum
1933     // indicates that cached blocks do not have checksum data because
1934     // checksums were already validated when the block was read from disk.
1935     if (!fileContext.isUseHBaseChecksum() || this.fileContext.getBytesPerChecksum() == 0) {
1936       return 0;
1937     }
1938     return (int) ChecksumUtil.numBytes(onDiskDataSizeWithHeader,
1939         this.fileContext.getBytesPerChecksum());
1940   }
1941 
1942   /**
1943    * Returns the size of this block header.
1944    */
1945   public int headerSize() {
1946     return headerSize(this.fileContext.isUseHBaseChecksum());
1947   }
1948 
1949   /**
1950    * Maps a minor version to the size of the header.
1951    */
1952   public static int headerSize(boolean usesHBaseChecksum) {
1953     return usesHBaseChecksum?
1954         HConstants.HFILEBLOCK_HEADER_SIZE: HConstants.HFILEBLOCK_HEADER_SIZE_NO_CHECKSUM;
1955   }
1956 
1957   /**
1958    * Return the appropriate DUMMY_HEADER for the minor version
1959    */
1960   byte[] getDummyHeaderForVersion() {
1961     return getDummyHeaderForVersion(this.fileContext.isUseHBaseChecksum());
1962   }
1963 
1964   /**
1965    * Return the appropriate DUMMY_HEADER for the minor version
1966    */
1967   static private byte[] getDummyHeaderForVersion(boolean usesHBaseChecksum) {
1968     return usesHBaseChecksum? HConstants.HFILEBLOCK_DUMMY_HEADER: DUMMY_HEADER_NO_CHECKSUM;
1969   }
1970 
1971   /**
1972    * @return This HFileBlocks fileContext which will a derivative of the
1973    * fileContext for the file from which this block's data was originally read.
1974    */
1975   HFileContext getHFileContext() {
1976     return this.fileContext;
1977   }
1978 
1979   /**
1980    * Convert the contents of the block header into a human readable string.
1981    * This is mostly helpful for debugging. This assumes that the block
1982    * has minor version > 0.
1983    */
1984   @VisibleForTesting
1985   static String toStringHeader(ByteBuffer buf) throws IOException {
1986     byte[] magicBuf = new byte[Math.min(buf.limit() - buf.position(), BlockType.MAGIC_LENGTH)];
1987     buf.get(magicBuf);
1988     BlockType bt = BlockType.parse(magicBuf, 0, BlockType.MAGIC_LENGTH);
1989     int compressedBlockSizeNoHeader = buf.getInt();
1990     int uncompressedBlockSizeNoHeader = buf.getInt();
1991     long prevBlockOffset = buf.getLong();
1992     byte cksumtype = buf.get();
1993     long bytesPerChecksum = buf.getInt();
1994     long onDiskDataSizeWithHeader = buf.getInt();
1995     return " Header dump: magic: " + Bytes.toString(magicBuf) +
1996                    " blockType " + bt +
1997                    " compressedBlockSizeNoHeader " +
1998                    compressedBlockSizeNoHeader +
1999                    " uncompressedBlockSizeNoHeader " +
2000                    uncompressedBlockSizeNoHeader +
2001                    " prevBlockOffset " + prevBlockOffset +
2002                    " checksumType " + ChecksumType.codeToType(cksumtype) +
2003                    " bytesPerChecksum " + bytesPerChecksum +
2004                    " onDiskDataSizeWithHeader " + onDiskDataSizeWithHeader;
2005   }
2006 }