github.com/hanks177/podman/v4@v4.1.3-0.20220613032544-16d90015bc83/pkg/specgenutil/volumes.go (about)

     1  package specgenutil
     2  
     3  import (
     4  	"encoding/csv"
     5  	"fmt"
     6  	"path"
     7  	"strings"
     8  
     9  	"github.com/containers/common/pkg/parse"
    10  	"github.com/hanks177/podman/v4/libpod/define"
    11  	"github.com/hanks177/podman/v4/pkg/specgen"
    12  	"github.com/hanks177/podman/v4/pkg/util"
    13  	spec "github.com/opencontainers/runtime-spec/specs-go"
    14  	"github.com/pkg/errors"
    15  )
    16  
    17  var (
    18  	errDuplicateDest = errors.Errorf("duplicate mount destination")
    19  	optionArgError   = errors.Errorf("must provide an argument for option")
    20  	noDestError      = errors.Errorf("must set volume destination")
    21  	errInvalidSyntax = errors.Errorf("incorrect mount format: should be --mount type=<bind|tmpfs|volume>,[src=<host-dir|volume-name>,]target=<ctr-dir>[,options]")
    22  )
    23  
    24  // Parse all volume-related options in the create config into a set of mounts
    25  // and named volumes to add to the container.
    26  // Handles --volumes, --mount, and --tmpfs flags.
    27  // Does not handle image volumes, init, and --volumes-from flags.
    28  // Can also add tmpfs mounts from read-only tmpfs.
    29  // TODO: handle options parsing/processing via containers/storage/pkg/mount
    30  func parseVolumes(volumeFlag, mountFlag, tmpfsFlag []string, addReadOnlyTmpfs bool) ([]spec.Mount, []*specgen.NamedVolume, []*specgen.OverlayVolume, []*specgen.ImageVolume, error) {
    31  	// Get mounts from the --mounts flag.
    32  	unifiedMounts, unifiedVolumes, unifiedImageVolumes, err := Mounts(mountFlag)
    33  	if err != nil {
    34  		return nil, nil, nil, nil, err
    35  	}
    36  
    37  	// Next --volumes flag.
    38  	volumeMounts, volumeVolumes, overlayVolumes, err := specgen.GenVolumeMounts(volumeFlag)
    39  	if err != nil {
    40  		return nil, nil, nil, nil, err
    41  	}
    42  
    43  	// Next --tmpfs flag.
    44  	tmpfsMounts, err := getTmpfsMounts(tmpfsFlag)
    45  	if err != nil {
    46  		return nil, nil, nil, nil, err
    47  	}
    48  
    49  	// Unify mounts from --mount, --volume, --tmpfs.
    50  	// Start with --volume.
    51  	for dest, mount := range volumeMounts {
    52  		if _, ok := unifiedMounts[dest]; ok {
    53  			return nil, nil, nil, nil, errors.Wrapf(errDuplicateDest, dest)
    54  		}
    55  		unifiedMounts[dest] = mount
    56  	}
    57  	for dest, volume := range volumeVolumes {
    58  		if _, ok := unifiedVolumes[dest]; ok {
    59  			return nil, nil, nil, nil, errors.Wrapf(errDuplicateDest, dest)
    60  		}
    61  		unifiedVolumes[dest] = volume
    62  	}
    63  	// Now --tmpfs
    64  	for dest, tmpfs := range tmpfsMounts {
    65  		if _, ok := unifiedMounts[dest]; ok {
    66  			return nil, nil, nil, nil, errors.Wrapf(errDuplicateDest, dest)
    67  		}
    68  		unifiedMounts[dest] = tmpfs
    69  	}
    70  
    71  	// If requested, add tmpfs filesystems for read-only containers.
    72  	if addReadOnlyTmpfs {
    73  		readonlyTmpfs := []string{"/tmp", "/var/tmp", "/run"}
    74  		options := []string{"rw", "rprivate", "nosuid", "nodev", "tmpcopyup"}
    75  		for _, dest := range readonlyTmpfs {
    76  			if _, ok := unifiedMounts[dest]; ok {
    77  				continue
    78  			}
    79  			if _, ok := unifiedVolumes[dest]; ok {
    80  				continue
    81  			}
    82  			unifiedMounts[dest] = spec.Mount{
    83  				Destination: dest,
    84  				Type:        define.TypeTmpfs,
    85  				Source:      "tmpfs",
    86  				Options:     options,
    87  			}
    88  		}
    89  	}
    90  
    91  	// Check for conflicts between named volumes, overlay & image volumes,
    92  	// and mounts
    93  	allMounts := make(map[string]bool)
    94  	testAndSet := func(dest string) error {
    95  		if _, ok := allMounts[dest]; ok {
    96  			return errors.Wrapf(errDuplicateDest, "conflict at mount destination %v", dest)
    97  		}
    98  		allMounts[dest] = true
    99  		return nil
   100  	}
   101  	for dest := range unifiedMounts {
   102  		if err := testAndSet(dest); err != nil {
   103  			return nil, nil, nil, nil, err
   104  		}
   105  	}
   106  	for dest := range unifiedVolumes {
   107  		if err := testAndSet(dest); err != nil {
   108  			return nil, nil, nil, nil, err
   109  		}
   110  	}
   111  	for dest := range overlayVolumes {
   112  		if err := testAndSet(dest); err != nil {
   113  			return nil, nil, nil, nil, err
   114  		}
   115  	}
   116  	for dest := range unifiedImageVolumes {
   117  		if err := testAndSet(dest); err != nil {
   118  			return nil, nil, nil, nil, err
   119  		}
   120  	}
   121  
   122  	// Final step: maps to arrays
   123  	finalMounts := make([]spec.Mount, 0, len(unifiedMounts))
   124  	for _, mount := range unifiedMounts {
   125  		if mount.Type == define.TypeBind {
   126  			absSrc, err := specgen.ConvertWinMountPath(mount.Source)
   127  			if err != nil {
   128  				return nil, nil, nil, nil, errors.Wrapf(err, "error getting absolute path of %s", mount.Source)
   129  			}
   130  			mount.Source = absSrc
   131  		}
   132  		finalMounts = append(finalMounts, mount)
   133  	}
   134  	finalVolumes := make([]*specgen.NamedVolume, 0, len(unifiedVolumes))
   135  	for _, volume := range unifiedVolumes {
   136  		finalVolumes = append(finalVolumes, volume)
   137  	}
   138  	finalOverlayVolume := make([]*specgen.OverlayVolume, 0)
   139  	for _, volume := range overlayVolumes {
   140  		finalOverlayVolume = append(finalOverlayVolume, volume)
   141  	}
   142  	finalImageVolumes := make([]*specgen.ImageVolume, 0, len(unifiedImageVolumes))
   143  	for _, volume := range unifiedImageVolumes {
   144  		finalImageVolumes = append(finalImageVolumes, volume)
   145  	}
   146  
   147  	return finalMounts, finalVolumes, finalOverlayVolume, finalImageVolumes, nil
   148  }
   149  
   150  // findMountType parses the input and extracts the type of the mount type and
   151  // the remaining non-type tokens.
   152  func findMountType(input string) (mountType string, tokens []string, err error) {
   153  	// Split by comma, iterate over the slice and look for
   154  	// "type=$mountType". Everything else is appended to tokens.
   155  	found := false
   156  	csvReader := csv.NewReader(strings.NewReader(input))
   157  	records, err := csvReader.ReadAll()
   158  	if err != nil {
   159  		return "", nil, err
   160  	}
   161  	if len(records) != 1 {
   162  		return "", nil, errInvalidSyntax
   163  	}
   164  	for _, s := range records[0] {
   165  		kv := strings.Split(s, "=")
   166  		if found || !(len(kv) == 2 && kv[0] == "type") {
   167  			tokens = append(tokens, s)
   168  			continue
   169  		}
   170  		mountType = kv[1]
   171  		found = true
   172  	}
   173  	if !found {
   174  		err = errInvalidSyntax
   175  	}
   176  	return
   177  }
   178  
   179  // Mounts takes user-provided input from the --mount flag and creates OCI
   180  // spec mounts and Libpod named volumes.
   181  // podman run --mount type=bind,src=/etc/resolv.conf,target=/etc/resolv.conf ...
   182  // podman run --mount type=tmpfs,target=/dev/shm ...
   183  // podman run --mount type=volume,source=test-volume, ...
   184  func Mounts(mountFlag []string) (map[string]spec.Mount, map[string]*specgen.NamedVolume, map[string]*specgen.ImageVolume, error) {
   185  	finalMounts := make(map[string]spec.Mount)
   186  	finalNamedVolumes := make(map[string]*specgen.NamedVolume)
   187  	finalImageVolumes := make(map[string]*specgen.ImageVolume)
   188  
   189  	for _, mount := range mountFlag {
   190  		// TODO: Docker defaults to "volume" if no mount type is specified.
   191  		mountType, tokens, err := findMountType(mount)
   192  		if err != nil {
   193  			return nil, nil, nil, err
   194  		}
   195  		switch mountType {
   196  		case define.TypeBind:
   197  			mount, err := getBindMount(tokens)
   198  			if err != nil {
   199  				return nil, nil, nil, err
   200  			}
   201  			if _, ok := finalMounts[mount.Destination]; ok {
   202  				return nil, nil, nil, errors.Wrapf(errDuplicateDest, mount.Destination)
   203  			}
   204  			finalMounts[mount.Destination] = mount
   205  		case define.TypeTmpfs:
   206  			mount, err := getTmpfsMount(tokens)
   207  			if err != nil {
   208  				return nil, nil, nil, err
   209  			}
   210  			if _, ok := finalMounts[mount.Destination]; ok {
   211  				return nil, nil, nil, errors.Wrapf(errDuplicateDest, mount.Destination)
   212  			}
   213  			finalMounts[mount.Destination] = mount
   214  		case define.TypeDevpts:
   215  			mount, err := getDevptsMount(tokens)
   216  			if err != nil {
   217  				return nil, nil, nil, err
   218  			}
   219  			if _, ok := finalMounts[mount.Destination]; ok {
   220  				return nil, nil, nil, errors.Wrapf(errDuplicateDest, mount.Destination)
   221  			}
   222  			finalMounts[mount.Destination] = mount
   223  		case "image":
   224  			volume, err := getImageVolume(tokens)
   225  			if err != nil {
   226  				return nil, nil, nil, err
   227  			}
   228  			if _, ok := finalImageVolumes[volume.Destination]; ok {
   229  				return nil, nil, nil, errors.Wrapf(errDuplicateDest, volume.Destination)
   230  			}
   231  			finalImageVolumes[volume.Destination] = volume
   232  		case "volume":
   233  			volume, err := getNamedVolume(tokens)
   234  			if err != nil {
   235  				return nil, nil, nil, err
   236  			}
   237  			if _, ok := finalNamedVolumes[volume.Dest]; ok {
   238  				return nil, nil, nil, errors.Wrapf(errDuplicateDest, volume.Dest)
   239  			}
   240  			finalNamedVolumes[volume.Dest] = volume
   241  		default:
   242  			return nil, nil, nil, errors.Errorf("invalid filesystem type %q", mountType)
   243  		}
   244  	}
   245  
   246  	return finalMounts, finalNamedVolumes, finalImageVolumes, nil
   247  }
   248  
   249  // Parse a single bind mount entry from the --mount flag.
   250  func getBindMount(args []string) (spec.Mount, error) {
   251  	newMount := spec.Mount{
   252  		Type: define.TypeBind,
   253  	}
   254  
   255  	var setSource, setDest, setRORW, setSuid, setDev, setExec, setRelabel, setOwnership bool
   256  
   257  	for _, val := range args {
   258  		kv := strings.SplitN(val, "=", 2)
   259  		switch kv[0] {
   260  		case "bind-nonrecursive":
   261  			newMount.Options = append(newMount.Options, "bind")
   262  		case "readonly", "ro", "rw":
   263  			if setRORW {
   264  				return newMount, errors.Wrapf(optionArgError, "cannot pass 'readonly', 'ro', or 'rw' options more than once")
   265  			}
   266  			setRORW = true
   267  			// Can be formatted as one of:
   268  			// readonly
   269  			// readonly=[true|false]
   270  			// ro
   271  			// ro=[true|false]
   272  			// rw
   273  			// rw=[true|false]
   274  			if kv[0] == "readonly" {
   275  				kv[0] = "ro"
   276  			}
   277  			switch len(kv) {
   278  			case 1:
   279  				newMount.Options = append(newMount.Options, kv[0])
   280  			case 2:
   281  				switch strings.ToLower(kv[1]) {
   282  				case "true":
   283  					newMount.Options = append(newMount.Options, kv[0])
   284  				case "false":
   285  					// Set the opposite only for rw
   286  					// ro's opposite is the default
   287  					if kv[0] == "rw" {
   288  						newMount.Options = append(newMount.Options, "ro")
   289  					}
   290  				default:
   291  					return newMount, errors.Wrapf(optionArgError, "'readonly', 'ro', or 'rw' must be set to true or false, instead received %q", kv[1])
   292  				}
   293  			default:
   294  				return newMount, errors.Wrapf(optionArgError, "badly formatted option %q", val)
   295  			}
   296  		case "nosuid", "suid":
   297  			if setSuid {
   298  				return newMount, errors.Wrapf(optionArgError, "cannot pass 'nosuid' and 'suid' options more than once")
   299  			}
   300  			setSuid = true
   301  			newMount.Options = append(newMount.Options, kv[0])
   302  		case "nodev", "dev":
   303  			if setDev {
   304  				return newMount, errors.Wrapf(optionArgError, "cannot pass 'nodev' and 'dev' options more than once")
   305  			}
   306  			setDev = true
   307  			newMount.Options = append(newMount.Options, kv[0])
   308  		case "noexec", "exec":
   309  			if setExec {
   310  				return newMount, errors.Wrapf(optionArgError, "cannot pass 'noexec' and 'exec' options more than once")
   311  			}
   312  			setExec = true
   313  			newMount.Options = append(newMount.Options, kv[0])
   314  		case "shared", "rshared", "private", "rprivate", "slave", "rslave", "unbindable", "runbindable", "Z", "z":
   315  			newMount.Options = append(newMount.Options, kv[0])
   316  		case "bind-propagation":
   317  			if len(kv) == 1 {
   318  				return newMount, errors.Wrapf(optionArgError, kv[0])
   319  			}
   320  			newMount.Options = append(newMount.Options, kv[1])
   321  		case "src", "source":
   322  			if len(kv) == 1 {
   323  				return newMount, errors.Wrapf(optionArgError, kv[0])
   324  			}
   325  			if len(kv[1]) == 0 {
   326  				return newMount, errors.Wrapf(optionArgError, "host directory cannot be empty")
   327  			}
   328  			newMount.Source = kv[1]
   329  			setSource = true
   330  		case "target", "dst", "destination":
   331  			if len(kv) == 1 {
   332  				return newMount, errors.Wrapf(optionArgError, kv[0])
   333  			}
   334  			if err := parse.ValidateVolumeCtrDir(kv[1]); err != nil {
   335  				return newMount, err
   336  			}
   337  			newMount.Destination = unixPathClean(kv[1])
   338  			setDest = true
   339  		case "relabel":
   340  			if setRelabel {
   341  				return newMount, errors.Wrapf(optionArgError, "cannot pass 'relabel' option more than once")
   342  			}
   343  			setRelabel = true
   344  			if len(kv) != 2 {
   345  				return newMount, errors.Wrapf(util.ErrBadMntOption, "%s mount option must be 'private' or 'shared'", kv[0])
   346  			}
   347  			switch kv[1] {
   348  			case "private":
   349  				newMount.Options = append(newMount.Options, "Z")
   350  			case "shared":
   351  				newMount.Options = append(newMount.Options, "z")
   352  			default:
   353  				return newMount, errors.Wrapf(util.ErrBadMntOption, "%s mount option must be 'private' or 'shared'", kv[0])
   354  			}
   355  		case "U", "chown":
   356  			if setOwnership {
   357  				return newMount, errors.Wrapf(optionArgError, "cannot pass 'U' or 'chown' option more than once")
   358  			}
   359  			ok, err := validChownFlag(val)
   360  			if err != nil {
   361  				return newMount, err
   362  			}
   363  			if ok {
   364  				newMount.Options = append(newMount.Options, "U")
   365  			}
   366  			setOwnership = true
   367  		case "idmap":
   368  			if len(kv) > 1 {
   369  				newMount.Options = append(newMount.Options, fmt.Sprintf("idmap=%s", kv[1]))
   370  			} else {
   371  				newMount.Options = append(newMount.Options, "idmap")
   372  			}
   373  		case "consistency":
   374  			// Often used on MACs and mistakenly on Linux platforms.
   375  			// Since Docker ignores this option so shall we.
   376  			continue
   377  		default:
   378  			return newMount, errors.Wrapf(util.ErrBadMntOption, "%s", kv[0])
   379  		}
   380  	}
   381  
   382  	if !setDest {
   383  		return newMount, noDestError
   384  	}
   385  
   386  	if !setSource {
   387  		newMount.Source = newMount.Destination
   388  	}
   389  
   390  	options, err := parse.ValidateVolumeOpts(newMount.Options)
   391  	if err != nil {
   392  		return newMount, err
   393  	}
   394  	newMount.Options = options
   395  	return newMount, nil
   396  }
   397  
   398  // Parse a single tmpfs mount entry from the --mount flag
   399  func getTmpfsMount(args []string) (spec.Mount, error) {
   400  	newMount := spec.Mount{
   401  		Type:   define.TypeTmpfs,
   402  		Source: define.TypeTmpfs,
   403  	}
   404  
   405  	var setDest, setRORW, setSuid, setDev, setExec, setTmpcopyup, setOwnership bool
   406  
   407  	for _, val := range args {
   408  		kv := strings.SplitN(val, "=", 2)
   409  		switch kv[0] {
   410  		case "tmpcopyup", "notmpcopyup":
   411  			if setTmpcopyup {
   412  				return newMount, errors.Wrapf(optionArgError, "cannot pass 'tmpcopyup' and 'notmpcopyup' options more than once")
   413  			}
   414  			setTmpcopyup = true
   415  			newMount.Options = append(newMount.Options, kv[0])
   416  		case "ro", "rw":
   417  			if setRORW {
   418  				return newMount, errors.Wrapf(optionArgError, "cannot pass 'ro' and 'rw' options more than once")
   419  			}
   420  			setRORW = true
   421  			newMount.Options = append(newMount.Options, kv[0])
   422  		case "nosuid", "suid":
   423  			if setSuid {
   424  				return newMount, errors.Wrapf(optionArgError, "cannot pass 'nosuid' and 'suid' options more than once")
   425  			}
   426  			setSuid = true
   427  			newMount.Options = append(newMount.Options, kv[0])
   428  		case "nodev", "dev":
   429  			if setDev {
   430  				return newMount, errors.Wrapf(optionArgError, "cannot pass 'nodev' and 'dev' options more than once")
   431  			}
   432  			setDev = true
   433  			newMount.Options = append(newMount.Options, kv[0])
   434  		case "noexec", "exec":
   435  			if setExec {
   436  				return newMount, errors.Wrapf(optionArgError, "cannot pass 'noexec' and 'exec' options more than once")
   437  			}
   438  			setExec = true
   439  			newMount.Options = append(newMount.Options, kv[0])
   440  		case "tmpfs-mode":
   441  			if len(kv) == 1 {
   442  				return newMount, errors.Wrapf(optionArgError, kv[0])
   443  			}
   444  			newMount.Options = append(newMount.Options, fmt.Sprintf("mode=%s", kv[1]))
   445  		case "tmpfs-size":
   446  			if len(kv) == 1 {
   447  				return newMount, errors.Wrapf(optionArgError, kv[0])
   448  			}
   449  			newMount.Options = append(newMount.Options, fmt.Sprintf("size=%s", kv[1]))
   450  		case "src", "source":
   451  			return newMount, errors.Errorf("source is not supported with tmpfs mounts")
   452  		case "target", "dst", "destination":
   453  			if len(kv) == 1 {
   454  				return newMount, errors.Wrapf(optionArgError, kv[0])
   455  			}
   456  			if err := parse.ValidateVolumeCtrDir(kv[1]); err != nil {
   457  				return newMount, err
   458  			}
   459  			newMount.Destination = unixPathClean(kv[1])
   460  			setDest = true
   461  		case "U", "chown":
   462  			if setOwnership {
   463  				return newMount, errors.Wrapf(optionArgError, "cannot pass 'U' or 'chown' option more than once")
   464  			}
   465  			ok, err := validChownFlag(val)
   466  			if err != nil {
   467  				return newMount, err
   468  			}
   469  			if ok {
   470  				newMount.Options = append(newMount.Options, "U")
   471  			}
   472  			setOwnership = true
   473  		case "consistency":
   474  			// Often used on MACs and mistakenly on Linux platforms.
   475  			// Since Docker ignores this option so shall we.
   476  			continue
   477  		default:
   478  			return newMount, errors.Wrapf(util.ErrBadMntOption, "%s", kv[0])
   479  		}
   480  	}
   481  
   482  	if !setDest {
   483  		return newMount, noDestError
   484  	}
   485  
   486  	return newMount, nil
   487  }
   488  
   489  // Parse a single devpts mount entry from the --mount flag
   490  func getDevptsMount(args []string) (spec.Mount, error) {
   491  	newMount := spec.Mount{
   492  		Type:   define.TypeDevpts,
   493  		Source: define.TypeDevpts,
   494  	}
   495  
   496  	var setDest bool
   497  
   498  	for _, val := range args {
   499  		kv := strings.SplitN(val, "=", 2)
   500  		switch kv[0] {
   501  		case "uid", "gid", "mode", "ptxmode", "newinstance", "max":
   502  			newMount.Options = append(newMount.Options, val)
   503  		case "target", "dst", "destination":
   504  			if len(kv) == 1 {
   505  				return newMount, errors.Wrapf(optionArgError, kv[0])
   506  			}
   507  			if err := parse.ValidateVolumeCtrDir(kv[1]); err != nil {
   508  				return newMount, err
   509  			}
   510  			newMount.Destination = unixPathClean(kv[1])
   511  			setDest = true
   512  		default:
   513  			return newMount, errors.Wrapf(util.ErrBadMntOption, "%s", kv[0])
   514  		}
   515  	}
   516  
   517  	if !setDest {
   518  		return newMount, noDestError
   519  	}
   520  
   521  	return newMount, nil
   522  }
   523  
   524  // Parse a single volume mount entry from the --mount flag.
   525  // Note that the volume-label option for named volumes is currently NOT supported.
   526  // TODO: add support for --volume-label
   527  func getNamedVolume(args []string) (*specgen.NamedVolume, error) {
   528  	newVolume := new(specgen.NamedVolume)
   529  
   530  	var setDest, setRORW, setSuid, setDev, setExec, setOwnership bool
   531  
   532  	for _, val := range args {
   533  		kv := strings.SplitN(val, "=", 2)
   534  		switch kv[0] {
   535  		case "volume-opt":
   536  			newVolume.Options = append(newVolume.Options, val)
   537  		case "ro", "rw":
   538  			if setRORW {
   539  				return nil, errors.Wrapf(optionArgError, "cannot pass 'ro' and 'rw' options more than once")
   540  			}
   541  			setRORW = true
   542  			newVolume.Options = append(newVolume.Options, kv[0])
   543  		case "nosuid", "suid":
   544  			if setSuid {
   545  				return nil, errors.Wrapf(optionArgError, "cannot pass 'nosuid' and 'suid' options more than once")
   546  			}
   547  			setSuid = true
   548  			newVolume.Options = append(newVolume.Options, kv[0])
   549  		case "nodev", "dev":
   550  			if setDev {
   551  				return nil, errors.Wrapf(optionArgError, "cannot pass 'nodev' and 'dev' options more than once")
   552  			}
   553  			setDev = true
   554  			newVolume.Options = append(newVolume.Options, kv[0])
   555  		case "noexec", "exec":
   556  			if setExec {
   557  				return nil, errors.Wrapf(optionArgError, "cannot pass 'noexec' and 'exec' options more than once")
   558  			}
   559  			setExec = true
   560  			newVolume.Options = append(newVolume.Options, kv[0])
   561  		case "volume-label":
   562  			return nil, errors.Errorf("the --volume-label option is not presently implemented")
   563  		case "src", "source":
   564  			if len(kv) == 1 {
   565  				return nil, errors.Wrapf(optionArgError, kv[0])
   566  			}
   567  			newVolume.Name = kv[1]
   568  		case "target", "dst", "destination":
   569  			if len(kv) == 1 {
   570  				return nil, errors.Wrapf(optionArgError, kv[0])
   571  			}
   572  			if err := parse.ValidateVolumeCtrDir(kv[1]); err != nil {
   573  				return nil, err
   574  			}
   575  			newVolume.Dest = unixPathClean(kv[1])
   576  			setDest = true
   577  		case "U", "chown":
   578  			if setOwnership {
   579  				return newVolume, errors.Wrapf(optionArgError, "cannot pass 'U' or 'chown' option more than once")
   580  			}
   581  			ok, err := validChownFlag(val)
   582  			if err != nil {
   583  				return newVolume, err
   584  			}
   585  			if ok {
   586  				newVolume.Options = append(newVolume.Options, "U")
   587  			}
   588  			setOwnership = true
   589  		case "consistency":
   590  			// Often used on MACs and mistakenly on Linux platforms.
   591  			// Since Docker ignores this option so shall we.
   592  			continue
   593  		default:
   594  			return nil, errors.Wrapf(util.ErrBadMntOption, "%s", kv[0])
   595  		}
   596  	}
   597  
   598  	if !setDest {
   599  		return nil, noDestError
   600  	}
   601  
   602  	return newVolume, nil
   603  }
   604  
   605  // Parse the arguments into an image volume. An image volume is a volume based
   606  // on a container image.  The container image is first mounted on the host and
   607  // is then bind-mounted into the container.  An ImageVolume is always mounted
   608  // read only.
   609  func getImageVolume(args []string) (*specgen.ImageVolume, error) {
   610  	newVolume := new(specgen.ImageVolume)
   611  
   612  	for _, val := range args {
   613  		kv := strings.SplitN(val, "=", 2)
   614  		switch kv[0] {
   615  		case "src", "source":
   616  			if len(kv) == 1 {
   617  				return nil, errors.Wrapf(optionArgError, kv[0])
   618  			}
   619  			newVolume.Source = kv[1]
   620  		case "target", "dst", "destination":
   621  			if len(kv) == 1 {
   622  				return nil, errors.Wrapf(optionArgError, kv[0])
   623  			}
   624  			if err := parse.ValidateVolumeCtrDir(kv[1]); err != nil {
   625  				return nil, err
   626  			}
   627  			newVolume.Destination = unixPathClean(kv[1])
   628  		case "rw", "readwrite":
   629  			switch kv[1] {
   630  			case "true":
   631  				newVolume.ReadWrite = true
   632  			case "false":
   633  				// Nothing to do. RO is default.
   634  			default:
   635  				return nil, errors.Wrapf(util.ErrBadMntOption, "invalid rw value %q", kv[1])
   636  			}
   637  		case "consistency":
   638  			// Often used on MACs and mistakenly on Linux platforms.
   639  			// Since Docker ignores this option so shall we.
   640  			continue
   641  		default:
   642  			return nil, errors.Wrapf(util.ErrBadMntOption, "%s", kv[0])
   643  		}
   644  	}
   645  
   646  	if len(newVolume.Source)*len(newVolume.Destination) == 0 {
   647  		return nil, errors.Errorf("must set source and destination for image volume")
   648  	}
   649  
   650  	return newVolume, nil
   651  }
   652  
   653  // GetTmpfsMounts creates spec.Mount structs for user-requested tmpfs mounts
   654  func getTmpfsMounts(tmpfsFlag []string) (map[string]spec.Mount, error) {
   655  	m := make(map[string]spec.Mount)
   656  	for _, i := range tmpfsFlag {
   657  		// Default options if nothing passed
   658  		var options []string
   659  		spliti := strings.Split(i, ":")
   660  		destPath := spliti[0]
   661  		if err := parse.ValidateVolumeCtrDir(spliti[0]); err != nil {
   662  			return nil, err
   663  		}
   664  		if len(spliti) > 1 {
   665  			options = strings.Split(spliti[1], ",")
   666  		}
   667  
   668  		if _, ok := m[destPath]; ok {
   669  			return nil, errors.Wrapf(errDuplicateDest, destPath)
   670  		}
   671  
   672  		mount := spec.Mount{
   673  			Destination: unixPathClean(destPath),
   674  			Type:        define.TypeTmpfs,
   675  			Options:     options,
   676  			Source:      define.TypeTmpfs,
   677  		}
   678  		m[destPath] = mount
   679  	}
   680  	return m, nil
   681  }
   682  
   683  // validChownFlag ensures that the U or chown flag is correctly used
   684  func validChownFlag(flag string) (bool, error) {
   685  	kv := strings.SplitN(flag, "=", 2)
   686  	switch len(kv) {
   687  	case 1:
   688  	case 2:
   689  		// U=[true|false]
   690  		switch strings.ToLower(kv[1]) {
   691  		case "true":
   692  		case "false":
   693  			return false, nil
   694  		default:
   695  			return false, errors.Wrapf(optionArgError, "'U' or 'chown' must be set to true or false, instead received %q", kv[1])
   696  		}
   697  	default:
   698  		return false, errors.Wrapf(optionArgError, "badly formatted option %q", flag)
   699  	}
   700  
   701  	return true, nil
   702  }
   703  
   704  // Use path instead of filepath to preserve Unix style paths on Windows
   705  func unixPathClean(p string) string {
   706  	return path.Clean(p)
   707  }