github.com/Microsoft/azure-vhd-utils@v0.0.0-20230613175315-7c30a3748a1b/vhdInspectCmdHandler.go (about) 1 package main 2 3 import ( 4 "bytes" 5 "encoding/hex" 6 "errors" 7 "fmt" 8 "os" 9 "strconv" 10 "text/template" 11 12 "github.com/Microsoft/azure-vhd-utils/vhdcore" 13 "github.com/Microsoft/azure-vhd-utils/vhdcore/block/bitmap" 14 "github.com/Microsoft/azure-vhd-utils/vhdcore/footer" 15 "github.com/Microsoft/azure-vhd-utils/vhdcore/vhdfile" 16 "gopkg.in/urfave/cli.v1" 17 ) 18 19 // FixedDiskBlocksInfo type describes general block information of a fixed disk 20 // 21 type FixedDiskBlocksInfo struct { 22 BlockSize int64 23 BlockCount int64 24 } 25 26 // ExpandableDiskBlocksInfo type describes general block information of a expandable disk 27 // 28 type ExpandableDiskBlocksInfo struct { 29 BlockDataSize int64 30 BlockBitmapSize int32 31 BlockBitmapPaddedSize int32 32 BlockCount int64 33 UsedBlockCount int64 34 EmptyBlockCount int64 35 } 36 37 func vhdInspectCmdHandler() cli.Command { 38 return cli.Command{ 39 Name: "inspect", 40 Usage: "Commands to inspect local VHD", 41 Subcommands: []cli.Command{ 42 { 43 Name: "header", 44 Usage: "Show VHD header", 45 Flags: []cli.Flag{ 46 cli.StringFlag{ 47 Name: "path", 48 Usage: "Path to VHD.", 49 }, 50 }, 51 Action: showVhdHeader, 52 }, 53 { 54 Name: "footer", 55 Usage: "Show VHD footer", 56 Flags: []cli.Flag{ 57 cli.StringFlag{ 58 Name: "path", 59 Usage: "Path to VHD.", 60 }, 61 }, 62 Action: showVhdFooter, 63 }, 64 { 65 Name: "bat", 66 Usage: "Show a range of VHD Block allocation table (BAT) entries", 67 Flags: []cli.Flag{ 68 cli.StringFlag{ 69 Name: "path", 70 Usage: "Path to VHD.", 71 }, 72 cli.StringFlag{ 73 Name: "start-range", 74 Usage: "Start range.", 75 }, 76 cli.StringFlag{ 77 Name: "end-range", 78 Usage: "End range.", 79 }, 80 cli.BoolFlag{ 81 Name: "skip-empty", 82 Usage: "Do not show BAT entries pointing to empty blocks.", 83 }, 84 }, 85 Action: showVhdBAT, 86 }, 87 { 88 Name: "block", 89 Usage: "Inspect VHD blocks", 90 Subcommands: []cli.Command{ 91 { 92 Name: "info", 93 Usage: "Show blocks general information", 94 Flags: []cli.Flag{ 95 cli.StringFlag{ 96 Name: "path", 97 Usage: "Path to VHD.", 98 }, 99 }, 100 Action: showVhdBlocksInfo, 101 }, 102 { 103 Name: "bitmap", 104 Usage: "Show sector bitmap of a expandable disk's block", 105 Flags: []cli.Flag{ 106 cli.StringFlag{ 107 Name: "path", 108 Usage: "Path to VHD.", 109 }, 110 cli.StringFlag{ 111 Name: "block-index", 112 Usage: "Index of the block.", 113 }, 114 }, 115 Action: showVhdBlockBitmap, 116 }, 117 }, 118 }, 119 }, 120 } 121 } 122 123 const headerTempl = `Cookie : {{.Cookie }} 124 DataOffset : {{.DataOffset}} 125 TableOffset : {{.TableOffset}} 126 HeaderVersion : {{.HeaderVersion}} 127 MaxTableEntries : {{.MaxTableEntries}} 128 BlockSize : {{.BlockSize}} bytes 129 CheckSum : {{.CheckSum}} 130 ParentUniqueID : {{.ParentUniqueID}} 131 ParentTimeStamp : {{.ParentTimeStamp | printf "%v"}} 132 Reserved : {{.Reserved}} 133 ParentPath : {{.ParentPath}} 134 {{range .ParentLocators}} 135 PlatformCode : {{.PlatformCode}} 136 PlatformDataSpace : {{.PlatformDataSpace}} 137 PlatformDataLength : {{.PlatformDataLength}} 138 Reserved : {{.Reserved}} 139 PlatformDataOffset : {{.PlatformDataOffset}} 140 PlatformSpecificFileLocator: {{.PlatformSpecificFileLocator}} 141 {{end}} 142 143 -- Hex dump -- 144 145 {{.RawData | dump }}` 146 147 func showVhdHeader(c *cli.Context) error { 148 vhdPath := c.String("path") 149 if vhdPath == "" { 150 return errors.New("Missing required argument --path") 151 } 152 153 vFileFactory := &vhdfile.FileFactory{} 154 vFile, err := vFileFactory.Create(vhdPath) 155 if err != nil { 156 return err 157 } 158 159 defer vFileFactory.Dispose(nil) 160 if vFile.GetDiskType() == footer.DiskTypeFixed { 161 return errors.New("Warn: Only expandable VHDs has header structure, this is a fixed VHD") 162 } 163 164 t, err := template.New("root"). 165 Funcs(template.FuncMap{"dump": hex.Dump}). 166 Parse(headerTempl) 167 t.Execute(os.Stdout, vFile.Header) 168 169 return nil 170 } 171 172 const footerTempl = `Cookie : {{.Cookie }} 173 Features : {{.Features}} 174 FileFormatVersion : {{.FileFormatVersion}} 175 HeaderOffset : {{.HeaderOffset}} 176 TimeStamp : {{.TimeStamp | printf "%v" }} 177 CreatorApplication: {{.CreatorApplication}} 178 CreatorVersion : {{.CreatorVersion}} 179 CreatorHostOsType : {{.CreatorHostOsType}} 180 PhysicalSize : {{.PhysicalSize}} bytes 181 VirtualSize : {{.VirtualSize}} bytes 182 DiskGeometry : {{.DiskGeometry}} 183 DiskType : {{.DiskType}} 184 CheckSum : {{.CheckSum}} 185 UniqueID : {{.UniqueID}} 186 SavedState : {{.SavedState | printf "%v" }} 187 188 -- Hex dump -- 189 190 {{.RawData | dump }}` 191 192 func showVhdFooter(c *cli.Context) error { 193 vhdPath := c.String("path") 194 if vhdPath == "" { 195 return errors.New("Missing required argument --path") 196 } 197 198 vFileFactory := &vhdfile.FileFactory{} 199 vFile, err := vFileFactory.Create(vhdPath) 200 if err != nil { 201 return err 202 } 203 204 defer vFileFactory.Dispose(nil) 205 t, err := template.New("root"). 206 Funcs(template.FuncMap{"dump": hex.Dump}). 207 Parse(footerTempl) 208 t.Execute(os.Stdout, vFile.Footer) 209 210 return nil 211 } 212 213 const batTempl = `{{range $index, $value := .}} BAT[{{adj $index}}] : {{$value | printf "0x%X"}} 214 {{end}}` 215 216 func showVhdBAT(c *cli.Context) error { 217 vhdPath := c.String("path") 218 if vhdPath == "" { 219 return errors.New("Missing required argument --path") 220 } 221 222 startRange := uint32(0) 223 var err error 224 if c.IsSet("start-range") { 225 r, err := strconv.ParseUint(c.String("start-range"), 10, 32) 226 if err != nil { 227 return fmt.Errorf("invalid index value --start-range: %s", err) 228 } 229 startRange = uint32(r) 230 } 231 232 endRange := uint32(0) 233 if c.IsSet("end-range") { 234 r, err := strconv.ParseUint(c.String("end-range"), 10, 32) 235 if err != nil { 236 return fmt.Errorf("invalid index value --end-range: %s", err) 237 } 238 endRange = uint32(r) 239 } 240 241 vFileFactory := &vhdfile.FileFactory{} 242 vFile, err := vFileFactory.Create(vhdPath) 243 if err != nil { 244 return err 245 } 246 247 defer vFileFactory.Dispose(nil) 248 if vFile.GetDiskType() == footer.DiskTypeFixed { 249 return errors.New("Warn: Only expandable VHDs has Block Allocation Table, this is a fixed VHD") 250 } 251 252 maxEntries := vFile.BlockAllocationTable.BATEntriesCount 253 if !c.IsSet("end-range") { 254 endRange = maxEntries - 1 255 } 256 257 if startRange > maxEntries || endRange > maxEntries { 258 return fmt.Errorf("index out of boundary, this vhd BAT index range is [0, %d]", maxEntries) 259 } 260 261 if startRange > endRange { 262 return errors.New("invalid range --start-range > --end-range") 263 } 264 265 fMap := template.FuncMap{ 266 "adj": func(i int) int { 267 return i + int(startRange) 268 }, 269 } 270 271 t, _ := template.New("root"). 272 Funcs(fMap). 273 Parse(batTempl) 274 275 if !c.IsSet("skip-empty") { 276 t.Execute(os.Stdout, vFile.BlockAllocationTable.BAT[startRange:endRange+1]) 277 } else { 278 nonEmptyBATEntries := make(map[int]uint32) 279 for blockIndex := startRange; blockIndex <= endRange; blockIndex++ { 280 if vFile.BlockAllocationTable.HasData(blockIndex) { 281 nonEmptyBATEntries[int(blockIndex-startRange)] = vFile.BlockAllocationTable.BAT[blockIndex] 282 } 283 } 284 285 t.Execute(os.Stdout, nonEmptyBATEntries) 286 } 287 288 return nil 289 } 290 291 const fixedDiskBlockInfoTempl = `Block sector size : 512 bytes 292 Block size : {{.BlockSize}} bytes 293 Total blocks : {{.BlockCount}} 294 ` 295 296 const expandableDiskBlockInfoTempl = `Block sector size : 512 bytes 297 Block data section size : {{.BlockDataSize}} bytes 298 Block bitmap section size : {{.BlockBitmapSize}} bytes 299 Block bitmap section size (padded) : {{.BlockBitmapPaddedSize}} bytes 300 Total blocks : {{.BlockCount}} (Used: {{.UsedBlockCount}} Empty: {{.EmptyBlockCount}}) 301 ` 302 303 func showVhdBlocksInfo(c *cli.Context) error { 304 vhdPath := c.String("path") 305 if vhdPath == "" { 306 return errors.New("Missing required argument --path") 307 } 308 309 vFileFactory := &vhdfile.FileFactory{} 310 vFile, err := vFileFactory.Create(vhdPath) 311 if err != nil { 312 panic(err) 313 } 314 defer vFileFactory.Dispose(nil) 315 316 vBlockFactory, err := vFile.GetBlockFactory() 317 if err != nil { 318 return err 319 } 320 321 if vFile.GetDiskType() == footer.DiskTypeFixed { 322 info := &FixedDiskBlocksInfo{ 323 BlockSize: vBlockFactory.GetBlockSize(), 324 BlockCount: vBlockFactory.GetBlockCount(), 325 } 326 // Note: Identifying empty and used blocks of a FixedDisk requires reading each 327 // block and checking it contains all zeros, which is time consuming so we don't 328 // show those information. 329 t, err := template.New("root"). 330 Parse(fixedDiskBlockInfoTempl) 331 if err != nil { 332 return err 333 } 334 t.Execute(os.Stdout, info) 335 } else { 336 info := &ExpandableDiskBlocksInfo{ 337 BlockDataSize: vBlockFactory.GetBlockSize(), 338 BlockBitmapSize: vFile.BlockAllocationTable.GetBitmapSizeInBytes(), 339 BlockBitmapPaddedSize: vFile.BlockAllocationTable.GetSectorPaddedBitmapSizeInBytes(), 340 BlockCount: vBlockFactory.GetBlockCount(), 341 } 342 343 for _, v := range vFile.BlockAllocationTable.BAT { 344 if v == vhdcore.VhdNoDataInt { 345 info.EmptyBlockCount++ 346 } else { 347 info.UsedBlockCount++ 348 } 349 } 350 351 t, err := template.New("root"). 352 Parse(expandableDiskBlockInfoTempl) 353 if err != nil { 354 return err 355 } 356 t.Execute(os.Stdout, info) 357 } 358 359 return nil 360 } 361 362 func showVhdBlockBitmap(c *cli.Context) error { 363 const bytesPerLine int32 = 8 364 const bitsPerLine int32 = 8 * bytesPerLine 365 366 vhdPath := c.String("path") 367 if vhdPath == "" { 368 return errors.New("missing required argument --path") 369 } 370 371 if !c.IsSet("block-index") { 372 return errors.New("missing required argument --block-index") 373 } 374 375 blockIndex := uint32(0) 376 id, err := strconv.ParseUint(c.String("block-index"), 10, 32) 377 if err != nil { 378 return fmt.Errorf("invalid index value --block-index: %s", err) 379 } 380 blockIndex = uint32(id) 381 382 vFileFactory := &vhdfile.FileFactory{} 383 vFile, err := vFileFactory.Create(vhdPath) 384 if err != nil { 385 return err 386 } 387 defer vFileFactory.Dispose(nil) 388 389 if vFile.GetDiskType() == footer.DiskTypeFixed { 390 return errors.New("warn: only expandable VHDs has bitmap associated with blocks, this is a fixed VHD") 391 } 392 393 vBlockFactory, err := vFile.GetBlockFactory() 394 if err != nil { 395 return err 396 } 397 398 if int64(blockIndex) > vBlockFactory.GetBlockCount()-1 { 399 return fmt.Errorf("warn: given block index %d is out of boundary, block index range is [0 : %d]", blockIndex, vBlockFactory.GetBlockCount()-1) 400 } 401 402 vBlock, err := vBlockFactory.Create(blockIndex) 403 if err != nil { 404 return err 405 } 406 407 if vBlock.IsEmpty { 408 fmt.Print("The block that this bitmap belongs to is marked as empty\n\n") 409 fmt.Print(createEmptyBitmapString(bytesPerLine, bitsPerLine, vFile.BlockAllocationTable.GetBitmapSizeInBytes())) 410 return nil 411 } 412 413 fmt.Print(createBitmapString(bitsPerLine, vBlock.BitMap)) 414 return nil 415 } 416 417 func createEmptyBitmapString(bytesPerLine, bitsPerLine, bitmapSizeInBytes int32) string { 418 var buffer bytes.Buffer 419 line := "" 420 for i := int32(0); i < bytesPerLine; i++ { 421 line = line + " " + "00000000" 422 } 423 424 count := bitmapSizeInBytes / bytesPerLine 425 pad := len(strconv.FormatInt(int64(bitmapSizeInBytes*8), 10)) 426 fmtLine := fmt.Sprintf("[%%-%dd - %%%dd]", pad, pad) 427 for i := int32(0); i < count; i++ { 428 buffer.WriteString(fmt.Sprintf(fmtLine, i*bitsPerLine, i*bitsPerLine+bitsPerLine-1)) 429 buffer.WriteString(line) 430 buffer.WriteString("\n") 431 } 432 433 remaining := bitmapSizeInBytes % bytesPerLine 434 if remaining != 0 { 435 buffer.WriteString(fmt.Sprintf(fmtLine, count*bitsPerLine, count*bitsPerLine+8*remaining-1)) 436 for i := int32(0); i < remaining; i++ { 437 buffer.WriteString(" 00000000") 438 } 439 } 440 441 buffer.WriteString("\n") 442 return buffer.String() 443 } 444 445 func createBitmapString(bitsPerLine int32, vBlockBitmap *bitmap.BitMap) string { 446 var buffer bytes.Buffer 447 pad := len(strconv.FormatInt(int64(vBlockBitmap.Length), 10)) 448 fmtLine := fmt.Sprintf("[%%-%dd - %%%dd]", pad, pad) 449 for i := int32(0); i < vBlockBitmap.Length; { 450 if i%bitsPerLine == 0 { 451 if i < vBlockBitmap.Length-bitsPerLine { 452 buffer.WriteString(fmt.Sprintf(fmtLine, i, i+bitsPerLine-1)) 453 } else { 454 buffer.WriteString(fmt.Sprintf(fmtLine, i, vBlockBitmap.Length-1)) 455 } 456 } 457 458 b := byte(0) 459 for j := uint32(0); j < 8; j++ { 460 if dirty, _ := vBlockBitmap.Get(i); dirty { 461 b |= byte(1 << (7 - j)) 462 } 463 i++ 464 } 465 buffer.WriteByte(' ') 466 buffer.WriteString(fmt.Sprintf("%08b", b)) 467 if i%bitsPerLine == 0 { 468 buffer.WriteString("\n") 469 } 470 } 471 buffer.WriteString("\n") 472 return buffer.String() 473 }