// Copyright © 2020 Intel Corporation // // SPDX-License-Identifier: GPL-3.0-only package storage import ( "bytes" "encoding/json" "io" "strconv" "github.com/clearlinux/clr-installer/errors" ) // Version used for reading and writing YAML type blockDeviceYAMLMarshal struct { Name string `yaml:"name,omitempty"` Path string `yaml:"path,omitempty"` Model string `yaml:"model,omitempty"` MajorMinor string `yaml:"majMin,omitempty"` FsType string `yaml:"fstype,omitempty"` UUID string `yaml:"uuid,omitempty"` Serial string `yaml:"serial,omitempty"` MountPoint string `yaml:"mountpoint,omitempty"` Label string `yaml:"label,omitempty"` Size string `yaml:"size,omitempty"` ReadOnly string `yaml:"ro,omitempty"` RemovableDevice string `yaml:"rm,omitempty"` Type string `yaml:"type,omitempty"` State string `yaml:"state,omitempty"` Children []*BlockDevice `yaml:"children,omitempty"` Options string `yaml:"options,omitempty"` } // UnmarshalJSON decodes a BlockDevice, targeted to integrate with json // decoding framework // nolint: gocyclo // TODO: Refactor this func (bd *BlockDevice) UnmarshalJSON(b []byte) error { dec := json.NewDecoder(bytes.NewReader(b)) for { t, err := dec.Token() if err == io.EOF { break } str, valid := t.(string) if !valid { continue } switch str { case "name": var name string if name, err = getNextStrToken(dec, "name"); err != nil { return err } bd.Name = name case "path": var path string if path, err = getNextStrToken(dec, "path"); err != nil { return err } bd.Path = path case "model": var model string if model, err = getNextStrToken(dec, "model"); err != nil { return err } bd.Model = model case "maj:min": var majMin string if majMin, err = getNextStrToken(dec, "maj:min"); err != nil { return err } bd.MajorMinor = majMin case "size": var size uint64 if size, err = getNextByteToken(dec, "size"); err != nil { return err } bd.Size = size case "pttype": var pttype string if pttype, err = getNextStrToken(dec, "pttype"); err != nil { return err } bd.PtType = pttype case "fstype": var fstype string if fstype, err = getNextStrToken(dec, "fstype"); err != nil { return err } bd.FsType = fstype case "uuid": var uuid string if uuid, err = getNextStrToken(dec, "uuid"); err != nil { return err } bd.UUID = uuid case "serial": var serial string if serial, err = getNextStrToken(dec, "serial"); err != nil { return err } bd.Serial = serial case "type": var tp string tp, err = getNextStrToken(dec, "type") if err != nil { return err } bd.Type, err = parseBlockDeviceType(tp) if err != nil { return err } case "state": var state string state, err = getNextStrToken(dec, "state") if err != nil { return err } bd.State, err = parseBlockDeviceState(state) if err != nil { return err } case "mountpoint": var mpoint string if mpoint, err = getNextStrToken(dec, "mountpoint"); err != nil { return err } bd.MountPoint = mpoint case "label": var label string if label, err = getNextStrToken(dec, "label"); err != nil { return err } bd.Label = label case "partlabel": var label string if label, err = getNextStrToken(dec, "partlabel"); err != nil { return err } bd.PartitionLabel = label case "ro": if bd.ReadOnly, err = getNextBoolToken(dec, "ro"); err != nil { return err } case "rm": if bd.RemovableDevice, err = getNextBoolToken(dec, "rm"); err != nil { return err } case "children": bd.Children = []*BlockDevice{} if err := dec.Decode(&bd.Children); err != nil { return errors.Errorf("Invalid \"children\" token: %s", err) } } } return nil } func getNextStrToken(dec *json.Decoder, name string) (string, error) { t, _ := dec.Token() if t == nil { return "", nil } str, valid := t.(string) if !valid { return "", errors.Errorf("\"%s\" token should have a string value", name) } return str, nil } func getNextByteToken(dec *json.Decoder, name string) (uint64, error) { var byteSize uint64 var err error dec.UseNumber() token, _ := dec.Token() if token == nil { return 0, nil } switch t := token.(type) { case json.Number: // Is it an unsigned int value (lsblk >= 2.33) var n int64 n, err = t.Int64() if err != nil { return 0, err } byteSize = uint64(n) case string: // Is it a string value (lsblk < 2.33) str, sValid := token.(string) if !sValid { return 0, errors.Errorf("\"%s\" token is neither an uint64 nor a string value", name) } byteSize, err = ParseVolumeSize(str) if err != nil { return 0, err } } return byteSize, nil } func getNextBoolToken(dec *json.Decoder, name string) (bool, error) { t, _ := dec.Token() if t == nil { return false, nil } // Is it a boolean value (lsblk >= 2.33) b, bValid := t.(bool) if bValid { return b, nil } // Is it a string value (lsblk < 2.33) str, sValid := t.(string) if !sValid { return false, errors.Errorf("\"%s\" token is neither a boolean nor a string value", name) } if str == "0" { return false, nil } else if str == "1" { return true, nil } else if str == "" { return false, nil } return false, errors.Errorf("Unknown ro value: %s", str) } // MarshalYAML is the yaml Marshaller implementation func (bd *BlockDevice) MarshalYAML() (interface{}, error) { var bdm blockDeviceYAMLMarshal bdm.Name = bd.Name bdm.Path = bd.Path bdm.Model = bd.Model bdm.MajorMinor = bd.MajorMinor bdm.FsType = bd.FsType bdm.UUID = bd.UUID bdm.Serial = bd.Serial bdm.MountPoint = bd.MountPoint bdm.Label = bd.Label bdm.Size = strconv.FormatUint(bd.Size, 10) bdm.ReadOnly = strconv.FormatBool(bd.ReadOnly) bdm.RemovableDevice = strconv.FormatBool(bd.RemovableDevice) bdm.Type = bd.Type.String() bdm.State = bd.State.String() bdm.Children = bd.Children bdm.Options = bd.Options return bdm, nil } // UnmarshalYAML is the yaml Unmarshaller implementation func (bd *BlockDevice) UnmarshalYAML(unmarshal func(interface{}) error) error { var unmarshBlockDevice blockDeviceYAMLMarshal if err := unmarshal(&unmarshBlockDevice); err != nil { return err } // Copy the unmarshaled data bd.Name = unmarshBlockDevice.Name bd.Path = unmarshBlockDevice.Path bd.Model = unmarshBlockDevice.Model bd.MajorMinor = unmarshBlockDevice.MajorMinor bd.FsType = unmarshBlockDevice.FsType bd.UUID = unmarshBlockDevice.UUID bd.Serial = unmarshBlockDevice.Serial bd.MountPoint = unmarshBlockDevice.MountPoint bd.Label = unmarshBlockDevice.Label bd.Children = unmarshBlockDevice.Children bd.Options = unmarshBlockDevice.Options // Convert String to Uint64 if unmarshBlockDevice.Size != "" { uSize, err := ParseVolumeSize(unmarshBlockDevice.Size) if err != nil { return err } bd.Size = uSize } // Map the BlockDeviceType if unmarshBlockDevice.Type != "" { iType, err := parseBlockDeviceType(unmarshBlockDevice.Type) if err != nil { return errors.Errorf("Device: %s: %v", unmarshBlockDevice.Name, err) } if iType < 0 || iType > BlockDeviceTypeUnknown { } bd.Type = iType if iType != BlockDeviceTypeDisk { bd.MakePartition = true bd.FormatPartition = true } } // Map the BlockDeviceState if unmarshBlockDevice.State != "" { iState, err := parseBlockDeviceState(unmarshBlockDevice.State) if err != nil { return errors.Errorf("Device: %s: %v", unmarshBlockDevice.Name, err) } bd.State = iState } // Map the ReanOnly bool if unmarshBlockDevice.ReadOnly != "" { bReadOnly, err := strconv.ParseBool(unmarshBlockDevice.ReadOnly) if err != nil { return err } bd.ReadOnly = bReadOnly } // Map the RemovableDevice bool if unmarshBlockDevice.RemovableDevice != "" { bRemovableDevice, err := strconv.ParseBool(unmarshBlockDevice.RemovableDevice) if err != nil { return err } bd.RemovableDevice = bRemovableDevice } return nil }