github.com/vmware/govmomi@v0.51.0/vmdk/descriptor.go (about)

     1  // © Broadcom. All Rights Reserved.
     2  // The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries.
     3  // SPDX-License-Identifier: Apache-2.0
     4  
     5  package vmdk
     6  
     7  import (
     8  	"bufio"
     9  	"bytes"
    10  	"fmt"
    11  	"io"
    12  	"slices"
    13  	"strconv"
    14  	"strings"
    15  	"text/template"
    16  
    17  	"github.com/vmware/govmomi/units"
    18  )
    19  
    20  type Descriptor struct {
    21  	Encoding  string            `json:"encoding"`
    22  	Version   int               `json:"version"`
    23  	CID       DiskContentID     `json:"cid"`
    24  	ParentCID DiskContentID     `json:"parentCID"`
    25  	Type      string            `json:"type"`
    26  	Extent    []Extent          `json:"extent"`
    27  	DDB       map[string]string `json:"ddb"`
    28  }
    29  
    30  type DiskContentID uint32
    31  
    32  func (cid DiskContentID) String() string {
    33  	return fmt.Sprintf("%0.8x", uint32(cid))
    34  }
    35  
    36  type Extent struct {
    37  	Type       string `json:"type"`
    38  	Permission string `json:"permission"`
    39  	Size       int64  `json:"size"`
    40  	Info       string `json:"info"`
    41  }
    42  
    43  func NewDescriptor(extent ...Extent) *Descriptor {
    44  	for i := range extent {
    45  		if extent[i].Type == "" {
    46  			extent[i].Type = "VMFS"
    47  		}
    48  		if extent[i].Permission == "" {
    49  			extent[i].Permission = "RW"
    50  		}
    51  	}
    52  	return &Descriptor{
    53  		Version:  1,
    54  		Encoding: "UTF-8",
    55  		Type:     "vmfs",
    56  		DDB:      map[string]string{},
    57  		Extent:   extent,
    58  	}
    59  }
    60  
    61  func ParseDescriptor(r io.Reader) (*Descriptor, error) {
    62  	d := NewDescriptor()
    63  
    64  	scanner := bufio.NewScanner(r)
    65  
    66  	// NOTE: not doing any validation currently, or using this function yet.
    67  	// Will add validation as needed when use-cases are implemented.
    68  	for scanner.Scan() {
    69  		token := bytes.Trim(scanner.Bytes(), "\x00")
    70  		line := strings.TrimSpace(string(token))
    71  		if line == "" || strings.HasPrefix(line, "#") {
    72  			continue
    73  		}
    74  		if d.parseExtent(line) {
    75  			continue
    76  		}
    77  
    78  		s := strings.SplitN(line, "=", 2)
    79  
    80  		if len(s) != 2 {
    81  			continue
    82  		}
    83  
    84  		key, val := strings.TrimSpace(s[0]), strings.TrimSpace(s[1])
    85  		val = strings.Trim(val, `"`)
    86  		if k := strings.TrimPrefix(key, "ddb."); k != key {
    87  			d.DDB[k] = val
    88  			continue
    89  		}
    90  
    91  		switch strings.ToLower(key) {
    92  		case "encoding":
    93  			d.Encoding = val
    94  		case "version":
    95  			d.Version, _ = strconv.Atoi(val)
    96  		case "cid":
    97  			_, _ = fmt.Sscanf(val, "%x", &d.CID)
    98  		case "parentcid":
    99  			_, _ = fmt.Sscanf(val, "%x", &d.ParentCID)
   100  		case "createType":
   101  			d.Type = val
   102  		}
   103  	}
   104  
   105  	return d, scanner.Err()
   106  }
   107  
   108  var permissions = []string{"RDONLY", "RW", "NOACCESS"}
   109  
   110  func (d *Descriptor) parseExtent(line string) bool {
   111  	// Each extent is defined by a line following this pattern:
   112  	// perm size type "%s"
   113  
   114  	s := strings.SplitN(line, " ", 2)
   115  
   116  	if len(s) != 2 || !slices.Contains(permissions, s[0]) {
   117  		return false
   118  	}
   119  
   120  	x := Extent{
   121  		Permission: s[0],
   122  	}
   123  
   124  	s = strings.SplitN(s[1], " ", 2)
   125  	size, err := strconv.ParseInt(s[0], 10, 64)
   126  	if len(s) != 2 || err != nil {
   127  		return false
   128  	}
   129  
   130  	x.Size = size
   131  
   132  	s = strings.SplitN(s[1], " ", 2)
   133  	x.Type = s[0]
   134  
   135  	if len(s) == 2 {
   136  		x.Info = strings.Trim(s[1], `"`)
   137  	}
   138  
   139  	d.Extent = append(d.Extent, x)
   140  
   141  	return true
   142  }
   143  
   144  var descriptor = `# Disk DescriptorFile
   145  version={{ .Version }}
   146  encoding="{{ .Encoding }}"
   147  CID={{ .CID }}
   148  parentCID={{ .ParentCID }}
   149  createType="{{ .Type }}"
   150  
   151  # Extent description ({{ cap }} capacity){{range .Extent }}
   152  {{ .Permission }} {{ .Size }} {{ .Type }} "{{ .Info }}"{{end}}
   153  
   154  # The Disk Data Base
   155  #DDB{{ range $key, $val := .DDB }}
   156  ddb.{{ $key }} = "{{ $val }}"{{end}}
   157  `
   158  
   159  func (d *Descriptor) Write(w io.Writer) error {
   160  	t, err := template.New("vmdk").Funcs(template.FuncMap{
   161  		"cap": func() string {
   162  			return units.ByteSize(d.Capacity()).String()
   163  		},
   164  	}).Parse(descriptor)
   165  	if err != nil {
   166  		return err
   167  	}
   168  	return t.Execute(w, d)
   169  }
   170  
   171  // Capacity in bytes of the vmdk
   172  func (d *Descriptor) Capacity() int64 {
   173  	var size int64
   174  
   175  	for i := range d.Extent {
   176  		size += d.Extent[i].Size * SectorSize
   177  	}
   178  
   179  	return size
   180  }