github.com/containers/podman/v2@v2.2.2-0.20210501105131-c1e07d070c4c/pkg/spec/parse.go (about)

     1  package createconfig
     2  
     3  import (
     4  	"fmt"
     5  	"regexp"
     6  	"strconv"
     7  	"strings"
     8  
     9  	"github.com/docker/go-units"
    10  	"github.com/pkg/errors"
    11  )
    12  
    13  // deviceCgroupRulegex defines the valid format of device-cgroup-rule
    14  var deviceCgroupRuleRegex = regexp.MustCompile(`^([acb]) ([0-9]+|\*):([0-9]+|\*) ([rwm]{1,3})$`)
    15  
    16  // Pod signifies a kernel namespace is being shared
    17  // by a container with the pod it is associated with
    18  const Pod = "pod"
    19  
    20  // weightDevice is a structure that holds device:weight pair
    21  type weightDevice struct {
    22  	Path   string
    23  	Weight uint16
    24  }
    25  
    26  func (w *weightDevice) String() string {
    27  	return fmt.Sprintf("%s:%d", w.Path, w.Weight)
    28  }
    29  
    30  // LinuxNS is a struct that contains namespace information
    31  // It implemented Valid to show it is a valid namespace
    32  type LinuxNS interface {
    33  	Valid() bool
    34  }
    35  
    36  // IsNS returns if the specified string has a ns: prefix
    37  func IsNS(s string) bool {
    38  	parts := strings.SplitN(s, ":", 2)
    39  	return len(parts) > 1 && parts[0] == "ns"
    40  }
    41  
    42  // IsPod returns if the specified string is pod
    43  func IsPod(s string) bool {
    44  	return s == Pod
    45  }
    46  
    47  // Valid checks the validity of a linux namespace
    48  // s should be the string representation of ns
    49  func Valid(s string, ns LinuxNS) bool {
    50  	return IsPod(s) || IsNS(s) || ns.Valid()
    51  }
    52  
    53  // NS is the path to the namespace to join.
    54  func NS(s string) string {
    55  	parts := strings.SplitN(s, ":", 2)
    56  	if len(parts) > 1 {
    57  		return parts[1]
    58  	}
    59  	return ""
    60  }
    61  
    62  // ValidateweightDevice validates that the specified string has a valid device-weight format
    63  // for blkio-weight-device flag
    64  func ValidateweightDevice(val string) (*weightDevice, error) {
    65  	split := strings.SplitN(val, ":", 2)
    66  	if len(split) != 2 {
    67  		return nil, fmt.Errorf("bad format: %s", val)
    68  	}
    69  	if !strings.HasPrefix(split[0], "/dev/") {
    70  		return nil, fmt.Errorf("bad format for device path: %s", val)
    71  	}
    72  	weight, err := strconv.ParseUint(split[1], 10, 0)
    73  	if err != nil {
    74  		return nil, fmt.Errorf("invalid weight for device: %s", val)
    75  	}
    76  	if weight > 0 && (weight < 10 || weight > 1000) {
    77  		return nil, fmt.Errorf("invalid weight for device: %s", val)
    78  	}
    79  
    80  	return &weightDevice{
    81  		Path:   split[0],
    82  		Weight: uint16(weight),
    83  	}, nil
    84  }
    85  
    86  // throttleDevice is a structure that holds device:rate_per_second pair
    87  type throttleDevice struct {
    88  	path string
    89  	rate uint64
    90  }
    91  
    92  func (t *throttleDevice) String() string {
    93  	return fmt.Sprintf("%s:%d", t.path, t.rate)
    94  }
    95  
    96  // validateBpsDevice validates that the specified string has a valid device-rate format
    97  // for device-read-bps and device-write-bps flags
    98  func validateBpsDevice(val string) (*throttleDevice, error) {
    99  	split := strings.SplitN(val, ":", 2)
   100  	if len(split) != 2 {
   101  		return nil, fmt.Errorf("bad format: %s", val)
   102  	}
   103  	if !strings.HasPrefix(split[0], "/dev/") {
   104  		return nil, fmt.Errorf("bad format for device path: %s", val)
   105  	}
   106  	rate, err := units.RAMInBytes(split[1])
   107  	if err != nil {
   108  		return nil, fmt.Errorf("invalid rate for device: %s. The correct format is <device-path>:<number>[<unit>]. Number must be a positive integer. Unit is optional and can be kb, mb, or gb", val)
   109  	}
   110  	if rate < 0 {
   111  		return nil, fmt.Errorf("invalid rate for device: %s. The correct format is <device-path>:<number>[<unit>]. Number must be a positive integer. Unit is optional and can be kb, mb, or gb", val)
   112  	}
   113  
   114  	return &throttleDevice{
   115  		path: split[0],
   116  		rate: uint64(rate),
   117  	}, nil
   118  }
   119  
   120  // validateIOpsDevice validates that the specified string has a valid device-rate format
   121  // for device-write-iops and device-read-iops flags
   122  func validateIOpsDevice(val string) (*throttleDevice, error) { //nolint
   123  	split := strings.SplitN(val, ":", 2)
   124  	if len(split) != 2 {
   125  		return nil, fmt.Errorf("bad format: %s", val)
   126  	}
   127  	if !strings.HasPrefix(split[0], "/dev/") {
   128  		return nil, fmt.Errorf("bad format for device path: %s", val)
   129  	}
   130  	rate, err := strconv.ParseUint(split[1], 10, 64)
   131  	if err != nil {
   132  		return nil, fmt.Errorf("invalid rate for device: %s. The correct format is <device-path>:<number>. Number must be a positive integer", val)
   133  	}
   134  	return &throttleDevice{
   135  		path: split[0],
   136  		rate: rate,
   137  	}, nil
   138  }
   139  
   140  // getLoggingOpts splits the path= and tag= options provided to --log-opt.
   141  func getLoggingOpts(opts []string) (string, string) {
   142  	var path, tag string
   143  	for _, opt := range opts {
   144  		arr := strings.SplitN(opt, "=", 2)
   145  		if len(arr) == 2 {
   146  			if strings.TrimSpace(arr[0]) == "path" {
   147  				path = strings.TrimSpace(arr[1])
   148  			} else if strings.TrimSpace(arr[0]) == "tag" {
   149  				tag = strings.TrimSpace(arr[1])
   150  			}
   151  		}
   152  		if path != "" && tag != "" {
   153  			break
   154  		}
   155  	}
   156  	return path, tag
   157  }
   158  
   159  // ParseDevice parses device mapping string to a src, dest & permissions string
   160  func ParseDevice(device string) (string, string, string, error) { //nolint
   161  	src := ""
   162  	dst := ""
   163  	permissions := "rwm"
   164  	arr := strings.Split(device, ":")
   165  	switch len(arr) {
   166  	case 3:
   167  		if !IsValidDeviceMode(arr[2]) {
   168  			return "", "", "", fmt.Errorf("invalid device mode: %s", arr[2])
   169  		}
   170  		permissions = arr[2]
   171  		fallthrough
   172  	case 2:
   173  		if IsValidDeviceMode(arr[1]) {
   174  			permissions = arr[1]
   175  		} else {
   176  			if len(arr[1]) == 0 || arr[1][0] != '/' {
   177  				return "", "", "", fmt.Errorf("invalid device mode: %s", arr[1])
   178  			}
   179  			dst = arr[1]
   180  		}
   181  		fallthrough
   182  	case 1:
   183  		src = arr[0]
   184  	default:
   185  		return "", "", "", fmt.Errorf("invalid device specification: %s", device)
   186  	}
   187  
   188  	if dst == "" {
   189  		dst = src
   190  	}
   191  	return src, dst, permissions, nil
   192  }
   193  
   194  // IsValidDeviceMode checks if the mode for device is valid or not.
   195  // IsValid mode is a composition of r (read), w (write), and m (mknod).
   196  func IsValidDeviceMode(mode string) bool {
   197  	var legalDeviceMode = map[rune]bool{
   198  		'r': true,
   199  		'w': true,
   200  		'm': true,
   201  	}
   202  	if mode == "" {
   203  		return false
   204  	}
   205  	for _, c := range mode {
   206  		if !legalDeviceMode[c] {
   207  			return false
   208  		}
   209  		legalDeviceMode[c] = false
   210  	}
   211  	return true
   212  }
   213  
   214  // validateDeviceCgroupRule validates the format of deviceCgroupRule
   215  func validateDeviceCgroupRule(deviceCgroupRule string) error {
   216  	if !deviceCgroupRuleRegex.MatchString(deviceCgroupRule) {
   217  		return errors.Errorf("invalid device cgroup rule format: '%s'", deviceCgroupRule)
   218  	}
   219  	return nil
   220  }
   221  
   222  // parseDeviceCgroupRule matches and parses the deviceCgroupRule into slice
   223  func parseDeviceCgroupRule(deviceCgroupRule string) [][]string {
   224  	return deviceCgroupRuleRegex.FindAllStringSubmatch(deviceCgroupRule, -1)
   225  }