github.com/kchristidis/fabric@v1.0.4-0.20171028114726-837acd08cde1/common/ledger/blkstorage/fsblkstorage/block_stream.go (about) 1 /* 2 Copyright IBM Corp. 2016 All Rights Reserved. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package fsblkstorage 18 19 import ( 20 "bufio" 21 "errors" 22 "fmt" 23 "io" 24 "os" 25 26 "github.com/golang/protobuf/proto" 27 ) 28 29 // ErrUnexpectedEndOfBlockfile error used to indicate an unexpected end of a file segment 30 // this can happen mainly if a crash occurs during appening a block and partial block contents 31 // get written towards the end of the file 32 var ErrUnexpectedEndOfBlockfile = errors.New("unexpected end of blockfile") 33 34 // blockfileStream reads blocks sequentially from a single file. 35 // It starts from the given offset and can traverse till the end of the file 36 type blockfileStream struct { 37 fileNum int 38 file *os.File 39 reader *bufio.Reader 40 currentOffset int64 41 } 42 43 // blockStream reads blocks sequentially from multiple files. 44 // it starts from a given file offset and continues with the next 45 // file segment until the end of the last segment (`endFileNum`) 46 type blockStream struct { 47 rootDir string 48 currentFileNum int 49 endFileNum int 50 currentFileStream *blockfileStream 51 } 52 53 // blockPlacementInfo captures the information related 54 // to block's placement in the file. 55 type blockPlacementInfo struct { 56 fileNum int 57 blockStartOffset int64 58 blockBytesOffset int64 59 } 60 61 /////////////////////////////////// 62 // blockfileStream functions 63 //////////////////////////////////// 64 func newBlockfileStream(rootDir string, fileNum int, startOffset int64) (*blockfileStream, error) { 65 filePath := deriveBlockfilePath(rootDir, fileNum) 66 logger.Debugf("newBlockfileStream(): filePath=[%s], startOffset=[%d]", filePath, startOffset) 67 var file *os.File 68 var err error 69 if file, err = os.OpenFile(filePath, os.O_RDONLY, 0600); err != nil { 70 return nil, err 71 } 72 var newPosition int64 73 if newPosition, err = file.Seek(startOffset, 0); err != nil { 74 return nil, err 75 } 76 if newPosition != startOffset { 77 panic(fmt.Sprintf("Could not seek file [%s] to given startOffset [%d]. New position = [%d]", 78 filePath, startOffset, newPosition)) 79 } 80 s := &blockfileStream{fileNum, file, bufio.NewReader(file), startOffset} 81 return s, nil 82 } 83 84 func (s *blockfileStream) nextBlockBytes() ([]byte, error) { 85 blockBytes, _, err := s.nextBlockBytesAndPlacementInfo() 86 return blockBytes, err 87 } 88 89 // nextBlockBytesAndPlacementInfo returns bytes for the next block 90 // along with the offset information in the block file. 91 // An error `ErrUnexpectedEndOfBlockfile` is returned if a partial written data is detected 92 // which is possible towards the tail of the file if a crash had taken place during appending of a block 93 func (s *blockfileStream) nextBlockBytesAndPlacementInfo() ([]byte, *blockPlacementInfo, error) { 94 var lenBytes []byte 95 var err error 96 var fileInfo os.FileInfo 97 moreContentAvailable := true 98 99 if fileInfo, err = s.file.Stat(); err != nil { 100 return nil, nil, err 101 } 102 if s.currentOffset == fileInfo.Size() { 103 logger.Debugf("Finished reading file number [%d]", s.fileNum) 104 return nil, nil, nil 105 } 106 remainingBytes := fileInfo.Size() - s.currentOffset 107 // Peek 8 or smaller number of bytes (if remaining bytes are less than 8) 108 // Assumption is that a block size would be small enough to be represented in 8 bytes varint 109 peekBytes := 8 110 if remainingBytes < int64(peekBytes) { 111 peekBytes = int(remainingBytes) 112 moreContentAvailable = false 113 } 114 logger.Debugf("Remaining bytes=[%d], Going to peek [%d] bytes", remainingBytes, peekBytes) 115 if lenBytes, err = s.reader.Peek(peekBytes); err != nil { 116 return nil, nil, err 117 } 118 length, n := proto.DecodeVarint(lenBytes) 119 if n == 0 { 120 // proto.DecodeVarint did not consume any byte at all which means that the bytes 121 // representing the size of the block are partial bytes 122 if !moreContentAvailable { 123 return nil, nil, ErrUnexpectedEndOfBlockfile 124 } 125 panic(fmt.Errorf("Error in decoding varint bytes [%#v]", lenBytes)) 126 } 127 bytesExpected := int64(n) + int64(length) 128 if bytesExpected > remainingBytes { 129 logger.Debugf("At least [%d] bytes expected. Remaining bytes = [%d]. Returning with error [%s]", 130 bytesExpected, remainingBytes, ErrUnexpectedEndOfBlockfile) 131 return nil, nil, ErrUnexpectedEndOfBlockfile 132 } 133 // skip the bytes representing the block size 134 if _, err = s.reader.Discard(n); err != nil { 135 return nil, nil, err 136 } 137 blockBytes := make([]byte, length) 138 if _, err = io.ReadAtLeast(s.reader, blockBytes, int(length)); err != nil { 139 logger.Debugf("Error while trying to read [%d] bytes from fileNum [%d]: %s", length, s.fileNum, err) 140 return nil, nil, err 141 } 142 blockPlacementInfo := &blockPlacementInfo{ 143 fileNum: s.fileNum, 144 blockStartOffset: s.currentOffset, 145 blockBytesOffset: s.currentOffset + int64(n)} 146 s.currentOffset += int64(n) + int64(length) 147 logger.Debugf("Returning blockbytes - length=[%d], placementInfo={%s}", len(blockBytes), blockPlacementInfo) 148 return blockBytes, blockPlacementInfo, nil 149 } 150 151 func (s *blockfileStream) close() error { 152 return s.file.Close() 153 } 154 155 /////////////////////////////////// 156 // blockStream functions 157 //////////////////////////////////// 158 func newBlockStream(rootDir string, startFileNum int, startOffset int64, endFileNum int) (*blockStream, error) { 159 startFileStream, err := newBlockfileStream(rootDir, startFileNum, startOffset) 160 if err != nil { 161 return nil, err 162 } 163 return &blockStream{rootDir, startFileNum, endFileNum, startFileStream}, nil 164 } 165 166 func (s *blockStream) moveToNextBlockfileStream() error { 167 var err error 168 if err = s.currentFileStream.close(); err != nil { 169 return err 170 } 171 s.currentFileNum++ 172 if s.currentFileStream, err = newBlockfileStream(s.rootDir, s.currentFileNum, 0); err != nil { 173 return err 174 } 175 return nil 176 } 177 178 func (s *blockStream) nextBlockBytes() ([]byte, error) { 179 blockBytes, _, err := s.nextBlockBytesAndPlacementInfo() 180 return blockBytes, err 181 } 182 183 func (s *blockStream) nextBlockBytesAndPlacementInfo() ([]byte, *blockPlacementInfo, error) { 184 var blockBytes []byte 185 var blockPlacementInfo *blockPlacementInfo 186 var err error 187 if blockBytes, blockPlacementInfo, err = s.currentFileStream.nextBlockBytesAndPlacementInfo(); err != nil { 188 logger.Debugf("current file [%d] length of blockbytes [%d]. Err:%s", s.currentFileNum, len(blockBytes), err) 189 return nil, nil, err 190 } 191 logger.Debugf("blockbytes [%d] read from file [%d]", len(blockBytes), s.currentFileNum) 192 if blockBytes == nil && (s.currentFileNum < s.endFileNum || s.endFileNum < 0) { 193 logger.Debugf("current file [%d] exhausted. Moving to next file", s.currentFileNum) 194 if err = s.moveToNextBlockfileStream(); err != nil { 195 return nil, nil, err 196 } 197 return s.nextBlockBytesAndPlacementInfo() 198 } 199 return blockBytes, blockPlacementInfo, nil 200 } 201 202 func (s *blockStream) close() error { 203 return s.currentFileStream.close() 204 } 205 206 func (i *blockPlacementInfo) String() string { 207 return fmt.Sprintf("fileNum=[%d], startOffset=[%d], bytesOffset=[%d]", 208 i.fileNum, i.blockStartOffset, i.blockBytesOffset) 209 }