github.com/Microsoft/azure-vhd-utils@v0.0.0-20230613175315-7c30a3748a1b/upload/detectEmptyRanges.go (about)

     1  package upload
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"math"
     7  
     8  	"github.com/Microsoft/azure-vhd-utils/vhdcore/block/bitmap"
     9  	"github.com/Microsoft/azure-vhd-utils/vhdcore/common"
    10  	"github.com/Microsoft/azure-vhd-utils/vhdcore/diskstream"
    11  	"github.com/Microsoft/azure-vhd-utils/vhdcore/footer"
    12  )
    13  
    14  // DataWithRange type describes a range and data associated with the range.
    15  //
    16  type DataWithRange struct {
    17  	Range *common.IndexRange
    18  	Data  []byte
    19  }
    20  
    21  // DetectEmptyRanges read the ranges identified by the parameter uploadableRanges from the disk stream, detect the empty
    22  // ranges and update the uploadableRanges slice by removing the empty ranges. This method returns the updated ranges.
    23  // The empty range detection required only for Fixed disk, if the stream is a expandable disk stream this method simply
    24  // returns the parameter uploadableRanges as it is.
    25  //
    26  func DetectEmptyRanges(diskStream *diskstream.DiskStream, uploadableRanges []*common.IndexRange) ([]*common.IndexRange, error) {
    27  	if diskStream.GetDiskType() != footer.DiskTypeFixed {
    28  		return uploadableRanges, nil
    29  	}
    30  
    31  	fmt.Println("\nDetecting empty ranges..")
    32  	totalRangesCount := len(uploadableRanges)
    33  	lastIndex := int32(-1)
    34  	emptyRangesCount := int32(0)
    35  	bits := make([]byte, int32(math.Ceil(float64(totalRangesCount)/float64(8))))
    36  	bmap := bitmap.NewBitMapFromByteSliceCopy(bits)
    37  	indexChan, errChan := LocateNonEmptyRangeIndices(diskStream, uploadableRanges)
    38  L:
    39  	for {
    40  		select {
    41  		case index, ok := <-indexChan:
    42  			if !ok {
    43  				break L
    44  			}
    45  			bmap.Set(index, true)
    46  			emptyRangesCount += index - lastIndex - 1
    47  			lastIndex = index
    48  			fmt.Printf("\r Empty ranges : %d/%d", emptyRangesCount, totalRangesCount)
    49  		case err := <-errChan:
    50  			return nil, err
    51  		}
    52  	}
    53  
    54  	// Remove empty ranges from the uploadable ranges slice.
    55  	i := int32(0)
    56  	for j := 0; j < totalRangesCount; j++ {
    57  		if set, _ := bmap.Get(int32(j)); set {
    58  			uploadableRanges[i] = uploadableRanges[j]
    59  			i++
    60  		}
    61  	}
    62  	uploadableRanges = uploadableRanges[:i]
    63  	return uploadableRanges, nil
    64  }
    65  
    66  // LocateNonEmptyRangeIndices scan the given range and detects  a subset of ranges which contains data.
    67  // It reports the indices of non-empty ranges via a channel. This method returns two channels, an int channel - used
    68  // to report the non-empty range indices and error channel - used to report any error while performing empty detection.
    69  // int channel will be closed on a successful completion, the caller must not expect any more value in the
    70  // int channel if the error channel is signaled.
    71  //
    72  func LocateNonEmptyRangeIndices(stream *diskstream.DiskStream, ranges []*common.IndexRange) (<-chan int32, <-chan error) {
    73  	indexChan := make(chan int32, 0)
    74  	errorChan := make(chan error, 0)
    75  	go func() {
    76  		count := int64(-1)
    77  		var buf []byte
    78  		for index, r := range ranges {
    79  			if count != r.Length() {
    80  				count = r.Length()
    81  				buf = make([]byte, count)
    82  			}
    83  
    84  			_, err := stream.Seek(r.Start, 0)
    85  			if err != nil {
    86  				errorChan <- err
    87  				return
    88  			}
    89  			_, err = io.ReadFull(stream, buf)
    90  			if err != nil {
    91  				errorChan <- err
    92  				return
    93  			}
    94  			if !isAllZero(buf) {
    95  				indexChan <- int32(index)
    96  			}
    97  		}
    98  		close(indexChan)
    99  	}()
   100  	return indexChan, errorChan
   101  }
   102  
   103  // isAllZero returns true if the given byte slice contain all zeros
   104  //
   105  func isAllZero(buf []byte) bool {
   106  	l := len(buf)
   107  	j := 0
   108  	for ; j < l; j++ {
   109  		if buf[j] != byte(0) {
   110  			break
   111  		}
   112  	}
   113  	return j == l
   114  }