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  }