vendor docker/docker volume utils

This commit is contained in:
Mahmood Ali
2019-02-24 21:00:59 -05:00
parent 2764fc8c3c
commit e8a4610017
11 changed files with 1286 additions and 0 deletions

View File

@@ -0,0 +1,34 @@
package mounts // import "github.com/docker/docker/volume/mounts"
import (
"errors"
"path"
"github.com/docker/docker/api/types/mount"
)
var lcowSpecificValidators mountValidator = func(m *mount.Mount) error {
if path.Clean(m.Target) == "/" {
return ErrVolumeTargetIsRoot
}
if m.Type == mount.TypeNamedPipe {
return errors.New("Linux containers on Windows do not support named pipe mounts")
}
return nil
}
type lcowParser struct {
windowsParser
}
func (p *lcowParser) ValidateMountConfig(mnt *mount.Mount) error {
return p.validateMountConfigReg(mnt, rxLCOWDestination, lcowSpecificValidators)
}
func (p *lcowParser) ParseMountRaw(raw, volumeDriver string) (*MountPoint, error) {
return p.parseMountRaw(raw, volumeDriver, rxLCOWDestination, false, lcowSpecificValidators)
}
func (p *lcowParser) ParseMountSpec(cfg mount.Mount) (*MountPoint, error) {
return p.parseMountSpec(cfg, rxLCOWDestination, false, lcowSpecificValidators)
}

View File

@@ -0,0 +1,420 @@
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"
)
type linuxParser struct {
}
func linuxSplitRawSpec(raw string) ([]string, error) {
if strings.Count(raw, ":") > 2 {
return nil, errInvalidSpec(raw)
}
arr := strings.SplitN(raw, ":", 3)
if arr[0] == "" {
return nil, errInvalidSpec(raw)
}
return arr, nil
}
func linuxValidateNotRoot(p string) error {
p = path.Clean(strings.Replace(p, `\`, `/`, -1))
if p == "/" {
return ErrVolumeTargetIsRoot
}
return nil
}
func linuxValidateAbsolute(p string) error {
p = strings.Replace(p, `\`, `/`, -1)
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, _, _ := currentFileInfoProvider.fileInfo(mnt.Source)
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
}
// read-write modes
var rwModes = map[string]bool{
"rw": true,
"ro": true,
}
// 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, err := linuxSplitRawSpec(raw)
if err != nil {
return nil, err
}
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.GenerateNonCryptoID()
} 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")
}
specParts := strings.SplitN(spec, ":", 2)
id := specParts[0]
mode := "rw"
if len(specParts) == 2 {
mode = specParts[1]
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)
}

181
vendor/github.com/docker/docker/volume/mounts/mounts.go generated vendored Normal file
View File

@@ -0,0 +1,181 @@
package mounts // import "github.com/docker/docker/volume/mounts"
import (
"fmt"
"os"
"path/filepath"
"syscall"
mounttypes "github.com/docker/docker/api/types/mount"
"github.com/docker/docker/pkg/idtools"
"github.com/docker/docker/pkg/stringid"
"github.com/docker/docker/volume"
"github.com/opencontainers/selinux/go-selinux/label"
"github.com/pkg/errors"
)
// MountPoint is the intersection point between a volume and a container. It
// specifies which volume is to be used and where inside a container it should
// be mounted.
//
// Note that this type is embedded in `container.Container` object and persisted to disk.
// Changes to this struct need to by synced with on disk state.
type MountPoint struct {
// Source is the source path of the mount.
// E.g. `mount --bind /foo /bar`, `/foo` is the `Source`.
Source string
// Destination is the path relative to the container root (`/`) to the mount point
// It is where the `Source` is mounted to
Destination string
// RW is set to true when the mountpoint should be mounted as read-write
RW bool
// Name is the name reference to the underlying data defined by `Source`
// e.g., the volume name
Name string
// Driver is the volume driver used to create the volume (if it is a volume)
Driver string
// Type of mount to use, see `Type<foo>` definitions in github.com/docker/docker/api/types/mount
Type mounttypes.Type `json:",omitempty"`
// Volume is the volume providing data to this mountpoint.
// This is nil unless `Type` is set to `TypeVolume`
Volume volume.Volume `json:"-"`
// Mode is the comma separated list of options supplied by the user when creating
// the bind/volume mount.
// Note Mode is not used on Windows
Mode string `json:"Relabel,omitempty"` // Originally field was `Relabel`"
// Propagation describes how the mounts are propagated from the host into the
// mount point, and vice-versa.
// See https://www.kernel.org/doc/Documentation/filesystems/sharedsubtree.txt
// Note Propagation is not used on Windows
Propagation mounttypes.Propagation `json:",omitempty"` // Mount propagation string
// Specifies if data should be copied from the container before the first mount
// Use a pointer here so we can tell if the user set this value explicitly
// This allows us to error out when the user explicitly enabled copy but we can't copy due to the volume being populated
CopyData bool `json:"-"`
// ID is the opaque ID used to pass to the volume driver.
// This should be set by calls to `Mount` and unset by calls to `Unmount`
ID string `json:",omitempty"`
// Sepc is a copy of the API request that created this mount.
Spec mounttypes.Mount
// Some bind mounts should not be automatically created.
// (Some are auto-created for backwards-compatibility)
// This is checked on the API but setting this here prevents race conditions.
// where a bind dir existed during validation was removed before reaching the setup code.
SkipMountpointCreation bool
// Track usage of this mountpoint
// Specifically needed for containers which are running and calls to `docker cp`
// because both these actions require mounting the volumes.
active int
}
// Cleanup frees resources used by the mountpoint
func (m *MountPoint) Cleanup() error {
if m.Volume == nil || m.ID == "" {
return nil
}
if err := m.Volume.Unmount(m.ID); err != nil {
return errors.Wrapf(err, "error unmounting volume %s", m.Volume.Name())
}
m.active--
if m.active == 0 {
m.ID = ""
}
return nil
}
// Setup sets up a mount point by either mounting the volume if it is
// configured, or creating the source directory if supplied.
// The, optional, checkFun parameter allows doing additional checking
// before creating the source directory on the host.
func (m *MountPoint) Setup(mountLabel string, rootIDs idtools.Identity, checkFun func(m *MountPoint) error) (path string, err error) {
if m.SkipMountpointCreation {
return m.Source, nil
}
defer func() {
if err != nil || !label.RelabelNeeded(m.Mode) {
return
}
var sourcePath string
sourcePath, err = filepath.EvalSymlinks(m.Source)
if err != nil {
path = ""
err = errors.Wrapf(err, "error evaluating symlinks from mount source %q", m.Source)
return
}
err = label.Relabel(sourcePath, mountLabel, label.IsShared(m.Mode))
if err == syscall.ENOTSUP {
err = nil
}
if err != nil {
path = ""
err = errors.Wrapf(err, "error setting label on mount source '%s'", sourcePath)
}
}()
if m.Volume != nil {
id := m.ID
if id == "" {
id = stringid.GenerateNonCryptoID()
}
path, err := m.Volume.Mount(id)
if err != nil {
return "", errors.Wrapf(err, "error while mounting volume '%s'", m.Source)
}
m.ID = id
m.active++
return path, nil
}
if len(m.Source) == 0 {
return "", fmt.Errorf("Unable to setup mount point, neither source nor volume defined")
}
if m.Type == mounttypes.TypeBind {
// Before creating the source directory on the host, invoke checkFun if it's not nil. One of
// the use case is to forbid creating the daemon socket as a directory if the daemon is in
// the process of shutting down.
if checkFun != nil {
if err := checkFun(m); err != nil {
return "", err
}
}
// idtools.MkdirAllNewAs() produces an error if m.Source exists and is a file (not a directory)
// also, makes sure that if the directory is created, the correct remapped rootUID/rootGID will own it
if err := idtools.MkdirAllAndChownNew(m.Source, 0755, rootIDs); err != nil {
if perr, ok := err.(*os.PathError); ok {
if perr.Err != syscall.ENOTDIR {
return "", errors.Wrapf(err, "error while creating mount source path '%s'", m.Source)
}
}
}
}
return m.Source, nil
}
// Path returns the path of a volume in a mount point.
func (m *MountPoint) Path() string {
if m.Volume != nil {
return m.Volume.Path()
}
return m.Source
}
func errInvalidMode(mode string) error {
return errors.Errorf("invalid mode: %v", mode)
}
func errInvalidSpec(spec string) error {
return errors.Errorf("invalid volume specification: '%s'", spec)
}

View File

@@ -0,0 +1,47 @@
package mounts // import "github.com/docker/docker/volume/mounts"
import (
"errors"
"runtime"
"github.com/docker/docker/api/types/mount"
)
const (
// OSLinux is the same as runtime.GOOS on linux
OSLinux = "linux"
// OSWindows is the same as runtime.GOOS on windows
OSWindows = "windows"
)
// ErrVolumeTargetIsRoot is returned when the target destination is root.
// It's used by both LCOW and Linux parsers.
var ErrVolumeTargetIsRoot = errors.New("invalid specification: destination can't be '/'")
// Parser represents a platform specific parser for mount expressions
type Parser interface {
ParseMountRaw(raw, volumeDriver string) (*MountPoint, error)
ParseMountSpec(cfg mount.Mount) (*MountPoint, error)
ParseVolumesFrom(spec string) (string, string, error)
DefaultPropagationMode() mount.Propagation
ConvertTmpfsOptions(opt *mount.TmpfsOptions, readOnly bool) (string, error)
DefaultCopyMode() bool
ValidateVolumeName(name string) error
ReadWrite(mode string) bool
IsBackwardCompatible(m *MountPoint) bool
HasResource(m *MountPoint, absPath string) bool
ValidateTmpfsMountDestination(dest string) error
ValidateMountConfig(mt *mount.Mount) error
}
// NewParser creates a parser for a given container OS, depending on the current host OS (linux on a windows host will resolve to an lcowParser)
func NewParser(containerOS string) Parser {
switch containerOS {
case OSWindows:
return &windowsParser{}
}
if runtime.GOOS == OSWindows {
return &lcowParser{}
}
return &linuxParser{}
}

View File

@@ -0,0 +1,28 @@
package mounts // import "github.com/docker/docker/volume/mounts"
import (
"fmt"
"github.com/docker/docker/api/types/mount"
"github.com/pkg/errors"
)
type errMountConfig struct {
mount *mount.Mount
err error
}
func (e *errMountConfig) Error() string {
return fmt.Sprintf("invalid mount config for type %q: %v", e.mount.Type, e.err.Error())
}
func errBindSourceDoesNotExist(path string) error {
return errors.Errorf("bind source path does not exist: %s", path)
}
func errExtraField(name string) error {
return errors.Errorf("field %s must not be specified", name)
}
func errMissingField(name string) error {
return errors.Errorf("field %s must not be empty", name)
}

View File

@@ -0,0 +1,23 @@
package mounts // import "github.com/docker/docker/volume/mounts"
import "strings"
// {<copy mode>=isEnabled}
var copyModes = map[string]bool{
"nocopy": false,
}
func copyModeExists(mode string) bool {
_, exists := copyModes[mode]
return exists
}
// GetCopyMode gets the copy mode from the mode string for mounts
func getCopyMode(mode string, def bool) (bool, bool) {
for _, o := range strings.Split(mode, ",") {
if isEnabled, exists := copyModes[o]; exists {
return isEnabled, true
}
}
return def, false
}

View File

@@ -0,0 +1,18 @@
// +build linux freebsd darwin
package mounts // import "github.com/docker/docker/volume/mounts"
import (
"fmt"
"path/filepath"
"strings"
)
func (p *linuxParser) HasResource(m *MountPoint, absolutePath string) bool {
relPath, err := filepath.Rel(m.Destination, absolutePath)
return err == nil && relPath != ".." && !strings.HasPrefix(relPath, fmt.Sprintf("..%c", filepath.Separator))
}
func (p *windowsParser) HasResource(m *MountPoint, absolutePath string) bool {
return false
}

View File

@@ -0,0 +1,8 @@
package mounts // import "github.com/docker/docker/volume/mounts"
func (p *windowsParser) HasResource(m *MountPoint, absolutePath string) bool {
return false
}
func (p *linuxParser) HasResource(m *MountPoint, absolutePath string) bool {
return false
}

View File

@@ -0,0 +1,456 @@
package mounts // import "github.com/docker/docker/volume/mounts"
import (
"errors"
"fmt"
"os"
"regexp"
"runtime"
"strings"
"github.com/docker/docker/api/types/mount"
"github.com/docker/docker/pkg/stringid"
)
type windowsParser struct {
}
const (
// Spec should be in the format [source:]destination[:mode]
//
// Examples: c:\foo bar:d:rw
// c:\foo:d:\bar
// myname:d:
// d:\
//
// Explanation of this regex! Thanks @thaJeztah on IRC and gist for help. See
// https://gist.github.com/thaJeztah/6185659e4978789fb2b2. A good place to
// test is https://regex-golang.appspot.com/assets/html/index.html
//
// Useful link for referencing named capturing groups:
// http://stackoverflow.com/questions/20750843/using-named-matches-from-go-regex
//
// There are three match groups: source, destination and mode.
//
// rxHostDir is the first option of a source
rxHostDir = `(?:\\\\\?\\)?[a-z]:[\\/](?:[^\\/:*?"<>|\r\n]+[\\/]?)*`
// rxName is the second option of a source
rxName = `[^\\/:*?"<>|\r\n]+`
// RXReservedNames are reserved names not possible on Windows
rxReservedNames = `(con)|(prn)|(nul)|(aux)|(com[1-9])|(lpt[1-9])`
// rxPipe is a named path pipe (starts with `\\.\pipe\`, possibly with / instead of \)
rxPipe = `[/\\]{2}.[/\\]pipe[/\\][^:*?"<>|\r\n]+`
// rxSource is the combined possibilities for a source
rxSource = `((?P<source>((` + rxHostDir + `)|(` + rxName + `)|(` + rxPipe + `))):)?`
// Source. Can be either a host directory, a name, or omitted:
// HostDir:
// - Essentially using the folder solution from
// https://www.safaribooksonline.com/library/view/regular-expressions-cookbook/9781449327453/ch08s18.html
// but adding case insensitivity.
// - Must be an absolute path such as c:\path
// - Can include spaces such as `c:\program files`
// - And then followed by a colon which is not in the capture group
// - And can be optional
// Name:
// - Must not contain invalid NTFS filename characters (https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx)
// - And then followed by a colon which is not in the capture group
// - And can be optional
// rxDestination is the regex expression for the mount destination
rxDestination = `(?P<destination>((?:\\\\\?\\)?([a-z]):((?:[\\/][^\\/:*?"<>\r\n]+)*[\\/]?))|(` + rxPipe + `))`
rxLCOWDestination = `(?P<destination>/(?:[^\\/:*?"<>\r\n]+[/]?)*)`
// Destination (aka container path):
// - Variation on hostdir but can be a drive followed by colon as well
// - If a path, must be absolute. Can include spaces
// - Drive cannot be c: (explicitly checked in code, not RegEx)
// rxMode is the regex expression for the mode of the mount
// Mode (optional):
// - Hopefully self explanatory in comparison to above regex's.
// - Colon is not in the capture group
rxMode = `(:(?P<mode>(?i)ro|rw))?`
)
type mountValidator func(mnt *mount.Mount) error
func windowsSplitRawSpec(raw, destRegex string) ([]string, error) {
specExp := regexp.MustCompile(`^` + rxSource + destRegex + rxMode + `$`)
match := specExp.FindStringSubmatch(strings.ToLower(raw))
// Must have something back
if len(match) == 0 {
return nil, errInvalidSpec(raw)
}
var split []string
matchgroups := make(map[string]string)
// Pull out the sub expressions from the named capture groups
for i, name := range specExp.SubexpNames() {
matchgroups[name] = strings.ToLower(match[i])
}
if source, exists := matchgroups["source"]; exists {
if source != "" {
split = append(split, source)
}
}
if destination, exists := matchgroups["destination"]; exists {
if destination != "" {
split = append(split, destination)
}
}
if mode, exists := matchgroups["mode"]; exists {
if mode != "" {
split = append(split, mode)
}
}
// Fix #26329. If the destination appears to be a file, and the source is null,
// it may be because we've fallen through the possible naming regex and hit a
// situation where the user intention was to map a file into a container through
// a local volume, but this is not supported by the platform.
if matchgroups["source"] == "" && matchgroups["destination"] != "" {
volExp := regexp.MustCompile(`^` + rxName + `$`)
reservedNameExp := regexp.MustCompile(`^` + rxReservedNames + `$`)
if volExp.MatchString(matchgroups["destination"]) {
if reservedNameExp.MatchString(matchgroups["destination"]) {
return nil, fmt.Errorf("volume name %q cannot be a reserved word for Windows filenames", matchgroups["destination"])
}
} else {
exists, isDir, _ := currentFileInfoProvider.fileInfo(matchgroups["destination"])
if exists && !isDir {
return nil, fmt.Errorf("file '%s' cannot be mapped. Only directories can be mapped on this platform", matchgroups["destination"])
}
}
}
return split, nil
}
func windowsValidMountMode(mode string) bool {
if mode == "" {
return true
}
return rwModes[strings.ToLower(mode)]
}
func windowsValidateNotRoot(p string) error {
p = strings.ToLower(strings.Replace(p, `/`, `\`, -1))
if p == "c:" || p == `c:\` {
return fmt.Errorf("destination path cannot be `c:` or `c:\\`: %v", p)
}
return nil
}
var windowsSpecificValidators mountValidator = func(mnt *mount.Mount) error {
return windowsValidateNotRoot(mnt.Target)
}
func windowsValidateRegex(p, r string) error {
if regexp.MustCompile(`^` + r + `$`).MatchString(strings.ToLower(p)) {
return nil
}
return fmt.Errorf("invalid mount path: '%s'", p)
}
func windowsValidateAbsolute(p string) error {
if err := windowsValidateRegex(p, rxDestination); err != nil {
return fmt.Errorf("invalid mount path: '%s' mount path must be absolute", p)
}
return nil
}
func windowsDetectMountType(p string) mount.Type {
if strings.HasPrefix(p, `\\.\pipe\`) {
return mount.TypeNamedPipe
} else if regexp.MustCompile(`^` + rxHostDir + `$`).MatchString(p) {
return mount.TypeBind
} else {
return mount.TypeVolume
}
}
func (p *windowsParser) ReadWrite(mode string) bool {
return strings.ToLower(mode) != "ro"
}
// IsVolumeNameValid checks a volume name in a platform specific manner.
func (p *windowsParser) ValidateVolumeName(name string) error {
nameExp := regexp.MustCompile(`^` + rxName + `$`)
if !nameExp.MatchString(name) {
return errors.New("invalid volume name")
}
nameExp = regexp.MustCompile(`^` + rxReservedNames + `$`)
if nameExp.MatchString(name) {
return fmt.Errorf("volume name %q cannot be a reserved word for Windows filenames", name)
}
return nil
}
func (p *windowsParser) ValidateMountConfig(mnt *mount.Mount) error {
return p.validateMountConfigReg(mnt, rxDestination, windowsSpecificValidators)
}
type fileInfoProvider interface {
fileInfo(path string) (exist, isDir bool, err error)
}
type defaultFileInfoProvider struct {
}
func (defaultFileInfoProvider) fileInfo(path string) (exist, isDir bool, err error) {
fi, err := os.Stat(path)
if err != nil {
if !os.IsNotExist(err) {
return false, false, err
}
return false, false, nil
}
return true, fi.IsDir(), nil
}
var currentFileInfoProvider fileInfoProvider = defaultFileInfoProvider{}
func (p *windowsParser) validateMountConfigReg(mnt *mount.Mount, destRegex string, additionalValidators ...mountValidator) error {
for _, v := range additionalValidators {
if err := v(mnt); err != nil {
return &errMountConfig{mnt, err}
}
}
if len(mnt.Target) == 0 {
return &errMountConfig{mnt, errMissingField("Target")}
}
if err := windowsValidateRegex(mnt.Target, destRegex); 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 {
return &errMountConfig{mnt, fmt.Errorf("invalid propagation mode: %s", opts.Propagation)}
}
}
if mnt.VolumeOptions != nil {
return &errMountConfig{mnt, errExtraField("VolumeOptions")}
}
if err := windowsValidateAbsolute(mnt.Source); err != nil {
return &errMountConfig{mnt, err}
}
exists, isdir, err := currentFileInfoProvider.fileInfo(mnt.Source)
if err != nil {
return &errMountConfig{mnt, err}
}
if !exists {
return &errMountConfig{mnt, errBindSourceDoesNotExist(mnt.Source)}
}
if !isdir {
return &errMountConfig{mnt, fmt.Errorf("source path must be a directory")}
}
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")}
}
if len(mnt.Source) != 0 {
if err := p.ValidateVolumeName(mnt.Source); err != nil {
return &errMountConfig{mnt, err}
}
}
case mount.TypeNamedPipe:
if len(mnt.Source) == 0 {
return &errMountConfig{mnt, errMissingField("Source")}
}
if mnt.BindOptions != nil {
return &errMountConfig{mnt, errExtraField("BindOptions")}
}
if mnt.ReadOnly {
return &errMountConfig{mnt, errExtraField("ReadOnly")}
}
if windowsDetectMountType(mnt.Source) != mount.TypeNamedPipe {
return &errMountConfig{mnt, fmt.Errorf("'%s' is not a valid pipe path", mnt.Source)}
}
if windowsDetectMountType(mnt.Target) != mount.TypeNamedPipe {
return &errMountConfig{mnt, fmt.Errorf("'%s' is not a valid pipe path", mnt.Target)}
}
default:
return &errMountConfig{mnt, errors.New("mount type unknown")}
}
return nil
}
func (p *windowsParser) ParseMountRaw(raw, volumeDriver string) (*MountPoint, error) {
return p.parseMountRaw(raw, volumeDriver, rxDestination, true, windowsSpecificValidators)
}
func (p *windowsParser) parseMountRaw(raw, volumeDriver, destRegex string, convertTargetToBackslash bool, additionalValidators ...mountValidator) (*MountPoint, error) {
arr, err := windowsSplitRawSpec(raw, destRegex)
if err != nil {
return nil, err
}
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 windowsValidMountMode(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 = strings.Replace(arr[0], `/`, `\`, -1)
spec.Target = arr[1]
case 3:
// HostSourcePath+DestinationPath+Mode
spec.Source = strings.Replace(arr[0], `/`, `\`, -1)
spec.Target = arr[1]
mode = arr[2]
default:
return nil, errInvalidSpec(raw)
}
if convertTargetToBackslash {
spec.Target = strings.Replace(spec.Target, `/`, `\`, -1)
}
if !windowsValidMountMode(mode) {
return nil, errInvalidMode(mode)
}
spec.Type = windowsDetectMountType(spec.Source)
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
}
mp, err := p.parseMountSpec(spec, destRegex, convertTargetToBackslash, additionalValidators...)
if mp != nil {
mp.Mode = mode
}
if err != nil {
err = fmt.Errorf("%v: %v", errInvalidSpec(raw), err)
}
return mp, err
}
func (p *windowsParser) ParseMountSpec(cfg mount.Mount) (*MountPoint, error) {
return p.parseMountSpec(cfg, rxDestination, true, windowsSpecificValidators)
}
func (p *windowsParser) parseMountSpec(cfg mount.Mount, destRegex string, convertTargetToBackslash bool, additionalValidators ...mountValidator) (*MountPoint, error) {
if err := p.validateMountConfigReg(&cfg, destRegex, additionalValidators...); err != nil {
return nil, err
}
mp := &MountPoint{
RW: !cfg.ReadOnly,
Destination: cfg.Target,
Type: cfg.Type,
Spec: cfg,
}
if convertTargetToBackslash {
mp.Destination = strings.Replace(cfg.Target, `/`, `\`, -1)
}
switch cfg.Type {
case mount.TypeVolume:
if cfg.Source == "" {
mp.Name = stringid.GenerateNonCryptoID()
} 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 = strings.Replace(cfg.Source, `/`, `\`, -1)
case mount.TypeNamedPipe:
mp.Source = strings.Replace(cfg.Source, `/`, `\`, -1)
}
// cleanup trailing `\` except for paths like `c:\`
if len(mp.Source) > 3 && mp.Source[len(mp.Source)-1] == '\\' {
mp.Source = mp.Source[:len(mp.Source)-1]
}
if len(mp.Destination) > 3 && mp.Destination[len(mp.Destination)-1] == '\\' {
mp.Destination = mp.Destination[:len(mp.Destination)-1]
}
return mp, nil
}
func (p *windowsParser) ParseVolumesFrom(spec string) (string, string, error) {
if len(spec) == 0 {
return "", "", fmt.Errorf("volumes-from specification cannot be an empty string")
}
specParts := strings.SplitN(spec, ":", 2)
id := specParts[0]
mode := "rw"
if len(specParts) == 2 {
mode = specParts[1]
if !windowsValidMountMode(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 *windowsParser) DefaultPropagationMode() mount.Propagation {
return mount.Propagation("")
}
func (p *windowsParser) ConvertTmpfsOptions(opt *mount.TmpfsOptions, readOnly bool) (string, error) {
return "", fmt.Errorf("%s does not support tmpfs", runtime.GOOS)
}
func (p *windowsParser) DefaultCopyMode() bool {
return false
}
func (p *windowsParser) IsBackwardCompatible(m *MountPoint) bool {
return false
}
func (p *windowsParser) ValidateTmpfsMountDestination(dest string) error {
return errors.New("Platform does not support tmpfs")
}

69
vendor/github.com/docker/docker/volume/volume.go generated vendored Normal file
View File

@@ -0,0 +1,69 @@
package volume // import "github.com/docker/docker/volume"
import (
"time"
)
// DefaultDriverName is the driver name used for the driver
// implemented in the local package.
const DefaultDriverName = "local"
// Scopes define if a volume has is cluster-wide (global) or local only.
// Scopes are returned by the volume driver when it is queried for capabilities and then set on a volume
const (
LocalScope = "local"
GlobalScope = "global"
)
// Driver is for creating and removing volumes.
type Driver interface {
// Name returns the name of the volume driver.
Name() string
// Create makes a new volume with the given name.
Create(name string, opts map[string]string) (Volume, error)
// Remove deletes the volume.
Remove(vol Volume) (err error)
// List lists all the volumes the driver has
List() ([]Volume, error)
// Get retrieves the volume with the requested name
Get(name string) (Volume, error)
// Scope returns the scope of the driver (e.g. `global` or `local`).
// Scope determines how the driver is handled at a cluster level
Scope() string
}
// Capability defines a set of capabilities that a driver is able to handle.
type Capability struct {
// Scope is the scope of the driver, `global` or `local`
// A `global` scope indicates that the driver manages volumes across the cluster
// A `local` scope indicates that the driver only manages volumes resources local to the host
// Scope is declared by the driver
Scope string
}
// Volume is a place to store data. It is backed by a specific driver, and can be mounted.
type Volume interface {
// Name returns the name of the volume
Name() string
// DriverName returns the name of the driver which owns this volume.
DriverName() string
// Path returns the absolute path to the volume.
Path() string
// Mount mounts the volume and returns the absolute path to
// where it can be consumed.
Mount(id string) (string, error)
// Unmount unmounts the volume when it is no longer in use.
Unmount(id string) error
// CreatedAt returns Volume Creation time
CreatedAt() (time.Time, error)
// Status returns low-level status information about a volume
Status() map[string]interface{}
}
// DetailedVolume wraps a Volume with user-defined labels, options, and cluster scope (e.g., `local` or `global`)
type DetailedVolume interface {
Labels() map[string]string
Options() map[string]string
Scope() string
Volume
}