github.com/justincormack/cli@v0.0.0-20201215022714-831ebeae9675/cli/command/container/opts.go (about)

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