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  }