| package mounts // import "github.com/docker/docker/volume/mounts" | |
| import ( | |
| "errors" | |
| "fmt" | |
| "path" | |
| "path/filepath" | |
| "strings" | |
| "github.com/docker/docker/api/types/mount" | |
| "github.com/docker/docker/pkg/stringid" | |
| "github.com/docker/docker/volume" | |
| ) | |
| // NewLinuxParser creates a parser with Linux semantics. | |
| func NewLinuxParser() Parser { | |
| return &linuxParser{ | |
| fi: defaultFileInfoProvider{}, | |
| } | |
| } | |
| type linuxParser struct { | |
| fi fileInfoProvider | |
| } | |
| func linuxValidateNotRoot(p string) error { | |
| p = path.Clean(strings.ReplaceAll(p, `\`, `/`)) | |
| if p == "/" { | |
| return ErrVolumeTargetIsRoot | |
| } | |
| return nil | |
| } | |
| func linuxValidateAbsolute(p string) error { | |
| p = strings.ReplaceAll(p, `\`, `/`) | |
| if path.IsAbs(p) { | |
| return nil | |
| } | |
| return fmt.Errorf("invalid mount path: '%s' mount path must be absolute", p) | |
| } | |
| func (p *linuxParser) ValidateMountConfig(mnt *mount.Mount) error { | |
| // there was something looking like a bug in existing codebase: | |
| // - validateMountConfig on linux was called with options skipping bind source existence when calling ParseMountRaw | |
| // - but not when calling ParseMountSpec directly... nor when the unit test called it directly | |
| return p.validateMountConfigImpl(mnt, true) | |
| } | |
| func (p *linuxParser) validateMountConfigImpl(mnt *mount.Mount, validateBindSourceExists bool) error { | |
| if len(mnt.Target) == 0 { | |
| return &errMountConfig{mnt, errMissingField("Target")} | |
| } | |
| if err := linuxValidateNotRoot(mnt.Target); err != nil { | |
| return &errMountConfig{mnt, err} | |
| } | |
| if err := linuxValidateAbsolute(mnt.Target); err != nil { | |
| return &errMountConfig{mnt, err} | |
| } | |
| switch mnt.Type { | |
| case mount.TypeBind: | |
| if len(mnt.Source) == 0 { | |
| return &errMountConfig{mnt, errMissingField("Source")} | |
| } | |
| // Don't error out just because the propagation mode is not supported on the platform | |
| if opts := mnt.BindOptions; opts != nil { | |
| if len(opts.Propagation) > 0 && len(linuxPropagationModes) > 0 { | |
| if _, ok := linuxPropagationModes[opts.Propagation]; !ok { | |
| return &errMountConfig{mnt, fmt.Errorf("invalid propagation mode: %s", opts.Propagation)} | |
| } | |
| } | |
| } | |
| if mnt.VolumeOptions != nil { | |
| return &errMountConfig{mnt, errExtraField("VolumeOptions")} | |
| } | |
| if err := linuxValidateAbsolute(mnt.Source); err != nil { | |
| return &errMountConfig{mnt, err} | |
| } | |
| if validateBindSourceExists { | |
| exists, _, err := p.fi.fileInfo(mnt.Source) | |
| if err != nil { | |
| return &errMountConfig{mnt, err} | |
| } | |
| if !exists { | |
| return &errMountConfig{mnt, errBindSourceDoesNotExist(mnt.Source)} | |
| } | |
| } | |
| case mount.TypeVolume: | |
| if mnt.BindOptions != nil { | |
| return &errMountConfig{mnt, errExtraField("BindOptions")} | |
| } | |
| if len(mnt.Source) == 0 && mnt.ReadOnly { | |
| return &errMountConfig{mnt, fmt.Errorf("must not set ReadOnly mode when using anonymous volumes")} | |
| } | |
| case mount.TypeTmpfs: | |
| if mnt.BindOptions != nil { | |
| return &errMountConfig{mnt, errExtraField("BindOptions")} | |
| } | |
| if len(mnt.Source) != 0 { | |
| return &errMountConfig{mnt, errExtraField("Source")} | |
| } | |
| if _, err := p.ConvertTmpfsOptions(mnt.TmpfsOptions, mnt.ReadOnly); err != nil { | |
| return &errMountConfig{mnt, err} | |
| } | |
| default: | |
| return &errMountConfig{mnt, errors.New("mount type unknown")} | |
| } | |
| return nil | |
| } | |
| // label modes | |
| var linuxLabelModes = map[string]bool{ | |
| "Z": true, | |
| "z": true, | |
| } | |
| // consistency modes | |
| var linuxConsistencyModes = map[mount.Consistency]bool{ | |
| mount.ConsistencyFull: true, | |
| mount.ConsistencyCached: true, | |
| mount.ConsistencyDelegated: true, | |
| } | |
| var linuxPropagationModes = map[mount.Propagation]bool{ | |
| mount.PropagationPrivate: true, | |
| mount.PropagationRPrivate: true, | |
| mount.PropagationSlave: true, | |
| mount.PropagationRSlave: true, | |
| mount.PropagationShared: true, | |
| mount.PropagationRShared: true, | |
| } | |
| const linuxDefaultPropagationMode = mount.PropagationRPrivate | |
| func linuxGetPropagation(mode string) mount.Propagation { | |
| for _, o := range strings.Split(mode, ",") { | |
| prop := mount.Propagation(o) | |
| if linuxPropagationModes[prop] { | |
| return prop | |
| } | |
| } | |
| return linuxDefaultPropagationMode | |
| } | |
| func linuxHasPropagation(mode string) bool { | |
| for _, o := range strings.Split(mode, ",") { | |
| if linuxPropagationModes[mount.Propagation(o)] { | |
| return true | |
| } | |
| } | |
| return false | |
| } | |
| func linuxValidMountMode(mode string) bool { | |
| if mode == "" { | |
| return true | |
| } | |
| rwModeCount := 0 | |
| labelModeCount := 0 | |
| propagationModeCount := 0 | |
| copyModeCount := 0 | |
| consistencyModeCount := 0 | |
| for _, o := range strings.Split(mode, ",") { | |
| switch { | |
| case rwModes[o]: | |
| rwModeCount++ | |
| case linuxLabelModes[o]: | |
| labelModeCount++ | |
| case linuxPropagationModes[mount.Propagation(o)]: | |
| propagationModeCount++ | |
| case copyModeExists(o): | |
| copyModeCount++ | |
| case linuxConsistencyModes[mount.Consistency(o)]: | |
| consistencyModeCount++ | |
| default: | |
| return false | |
| } | |
| } | |
| // Only one string for each mode is allowed. | |
| if rwModeCount > 1 || labelModeCount > 1 || propagationModeCount > 1 || copyModeCount > 1 || consistencyModeCount > 1 { | |
| return false | |
| } | |
| return true | |
| } | |
| func (p *linuxParser) ReadWrite(mode string) bool { | |
| if !linuxValidMountMode(mode) { | |
| return false | |
| } | |
| for _, o := range strings.Split(mode, ",") { | |
| if o == "ro" { | |
| return false | |
| } | |
| } | |
| return true | |
| } | |
| func (p *linuxParser) ParseMountRaw(raw, volumeDriver string) (*MountPoint, error) { | |
| arr := strings.SplitN(raw, ":", 4) | |
| if arr[0] == "" { | |
| return nil, errInvalidSpec(raw) | |
| } | |
| var spec mount.Mount | |
| var mode string | |
| switch len(arr) { | |
| case 1: | |
| // Just a destination path in the container | |
| spec.Target = arr[0] | |
| case 2: | |
| if linuxValidMountMode(arr[1]) { | |
| // Destination + Mode is not a valid volume - volumes | |
| // cannot include a mode. e.g. /foo:rw | |
| return nil, errInvalidSpec(raw) | |
| } | |
| // Host Source Path or Name + Destination | |
| spec.Source = arr[0] | |
| spec.Target = arr[1] | |
| case 3: | |
| // HostSourcePath+DestinationPath+Mode | |
| spec.Source = arr[0] | |
| spec.Target = arr[1] | |
| mode = arr[2] | |
| default: | |
| return nil, errInvalidSpec(raw) | |
| } | |
| if !linuxValidMountMode(mode) { | |
| return nil, errInvalidMode(mode) | |
| } | |
| if path.IsAbs(spec.Source) { | |
| spec.Type = mount.TypeBind | |
| } else { | |
| spec.Type = mount.TypeVolume | |
| } | |
| spec.ReadOnly = !p.ReadWrite(mode) | |
| // cannot assume that if a volume driver is passed in that we should set it | |
| if volumeDriver != "" && spec.Type == mount.TypeVolume { | |
| spec.VolumeOptions = &mount.VolumeOptions{ | |
| DriverConfig: &mount.Driver{Name: volumeDriver}, | |
| } | |
| } | |
| if copyData, isSet := getCopyMode(mode, p.DefaultCopyMode()); isSet { | |
| if spec.VolumeOptions == nil { | |
| spec.VolumeOptions = &mount.VolumeOptions{} | |
| } | |
| spec.VolumeOptions.NoCopy = !copyData | |
| } | |
| if linuxHasPropagation(mode) { | |
| spec.BindOptions = &mount.BindOptions{ | |
| Propagation: linuxGetPropagation(mode), | |
| } | |
| } | |
| mp, err := p.parseMountSpec(spec, false) | |
| if mp != nil { | |
| mp.Mode = mode | |
| } | |
| if err != nil { | |
| err = fmt.Errorf("%v: %v", errInvalidSpec(raw), err) | |
| } | |
| return mp, err | |
| } | |
| func (p *linuxParser) ParseMountSpec(cfg mount.Mount) (*MountPoint, error) { | |
| return p.parseMountSpec(cfg, true) | |
| } | |
| func (p *linuxParser) parseMountSpec(cfg mount.Mount, validateBindSourceExists bool) (*MountPoint, error) { | |
| if err := p.validateMountConfigImpl(&cfg, validateBindSourceExists); err != nil { | |
| return nil, err | |
| } | |
| mp := &MountPoint{ | |
| RW: !cfg.ReadOnly, | |
| Destination: path.Clean(filepath.ToSlash(cfg.Target)), | |
| Type: cfg.Type, | |
| Spec: cfg, | |
| } | |
| switch cfg.Type { | |
| case mount.TypeVolume: | |
| if cfg.Source == "" { | |
| mp.Name = stringid.GenerateRandomID() | |
| } else { | |
| mp.Name = cfg.Source | |
| } | |
| mp.CopyData = p.DefaultCopyMode() | |
| if cfg.VolumeOptions != nil { | |
| if cfg.VolumeOptions.DriverConfig != nil { | |
| mp.Driver = cfg.VolumeOptions.DriverConfig.Name | |
| } | |
| if cfg.VolumeOptions.NoCopy { | |
| mp.CopyData = false | |
| } | |
| } | |
| case mount.TypeBind: | |
| mp.Source = path.Clean(filepath.ToSlash(cfg.Source)) | |
| if cfg.BindOptions != nil && len(cfg.BindOptions.Propagation) > 0 { | |
| mp.Propagation = cfg.BindOptions.Propagation | |
| } else { | |
| // If user did not specify a propagation mode, get | |
| // default propagation mode. | |
| mp.Propagation = linuxDefaultPropagationMode | |
| } | |
| case mount.TypeTmpfs: | |
| // NOP | |
| } | |
| return mp, nil | |
| } | |
| func (p *linuxParser) ParseVolumesFrom(spec string) (string, string, error) { | |
| if len(spec) == 0 { | |
| return "", "", fmt.Errorf("volumes-from specification cannot be an empty string") | |
| } | |
| id, mode, _ := strings.Cut(spec, ":") | |
| if mode == "" { | |
| return id, "rw", nil | |
| } | |
| if !linuxValidMountMode(mode) { | |
| return "", "", errInvalidMode(mode) | |
| } | |
| // For now don't allow propagation properties while importing | |
| // volumes from data container. These volumes will inherit | |
| // the same propagation property as of the original volume | |
| // in data container. This probably can be relaxed in future. | |
| if linuxHasPropagation(mode) { | |
| return "", "", errInvalidMode(mode) | |
| } | |
| // Do not allow copy modes on volumes-from | |
| if _, isSet := getCopyMode(mode, p.DefaultCopyMode()); isSet { | |
| return "", "", errInvalidMode(mode) | |
| } | |
| return id, mode, nil | |
| } | |
| func (p *linuxParser) DefaultPropagationMode() mount.Propagation { | |
| return linuxDefaultPropagationMode | |
| } | |
| func (p *linuxParser) ConvertTmpfsOptions(opt *mount.TmpfsOptions, readOnly bool) (string, error) { | |
| var rawOpts []string | |
| if readOnly { | |
| rawOpts = append(rawOpts, "ro") | |
| } | |
| if opt != nil && opt.Mode != 0 { | |
| rawOpts = append(rawOpts, fmt.Sprintf("mode=%o", opt.Mode)) | |
| } | |
| if opt != nil && opt.SizeBytes != 0 { | |
| // calculate suffix here, making this linux specific, but that is | |
| // okay, since API is that way anyways. | |
| // we do this by finding the suffix that divides evenly into the | |
| // value, returning the value itself, with no suffix, if it fails. | |
| // | |
| // For the most part, we don't enforce any semantic to this values. | |
| // The operating system will usually align this and enforce minimum | |
| // and maximums. | |
| var ( | |
| size = opt.SizeBytes | |
| suffix string | |
| ) | |
| for _, r := range []struct { | |
| suffix string | |
| divisor int64 | |
| }{ | |
| {"g", 1 << 30}, | |
| {"m", 1 << 20}, | |
| {"k", 1 << 10}, | |
| } { | |
| if size%r.divisor == 0 { | |
| size = size / r.divisor | |
| suffix = r.suffix | |
| break | |
| } | |
| } | |
| rawOpts = append(rawOpts, fmt.Sprintf("size=%d%s", size, suffix)) | |
| } | |
| return strings.Join(rawOpts, ","), nil | |
| } | |
| func (p *linuxParser) DefaultCopyMode() bool { | |
| return true | |
| } | |
| func (p *linuxParser) ValidateVolumeName(name string) error { | |
| return nil | |
| } | |
| func (p *linuxParser) IsBackwardCompatible(m *MountPoint) bool { | |
| return len(m.Source) > 0 || m.Driver == volume.DefaultDriverName | |
| } | |
| func (p *linuxParser) ValidateTmpfsMountDestination(dest string) error { | |
| if err := linuxValidateNotRoot(dest); err != nil { | |
| return err | |
| } | |
| return linuxValidateAbsolute(dest) | |
| } |