github.1git.de/docker/cli@v26.1.3+incompatible/cli/command/container/opts.go (about)

     1  package container
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"os"
     8  	"path"
     9  	"path/filepath"
    10  	"reflect"
    11  	"regexp"
    12  	"strconv"
    13  	"strings"
    14  	"time"
    15  
    16  	"github.com/docker/cli/cli/command"
    17  	"github.com/docker/cli/cli/compose/loader"
    18  	"github.com/docker/cli/opts"
    19  	"github.com/docker/docker/api/types/container"
    20  	mounttypes "github.com/docker/docker/api/types/mount"
    21  	networktypes "github.com/docker/docker/api/types/network"
    22  	"github.com/docker/docker/api/types/strslice"
    23  	"github.com/docker/docker/errdefs"
    24  	"github.com/docker/go-connections/nat"
    25  	"github.com/pkg/errors"
    26  	"github.com/sirupsen/logrus"
    27  	"github.com/spf13/pflag"
    28  	cdi "tags.cncf.io/container-device-interface/pkg/parser"
    29  )
    30  
    31  const (
    32  	// TODO(thaJeztah): define these in the API-types, or query available defaults
    33  	//  from the daemon, or require "local" profiles to be an absolute path or
    34  	//  relative paths starting with "./". The daemon-config has consts for this
    35  	//  but we don't want to import that package:
    36  	//  https://github.com/moby/moby/blob/v23.0.0/daemon/config/config.go#L63-L67
    37  
    38  	// seccompProfileDefault is the built-in default seccomp profile.
    39  	seccompProfileDefault = "builtin"
    40  	// seccompProfileUnconfined is a special profile name for seccomp to use an
    41  	// "unconfined" seccomp profile.
    42  	seccompProfileUnconfined = "unconfined"
    43  )
    44  
    45  var deviceCgroupRuleRegexp = regexp.MustCompile(`^[acb] ([0-9]+|\*):([0-9]+|\*) [rwm]{1,3}$`)
    46  
    47  // containerOptions is a data object with all the options for creating a container
    48  type containerOptions struct {
    49  	attach              opts.ListOpts
    50  	volumes             opts.ListOpts
    51  	tmpfs               opts.ListOpts
    52  	mounts              opts.MountOpt
    53  	blkioWeightDevice   opts.WeightdeviceOpt
    54  	deviceReadBps       opts.ThrottledeviceOpt
    55  	deviceWriteBps      opts.ThrottledeviceOpt
    56  	links               opts.ListOpts
    57  	aliases             opts.ListOpts
    58  	linkLocalIPs        opts.ListOpts
    59  	deviceReadIOps      opts.ThrottledeviceOpt
    60  	deviceWriteIOps     opts.ThrottledeviceOpt
    61  	env                 opts.ListOpts
    62  	labels              opts.ListOpts
    63  	deviceCgroupRules   opts.ListOpts
    64  	devices             opts.ListOpts
    65  	gpus                opts.GpuOpts
    66  	ulimits             *opts.UlimitOpt
    67  	sysctls             *opts.MapOpts
    68  	publish             opts.ListOpts
    69  	expose              opts.ListOpts
    70  	dns                 opts.ListOpts
    71  	dnsSearch           opts.ListOpts
    72  	dnsOptions          opts.ListOpts
    73  	extraHosts          opts.ListOpts
    74  	volumesFrom         opts.ListOpts
    75  	envFile             opts.ListOpts
    76  	capAdd              opts.ListOpts
    77  	capDrop             opts.ListOpts
    78  	groupAdd            opts.ListOpts
    79  	securityOpt         opts.ListOpts
    80  	storageOpt          opts.ListOpts
    81  	labelsFile          opts.ListOpts
    82  	loggingOpts         opts.ListOpts
    83  	privileged          bool
    84  	pidMode             string
    85  	utsMode             string
    86  	usernsMode          string
    87  	cgroupnsMode        string
    88  	publishAll          bool
    89  	stdin               bool
    90  	tty                 bool
    91  	oomKillDisable      bool
    92  	oomScoreAdj         int
    93  	containerIDFile     string
    94  	entrypoint          string
    95  	hostname            string
    96  	domainname          string
    97  	memory              opts.MemBytes
    98  	memoryReservation   opts.MemBytes
    99  	memorySwap          opts.MemSwapBytes
   100  	kernelMemory        opts.MemBytes
   101  	user                string
   102  	workingDir          string
   103  	cpuCount            int64
   104  	cpuShares           int64
   105  	cpuPercent          int64
   106  	cpuPeriod           int64
   107  	cpuRealtimePeriod   int64
   108  	cpuRealtimeRuntime  int64
   109  	cpuQuota            int64
   110  	cpus                opts.NanoCPUs
   111  	cpusetCpus          string
   112  	cpusetMems          string
   113  	blkioWeight         uint16
   114  	ioMaxBandwidth      opts.MemBytes
   115  	ioMaxIOps           uint64
   116  	swappiness          int64
   117  	netMode             opts.NetworkOpt
   118  	macAddress          string
   119  	ipv4Address         string
   120  	ipv6Address         string
   121  	ipcMode             string
   122  	pidsLimit           int64
   123  	restartPolicy       string
   124  	readonlyRootfs      bool
   125  	loggingDriver       string
   126  	cgroupParent        string
   127  	volumeDriver        string
   128  	stopSignal          string
   129  	stopTimeout         int
   130  	isolation           string
   131  	shmSize             opts.MemBytes
   132  	noHealthcheck       bool
   133  	healthCmd           string
   134  	healthInterval      time.Duration
   135  	healthTimeout       time.Duration
   136  	healthStartPeriod   time.Duration
   137  	healthStartInterval time.Duration
   138  	healthRetries       int
   139  	runtime             string
   140  	autoRemove          bool
   141  	init                bool
   142  	annotations         *opts.MapOpts
   143  
   144  	Image string
   145  	Args  []string
   146  }
   147  
   148  // addFlags adds all command line flags that will be used by parse to the FlagSet
   149  func addFlags(flags *pflag.FlagSet) *containerOptions {
   150  	copts := &containerOptions{
   151  		aliases:           opts.NewListOpts(nil),
   152  		attach:            opts.NewListOpts(validateAttach),
   153  		blkioWeightDevice: opts.NewWeightdeviceOpt(opts.ValidateWeightDevice),
   154  		capAdd:            opts.NewListOpts(nil),
   155  		capDrop:           opts.NewListOpts(nil),
   156  		dns:               opts.NewListOpts(opts.ValidateIPAddress),
   157  		dnsOptions:        opts.NewListOpts(nil),
   158  		dnsSearch:         opts.NewListOpts(opts.ValidateDNSSearch),
   159  		deviceCgroupRules: opts.NewListOpts(validateDeviceCgroupRule),
   160  		deviceReadBps:     opts.NewThrottledeviceOpt(opts.ValidateThrottleBpsDevice),
   161  		deviceReadIOps:    opts.NewThrottledeviceOpt(opts.ValidateThrottleIOpsDevice),
   162  		deviceWriteBps:    opts.NewThrottledeviceOpt(opts.ValidateThrottleBpsDevice),
   163  		deviceWriteIOps:   opts.NewThrottledeviceOpt(opts.ValidateThrottleIOpsDevice),
   164  		devices:           opts.NewListOpts(nil), // devices can only be validated after we know the server OS
   165  		env:               opts.NewListOpts(opts.ValidateEnv),
   166  		envFile:           opts.NewListOpts(nil),
   167  		expose:            opts.NewListOpts(nil),
   168  		extraHosts:        opts.NewListOpts(opts.ValidateExtraHost),
   169  		groupAdd:          opts.NewListOpts(nil),
   170  		labels:            opts.NewListOpts(opts.ValidateLabel),
   171  		labelsFile:        opts.NewListOpts(nil),
   172  		linkLocalIPs:      opts.NewListOpts(nil),
   173  		links:             opts.NewListOpts(opts.ValidateLink),
   174  		loggingOpts:       opts.NewListOpts(nil),
   175  		publish:           opts.NewListOpts(nil),
   176  		securityOpt:       opts.NewListOpts(nil),
   177  		storageOpt:        opts.NewListOpts(nil),
   178  		sysctls:           opts.NewMapOpts(nil, opts.ValidateSysctl),
   179  		tmpfs:             opts.NewListOpts(nil),
   180  		ulimits:           opts.NewUlimitOpt(nil),
   181  		volumes:           opts.NewListOpts(nil),
   182  		volumesFrom:       opts.NewListOpts(nil),
   183  		annotations:       opts.NewMapOpts(nil, nil),
   184  	}
   185  
   186  	// General purpose flags
   187  	flags.VarP(&copts.attach, "attach", "a", "Attach to STDIN, STDOUT or STDERR")
   188  	flags.Var(&copts.deviceCgroupRules, "device-cgroup-rule", "Add a rule to the cgroup allowed devices list")
   189  	flags.Var(&copts.devices, "device", "Add a host device to the container")
   190  	flags.Var(&copts.gpus, "gpus", "GPU devices to add to the container ('all' to pass all GPUs)")
   191  	flags.SetAnnotation("gpus", "version", []string{"1.40"})
   192  	flags.VarP(&copts.env, "env", "e", "Set environment variables")
   193  	flags.Var(&copts.envFile, "env-file", "Read in a file of environment variables")
   194  	flags.StringVar(&copts.entrypoint, "entrypoint", "", "Overwrite the default ENTRYPOINT of the image")
   195  	flags.Var(&copts.groupAdd, "group-add", "Add additional groups to join")
   196  	flags.StringVarP(&copts.hostname, "hostname", "h", "", "Container host name")
   197  	flags.StringVar(&copts.domainname, "domainname", "", "Container NIS domain name")
   198  	flags.BoolVarP(&copts.stdin, "interactive", "i", false, "Keep STDIN open even if not attached")
   199  	flags.VarP(&copts.labels, "label", "l", "Set meta data on a container")
   200  	flags.Var(&copts.labelsFile, "label-file", "Read in a line delimited file of labels")
   201  	flags.BoolVar(&copts.readonlyRootfs, "read-only", false, "Mount the container's root filesystem as read only")
   202  	flags.StringVar(&copts.restartPolicy, "restart", string(container.RestartPolicyDisabled), "Restart policy to apply when a container exits")
   203  	flags.StringVar(&copts.stopSignal, "stop-signal", "", "Signal to stop the container")
   204  	flags.IntVar(&copts.stopTimeout, "stop-timeout", 0, "Timeout (in seconds) to stop a container")
   205  	flags.SetAnnotation("stop-timeout", "version", []string{"1.25"})
   206  	flags.Var(copts.sysctls, "sysctl", "Sysctl options")
   207  	flags.BoolVarP(&copts.tty, "tty", "t", false, "Allocate a pseudo-TTY")
   208  	flags.Var(copts.ulimits, "ulimit", "Ulimit options")
   209  	flags.StringVarP(&copts.user, "user", "u", "", "Username or UID (format: <name|uid>[:<group|gid>])")
   210  	flags.StringVarP(&copts.workingDir, "workdir", "w", "", "Working directory inside the container")
   211  	flags.BoolVar(&copts.autoRemove, "rm", false, "Automatically remove the container when it exits")
   212  
   213  	// Security
   214  	flags.Var(&copts.capAdd, "cap-add", "Add Linux capabilities")
   215  	flags.Var(&copts.capDrop, "cap-drop", "Drop Linux capabilities")
   216  	flags.BoolVar(&copts.privileged, "privileged", false, "Give extended privileges to this container")
   217  	flags.Var(&copts.securityOpt, "security-opt", "Security Options")
   218  	flags.StringVar(&copts.usernsMode, "userns", "", "User namespace to use")
   219  	flags.StringVar(&copts.cgroupnsMode, "cgroupns", "", `Cgroup namespace to use (host|private)
   220  'host':    Run the container in the Docker host's cgroup namespace
   221  'private': Run the container in its own private cgroup namespace
   222  '':        Use the cgroup namespace as configured by the
   223             default-cgroupns-mode option on the daemon (default)`)
   224  	flags.SetAnnotation("cgroupns", "version", []string{"1.41"})
   225  
   226  	// Network and port publishing flag
   227  	flags.Var(&copts.extraHosts, "add-host", "Add a custom host-to-IP mapping (host:ip)")
   228  	flags.Var(&copts.dns, "dns", "Set custom DNS servers")
   229  	// We allow for both "--dns-opt" and "--dns-option", although the latter is the recommended way.
   230  	// This is to be consistent with service create/update
   231  	flags.Var(&copts.dnsOptions, "dns-opt", "Set DNS options")
   232  	flags.Var(&copts.dnsOptions, "dns-option", "Set DNS options")
   233  	flags.MarkHidden("dns-opt")
   234  	flags.Var(&copts.dnsSearch, "dns-search", "Set custom DNS search domains")
   235  	flags.Var(&copts.expose, "expose", "Expose a port or a range of ports")
   236  	flags.StringVar(&copts.ipv4Address, "ip", "", "IPv4 address (e.g., 172.30.100.104)")
   237  	flags.StringVar(&copts.ipv6Address, "ip6", "", "IPv6 address (e.g., 2001:db8::33)")
   238  	flags.Var(&copts.links, "link", "Add link to another container")
   239  	flags.Var(&copts.linkLocalIPs, "link-local-ip", "Container IPv4/IPv6 link-local addresses")
   240  	flags.StringVar(&copts.macAddress, "mac-address", "", "Container MAC address (e.g., 92:d0:c6:0a:29:33)")
   241  	flags.VarP(&copts.publish, "publish", "p", "Publish a container's port(s) to the host")
   242  	flags.BoolVarP(&copts.publishAll, "publish-all", "P", false, "Publish all exposed ports to random ports")
   243  	// We allow for both "--net" and "--network", although the latter is the recommended way.
   244  	flags.Var(&copts.netMode, "net", "Connect a container to a network")
   245  	flags.Var(&copts.netMode, "network", "Connect a container to a network")
   246  	flags.MarkHidden("net")
   247  	// We allow for both "--net-alias" and "--network-alias", although the latter is the recommended way.
   248  	flags.Var(&copts.aliases, "net-alias", "Add network-scoped alias for the container")
   249  	flags.Var(&copts.aliases, "network-alias", "Add network-scoped alias for the container")
   250  	flags.MarkHidden("net-alias")
   251  
   252  	// Logging and storage
   253  	flags.StringVar(&copts.loggingDriver, "log-driver", "", "Logging driver for the container")
   254  	flags.StringVar(&copts.volumeDriver, "volume-driver", "", "Optional volume driver for the container")
   255  	flags.Var(&copts.loggingOpts, "log-opt", "Log driver options")
   256  	flags.Var(&copts.storageOpt, "storage-opt", "Storage driver options for the container")
   257  	flags.Var(&copts.tmpfs, "tmpfs", "Mount a tmpfs directory")
   258  	flags.Var(&copts.volumesFrom, "volumes-from", "Mount volumes from the specified container(s)")
   259  	flags.VarP(&copts.volumes, "volume", "v", "Bind mount a volume")
   260  	flags.Var(&copts.mounts, "mount", "Attach a filesystem mount to the container")
   261  
   262  	// Health-checking
   263  	flags.StringVar(&copts.healthCmd, "health-cmd", "", "Command to run to check health")
   264  	flags.DurationVar(&copts.healthInterval, "health-interval", 0, "Time between running the check (ms|s|m|h) (default 0s)")
   265  	flags.IntVar(&copts.healthRetries, "health-retries", 0, "Consecutive failures needed to report unhealthy")
   266  	flags.DurationVar(&copts.healthTimeout, "health-timeout", 0, "Maximum time to allow one check to run (ms|s|m|h) (default 0s)")
   267  	flags.DurationVar(&copts.healthStartPeriod, "health-start-period", 0, "Start period for the container to initialize before starting health-retries countdown (ms|s|m|h) (default 0s)")
   268  	flags.SetAnnotation("health-start-period", "version", []string{"1.29"})
   269  	flags.DurationVar(&copts.healthStartInterval, "health-start-interval", 0, "Time between running the check during the start period (ms|s|m|h) (default 0s)")
   270  	flags.SetAnnotation("health-start-interval", "version", []string{"1.44"})
   271  	flags.BoolVar(&copts.noHealthcheck, "no-healthcheck", false, "Disable any container-specified HEALTHCHECK")
   272  
   273  	// Resource management
   274  	flags.Uint16Var(&copts.blkioWeight, "blkio-weight", 0, "Block IO (relative weight), between 10 and 1000, or 0 to disable (default 0)")
   275  	flags.Var(&copts.blkioWeightDevice, "blkio-weight-device", "Block IO weight (relative device weight)")
   276  	flags.StringVar(&copts.containerIDFile, "cidfile", "", "Write the container ID to the file")
   277  	flags.StringVar(&copts.cpusetCpus, "cpuset-cpus", "", "CPUs in which to allow execution (0-3, 0,1)")
   278  	flags.StringVar(&copts.cpusetMems, "cpuset-mems", "", "MEMs in which to allow execution (0-3, 0,1)")
   279  	flags.Int64Var(&copts.cpuCount, "cpu-count", 0, "CPU count (Windows only)")
   280  	flags.SetAnnotation("cpu-count", "ostype", []string{"windows"})
   281  	flags.Int64Var(&copts.cpuPercent, "cpu-percent", 0, "CPU percent (Windows only)")
   282  	flags.SetAnnotation("cpu-percent", "ostype", []string{"windows"})
   283  	flags.Int64Var(&copts.cpuPeriod, "cpu-period", 0, "Limit CPU CFS (Completely Fair Scheduler) period")
   284  	flags.Int64Var(&copts.cpuQuota, "cpu-quota", 0, "Limit CPU CFS (Completely Fair Scheduler) quota")
   285  	flags.Int64Var(&copts.cpuRealtimePeriod, "cpu-rt-period", 0, "Limit CPU real-time period in microseconds")
   286  	flags.SetAnnotation("cpu-rt-period", "version", []string{"1.25"})
   287  	flags.Int64Var(&copts.cpuRealtimeRuntime, "cpu-rt-runtime", 0, "Limit CPU real-time runtime in microseconds")
   288  	flags.SetAnnotation("cpu-rt-runtime", "version", []string{"1.25"})
   289  	flags.Int64VarP(&copts.cpuShares, "cpu-shares", "c", 0, "CPU shares (relative weight)")
   290  	flags.Var(&copts.cpus, "cpus", "Number of CPUs")
   291  	flags.SetAnnotation("cpus", "version", []string{"1.25"})
   292  	flags.Var(&copts.deviceReadBps, "device-read-bps", "Limit read rate (bytes per second) from a device")
   293  	flags.Var(&copts.deviceReadIOps, "device-read-iops", "Limit read rate (IO per second) from a device")
   294  	flags.Var(&copts.deviceWriteBps, "device-write-bps", "Limit write rate (bytes per second) to a device")
   295  	flags.Var(&copts.deviceWriteIOps, "device-write-iops", "Limit write rate (IO per second) to a device")
   296  	flags.Var(&copts.ioMaxBandwidth, "io-maxbandwidth", "Maximum IO bandwidth limit for the system drive (Windows only)")
   297  	flags.SetAnnotation("io-maxbandwidth", "ostype", []string{"windows"})
   298  	flags.Uint64Var(&copts.ioMaxIOps, "io-maxiops", 0, "Maximum IOps limit for the system drive (Windows only)")
   299  	flags.SetAnnotation("io-maxiops", "ostype", []string{"windows"})
   300  	flags.Var(&copts.kernelMemory, "kernel-memory", "Kernel memory limit")
   301  	flags.VarP(&copts.memory, "memory", "m", "Memory limit")
   302  	flags.Var(&copts.memoryReservation, "memory-reservation", "Memory soft limit")
   303  	flags.Var(&copts.memorySwap, "memory-swap", "Swap limit equal to memory plus swap: '-1' to enable unlimited swap")
   304  	flags.Int64Var(&copts.swappiness, "memory-swappiness", -1, "Tune container memory swappiness (0 to 100)")
   305  	flags.BoolVar(&copts.oomKillDisable, "oom-kill-disable", false, "Disable OOM Killer")
   306  	flags.IntVar(&copts.oomScoreAdj, "oom-score-adj", 0, "Tune host's OOM preferences (-1000 to 1000)")
   307  	flags.Int64Var(&copts.pidsLimit, "pids-limit", 0, "Tune container pids limit (set -1 for unlimited)")
   308  
   309  	// Low-level execution (cgroups, namespaces, ...)
   310  	flags.StringVar(&copts.cgroupParent, "cgroup-parent", "", "Optional parent cgroup for the container")
   311  	flags.StringVar(&copts.ipcMode, "ipc", "", "IPC mode to use")
   312  	flags.StringVar(&copts.isolation, "isolation", "", "Container isolation technology")
   313  	flags.StringVar(&copts.pidMode, "pid", "", "PID namespace to use")
   314  	flags.Var(&copts.shmSize, "shm-size", "Size of /dev/shm")
   315  	flags.StringVar(&copts.utsMode, "uts", "", "UTS namespace to use")
   316  	flags.StringVar(&copts.runtime, "runtime", "", "Runtime to use for this container")
   317  
   318  	flags.BoolVar(&copts.init, "init", false, "Run an init inside the container that forwards signals and reaps processes")
   319  	flags.SetAnnotation("init", "version", []string{"1.25"})
   320  
   321  	flags.Var(copts.annotations, "annotation", "Add an annotation to the container (passed through to the OCI runtime)")
   322  	flags.SetAnnotation("annotation", "version", []string{"1.43"})
   323  
   324  	return copts
   325  }
   326  
   327  type containerConfig struct {
   328  	Config           *container.Config
   329  	HostConfig       *container.HostConfig
   330  	NetworkingConfig *networktypes.NetworkingConfig
   331  }
   332  
   333  // parse parses the args for the specified command and generates a Config,
   334  // a HostConfig and returns them with the specified command.
   335  // If the specified args are not valid, it will return an error.
   336  //
   337  //nolint:gocyclo
   338  func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*containerConfig, error) {
   339  	var (
   340  		attachStdin  = copts.attach.Get("stdin")
   341  		attachStdout = copts.attach.Get("stdout")
   342  		attachStderr = copts.attach.Get("stderr")
   343  	)
   344  
   345  	// Validate the input mac address
   346  	if copts.macAddress != "" {
   347  		if _, err := opts.ValidateMACAddress(copts.macAddress); err != nil {
   348  			return nil, errors.Errorf("%s is not a valid mac address", copts.macAddress)
   349  		}
   350  	}
   351  	if copts.stdin {
   352  		attachStdin = true
   353  	}
   354  	// If -a is not set, attach to stdout and stderr
   355  	if copts.attach.Len() == 0 {
   356  		attachStdout = true
   357  		attachStderr = true
   358  	}
   359  
   360  	var err error
   361  
   362  	swappiness := copts.swappiness
   363  	if swappiness != -1 && (swappiness < 0 || swappiness > 100) {
   364  		return nil, errors.Errorf("invalid value: %d. Valid memory swappiness range is 0-100", swappiness)
   365  	}
   366  
   367  	mounts := copts.mounts.Value()
   368  	if len(mounts) > 0 && copts.volumeDriver != "" {
   369  		logrus.Warn("`--volume-driver` is ignored for volumes specified via `--mount`. Use `--mount type=volume,volume-driver=...` instead.")
   370  	}
   371  	var binds []string
   372  	volumes := copts.volumes.GetMap()
   373  	// add any bind targets to the list of container volumes
   374  	for bind := range copts.volumes.GetMap() {
   375  		parsed, err := loader.ParseVolume(bind)
   376  		if err != nil {
   377  			return nil, err
   378  		}
   379  
   380  		if parsed.Source != "" {
   381  			toBind := bind
   382  
   383  			if parsed.Type == string(mounttypes.TypeBind) {
   384  				if hostPart, targetPath, ok := strings.Cut(bind, ":"); ok {
   385  					if strings.HasPrefix(hostPart, "."+string(filepath.Separator)) || hostPart == "." {
   386  						if absHostPart, err := filepath.Abs(hostPart); err == nil {
   387  							hostPart = absHostPart
   388  						}
   389  					}
   390  					toBind = hostPart + ":" + targetPath
   391  				}
   392  			}
   393  
   394  			// after creating the bind mount we want to delete it from the copts.volumes values because
   395  			// we do not want bind mounts being committed to image configs
   396  			binds = append(binds, toBind)
   397  			// We should delete from the map (`volumes`) here, as deleting from copts.volumes will not work if
   398  			// there are duplicates entries.
   399  			delete(volumes, bind)
   400  		}
   401  	}
   402  
   403  	// Can't evaluate options passed into --tmpfs until we actually mount
   404  	tmpfs := make(map[string]string)
   405  	for _, t := range copts.tmpfs.GetAll() {
   406  		k, v, _ := strings.Cut(t, ":")
   407  		tmpfs[k] = v
   408  	}
   409  
   410  	var (
   411  		runCmd     strslice.StrSlice
   412  		entrypoint strslice.StrSlice
   413  	)
   414  
   415  	if len(copts.Args) > 0 {
   416  		runCmd = copts.Args
   417  	}
   418  
   419  	if copts.entrypoint != "" {
   420  		entrypoint = strslice.StrSlice{copts.entrypoint}
   421  	} else if flags.Changed("entrypoint") {
   422  		// if `--entrypoint=` is parsed then Entrypoint is reset
   423  		entrypoint = []string{""}
   424  	}
   425  
   426  	publishOpts := copts.publish.GetAll()
   427  	var (
   428  		ports         map[nat.Port]struct{}
   429  		portBindings  map[nat.Port][]nat.PortBinding
   430  		convertedOpts []string
   431  	)
   432  
   433  	convertedOpts, err = convertToStandardNotation(publishOpts)
   434  	if err != nil {
   435  		return nil, err
   436  	}
   437  
   438  	ports, portBindings, err = nat.ParsePortSpecs(convertedOpts)
   439  	if err != nil {
   440  		return nil, err
   441  	}
   442  
   443  	// Merge in exposed ports to the map of published ports
   444  	for _, e := range copts.expose.GetAll() {
   445  		if strings.Contains(e, ":") {
   446  			return nil, errors.Errorf("invalid port format for --expose: %s", e)
   447  		}
   448  		// support two formats for expose, original format <portnum>/[<proto>]
   449  		// or <startport-endport>/[<proto>]
   450  		proto, port := nat.SplitProtoPort(e)
   451  		// parse the start and end port and create a sequence of ports to expose
   452  		// if expose a port, the start and end port are the same
   453  		start, end, err := nat.ParsePortRange(port)
   454  		if err != nil {
   455  			return nil, errors.Errorf("invalid range format for --expose: %s, error: %s", e, err)
   456  		}
   457  		for i := start; i <= end; i++ {
   458  			p, err := nat.NewPort(proto, strconv.FormatUint(i, 10))
   459  			if err != nil {
   460  				return nil, err
   461  			}
   462  			if _, exists := ports[p]; !exists {
   463  				ports[p] = struct{}{}
   464  			}
   465  		}
   466  	}
   467  
   468  	// validate and parse device mappings. Note we do late validation of the
   469  	// device path (as opposed to during flag parsing), as at the time we are
   470  	// parsing flags, we haven't yet sent a _ping to the daemon to determine
   471  	// what operating system it is.
   472  	deviceMappings := []container.DeviceMapping{}
   473  	var cdiDeviceNames []string
   474  	for _, device := range copts.devices.GetAll() {
   475  		var (
   476  			validated     string
   477  			deviceMapping container.DeviceMapping
   478  			err           error
   479  		)
   480  		if cdi.IsQualifiedName(device) {
   481  			cdiDeviceNames = append(cdiDeviceNames, device)
   482  			continue
   483  		}
   484  		validated, err = validateDevice(device, serverOS)
   485  		if err != nil {
   486  			return nil, err
   487  		}
   488  		deviceMapping, err = parseDevice(validated, serverOS)
   489  		if err != nil {
   490  			return nil, err
   491  		}
   492  		deviceMappings = append(deviceMappings, deviceMapping)
   493  	}
   494  
   495  	// collect all the environment variables for the container
   496  	envVariables, err := opts.ReadKVEnvStrings(copts.envFile.GetAll(), copts.env.GetAll())
   497  	if err != nil {
   498  		return nil, err
   499  	}
   500  
   501  	// collect all the labels for the container
   502  	labels, err := opts.ReadKVStrings(copts.labelsFile.GetAll(), copts.labels.GetAll())
   503  	if err != nil {
   504  		return nil, err
   505  	}
   506  
   507  	pidMode := container.PidMode(copts.pidMode)
   508  	if !pidMode.Valid() {
   509  		return nil, errors.Errorf("--pid: invalid PID mode")
   510  	}
   511  
   512  	utsMode := container.UTSMode(copts.utsMode)
   513  	if !utsMode.Valid() {
   514  		return nil, errors.Errorf("--uts: invalid UTS mode")
   515  	}
   516  
   517  	usernsMode := container.UsernsMode(copts.usernsMode)
   518  	if !usernsMode.Valid() {
   519  		return nil, errors.Errorf("--userns: invalid USER mode")
   520  	}
   521  
   522  	cgroupnsMode := container.CgroupnsMode(copts.cgroupnsMode)
   523  	if !cgroupnsMode.Valid() {
   524  		return nil, errors.Errorf("--cgroupns: invalid CGROUP mode")
   525  	}
   526  
   527  	restartPolicy, err := opts.ParseRestartPolicy(copts.restartPolicy)
   528  	if err != nil {
   529  		return nil, err
   530  	}
   531  
   532  	loggingOpts, err := parseLoggingOpts(copts.loggingDriver, copts.loggingOpts.GetAll())
   533  	if err != nil {
   534  		return nil, err
   535  	}
   536  
   537  	securityOpts, err := parseSecurityOpts(copts.securityOpt.GetAll())
   538  	if err != nil {
   539  		return nil, err
   540  	}
   541  
   542  	securityOpts, maskedPaths, readonlyPaths := parseSystemPaths(securityOpts)
   543  
   544  	storageOpts, err := parseStorageOpts(copts.storageOpt.GetAll())
   545  	if err != nil {
   546  		return nil, err
   547  	}
   548  
   549  	// Healthcheck
   550  	var healthConfig *container.HealthConfig
   551  	haveHealthSettings := copts.healthCmd != "" ||
   552  		copts.healthInterval != 0 ||
   553  		copts.healthTimeout != 0 ||
   554  		copts.healthStartPeriod != 0 ||
   555  		copts.healthRetries != 0 ||
   556  		copts.healthStartInterval != 0
   557  	if copts.noHealthcheck {
   558  		if haveHealthSettings {
   559  			return nil, errors.Errorf("--no-healthcheck conflicts with --health-* options")
   560  		}
   561  		healthConfig = &container.HealthConfig{Test: strslice.StrSlice{"NONE"}}
   562  	} else if haveHealthSettings {
   563  		var probe strslice.StrSlice
   564  		if copts.healthCmd != "" {
   565  			probe = []string{"CMD-SHELL", copts.healthCmd}
   566  		}
   567  		if copts.healthInterval < 0 {
   568  			return nil, errors.Errorf("--health-interval cannot be negative")
   569  		}
   570  		if copts.healthTimeout < 0 {
   571  			return nil, errors.Errorf("--health-timeout cannot be negative")
   572  		}
   573  		if copts.healthRetries < 0 {
   574  			return nil, errors.Errorf("--health-retries cannot be negative")
   575  		}
   576  		if copts.healthStartPeriod < 0 {
   577  			return nil, fmt.Errorf("--health-start-period cannot be negative")
   578  		}
   579  		if copts.healthStartInterval < 0 {
   580  			return nil, fmt.Errorf("--health-start-interval cannot be negative")
   581  		}
   582  
   583  		healthConfig = &container.HealthConfig{
   584  			Test:          probe,
   585  			Interval:      copts.healthInterval,
   586  			Timeout:       copts.healthTimeout,
   587  			StartPeriod:   copts.healthStartPeriod,
   588  			StartInterval: copts.healthStartInterval,
   589  			Retries:       copts.healthRetries,
   590  		}
   591  	}
   592  
   593  	deviceRequests := copts.gpus.Value()
   594  	if len(cdiDeviceNames) > 0 {
   595  		cdiDeviceRequest := container.DeviceRequest{
   596  			Driver:    "cdi",
   597  			DeviceIDs: cdiDeviceNames,
   598  		}
   599  		deviceRequests = append(deviceRequests, cdiDeviceRequest)
   600  	}
   601  
   602  	resources := container.Resources{
   603  		CgroupParent:         copts.cgroupParent,
   604  		Memory:               copts.memory.Value(),
   605  		MemoryReservation:    copts.memoryReservation.Value(),
   606  		MemorySwap:           copts.memorySwap.Value(),
   607  		MemorySwappiness:     &copts.swappiness,
   608  		KernelMemory:         copts.kernelMemory.Value(),
   609  		OomKillDisable:       &copts.oomKillDisable,
   610  		NanoCPUs:             copts.cpus.Value(),
   611  		CPUCount:             copts.cpuCount,
   612  		CPUPercent:           copts.cpuPercent,
   613  		CPUShares:            copts.cpuShares,
   614  		CPUPeriod:            copts.cpuPeriod,
   615  		CpusetCpus:           copts.cpusetCpus,
   616  		CpusetMems:           copts.cpusetMems,
   617  		CPUQuota:             copts.cpuQuota,
   618  		CPURealtimePeriod:    copts.cpuRealtimePeriod,
   619  		CPURealtimeRuntime:   copts.cpuRealtimeRuntime,
   620  		PidsLimit:            &copts.pidsLimit,
   621  		BlkioWeight:          copts.blkioWeight,
   622  		BlkioWeightDevice:    copts.blkioWeightDevice.GetList(),
   623  		BlkioDeviceReadBps:   copts.deviceReadBps.GetList(),
   624  		BlkioDeviceWriteBps:  copts.deviceWriteBps.GetList(),
   625  		BlkioDeviceReadIOps:  copts.deviceReadIOps.GetList(),
   626  		BlkioDeviceWriteIOps: copts.deviceWriteIOps.GetList(),
   627  		IOMaximumIOps:        copts.ioMaxIOps,
   628  		IOMaximumBandwidth:   uint64(copts.ioMaxBandwidth),
   629  		Ulimits:              copts.ulimits.GetList(),
   630  		DeviceCgroupRules:    copts.deviceCgroupRules.GetAll(),
   631  		Devices:              deviceMappings,
   632  		DeviceRequests:       deviceRequests,
   633  	}
   634  
   635  	config := &container.Config{
   636  		Hostname:     copts.hostname,
   637  		Domainname:   copts.domainname,
   638  		ExposedPorts: ports,
   639  		User:         copts.user,
   640  		Tty:          copts.tty,
   641  		OpenStdin:    copts.stdin,
   642  		AttachStdin:  attachStdin,
   643  		AttachStdout: attachStdout,
   644  		AttachStderr: attachStderr,
   645  		Env:          envVariables,
   646  		Cmd:          runCmd,
   647  		Image:        copts.Image,
   648  		Volumes:      volumes,
   649  		MacAddress:   copts.macAddress,
   650  		Entrypoint:   entrypoint,
   651  		WorkingDir:   copts.workingDir,
   652  		Labels:       opts.ConvertKVStringsToMap(labels),
   653  		StopSignal:   copts.stopSignal,
   654  		Healthcheck:  healthConfig,
   655  	}
   656  	if flags.Changed("stop-timeout") {
   657  		config.StopTimeout = &copts.stopTimeout
   658  	}
   659  
   660  	hostConfig := &container.HostConfig{
   661  		Binds:           binds,
   662  		ContainerIDFile: copts.containerIDFile,
   663  		OomScoreAdj:     copts.oomScoreAdj,
   664  		AutoRemove:      copts.autoRemove,
   665  		Privileged:      copts.privileged,
   666  		PortBindings:    portBindings,
   667  		Links:           copts.links.GetAll(),
   668  		PublishAllPorts: copts.publishAll,
   669  		// Make sure the dns fields are never nil.
   670  		// New containers don't ever have those fields nil,
   671  		// but pre created containers can still have those nil values.
   672  		// See https://github.com/docker/docker/pull/17779
   673  		// for a more detailed explanation on why we don't want that.
   674  		DNS:            copts.dns.GetAllOrEmpty(),
   675  		DNSSearch:      copts.dnsSearch.GetAllOrEmpty(),
   676  		DNSOptions:     copts.dnsOptions.GetAllOrEmpty(),
   677  		ExtraHosts:     copts.extraHosts.GetAll(),
   678  		VolumesFrom:    copts.volumesFrom.GetAll(),
   679  		IpcMode:        container.IpcMode(copts.ipcMode),
   680  		NetworkMode:    container.NetworkMode(copts.netMode.NetworkMode()),
   681  		PidMode:        pidMode,
   682  		UTSMode:        utsMode,
   683  		UsernsMode:     usernsMode,
   684  		CgroupnsMode:   cgroupnsMode,
   685  		CapAdd:         strslice.StrSlice(copts.capAdd.GetAll()),
   686  		CapDrop:        strslice.StrSlice(copts.capDrop.GetAll()),
   687  		GroupAdd:       copts.groupAdd.GetAll(),
   688  		RestartPolicy:  restartPolicy,
   689  		SecurityOpt:    securityOpts,
   690  		StorageOpt:     storageOpts,
   691  		ReadonlyRootfs: copts.readonlyRootfs,
   692  		LogConfig:      container.LogConfig{Type: copts.loggingDriver, Config: loggingOpts},
   693  		VolumeDriver:   copts.volumeDriver,
   694  		Isolation:      container.Isolation(copts.isolation),
   695  		ShmSize:        copts.shmSize.Value(),
   696  		Resources:      resources,
   697  		Tmpfs:          tmpfs,
   698  		Sysctls:        copts.sysctls.GetAll(),
   699  		Runtime:        copts.runtime,
   700  		Mounts:         mounts,
   701  		MaskedPaths:    maskedPaths,
   702  		ReadonlyPaths:  readonlyPaths,
   703  		Annotations:    copts.annotations.GetAll(),
   704  	}
   705  
   706  	if copts.autoRemove && !hostConfig.RestartPolicy.IsNone() {
   707  		return nil, errors.Errorf("Conflicting options: --restart and --rm")
   708  	}
   709  
   710  	// only set this value if the user provided the flag, else it should default to nil
   711  	if flags.Changed("init") {
   712  		hostConfig.Init = &copts.init
   713  	}
   714  
   715  	// When allocating stdin in attached mode, close stdin at client disconnect
   716  	if config.OpenStdin && config.AttachStdin {
   717  		config.StdinOnce = true
   718  	}
   719  
   720  	networkingConfig := &networktypes.NetworkingConfig{
   721  		EndpointsConfig: make(map[string]*networktypes.EndpointSettings),
   722  	}
   723  
   724  	networkingConfig.EndpointsConfig, err = parseNetworkOpts(copts)
   725  	if err != nil {
   726  		return nil, err
   727  	}
   728  
   729  	// Put the endpoint-specific MacAddress of the "main" network attachment into the container Config for backward
   730  	// compatibility with older daemons.
   731  	if nw, ok := networkingConfig.EndpointsConfig[hostConfig.NetworkMode.NetworkName()]; ok {
   732  		config.MacAddress = nw.MacAddress //nolint:staticcheck // ignore SA1019: field is deprecated, but still used on API < v1.44.
   733  	}
   734  
   735  	return &containerConfig{
   736  		Config:           config,
   737  		HostConfig:       hostConfig,
   738  		NetworkingConfig: networkingConfig,
   739  	}, nil
   740  }
   741  
   742  // parseNetworkOpts converts --network advanced options to endpoint-specs, and combines
   743  // them with the old --network-alias and --links. If returns an error if conflicting options
   744  // are found.
   745  //
   746  // this function may return _multiple_ endpoints, which is not currently supported
   747  // by the daemon, but may be in future; it's up to the daemon to produce an error
   748  // in case that is not supported.
   749  func parseNetworkOpts(copts *containerOptions) (map[string]*networktypes.EndpointSettings, error) {
   750  	var (
   751  		endpoints                         = make(map[string]*networktypes.EndpointSettings, len(copts.netMode.Value()))
   752  		hasUserDefined, hasNonUserDefined bool
   753  	)
   754  
   755  	if len(copts.netMode.Value()) == 0 {
   756  		n := opts.NetworkAttachmentOpts{
   757  			Target: "default",
   758  		}
   759  		if err := applyContainerOptions(&n, copts); err != nil {
   760  			return nil, err
   761  		}
   762  		ep, err := parseNetworkAttachmentOpt(n)
   763  		if err != nil {
   764  			return nil, err
   765  		}
   766  		endpoints["default"] = ep
   767  	}
   768  
   769  	for i, n := range copts.netMode.Value() {
   770  		n := n
   771  		if container.NetworkMode(n.Target).IsUserDefined() {
   772  			hasUserDefined = true
   773  		} else {
   774  			hasNonUserDefined = true
   775  		}
   776  		if i == 0 {
   777  			// The first network corresponds with what was previously the "only"
   778  			// network, and what would be used when using the non-advanced syntax
   779  			// `--network-alias`, `--link`, `--ip`, `--ip6`, and `--link-local-ip`
   780  			// are set on this network, to preserve backward compatibility with
   781  			// the non-advanced notation
   782  			if err := applyContainerOptions(&n, copts); err != nil {
   783  				return nil, err
   784  			}
   785  		}
   786  		ep, err := parseNetworkAttachmentOpt(n)
   787  		if err != nil {
   788  			return nil, err
   789  		}
   790  		if _, ok := endpoints[n.Target]; ok {
   791  			return nil, errdefs.InvalidParameter(errors.Errorf("network %q is specified multiple times", n.Target))
   792  		}
   793  
   794  		// For backward compatibility: if no custom options are provided for the network,
   795  		// and only a single network is specified, omit the endpoint-configuration
   796  		// on the client (the daemon will still create it when creating the container)
   797  		if i == 0 && len(copts.netMode.Value()) == 1 {
   798  			if ep == nil || reflect.DeepEqual(*ep, networktypes.EndpointSettings{}) {
   799  				continue
   800  			}
   801  		}
   802  		endpoints[n.Target] = ep
   803  	}
   804  	if hasUserDefined && hasNonUserDefined {
   805  		return nil, errdefs.InvalidParameter(errors.New("conflicting options: cannot attach both user-defined and non-user-defined network-modes"))
   806  	}
   807  	return endpoints, nil
   808  }
   809  
   810  func applyContainerOptions(n *opts.NetworkAttachmentOpts, copts *containerOptions) error { //nolint:gocyclo
   811  	// TODO should we error if _any_ advanced option is used? (i.e. forbid to combine advanced notation with the "old" flags (`--network-alias`, `--link`, `--ip`, `--ip6`)?
   812  	if len(n.Aliases) > 0 && copts.aliases.Len() > 0 {
   813  		return errdefs.InvalidParameter(errors.New("conflicting options: cannot specify both --network-alias and per-network alias"))
   814  	}
   815  	if len(n.Links) > 0 && copts.links.Len() > 0 {
   816  		return errdefs.InvalidParameter(errors.New("conflicting options: cannot specify both --link and per-network links"))
   817  	}
   818  	if n.IPv4Address != "" && copts.ipv4Address != "" {
   819  		return errdefs.InvalidParameter(errors.New("conflicting options: cannot specify both --ip and per-network IPv4 address"))
   820  	}
   821  	if n.IPv6Address != "" && copts.ipv6Address != "" {
   822  		return errdefs.InvalidParameter(errors.New("conflicting options: cannot specify both --ip6 and per-network IPv6 address"))
   823  	}
   824  	if n.MacAddress != "" && copts.macAddress != "" {
   825  		return errdefs.InvalidParameter(errors.New("conflicting options: cannot specify both --mac-address and per-network MAC address"))
   826  	}
   827  	if len(n.LinkLocalIPs) > 0 && copts.linkLocalIPs.Len() > 0 {
   828  		return errdefs.InvalidParameter(errors.New("conflicting options: cannot specify both --link-local-ip and per-network link-local IP addresses"))
   829  	}
   830  	if copts.aliases.Len() > 0 {
   831  		n.Aliases = make([]string, copts.aliases.Len())
   832  		copy(n.Aliases, copts.aliases.GetAll())
   833  	}
   834  	if n.Target != "default" && copts.links.Len() > 0 {
   835  		n.Links = make([]string, copts.links.Len())
   836  		copy(n.Links, copts.links.GetAll())
   837  	}
   838  	if copts.ipv4Address != "" {
   839  		n.IPv4Address = copts.ipv4Address
   840  	}
   841  	if copts.ipv6Address != "" {
   842  		n.IPv6Address = copts.ipv6Address
   843  	}
   844  	if copts.macAddress != "" {
   845  		n.MacAddress = copts.macAddress
   846  	}
   847  	if copts.linkLocalIPs.Len() > 0 {
   848  		n.LinkLocalIPs = make([]string, copts.linkLocalIPs.Len())
   849  		copy(n.LinkLocalIPs, copts.linkLocalIPs.GetAll())
   850  	}
   851  	return nil
   852  }
   853  
   854  func parseNetworkAttachmentOpt(ep opts.NetworkAttachmentOpts) (*networktypes.EndpointSettings, error) {
   855  	if strings.TrimSpace(ep.Target) == "" {
   856  		return nil, errors.New("no name set for network")
   857  	}
   858  	if !container.NetworkMode(ep.Target).IsUserDefined() {
   859  		if len(ep.Aliases) > 0 {
   860  			return nil, errors.New("network-scoped aliases are only supported for user-defined networks")
   861  		}
   862  		if len(ep.Links) > 0 {
   863  			return nil, errors.New("links are only supported for user-defined networks")
   864  		}
   865  	}
   866  
   867  	epConfig := &networktypes.EndpointSettings{}
   868  	epConfig.Aliases = append(epConfig.Aliases, ep.Aliases...)
   869  	if len(ep.DriverOpts) > 0 {
   870  		epConfig.DriverOpts = make(map[string]string)
   871  		epConfig.DriverOpts = ep.DriverOpts
   872  	}
   873  	if len(ep.Links) > 0 {
   874  		epConfig.Links = ep.Links
   875  	}
   876  	if ep.IPv4Address != "" || ep.IPv6Address != "" || len(ep.LinkLocalIPs) > 0 {
   877  		epConfig.IPAMConfig = &networktypes.EndpointIPAMConfig{
   878  			IPv4Address:  ep.IPv4Address,
   879  			IPv6Address:  ep.IPv6Address,
   880  			LinkLocalIPs: ep.LinkLocalIPs,
   881  		}
   882  	}
   883  	if ep.MacAddress != "" {
   884  		if _, err := opts.ValidateMACAddress(ep.MacAddress); err != nil {
   885  			return nil, errors.Errorf("%s is not a valid mac address", ep.MacAddress)
   886  		}
   887  		epConfig.MacAddress = ep.MacAddress
   888  	}
   889  	return epConfig, nil
   890  }
   891  
   892  func convertToStandardNotation(ports []string) ([]string, error) {
   893  	optsList := []string{}
   894  	for _, publish := range ports {
   895  		if strings.Contains(publish, "=") {
   896  			params := map[string]string{"protocol": "tcp"}
   897  			for _, param := range strings.Split(publish, ",") {
   898  				k, v, ok := strings.Cut(param, "=")
   899  				if !ok || k == "" {
   900  					return optsList, errors.Errorf("invalid publish opts format (should be name=value but got '%s')", param)
   901  				}
   902  				params[k] = v
   903  			}
   904  			optsList = append(optsList, fmt.Sprintf("%s:%s/%s", params["published"], params["target"], params["protocol"]))
   905  		} else {
   906  			optsList = append(optsList, publish)
   907  		}
   908  	}
   909  	return optsList, nil
   910  }
   911  
   912  func parseLoggingOpts(loggingDriver string, loggingOpts []string) (map[string]string, error) {
   913  	loggingOptsMap := opts.ConvertKVStringsToMap(loggingOpts)
   914  	if loggingDriver == "none" && len(loggingOpts) > 0 {
   915  		return map[string]string{}, errors.Errorf("invalid logging opts for driver %s", loggingDriver)
   916  	}
   917  	return loggingOptsMap, nil
   918  }
   919  
   920  // takes a local seccomp daemon, reads the file contents for sending to the daemon
   921  func parseSecurityOpts(securityOpts []string) ([]string, error) {
   922  	for key, opt := range securityOpts {
   923  		k, v, ok := strings.Cut(opt, "=")
   924  		if !ok && k != "no-new-privileges" {
   925  			k, v, ok = strings.Cut(opt, ":")
   926  		}
   927  		if (!ok || v == "") && k != "no-new-privileges" {
   928  			// "no-new-privileges" is the only option that does not require a value.
   929  			return securityOpts, errors.Errorf("Invalid --security-opt: %q", opt)
   930  		}
   931  		if k == "seccomp" {
   932  			switch v {
   933  			case seccompProfileDefault, seccompProfileUnconfined:
   934  				// known special names for built-in profiles, nothing to do.
   935  			default:
   936  				// value may be a filename, in which case we send the profile's
   937  				// content if it's valid JSON.
   938  				f, err := os.ReadFile(v)
   939  				if err != nil {
   940  					return securityOpts, errors.Errorf("opening seccomp profile (%s) failed: %v", v, err)
   941  				}
   942  				b := bytes.NewBuffer(nil)
   943  				if err := json.Compact(b, f); err != nil {
   944  					return securityOpts, errors.Errorf("compacting json for seccomp profile (%s) failed: %v", v, err)
   945  				}
   946  				securityOpts[key] = fmt.Sprintf("seccomp=%s", b.Bytes())
   947  			}
   948  		}
   949  	}
   950  
   951  	return securityOpts, nil
   952  }
   953  
   954  // parseSystemPaths checks if `systempaths=unconfined` security option is set,
   955  // and returns the `MaskedPaths` and `ReadonlyPaths` accordingly. An updated
   956  // list of security options is returned with this option removed, because the
   957  // `unconfined` option is handled client-side, and should not be sent to the
   958  // daemon.
   959  func parseSystemPaths(securityOpts []string) (filtered, maskedPaths, readonlyPaths []string) {
   960  	filtered = securityOpts[:0]
   961  	for _, opt := range securityOpts {
   962  		if opt == "systempaths=unconfined" {
   963  			maskedPaths = []string{}
   964  			readonlyPaths = []string{}
   965  		} else {
   966  			filtered = append(filtered, opt)
   967  		}
   968  	}
   969  
   970  	return filtered, maskedPaths, readonlyPaths
   971  }
   972  
   973  // parses storage options per container into a map
   974  func parseStorageOpts(storageOpts []string) (map[string]string, error) {
   975  	m := make(map[string]string)
   976  	for _, option := range storageOpts {
   977  		k, v, ok := strings.Cut(option, "=")
   978  		if !ok {
   979  			return nil, errors.Errorf("invalid storage option")
   980  		}
   981  		m[k] = v
   982  	}
   983  	return m, nil
   984  }
   985  
   986  // parseDevice parses a device mapping string to a container.DeviceMapping struct
   987  func parseDevice(device, serverOS string) (container.DeviceMapping, error) {
   988  	switch serverOS {
   989  	case "linux":
   990  		return parseLinuxDevice(device)
   991  	case "windows":
   992  		return parseWindowsDevice(device)
   993  	}
   994  	return container.DeviceMapping{}, errors.Errorf("unknown server OS: %s", serverOS)
   995  }
   996  
   997  // parseLinuxDevice parses a device mapping string to a container.DeviceMapping struct
   998  // knowing that the target is a Linux daemon
   999  func parseLinuxDevice(device string) (container.DeviceMapping, error) {
  1000  	var src, dst string
  1001  	permissions := "rwm"
  1002  	// We expect 3 parts at maximum; limit to 4 parts to detect invalid options.
  1003  	arr := strings.SplitN(device, ":", 4)
  1004  	switch len(arr) {
  1005  	case 3:
  1006  		permissions = arr[2]
  1007  		fallthrough
  1008  	case 2:
  1009  		if validDeviceMode(arr[1]) {
  1010  			permissions = arr[1]
  1011  		} else {
  1012  			dst = arr[1]
  1013  		}
  1014  		fallthrough
  1015  	case 1:
  1016  		src = arr[0]
  1017  	default:
  1018  		return container.DeviceMapping{}, errors.Errorf("invalid device specification: %s", device)
  1019  	}
  1020  
  1021  	if dst == "" {
  1022  		dst = src
  1023  	}
  1024  
  1025  	deviceMapping := container.DeviceMapping{
  1026  		PathOnHost:        src,
  1027  		PathInContainer:   dst,
  1028  		CgroupPermissions: permissions,
  1029  	}
  1030  	return deviceMapping, nil
  1031  }
  1032  
  1033  // parseWindowsDevice parses a device mapping string to a container.DeviceMapping struct
  1034  // knowing that the target is a Windows daemon
  1035  func parseWindowsDevice(device string) (container.DeviceMapping, error) {
  1036  	return container.DeviceMapping{PathOnHost: device}, nil
  1037  }
  1038  
  1039  // validateDeviceCgroupRule validates a device cgroup rule string format
  1040  // It will make sure 'val' is in the form:
  1041  //
  1042  //	'type major:minor mode'
  1043  func validateDeviceCgroupRule(val string) (string, error) {
  1044  	if deviceCgroupRuleRegexp.MatchString(val) {
  1045  		return val, nil
  1046  	}
  1047  
  1048  	return val, errors.Errorf("invalid device cgroup format '%s'", val)
  1049  }
  1050  
  1051  // validDeviceMode checks if the mode for device is valid or not.
  1052  // Valid mode is a composition of r (read), w (write), and m (mknod).
  1053  func validDeviceMode(mode string) bool {
  1054  	legalDeviceMode := map[rune]bool{
  1055  		'r': true,
  1056  		'w': true,
  1057  		'm': true,
  1058  	}
  1059  	if mode == "" {
  1060  		return false
  1061  	}
  1062  	for _, c := range mode {
  1063  		if !legalDeviceMode[c] {
  1064  			return false
  1065  		}
  1066  		legalDeviceMode[c] = false
  1067  	}
  1068  	return true
  1069  }
  1070  
  1071  // validateDevice validates a path for devices
  1072  func validateDevice(val string, serverOS string) (string, error) {
  1073  	switch serverOS {
  1074  	case "linux":
  1075  		return validateLinuxPath(val, validDeviceMode)
  1076  	case "windows":
  1077  		// Windows does validation entirely server-side
  1078  		return val, nil
  1079  	}
  1080  	return "", errors.Errorf("unknown server OS: %s", serverOS)
  1081  }
  1082  
  1083  // validateLinuxPath is the implementation of validateDevice knowing that the
  1084  // target server operating system is a Linux daemon.
  1085  // It will make sure 'val' is in the form:
  1086  //
  1087  //	[host-dir:]container-path[:mode]
  1088  //
  1089  // It also validates the device mode.
  1090  func validateLinuxPath(val string, validator func(string) bool) (string, error) {
  1091  	var containerPath string
  1092  	var mode string
  1093  
  1094  	if strings.Count(val, ":") > 2 {
  1095  		return val, errors.Errorf("bad format for path: %s", val)
  1096  	}
  1097  
  1098  	split := strings.SplitN(val, ":", 3)
  1099  	if split[0] == "" {
  1100  		return val, errors.Errorf("bad format for path: %s", val)
  1101  	}
  1102  	switch len(split) {
  1103  	case 1:
  1104  		containerPath = split[0]
  1105  		val = path.Clean(containerPath)
  1106  	case 2:
  1107  		if isValid := validator(split[1]); isValid {
  1108  			containerPath = split[0]
  1109  			mode = split[1]
  1110  			val = fmt.Sprintf("%s:%s", path.Clean(containerPath), mode)
  1111  		} else {
  1112  			containerPath = split[1]
  1113  			val = fmt.Sprintf("%s:%s", split[0], path.Clean(containerPath))
  1114  		}
  1115  	case 3:
  1116  		containerPath = split[1]
  1117  		mode = split[2]
  1118  		if isValid := validator(split[2]); !isValid {
  1119  			return val, errors.Errorf("bad mode specified: %s", mode)
  1120  		}
  1121  		val = fmt.Sprintf("%s:%s:%s", split[0], containerPath, mode)
  1122  	}
  1123  
  1124  	if !path.IsAbs(containerPath) {
  1125  		return val, errors.Errorf("%s is not an absolute path", containerPath)
  1126  	}
  1127  	return val, nil
  1128  }
  1129  
  1130  // validateAttach validates that the specified string is a valid attach option.
  1131  func validateAttach(val string) (string, error) {
  1132  	s := strings.ToLower(val)
  1133  	for _, str := range []string{"stdin", "stdout", "stderr"} {
  1134  		if s == str {
  1135  			return s, nil
  1136  		}
  1137  	}
  1138  	return val, errors.Errorf("valid streams are STDIN, STDOUT and STDERR")
  1139  }
  1140  
  1141  func validateAPIVersion(c *containerConfig, serverAPIVersion string) error {
  1142  	for _, m := range c.HostConfig.Mounts {
  1143  		if err := command.ValidateMountWithAPIVersion(m, serverAPIVersion); err != nil {
  1144  			return err
  1145  		}
  1146  	}
  1147  	return nil
  1148  }