github.com/Microsoft/azure-vhd-utils@v0.0.0-20230613175315-7c30a3748a1b/vhdcore/header/factory.go (about) 1 package header 2 3 import ( 4 "fmt" 5 "strings" 6 "time" 7 8 "github.com/Microsoft/azure-vhd-utils/vhdcore" 9 "github.com/Microsoft/azure-vhd-utils/vhdcore/common" 10 "github.com/Microsoft/azure-vhd-utils/vhdcore/header/parentlocator" 11 "github.com/Microsoft/azure-vhd-utils/vhdcore/reader" 12 ) 13 14 // Factory type is used to create VhdHeader instance by reading vhd header section. 15 // 16 type Factory struct { 17 vhdReader *reader.VhdReader 18 headerOffset int64 19 } 20 21 // NewFactory creates a new instance of Factory, which can be used to create 22 // a VhdHeader instance by reading the header section using vhdReader. 23 // 24 func NewFactory(vhdReader *reader.VhdReader, headerOffset int64) *Factory { 25 return &Factory{vhdReader: vhdReader, headerOffset: headerOffset} 26 } 27 28 // Create creates a Header instance by reading the header section of a expandable disk. 29 // This function return error if any error occurs while reading or parsing the header fields. 30 // 31 func (f *Factory) Create() (*Header, error) { 32 header := &Header{} 33 var err error 34 errDone := func() (*Header, error) { 35 return nil, err 36 } 37 38 header.Cookie, err = f.readHeaderCookie() 39 if err != nil { 40 return errDone() 41 } 42 43 header.DataOffset, err = f.readDataOffset() 44 if err != nil { 45 return errDone() 46 } 47 48 header.TableOffset, err = f.readBATOffset() 49 if err != nil { 50 return errDone() 51 } 52 53 header.HeaderVersion, err = f.readHeaderVersion() 54 if err != nil { 55 return errDone() 56 } 57 58 header.MaxTableEntries, err = f.readMaxBATEntries() 59 if err != nil { 60 return errDone() 61 } 62 63 header.BlockSize, err = f.readBlockSize() 64 if err != nil { 65 return errDone() 66 } 67 68 header.CheckSum, err = f.readCheckSum() 69 if err != nil { 70 return errDone() 71 } 72 73 header.ParentUniqueID, err = f.readParentUniqueID() 74 if err != nil { 75 return errDone() 76 } 77 78 header.ParentTimeStamp, err = f.readParentTimeStamp() 79 if err != nil { 80 return errDone() 81 } 82 83 header.Reserved, err = f.readReserved() 84 if err != nil { 85 return errDone() 86 } 87 88 header.ParentPath, err = f.readParentPath() 89 if err != nil { 90 return errDone() 91 } 92 93 header.ParentLocators, err = f.readParentLocators() 94 if err != nil { 95 return errDone() 96 } 97 98 header.RawData, err = f.readWholeHeader() 99 if err != nil { 100 return errDone() 101 } 102 103 return header, nil 104 } 105 106 // readHeaderCookie reads the vhd cookie string and returns it as an instance of VhdCookie. 107 // This function return error if the cookie is invalid, if no or fewer bytes could be read. 108 // Cookie is stored as eight-character ASCII string starting at offset 0 relative to the beginning 109 // of header. 110 // 111 func (f *Factory) readHeaderCookie() (*vhdcore.Cookie, error) { 112 cookieData := make([]byte, 8) 113 if _, err := f.vhdReader.ReadBytes(f.headerOffset+0, cookieData); err != nil { 114 return nil, NewParseError("Cookie", err) 115 } 116 117 cookie := vhdcore.CreateNewVhdCookie(true, cookieData) 118 if !cookie.IsValid() { 119 return nil, NewParseError("Cookie", fmt.Errorf("Invalid header cookie data %v", cookieData)) 120 } 121 return cookie, nil 122 } 123 124 // readDataOffset reads and return the absolute offset to the next structure in the disk. This field 125 // is currently unused and holds the value 0xFFFFFFFF. This function return error if no or fewer 126 // bytes could be read. 127 // This value is stored as 8 bytes value starting at offset 8 relative to the beginning of header. 128 // This value is stored in big-endian format. 129 // 130 func (f *Factory) readDataOffset() (int64, error) { 131 value, err := f.vhdReader.ReadInt64(f.headerOffset + 8) 132 if err != nil { 133 return -1, NewParseError("DataOffset", err) 134 } 135 return value, nil 136 } 137 138 // readBATOffset reads and return the absolute offset to the the Block Allocation Table (BAT) in the 139 // disk. This function return error if no or fewer bytes could be read. 140 // BATOffset is stored as 8 bytes value starting at offset 16 relative to the beginning of header. 141 // This value is stored in big-endian format. 142 // 143 func (f *Factory) readBATOffset() (int64, error) { 144 value, err := f.vhdReader.ReadInt64(f.headerOffset + 16) 145 if err != nil { 146 return -1, NewParseError("BATOffset", err) 147 } 148 return value, nil 149 } 150 151 // readHeaderVersion reads the value of the field the holds the major/minor version of the disk header. 152 // This function return error if no or fewer bytes could be read. HeaderVersion is stored as 4 bytes 153 // value starting at offset 24 relative to the beginning of header. 154 // 155 func (f *Factory) readHeaderVersion() (VhdHeaderVersion, error) { 156 value, err := f.vhdReader.ReadUInt32(f.headerOffset + 24) 157 if err != nil { 158 return VhdHeaderVersionNone, NewParseError("HeaderVersion", err) 159 } 160 v := VhdHeaderVersion(value) 161 if !v.IsSupported() { 162 return VhdHeaderVersionNone, 163 NewParseError("HeaderVersion", fmt.Errorf("Invalid header version %v, unsupported format", v)) 164 } 165 return v, nil 166 } 167 168 // readMaxTableEntries reads and return maximum entries present in the BAT. This function return 169 // error if no or fewer bytes could be read. 170 // MaxTableEntries is stored as 4 bytes value starting at offset 28 relative to the beginning of 171 // header. This value is stored in big-endian format. 172 // 173 func (f *Factory) readMaxBATEntries() (uint32, error) { 174 value, err := f.vhdReader.ReadUInt32(f.headerOffset + 28) 175 if err != nil { 176 return 0, NewParseError("MaxBATEntries", err) 177 } 178 return value, nil 179 } 180 181 // readBlockSize reads size of the 'data section' of a block, this does not include size of 'block 182 // bitmap section'. This function return error if no or fewer bytes could be read. 183 // BlockSize is stored as 4 bytes value starting at offset 32 relative to the beginning of header. 184 // This value is stored in big-endian format. 185 // 186 func (f *Factory) readBlockSize() (uint32, error) { 187 value, err := f.vhdReader.ReadUInt32(f.headerOffset + 32) 188 if err != nil { 189 return 0, NewParseError("BlockSize", err) 190 } 191 return value, nil 192 } 193 194 // readCheckSum reads the field that stores basic checksum of the hard disk header. 195 // This function return error if no or fewer bytes could be read. 196 // The value is stored as 4 byte value starting at offset 36 relative to the beginning of header. 197 // This value is stored in big-endian format. 198 // 199 func (f *Factory) readCheckSum() (uint32, error) { 200 value, err := f.vhdReader.ReadUInt32(f.headerOffset + 36) 201 if err != nil { 202 return 0, NewParseError("CheckSum", err) 203 } 204 return value, nil 205 } 206 207 // readParentUniqueId reads the field that stores unique ID used to identify the parent disk. This 208 // field is used only for differencing disk. This is a 128-bit universally unique identifier (UUID). 209 // This function return error if no or fewer bytes could be read. 210 // The value is stored as 16 byte value starting at offset 40 relative to the beginning of header. 211 // 212 func (f *Factory) readParentUniqueID() (*common.UUID, error) { 213 value, err := f.vhdReader.ReadUUID(f.headerOffset + 40) 214 if err != nil { 215 return nil, NewParseError("ParentUniqueId", err) 216 } 217 return value, nil 218 } 219 220 // readTimeStamp reads the field storing modification time stamp of the parent hard disk which is 221 // stored as the number of seconds since January 1, 2000 12:00:00 AM in UTC/GMT and return it as 222 // instance of time.Time. This function return error if no or fewer bytes could be read. 223 // TimeStamp is stored as 4 bytes value starting at offset 56 relative to the beginning of header. 224 // This value is stored in big-endian format. 225 // 226 func (f *Factory) readParentTimeStamp() (*time.Time, error) { 227 value, err := f.vhdReader.ReadDateTime(f.headerOffset + 56) 228 if err != nil { 229 return nil, NewParseError("ParentTimeStamp", err) 230 } 231 return value, nil 232 } 233 234 // readReserved reads the reserved field which is not used and all set to zero. This function return 235 // error if no or fewer bytes could be read. Reserved is stored as 4 bytes value starting at offset 236 // 60 relative to the beginning of header. This value is stored in big-endian format. 237 // 238 func (f *Factory) readReserved() (uint32, error) { 239 value, err := f.vhdReader.ReadUInt32(f.headerOffset + 60) 240 if err != nil { 241 return 0, NewParseError("Reserved", err) 242 } 243 return value, nil 244 } 245 246 // readParentPath reads the field storing parent hard disk file name. This function return error if 247 // no or fewer bytes could be read. ParentPath is stored in UTF-16 as big-endian format, its length is 248 // 512 bytes, starting at offset 64 relative to the beginning of header. 249 // 250 func (f *Factory) readParentPath() (string, error) { 251 parentPath := make([]byte, 512) 252 _, err := f.vhdReader.ReadBytes(f.headerOffset+64, parentPath) 253 if err != nil { 254 return "", NewParseError("ParentPath", err) 255 } 256 return strings.TrimSuffix(common.Utf16BytesToStringBE(parentPath), "\x00"), nil 257 } 258 259 // readParentLocators reads the collection of parent locator entries. This function return error if 260 // no or fewer bytes could be read. There are 8 entries, each 24 bytes, starting at offset 576 relative 261 // to the beginning of header. 262 // 263 func (f *Factory) readParentLocators() (parentlocator.ParentLocators, error) { 264 var err error 265 count := 8 266 parentLocators := make(parentlocator.ParentLocators, count) 267 offset := f.headerOffset + 576 268 for i := 0; i < count; i++ { 269 parentLocFac := parentlocator.NewFactory(f.vhdReader, offset) 270 parentLocators[i], err = parentLocFac.Create() 271 if err != nil { 272 return nil, NewParseError("ParentLocator", err) 273 } 274 offset += 24 275 } 276 277 return parentLocators, nil 278 } 279 280 // readWholeHeader reads the entire header as a raw bytes. This function return error if the byte 281 // could be read. 282 // 283 func (f *Factory) readWholeHeader() ([]byte, error) { 284 rawData := make([]byte, 1024) 285 _, err := f.vhdReader.ReadBytes(f.headerOffset+0, rawData) 286 if err != nil { 287 return nil, err 288 } 289 return rawData, nil 290 }