github.com/anonymouse64/snapd@v0.0.0-20210824153203-04c4c42d842d/gadget/quantity/size.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2020 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package quantity
    21  
    22  import (
    23  	"errors"
    24  	"fmt"
    25  	"math"
    26  
    27  	"github.com/snapcore/snapd/strutil"
    28  )
    29  
    30  // Size describes the size in bytes.
    31  type Size uint64
    32  
    33  const (
    34  	// SizeKiB is the byte size of one kibibyte (2^10 = 1024 bytes)
    35  	SizeKiB = Size(1 << 10)
    36  	// SizeMiB is the size of one mebibyte (2^20)
    37  	SizeMiB = Size(1 << 20)
    38  	// SizeGiB is the size of one gibibyte (2^30)
    39  	SizeGiB = Size(1 << 30)
    40  )
    41  
    42  func (s *Size) String() string {
    43  	if s == nil {
    44  		return "unspecified"
    45  	}
    46  	return fmt.Sprintf("%d", *s)
    47  }
    48  
    49  // iecSizeString formats the size using multiples from IEC units (i.e.
    50  // kibibytes, mebibytes), that is as multiples of 1024. Printed values are
    51  // truncated to 2 decimal points.
    52  func iecSizeString(sz int64) string {
    53  	maxFloat := float64(1023.5)
    54  	r := float64(sz)
    55  	unit := "B"
    56  	for _, rangeUnit := range []string{"KiB", "MiB", "GiB", "TiB", "PiB"} {
    57  		if r < maxFloat {
    58  			break
    59  		}
    60  		r /= 1024
    61  		unit = rangeUnit
    62  	}
    63  	precision := 0
    64  	if math.Floor(r) != r {
    65  		precision = 2
    66  	}
    67  	return fmt.Sprintf("%.*f %s", precision, r, unit)
    68  }
    69  
    70  // IECString formats the size using multiples from IEC units (i.e. kibibytes,
    71  // mebibytes), that is as multiples of 1024. Printed values are truncated to 2
    72  // decimal points.
    73  func (s *Size) IECString() string {
    74  	return iecSizeString(int64(*s))
    75  }
    76  
    77  func (s *Size) UnmarshalYAML(unmarshal func(interface{}) error) error {
    78  	var gs string
    79  	if err := unmarshal(&gs); err != nil {
    80  		return errors.New(`cannot unmarshal gadget size`)
    81  	}
    82  
    83  	var err error
    84  	*s, err = ParseSize(gs)
    85  	if err != nil {
    86  		return fmt.Errorf("cannot parse size %q: %v", gs, err)
    87  	}
    88  	return err
    89  }
    90  
    91  // parseSizeOrOffset parses a string expressing size or offset in a gadget
    92  // specific format.
    93  func parseSizeOrOffset(szOrOffs string) (int64, error) {
    94  	number, unit, err := strutil.SplitUnit(szOrOffs)
    95  	if err != nil {
    96  		return 0, err
    97  	}
    98  	switch unit {
    99  	case "M":
   100  		// MiB
   101  		number = number * int64(SizeMiB)
   102  	case "G":
   103  		// GiB
   104  		number = number * int64(SizeGiB)
   105  	case "":
   106  		// straight bytes
   107  
   108  	default:
   109  		return 0, fmt.Errorf("invalid suffix %q", unit)
   110  	}
   111  	return number, nil
   112  }
   113  
   114  // ParseSize parses a string expressing size in a gadget specific format. The
   115  // accepted format is one of: <bytes> | <bytes/2^20>M | <bytes/2^30>G.
   116  func ParseSize(gs string) (Size, error) {
   117  	sz, err := parseSizeOrOffset(gs)
   118  	if sz < 0 {
   119  		return 0, errors.New("size cannot be negative")
   120  	}
   121  	return Size(sz), err
   122  }