github.com/rish1988/moby@v25.0.2+incompatible/volume/mounts/linux_parser.go (about)

     1  package mounts // import "github.com/docker/docker/volume/mounts"
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"path"
     7  	"path/filepath"
     8  	"strings"
     9  
    10  	"github.com/docker/docker/api/types/mount"
    11  	"github.com/docker/docker/pkg/stringid"
    12  	"github.com/docker/docker/volume"
    13  )
    14  
    15  // NewLinuxParser creates a parser with Linux semantics.
    16  func NewLinuxParser() Parser {
    17  	return &linuxParser{
    18  		fi: defaultFileInfoProvider{},
    19  	}
    20  }
    21  
    22  type linuxParser struct {
    23  	fi fileInfoProvider
    24  }
    25  
    26  func linuxValidateNotRoot(p string) error {
    27  	p = path.Clean(strings.ReplaceAll(p, `\`, `/`))
    28  	if p == "/" {
    29  		return ErrVolumeTargetIsRoot
    30  	}
    31  	return nil
    32  }
    33  
    34  func linuxValidateAbsolute(p string) error {
    35  	p = strings.ReplaceAll(p, `\`, `/`)
    36  	if path.IsAbs(p) {
    37  		return nil
    38  	}
    39  	return fmt.Errorf("invalid mount path: '%s' mount path must be absolute", p)
    40  }
    41  
    42  func (p *linuxParser) ValidateMountConfig(mnt *mount.Mount) error {
    43  	// there was something looking like a bug in existing codebase:
    44  	// - validateMountConfig on linux was called with options skipping bind source existence when calling ParseMountRaw
    45  	// - but not when calling ParseMountSpec directly... nor when the unit test called it directly
    46  	return p.validateMountConfigImpl(mnt, true)
    47  }
    48  
    49  func (p *linuxParser) validateMountConfigImpl(mnt *mount.Mount, validateBindSourceExists bool) error {
    50  	if len(mnt.Target) == 0 {
    51  		return &errMountConfig{mnt, errMissingField("Target")}
    52  	}
    53  
    54  	if err := linuxValidateNotRoot(mnt.Target); err != nil {
    55  		return &errMountConfig{mnt, err}
    56  	}
    57  
    58  	if err := linuxValidateAbsolute(mnt.Target); err != nil {
    59  		return &errMountConfig{mnt, err}
    60  	}
    61  
    62  	switch mnt.Type {
    63  	case mount.TypeBind:
    64  		if len(mnt.Source) == 0 {
    65  			return &errMountConfig{mnt, errMissingField("Source")}
    66  		}
    67  		// Don't error out just because the propagation mode is not supported on the platform
    68  		if opts := mnt.BindOptions; opts != nil {
    69  			if len(opts.Propagation) > 0 && len(linuxPropagationModes) > 0 {
    70  				if _, ok := linuxPropagationModes[opts.Propagation]; !ok {
    71  					return &errMountConfig{mnt, fmt.Errorf("invalid propagation mode: %s", opts.Propagation)}
    72  				}
    73  			}
    74  		}
    75  		if mnt.VolumeOptions != nil {
    76  			return &errMountConfig{mnt, errExtraField("VolumeOptions")}
    77  		}
    78  
    79  		if err := linuxValidateAbsolute(mnt.Source); err != nil {
    80  			return &errMountConfig{mnt, err}
    81  		}
    82  
    83  		if validateBindSourceExists {
    84  			exists, _, err := p.fi.fileInfo(mnt.Source)
    85  			if err != nil {
    86  				return &errMountConfig{mnt, err}
    87  			}
    88  			if !exists {
    89  				return &errMountConfig{mnt, errBindSourceDoesNotExist(mnt.Source)}
    90  			}
    91  		}
    92  
    93  	case mount.TypeVolume:
    94  		if mnt.BindOptions != nil {
    95  			return &errMountConfig{mnt, errExtraField("BindOptions")}
    96  		}
    97  		anonymousVolume := len(mnt.Source) == 0
    98  
    99  		if mnt.VolumeOptions != nil && mnt.VolumeOptions.Subpath != "" {
   100  			if anonymousVolume {
   101  				return &errMountConfig{mnt, errAnonymousVolumeWithSubpath}
   102  			}
   103  
   104  			if !filepath.IsLocal(mnt.VolumeOptions.Subpath) {
   105  				return &errMountConfig{mnt, errInvalidSubpath}
   106  			}
   107  		}
   108  		if mnt.ReadOnly && anonymousVolume {
   109  			return &errMountConfig{mnt, fmt.Errorf("must not set ReadOnly mode when using anonymous volumes")}
   110  		}
   111  	case mount.TypeTmpfs:
   112  		if mnt.BindOptions != nil {
   113  			return &errMountConfig{mnt, errExtraField("BindOptions")}
   114  		}
   115  		if len(mnt.Source) != 0 {
   116  			return &errMountConfig{mnt, errExtraField("Source")}
   117  		}
   118  		if _, err := p.ConvertTmpfsOptions(mnt.TmpfsOptions, mnt.ReadOnly); err != nil {
   119  			return &errMountConfig{mnt, err}
   120  		}
   121  	default:
   122  		return &errMountConfig{mnt, errors.New("mount type unknown")}
   123  	}
   124  	return nil
   125  }
   126  
   127  // label modes
   128  var linuxLabelModes = map[string]bool{
   129  	"Z": true,
   130  	"z": true,
   131  }
   132  
   133  // consistency modes
   134  var linuxConsistencyModes = map[mount.Consistency]bool{
   135  	mount.ConsistencyFull:      true,
   136  	mount.ConsistencyCached:    true,
   137  	mount.ConsistencyDelegated: true,
   138  }
   139  
   140  var linuxPropagationModes = map[mount.Propagation]bool{
   141  	mount.PropagationPrivate:  true,
   142  	mount.PropagationRPrivate: true,
   143  	mount.PropagationSlave:    true,
   144  	mount.PropagationRSlave:   true,
   145  	mount.PropagationShared:   true,
   146  	mount.PropagationRShared:  true,
   147  }
   148  
   149  const linuxDefaultPropagationMode = mount.PropagationRPrivate
   150  
   151  func linuxGetPropagation(mode string) mount.Propagation {
   152  	for _, o := range strings.Split(mode, ",") {
   153  		prop := mount.Propagation(o)
   154  		if linuxPropagationModes[prop] {
   155  			return prop
   156  		}
   157  	}
   158  	return linuxDefaultPropagationMode
   159  }
   160  
   161  func linuxHasPropagation(mode string) bool {
   162  	for _, o := range strings.Split(mode, ",") {
   163  		if linuxPropagationModes[mount.Propagation(o)] {
   164  			return true
   165  		}
   166  	}
   167  	return false
   168  }
   169  
   170  func linuxValidMountMode(mode string) bool {
   171  	if mode == "" {
   172  		return true
   173  	}
   174  
   175  	rwModeCount := 0
   176  	labelModeCount := 0
   177  	propagationModeCount := 0
   178  	copyModeCount := 0
   179  	consistencyModeCount := 0
   180  
   181  	for _, o := range strings.Split(mode, ",") {
   182  		switch {
   183  		case rwModes[o]:
   184  			rwModeCount++
   185  		case linuxLabelModes[o]:
   186  			labelModeCount++
   187  		case linuxPropagationModes[mount.Propagation(o)]:
   188  			propagationModeCount++
   189  		case copyModeExists(o):
   190  			copyModeCount++
   191  		case linuxConsistencyModes[mount.Consistency(o)]:
   192  			consistencyModeCount++
   193  		default:
   194  			return false
   195  		}
   196  	}
   197  
   198  	// Only one string for each mode is allowed.
   199  	if rwModeCount > 1 || labelModeCount > 1 || propagationModeCount > 1 || copyModeCount > 1 || consistencyModeCount > 1 {
   200  		return false
   201  	}
   202  	return true
   203  }
   204  
   205  func (p *linuxParser) ReadWrite(mode string) bool {
   206  	if !linuxValidMountMode(mode) {
   207  		return false
   208  	}
   209  
   210  	for _, o := range strings.Split(mode, ",") {
   211  		if o == "ro" {
   212  			return false
   213  		}
   214  	}
   215  	return true
   216  }
   217  
   218  func (p *linuxParser) ParseMountRaw(raw, volumeDriver string) (*MountPoint, error) {
   219  	arr := strings.SplitN(raw, ":", 4)
   220  	if arr[0] == "" {
   221  		return nil, errInvalidSpec(raw)
   222  	}
   223  
   224  	var spec mount.Mount
   225  	var mode string
   226  	switch len(arr) {
   227  	case 1:
   228  		// Just a destination path in the container
   229  		spec.Target = arr[0]
   230  	case 2:
   231  		if linuxValidMountMode(arr[1]) {
   232  			// Destination + Mode is not a valid volume - volumes
   233  			// cannot include a mode. e.g. /foo:rw
   234  			return nil, errInvalidSpec(raw)
   235  		}
   236  		// Host Source Path or Name + Destination
   237  		spec.Source = arr[0]
   238  		spec.Target = arr[1]
   239  	case 3:
   240  		// HostSourcePath+DestinationPath+Mode
   241  		spec.Source = arr[0]
   242  		spec.Target = arr[1]
   243  		mode = arr[2]
   244  	default:
   245  		return nil, errInvalidSpec(raw)
   246  	}
   247  
   248  	if !linuxValidMountMode(mode) {
   249  		return nil, errInvalidMode(mode)
   250  	}
   251  
   252  	if path.IsAbs(spec.Source) {
   253  		spec.Type = mount.TypeBind
   254  	} else {
   255  		spec.Type = mount.TypeVolume
   256  	}
   257  
   258  	spec.ReadOnly = !p.ReadWrite(mode)
   259  
   260  	// cannot assume that if a volume driver is passed in that we should set it
   261  	if volumeDriver != "" && spec.Type == mount.TypeVolume {
   262  		spec.VolumeOptions = &mount.VolumeOptions{
   263  			DriverConfig: &mount.Driver{Name: volumeDriver},
   264  		}
   265  	}
   266  
   267  	if copyData, isSet := getCopyMode(mode, p.DefaultCopyMode()); isSet {
   268  		if spec.VolumeOptions == nil {
   269  			spec.VolumeOptions = &mount.VolumeOptions{}
   270  		}
   271  		spec.VolumeOptions.NoCopy = !copyData
   272  	}
   273  	if linuxHasPropagation(mode) {
   274  		spec.BindOptions = &mount.BindOptions{
   275  			Propagation: linuxGetPropagation(mode),
   276  		}
   277  	}
   278  
   279  	mp, err := p.parseMountSpec(spec, false)
   280  	if mp != nil {
   281  		mp.Mode = mode
   282  	}
   283  	if err != nil {
   284  		err = fmt.Errorf("%v: %v", errInvalidSpec(raw), err)
   285  	}
   286  	return mp, err
   287  }
   288  
   289  func (p *linuxParser) ParseMountSpec(cfg mount.Mount) (*MountPoint, error) {
   290  	return p.parseMountSpec(cfg, true)
   291  }
   292  
   293  func (p *linuxParser) parseMountSpec(cfg mount.Mount, validateBindSourceExists bool) (*MountPoint, error) {
   294  	if err := p.validateMountConfigImpl(&cfg, validateBindSourceExists); err != nil {
   295  		return nil, err
   296  	}
   297  	mp := &MountPoint{
   298  		RW:          !cfg.ReadOnly,
   299  		Destination: path.Clean(filepath.ToSlash(cfg.Target)),
   300  		Type:        cfg.Type,
   301  		Spec:        cfg,
   302  	}
   303  
   304  	switch cfg.Type {
   305  	case mount.TypeVolume:
   306  		if cfg.Source == "" {
   307  			mp.Name = stringid.GenerateRandomID()
   308  		} else {
   309  			mp.Name = cfg.Source
   310  		}
   311  		mp.CopyData = p.DefaultCopyMode()
   312  
   313  		if cfg.VolumeOptions != nil {
   314  			if cfg.VolumeOptions.DriverConfig != nil {
   315  				mp.Driver = cfg.VolumeOptions.DriverConfig.Name
   316  			}
   317  			if cfg.VolumeOptions.NoCopy {
   318  				mp.CopyData = false
   319  			}
   320  		}
   321  	case mount.TypeBind:
   322  		mp.Source = path.Clean(filepath.ToSlash(cfg.Source))
   323  		if cfg.BindOptions != nil && len(cfg.BindOptions.Propagation) > 0 {
   324  			mp.Propagation = cfg.BindOptions.Propagation
   325  		} else {
   326  			// If user did not specify a propagation mode, get
   327  			// default propagation mode.
   328  			mp.Propagation = linuxDefaultPropagationMode
   329  		}
   330  	case mount.TypeTmpfs:
   331  		// NOP
   332  	}
   333  	return mp, nil
   334  }
   335  
   336  func (p *linuxParser) ParseVolumesFrom(spec string) (string, string, error) {
   337  	if len(spec) == 0 {
   338  		return "", "", fmt.Errorf("volumes-from specification cannot be an empty string")
   339  	}
   340  
   341  	id, mode, _ := strings.Cut(spec, ":")
   342  	if mode == "" {
   343  		return id, "rw", nil
   344  	}
   345  	if !linuxValidMountMode(mode) {
   346  		return "", "", errInvalidMode(mode)
   347  	}
   348  	// For now don't allow propagation properties while importing
   349  	// volumes from data container. These volumes will inherit
   350  	// the same propagation property as of the original volume
   351  	// in data container. This probably can be relaxed in future.
   352  	if linuxHasPropagation(mode) {
   353  		return "", "", errInvalidMode(mode)
   354  	}
   355  	// Do not allow copy modes on volumes-from
   356  	if _, isSet := getCopyMode(mode, p.DefaultCopyMode()); isSet {
   357  		return "", "", errInvalidMode(mode)
   358  	}
   359  	return id, mode, nil
   360  }
   361  
   362  func (p *linuxParser) DefaultPropagationMode() mount.Propagation {
   363  	return linuxDefaultPropagationMode
   364  }
   365  
   366  func (p *linuxParser) ConvertTmpfsOptions(opt *mount.TmpfsOptions, readOnly bool) (string, error) {
   367  	var rawOpts []string
   368  	if readOnly {
   369  		rawOpts = append(rawOpts, "ro")
   370  	}
   371  
   372  	if opt != nil && opt.Mode != 0 {
   373  		rawOpts = append(rawOpts, fmt.Sprintf("mode=%o", opt.Mode))
   374  	}
   375  
   376  	if opt != nil && opt.SizeBytes != 0 {
   377  		// calculate suffix here, making this linux specific, but that is
   378  		// okay, since API is that way anyways.
   379  
   380  		// we do this by finding the suffix that divides evenly into the
   381  		// value, returning the value itself, with no suffix, if it fails.
   382  		//
   383  		// For the most part, we don't enforce any semantic to this values.
   384  		// The operating system will usually align this and enforce minimum
   385  		// and maximums.
   386  		var (
   387  			size   = opt.SizeBytes
   388  			suffix string
   389  		)
   390  		for _, r := range []struct {
   391  			suffix  string
   392  			divisor int64
   393  		}{
   394  			{"g", 1 << 30},
   395  			{"m", 1 << 20},
   396  			{"k", 1 << 10},
   397  		} {
   398  			if size%r.divisor == 0 {
   399  				size = size / r.divisor
   400  				suffix = r.suffix
   401  				break
   402  			}
   403  		}
   404  
   405  		rawOpts = append(rawOpts, fmt.Sprintf("size=%d%s", size, suffix))
   406  	}
   407  	return strings.Join(rawOpts, ","), nil
   408  }
   409  
   410  func (p *linuxParser) DefaultCopyMode() bool {
   411  	return true
   412  }
   413  
   414  func (p *linuxParser) ValidateVolumeName(name string) error {
   415  	return nil
   416  }
   417  
   418  func (p *linuxParser) IsBackwardCompatible(m *MountPoint) bool {
   419  	return len(m.Source) > 0 || m.Driver == volume.DefaultDriverName
   420  }
   421  
   422  func (p *linuxParser) ValidateTmpfsMountDestination(dest string) error {
   423  	if err := linuxValidateNotRoot(dest); err != nil {
   424  		return err
   425  	}
   426  	return linuxValidateAbsolute(dest)
   427  }