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