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 }