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