volume/local/local.go
9af963ab
 // Package local provides the default implementation for volumes. It
 // is used to mount data volume containers and directories local to
 // the host server.
81fa9feb
 package local
 
 import (
b05b2370
 	"encoding/json"
81fa9feb
 	"fmt"
 	"io/ioutil"
 	"os"
 	"path/filepath"
246d1eb5
 	"reflect"
668fa8af
 	"strings"
81fa9feb
 	"sync"
 
2a5e85e2
 	"github.com/pkg/errors"
 
dba271a4
 	"github.com/docker/docker/api"
442b4562
 	"github.com/docker/docker/pkg/idtools"
b05b2370
 	"github.com/docker/docker/pkg/mount"
81fa9feb
 	"github.com/docker/docker/volume"
1009e6a4
 	"github.com/sirupsen/logrus"
81fa9feb
 )
 
bd9814f0
 // VolumeDataPathName is the name of the directory where the volume data is stored.
927b334e
 // It uses a very distinctive name to avoid collisions migrating data between
bd9814f0
 // Docker versions.
 const (
 	VolumeDataPathName = "_data"
 	volumesPathName    = "volumes"
 )
 
d6d60287
 var (
 	// ErrNotFound is the typed error returned when the requested volume name can't be found
a793564b
 	ErrNotFound = fmt.Errorf("volume not found")
927b334e
 	// volumeNameRegex ensures the name assigned for the volume is valid.
d6d60287
 	// This name is used to create the bind directory, so we need to avoid characters that
 	// would make the path to escape the root directory.
dba271a4
 	volumeNameRegex = api.RestrictedNamePattern
d6d60287
 )
bd9814f0
 
a793564b
 type validationError struct {
 	error
 }
 
 func (validationError) IsValidationError() bool {
 	return true
 }
 
b05b2370
 type activeMount struct {
 	count   uint64
 	mounted bool
 }
 
9af963ab
 // New instantiates a new Root instance with the provided scope. Scope
 // is the base path that the Root instance uses to store its
 // volumes. The base path is created here if it does not exist.
09cd96c5
 func New(scope string, rootIDs idtools.IDPair) (*Root, error) {
bd9814f0
 	rootDirectory := filepath.Join(scope, volumesPathName)
 
09cd96c5
 	if err := idtools.MkdirAllAndChown(rootDirectory, 0700, rootIDs); err != nil {
81fa9feb
 		return nil, err
 	}
bd9814f0
 
81fa9feb
 	r := &Root{
bd9814f0
 		scope:   scope,
81fa9feb
 		path:    rootDirectory,
9af963ab
 		volumes: make(map[string]*localVolume),
09cd96c5
 		rootIDs: rootIDs,
81fa9feb
 	}
bd9814f0
 
81fa9feb
 	dirs, err := ioutil.ReadDir(rootDirectory)
 	if err != nil {
 		return nil, err
 	}
bd9814f0
 
b05b2370
 	mountInfos, err := mount.GetMounts()
 	if err != nil {
 		logrus.Debugf("error looking up mounts for local volume cleanup: %v", err)
 	}
 
81fa9feb
 	for _, d := range dirs {
fc214b44
 		if !d.IsDir() {
 			continue
 		}
 
81fa9feb
 		name := filepath.Base(d.Name())
b05b2370
 		v := &localVolume{
81fa9feb
 			driverName: r.Name(),
 			name:       name,
bd9814f0
 			path:       r.DataPath(name),
81fa9feb
 		}
b05b2370
 		r.volumes[name] = v
c560dd96
 		optsFilePath := filepath.Join(rootDirectory, name, "opts.json")
 		if b, err := ioutil.ReadFile(optsFilePath); err == nil {
246d1eb5
 			opts := optsConfig{}
 			if err := json.Unmarshal(b, &opts); err != nil {
2a5e85e2
 				return nil, errors.Wrapf(err, "error while unmarshaling volume options for volume: %s", name)
b05b2370
 			}
c7075bd1
 			// Make sure this isn't an empty optsConfig.
 			// This could be empty due to buggy behavior in older versions of Docker.
246d1eb5
 			if !reflect.DeepEqual(opts, optsConfig{}) {
 				v.opts = &opts
 			}
b05b2370
 
 			// unmount anything that may still be mounted (for example, from an unclean shutdown)
 			for _, info := range mountInfos {
 				if info.Mountpoint == v.path {
 					mount.Unmount(v.path)
 					break
 				}
 			}
 		}
81fa9feb
 	}
b3b7eb27
 
81fa9feb
 	return r, nil
 }
 
9af963ab
 // Root implements the Driver interface for the volume package and
 // manages the creation/removal of volumes. It uses only standard vfs
 // commands to create/remove dirs within its provided scope.
81fa9feb
 type Root struct {
 	m       sync.Mutex
bd9814f0
 	scope   string
81fa9feb
 	path    string
9af963ab
 	volumes map[string]*localVolume
09cd96c5
 	rootIDs idtools.IDPair
81fa9feb
 }
 
b3b7eb27
 // List lists all the volumes
d3eca445
 func (r *Root) List() ([]volume.Volume, error) {
b3b7eb27
 	var ls []volume.Volume
3536c09c
 	r.m.Lock()
b3b7eb27
 	for _, v := range r.volumes {
 		ls = append(ls, v)
 	}
3536c09c
 	r.m.Unlock()
d3eca445
 	return ls, nil
b3b7eb27
 }
 
9af963ab
 // DataPath returns the constructed path of this volume.
bd9814f0
 func (r *Root) DataPath(volumeName string) string {
 	return filepath.Join(r.path, volumeName, VolumeDataPathName)
 }
 
9af963ab
 // Name returns the name of Root, defined in the volume package in the DefaultDriverName constant.
81fa9feb
 func (r *Root) Name() string {
9af963ab
 	return volume.DefaultDriverName
81fa9feb
 }
 
9af963ab
 // Create creates a new volume.Volume with the provided name, creating
 // the underlying directory tree required for this volume in the
 // process.
b05b2370
 func (r *Root) Create(name string, opts map[string]string) (volume.Volume, error) {
d6d60287
 	if err := r.validateName(name); err != nil {
 		return nil, err
 	}
 
81fa9feb
 	r.m.Lock()
 	defer r.m.Unlock()
bd9814f0
 
81fa9feb
 	v, exists := r.volumes[name]
b3b7eb27
 	if exists {
 		return v, nil
 	}
 
 	path := r.DataPath(name)
09cd96c5
 	if err := idtools.MkdirAllAndChown(path, 0755, r.rootIDs); err != nil {
b3b7eb27
 		if os.IsExist(err) {
 			return nil, fmt.Errorf("volume already exists under %s", filepath.Dir(path))
81fa9feb
 		}
2a5e85e2
 		return nil, errors.Wrapf(err, "error while creating volume path '%s'", path)
81fa9feb
 	}
b05b2370
 
 	var err error
 	defer func() {
 		if err != nil {
 			os.RemoveAll(filepath.Dir(path))
 		}
 	}()
 
b3b7eb27
 	v = &localVolume{
 		driverName: r.Name(),
 		name:       name,
 		path:       path,
 	}
b05b2370
 
246d1eb5
 	if len(opts) != 0 {
b05b2370
 		if err = setOpts(v, opts); err != nil {
 			return nil, err
 		}
 		var b []byte
 		b, err = json.Marshal(v.opts)
 		if err != nil {
 			return nil, err
 		}
 		if err = ioutil.WriteFile(filepath.Join(filepath.Dir(path), "opts.json"), b, 600); err != nil {
2a5e85e2
 			return nil, errors.Wrap(err, "error while persisting volume options")
b05b2370
 		}
 	}
 
b3b7eb27
 	r.volumes[name] = v
81fa9feb
 	return v, nil
 }
 
9af963ab
 // Remove removes the specified volume and all underlying data. If the
 // given volume does not belong to this driver and an error is
 // returned. The volume is reference counted, if all references are
 // not released then the volume is not removed.
81fa9feb
 func (r *Root) Remove(v volume.Volume) error {
 	r.m.Lock()
 	defer r.m.Unlock()
8d27417b
 
9af963ab
 	lv, ok := v.(*localVolume)
81fa9feb
 	if !ok {
fc214b44
 		return fmt.Errorf("unknown volume type %T", v)
81fa9feb
 	}
bd9814f0
 
db3576f8
 	if lv.active.count > 0 {
 		return fmt.Errorf("volume has active mounts")
 	}
 
 	if err := lv.unmount(); err != nil {
 		return err
 	}
 
b3b7eb27
 	realPath, err := filepath.EvalSymlinks(lv.path)
 	if err != nil {
8d27417b
 		if !os.IsNotExist(err) {
 			return err
 		}
 		realPath = filepath.Dir(lv.path)
b3b7eb27
 	}
8d27417b
 
b3b7eb27
 	if !r.scopedPath(realPath) {
8d27417b
 		return fmt.Errorf("Unable to remove a directory of out the Docker root %s: %s", r.scope, realPath)
b3b7eb27
 	}
bd9814f0
 
8d27417b
 	if err := removePath(realPath); err != nil {
b3b7eb27
 		return err
81fa9feb
 	}
b3b7eb27
 
 	delete(r.volumes, lv.name)
8d27417b
 	return removePath(filepath.Dir(lv.path))
 }
 
 func removePath(path string) error {
 	if err := os.RemoveAll(path); err != nil {
 		if os.IsNotExist(err) {
 			return nil
 		}
2a5e85e2
 		return errors.Wrapf(err, "error removing volume path '%s'", path)
8d27417b
 	}
 	return nil
b3b7eb27
 }
 
 // Get looks up the volume for the given name and returns it if found
 func (r *Root) Get(name string) (volume.Volume, error) {
 	r.m.Lock()
 	v, exists := r.volumes[name]
 	r.m.Unlock()
 	if !exists {
 		return nil, ErrNotFound
 	}
 	return v, nil
81fa9feb
 }
 
2f40b1b2
 // Scope returns the local volume scope
 func (r *Root) Scope() string {
 	return volume.LocalScope
 }
 
d6d60287
 func (r *Root) validateName(name string) error {
8d5a6150
 	if len(name) == 1 {
 		return validationError{fmt.Errorf("volume name is too short, names should be at least two alphanumeric characters")}
 	}
d6d60287
 	if !volumeNameRegex.MatchString(name) {
eee0cfa4
 		return validationError{fmt.Errorf("%q includes invalid characters for a local volume name, only %q are allowed. If you intended to pass a host directory, use absolute path", name, api.RestrictedNameChars)}
d6d60287
 	}
 	return nil
 }
 
9af963ab
 // localVolume implements the Volume interface from the volume package and
 // represents the volumes created by Root.
 type localVolume struct {
d2312608
 	m sync.Mutex
81fa9feb
 	// unique name of the volume
 	name string
 	// path is the path on the host where the data lives
 	path string
 	// driverName is the name of the driver that created the volume.
 	driverName string
b05b2370
 	// opts is the parsed list of options used to create the volume
 	opts *optsConfig
 	// active refcounts the active mounts
 	active activeMount
81fa9feb
 }
 
9af963ab
 // Name returns the name of the given Volume.
 func (v *localVolume) Name() string {
81fa9feb
 	return v.name
 }
 
9af963ab
 // DriverName returns the driver that created the given Volume.
 func (v *localVolume) DriverName() string {
81fa9feb
 	return v.driverName
 }
 
9af963ab
 // Path returns the data location.
 func (v *localVolume) Path() string {
81fa9feb
 	return v.path
 }
 
9af963ab
 // Mount implements the localVolume interface, returning the data location.
db3576f8
 // If there are any provided mount options, the resources will be mounted at this point
2b6bc294
 func (v *localVolume) Mount(id string) (string, error) {
b05b2370
 	v.m.Lock()
 	defer v.m.Unlock()
 	if v.opts != nil {
 		if !v.active.mounted {
 			if err := v.mount(); err != nil {
 				return "", err
 			}
 			v.active.mounted = true
 		}
 		v.active.count++
 	}
81fa9feb
 	return v.path, nil
 }
 
db3576f8
 // Unmount dereferences the id, and if it is the last reference will unmount any resources
 // that were previously mounted.
2b6bc294
 func (v *localVolume) Unmount(id string) error {
b05b2370
 	v.m.Lock()
 	defer v.m.Unlock()
db3576f8
 
 	// Always decrement the count, even if the unmount fails
 	// Essentially docker doesn't care if this fails, it will send an error, but
 	// ultimately there's nothing that can be done. If we don't decrement the count
 	// this volume can never be removed until a daemon restart occurs.
b05b2370
 	if v.opts != nil {
 		v.active.count--
db3576f8
 	}
 
 	if v.active.count > 0 {
 		return nil
 	}
 
 	return v.unmount()
 }
 
 func (v *localVolume) unmount() error {
 	if v.opts != nil {
 		if err := mount.Unmount(v.path); err != nil {
 			if mounted, mErr := mount.Mounted(v.path); mounted || mErr != nil {
2a5e85e2
 				return errors.Wrapf(err, "error while unmounting volume path '%s'", v.path)
b05b2370
 			}
 		}
db3576f8
 		v.active.mounted = false
b05b2370
 	}
 	return nil
 }
 
 func validateOpts(opts map[string]string) error {
 	for opt := range opts {
 		if !validOpts[opt] {
 			return validationError{fmt.Errorf("invalid option key: %q", opt)}
 		}
 	}
81fa9feb
 	return nil
 }
36a1c56c
 
 func (v *localVolume) Status() map[string]interface{} {
 	return nil
 }
668fa8af
 
 // getAddress finds out address/hostname from options
 func getAddress(opts string) string {
 	optsList := strings.Split(opts, ",")
 	for i := 0; i < len(optsList); i++ {
 		if strings.HasPrefix(optsList[i], "addr=") {
 			addr := (strings.SplitN(optsList[i], "=", 2)[1])
 			return addr
 		}
 	}
 	return ""
 }