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