github.com/nektos/act@v0.2.63-0.20240520024548-8acde99bfa9c/pkg/container/docker_cli.go (about)

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