github.com/Microsoft/azure-vhd-utils@v0.0.0-20230613175315-7c30a3748a1b/vhdcore/diskstream/diskstream.go (about) 1 package diskstream 2 3 import ( 4 "errors" 5 "io" 6 7 "github.com/Microsoft/azure-vhd-utils/vhdcore" 8 "github.com/Microsoft/azure-vhd-utils/vhdcore/block" 9 "github.com/Microsoft/azure-vhd-utils/vhdcore/common" 10 "github.com/Microsoft/azure-vhd-utils/vhdcore/footer" 11 "github.com/Microsoft/azure-vhd-utils/vhdcore/vhdfile" 12 ) 13 14 // DiskStream provides a logical stream over a VHD file. 15 // The type exposes the VHD as a fixed VHD, regardless of actual underlying VHD type (dynamic, differencing 16 // or fixed type) 17 // 18 type DiskStream struct { 19 offset int64 20 size int64 21 isClosed bool 22 vhdFactory *vhdfile.FileFactory 23 vhdFile *vhdfile.VhdFile 24 vhdBlockFactory block.Factory 25 vhdFooterRange *common.IndexRange 26 vhdDataRange *common.IndexRange 27 } 28 29 // StreamExtent describes a block range of a disk which contains data. 30 // 31 type StreamExtent struct { 32 Range *common.IndexRange 33 OwnerVhdUniqueID *common.UUID 34 } 35 36 // CreateNewDiskStream creates a new DiskStream. 37 // Parameter vhdPath is the path to VHD 38 // 39 func CreateNewDiskStream(vhdPath string) (*DiskStream, error) { 40 var err error 41 stream := &DiskStream{offset: 0, isClosed: false} 42 stream.vhdFactory = &vhdfile.FileFactory{} 43 if stream.vhdFile, err = stream.vhdFactory.Create(vhdPath); err != nil { 44 return nil, err 45 } 46 47 if stream.vhdBlockFactory, err = stream.vhdFile.GetBlockFactory(); err != nil { 48 return nil, err 49 } 50 51 stream.vhdFooterRange = stream.vhdBlockFactory.GetFooterRange() 52 stream.size = stream.vhdFooterRange.End + 1 53 stream.vhdDataRange = common.NewIndexRangeFromLength(0, stream.size-stream.vhdFooterRange.Length()) 54 return stream, nil 55 } 56 57 // GetDiskType returns the type of the disk, expected values are DiskTypeFixed, DiskTypeDynamic 58 // or DiskTypeDifferencing 59 // 60 func (s *DiskStream) GetDiskType() footer.DiskType { 61 return s.vhdFile.GetDiskType() 62 } 63 64 // GetSize returns the length of the stream in bytes. 65 // 66 func (s *DiskStream) GetSize() int64 { 67 return s.size 68 } 69 70 // Read reads up to len(b) bytes from the Vhd file. It returns the number of bytes read and an error, 71 // if any. EOF is signaled when no more data to read and n will set to 0. 72 // 73 // If the internal read offset is a byte offset in the data segment of the VHD and If reader reaches 74 // end of data section after reading some but not all the bytes then Read won't read from the footer 75 // section, the next Read will read from the footer. 76 // 77 // If the internal read offset is a byte offset in the footer segment of the VHD and if reader reaches 78 // end of footer section after reading some but not all the bytes then Read won't return any error. 79 // 80 // Read satisfies io.Reader interface 81 // 82 func (s *DiskStream) Read(p []byte) (n int, err error) { 83 if s.offset >= s.size { 84 return 0, io.EOF 85 } 86 87 count := len(p) 88 if count == 0 { 89 return 0, nil 90 } 91 92 rangeToRead := common.NewIndexRangeFromLength(s.offset, int64(count)) 93 if s.vhdDataRange.Intersects(rangeToRead) { 94 writtenCount, err := s.readFromBlocks(rangeToRead, p) 95 s.offset += int64(writtenCount) 96 return writtenCount, err 97 } 98 99 if s.vhdFooterRange.Intersects(rangeToRead) { 100 writtenCount, err := s.readFromFooter(rangeToRead, p) 101 s.offset += int64(writtenCount) 102 return writtenCount, err 103 } 104 105 return 0, nil 106 } 107 108 // Seek sets the offset for the next Read on the stream to offset, interpreted according to whence: 109 // 0 means relative to the origin of the stream, 1 means relative to the current offset, and 2 110 // means relative to the end. It returns the new offset and an error, if any. 111 // 112 // Seek satisfies io.Seeker interface 113 // 114 func (s *DiskStream) Seek(offset int64, whence int) (int64, error) { 115 switch whence { 116 default: 117 return 0, errors.New("Seek: invalid whence") 118 case 0: 119 offset += 0 120 case 1: 121 offset += s.offset 122 case 2: 123 offset += s.size - 1 124 } 125 126 if offset < 0 || offset >= s.size { 127 return 0, errors.New("Seek: invalid offset") 128 } 129 130 s.offset = offset 131 return offset, nil 132 } 133 134 // Close closes the VHD file, rendering it unusable for I/O. It returns an error, if any. 135 // 136 // Close satisfies io.Closer interface 137 // 138 func (s *DiskStream) Close() error { 139 if !s.isClosed { 140 s.vhdFactory.Dispose(nil) 141 s.isClosed = true 142 } 143 144 return nil 145 } 146 147 // GetExtents gets the extents of the stream that contain non-zero data. Each extent describes a block's data 148 // section range which contains data. 149 // For dynamic or differencing disk - a block is empty if the BAT corresponding to the block contains 0xFFFFFFFF 150 // so returned extents slice will not contain such range. 151 // For fixed disk - this method returns extents describing ranges of all blocks, to rule out fixed disk block 152 // ranges containing zero bytes use DetectEmptyRanges function in upload package. 153 // 154 func (s *DiskStream) GetExtents() ([]*StreamExtent, error) { 155 extents := make([]*StreamExtent, 1) 156 blocksCount := s.vhdBlockFactory.GetBlockCount() 157 for i := int64(0); i < blocksCount; i++ { 158 currentBlock, err := s.vhdBlockFactory.Create(uint32(i)) 159 if err != nil { 160 return nil, err 161 } 162 if !currentBlock.IsEmpty { 163 extents = append(extents, &StreamExtent{ 164 Range: currentBlock.LogicalRange, 165 OwnerVhdUniqueID: currentBlock.VhdUniqueID, 166 }) 167 } 168 } 169 extents = append(extents, &StreamExtent{ 170 Range: s.vhdFooterRange, 171 OwnerVhdUniqueID: s.vhdFile.Footer.UniqueID, 172 }) 173 174 return extents, nil 175 } 176 177 // EnumerateExtents iterate through the extents of the stream that contain non-zero data and invokes the function 178 // identified by the parameter f for each extent. Each extent describes a block's data section range which 179 // contains data. 180 // For dynamic or differencing disk - a block is empty if the BAT corresponding to the block contains 0xFFFFFFFF 181 // so returned extents slice will not contain such range. 182 // For fixed disk - this method returns extents describing ranges of all blocks, to rule out fixed disk block 183 // ranges containing zero bytes use DetectEmptyRanges function in upload package. 184 // 185 func (s *DiskStream) EnumerateExtents(f func(*StreamExtent, error) bool) { 186 blocksCount := s.vhdBlockFactory.GetBlockCount() 187 i := int64(0) 188 for ; i < blocksCount; i++ { 189 if currentBlock, err := s.vhdBlockFactory.Create(uint32(i)); err != nil { 190 continueEnumerate := f(nil, err) 191 if !continueEnumerate { 192 break 193 } 194 } else { 195 if !currentBlock.IsEmpty { 196 continueEnumerate := f(&StreamExtent{ 197 Range: currentBlock.LogicalRange, 198 OwnerVhdUniqueID: currentBlock.VhdUniqueID, 199 }, nil) 200 if !continueEnumerate { 201 break 202 } 203 } 204 } 205 } 206 if i == blocksCount { 207 f(&StreamExtent{ 208 Range: s.vhdFooterRange, 209 OwnerVhdUniqueID: s.vhdFile.Footer.UniqueID, 210 }, nil) 211 } 212 } 213 214 // readFromBlocks identifies the blocks constituting the range rangeToRead, and read data from these 215 // blocks into p. It returns the number of bytes read, which will be the minimum of sum of lengths 216 // of all constituting range and len(p), provided there is no error. 217 // 218 func (s *DiskStream) readFromBlocks(rangeToRead *common.IndexRange, p []byte) (n int, err error) { 219 rangeToReadFromBlocks := s.vhdDataRange.Intersection(rangeToRead) 220 if rangeToReadFromBlocks == nil { 221 return 0, nil 222 } 223 224 writtenCount := 0 225 maxCount := len(p) 226 blockSize := s.vhdBlockFactory.GetBlockSize() 227 startingBlock := s.byteToBlock(rangeToReadFromBlocks.Start) 228 endingBlock := s.byteToBlock(rangeToReadFromBlocks.End) 229 230 for blockIndex := startingBlock; blockIndex <= endingBlock && writtenCount < maxCount; blockIndex++ { 231 currentBlock, err := s.vhdBlockFactory.Create(uint32(blockIndex)) 232 if err != nil { 233 return writtenCount, err 234 } 235 236 blockData, err := currentBlock.Data() 237 if err != nil { 238 return writtenCount, err 239 } 240 241 rangeToReadInBlock := currentBlock.LogicalRange.Intersection(rangeToReadFromBlocks) 242 copyStartIndex := rangeToReadInBlock.Start % blockSize 243 writtenCount += copy(p[writtenCount:], blockData[copyStartIndex:(copyStartIndex+rangeToReadInBlock.Length())]) 244 } 245 246 return writtenCount, nil 247 } 248 249 // readFromFooter reads the range rangeToRead from footer into p. It returns the number of bytes read, which 250 // will be minimum of the given range length and len(p), provided there is no error. 251 // 252 func (s *DiskStream) readFromFooter(rangeToRead *common.IndexRange, p []byte) (n int, err error) { 253 rangeToReadFromFooter := s.vhdFooterRange.Intersection(rangeToRead) 254 if rangeToReadFromFooter == nil { 255 return 0, nil 256 } 257 258 vhdFooter := s.vhdFile.Footer.CreateCopy() 259 if vhdFooter.DiskType != footer.DiskTypeFixed { 260 vhdFooter.DiskType = footer.DiskTypeFixed 261 vhdFooter.HeaderOffset = vhdcore.VhdNoDataLong 262 vhdFooter.CreatorApplication = "wa" 263 } 264 // As per VHD spec, the size reported by the footer should same as 'header.MaxTableEntries * header.BlockSize' 265 // But the VHD created by some tool (e.g. qemu) are not honoring this. Azure will reject the VHD if the size 266 // specified in the footer of VHD not match 'VHD blob size - VHD Footer Size' 267 // 268 vhdFooter.PhysicalSize = s.GetSize() - vhdcore.VhdFooterSize 269 vhdFooter.VirtualSize = s.GetSize() - vhdcore.VhdFooterSize 270 271 // Calculate the checksum and serialize the footer 272 // 273 vhdFooterBytes := footer.SerializeFooter(vhdFooter) 274 copyStartIndex := rangeToReadFromFooter.Start - s.vhdFooterRange.Start 275 writtenCount := copy(p, vhdFooterBytes[copyStartIndex:copyStartIndex+rangeToReadFromFooter.Length()]) 276 return writtenCount, nil 277 } 278 279 // byteToBlock returns the block index corresponding to the given byte position. 280 // 281 func (s *DiskStream) byteToBlock(position int64) int64 { 282 sectorsPerBlock := s.vhdBlockFactory.GetBlockSize() / vhdcore.VhdSectorLength 283 return position / vhdcore.VhdSectorLength / sectorsPerBlock 284 }