gopkg.in/docker/docker.v20@v20.10.27/volume/local/local.go (about)

     1  // Package local provides the default implementation for volumes. It
     2  // is used to mount data volume containers and directories local to
     3  // the host server.
     4  package local // import "github.com/docker/docker/volume/local"
     5  
     6  import (
     7  	"context"
     8  	"encoding/json"
     9  	"fmt"
    10  	"os"
    11  	"path/filepath"
    12  	"reflect"
    13  	"strings"
    14  	"sync"
    15  
    16  	"github.com/docker/docker/daemon/names"
    17  	"github.com/docker/docker/errdefs"
    18  	"github.com/docker/docker/pkg/idtools"
    19  	"github.com/docker/docker/quota"
    20  	"github.com/docker/docker/volume"
    21  	"github.com/pkg/errors"
    22  	"github.com/sirupsen/logrus"
    23  )
    24  
    25  // VolumeDataPathName is the name of the directory where the volume data is stored.
    26  // It uses a very distinctive name to avoid collisions migrating data between
    27  // Docker versions.
    28  const (
    29  	VolumeDataPathName = "_data"
    30  	volumesPathName    = "volumes"
    31  )
    32  
    33  var (
    34  	// ErrNotFound is the typed error returned when the requested volume name can't be found
    35  	ErrNotFound = fmt.Errorf("volume not found")
    36  	// volumeNameRegex ensures the name assigned for the volume is valid.
    37  	// This name is used to create the bind directory, so we need to avoid characters that
    38  	// would make the path to escape the root directory.
    39  	volumeNameRegex = names.RestrictedNamePattern
    40  
    41  	_ volume.LiveRestorer = (*localVolume)(nil)
    42  )
    43  
    44  type activeMount struct {
    45  	count   uint64
    46  	mounted bool
    47  }
    48  
    49  // New instantiates a new Root instance with the provided scope. Scope
    50  // is the base path that the Root instance uses to store its
    51  // volumes. The base path is created here if it does not exist.
    52  func New(scope string, rootIdentity idtools.Identity) (*Root, error) {
    53  	rootDirectory := filepath.Join(scope, volumesPathName)
    54  
    55  	if err := idtools.MkdirAllAndChown(rootDirectory, 0701, idtools.CurrentIdentity()); err != nil {
    56  		return nil, err
    57  	}
    58  
    59  	r := &Root{
    60  		scope:        scope,
    61  		path:         rootDirectory,
    62  		volumes:      make(map[string]*localVolume),
    63  		rootIdentity: rootIdentity,
    64  	}
    65  
    66  	dirs, err := os.ReadDir(rootDirectory)
    67  	if err != nil {
    68  		return nil, err
    69  	}
    70  
    71  	if r.quotaCtl, err = quota.NewControl(rootDirectory); err != nil {
    72  		logrus.Debugf("No quota support for local volumes in %s: %v", rootDirectory, err)
    73  	}
    74  
    75  	for _, d := range dirs {
    76  		if !d.IsDir() {
    77  			continue
    78  		}
    79  
    80  		name := filepath.Base(d.Name())
    81  		v := &localVolume{
    82  			driverName: r.Name(),
    83  			name:       name,
    84  			path:       r.DataPath(name),
    85  			quotaCtl:   r.quotaCtl,
    86  		}
    87  		r.volumes[name] = v
    88  		optsFilePath := filepath.Join(rootDirectory, name, "opts.json")
    89  		if b, err := os.ReadFile(optsFilePath); err == nil {
    90  			opts := optsConfig{}
    91  			if err := json.Unmarshal(b, &opts); err != nil {
    92  				return nil, errors.Wrapf(err, "error while unmarshaling volume options for volume: %s", name)
    93  			}
    94  			// Make sure this isn't an empty optsConfig.
    95  			// This could be empty due to buggy behavior in older versions of Docker.
    96  			if !reflect.DeepEqual(opts, optsConfig{}) {
    97  				v.opts = &opts
    98  			}
    99  			// unmount anything that may still be mounted (for example, from an
   100  			// unclean shutdown). This is a no-op on windows
   101  			unmount(v.path)
   102  		}
   103  	}
   104  
   105  	return r, nil
   106  }
   107  
   108  // Root implements the Driver interface for the volume package and
   109  // manages the creation/removal of volumes. It uses only standard vfs
   110  // commands to create/remove dirs within its provided scope.
   111  type Root struct {
   112  	m            sync.Mutex
   113  	scope        string
   114  	path         string
   115  	quotaCtl     *quota.Control
   116  	volumes      map[string]*localVolume
   117  	rootIdentity idtools.Identity
   118  }
   119  
   120  // List lists all the volumes
   121  func (r *Root) List() ([]volume.Volume, error) {
   122  	var ls []volume.Volume
   123  	r.m.Lock()
   124  	for _, v := range r.volumes {
   125  		ls = append(ls, v)
   126  	}
   127  	r.m.Unlock()
   128  	return ls, nil
   129  }
   130  
   131  // DataPath returns the constructed path of this volume.
   132  func (r *Root) DataPath(volumeName string) string {
   133  	return filepath.Join(r.path, volumeName, VolumeDataPathName)
   134  }
   135  
   136  // Name returns the name of Root, defined in the volume package in the DefaultDriverName constant.
   137  func (r *Root) Name() string {
   138  	return volume.DefaultDriverName
   139  }
   140  
   141  // Create creates a new volume.Volume with the provided name, creating
   142  // the underlying directory tree required for this volume in the
   143  // process.
   144  func (r *Root) Create(name string, opts map[string]string) (volume.Volume, error) {
   145  	if err := r.validateName(name); err != nil {
   146  		return nil, err
   147  	}
   148  
   149  	r.m.Lock()
   150  	defer r.m.Unlock()
   151  
   152  	v, exists := r.volumes[name]
   153  	if exists {
   154  		return v, nil
   155  	}
   156  
   157  	path := r.DataPath(name)
   158  	volRoot := filepath.Dir(path)
   159  	// Root dir does not need to be accessed by the remapped root
   160  	if err := idtools.MkdirAllAndChown(volRoot, 0701, idtools.CurrentIdentity()); err != nil {
   161  		return nil, errors.Wrapf(errdefs.System(err), "error while creating volume root path '%s'", volRoot)
   162  	}
   163  
   164  	// Remapped root does need access to the data path
   165  	if err := idtools.MkdirAllAndChown(path, 0755, r.rootIdentity); err != nil {
   166  		return nil, errors.Wrapf(errdefs.System(err), "error while creating volume data path '%s'", path)
   167  	}
   168  
   169  	var err error
   170  	defer func() {
   171  		if err != nil {
   172  			os.RemoveAll(filepath.Dir(path))
   173  		}
   174  	}()
   175  
   176  	v = &localVolume{
   177  		driverName: r.Name(),
   178  		name:       name,
   179  		path:       path,
   180  		quotaCtl:   r.quotaCtl,
   181  	}
   182  
   183  	if len(opts) != 0 {
   184  		if err = setOpts(v, opts); err != nil {
   185  			return nil, err
   186  		}
   187  		var b []byte
   188  		b, err = json.Marshal(v.opts)
   189  		if err != nil {
   190  			return nil, err
   191  		}
   192  		if err = os.WriteFile(filepath.Join(filepath.Dir(path), "opts.json"), b, 0600); err != nil {
   193  			return nil, errdefs.System(errors.Wrap(err, "error while persisting volume options"))
   194  		}
   195  	}
   196  
   197  	r.volumes[name] = v
   198  	return v, nil
   199  }
   200  
   201  // Remove removes the specified volume and all underlying data. If the
   202  // given volume does not belong to this driver and an error is
   203  // returned. The volume is reference counted, if all references are
   204  // not released then the volume is not removed.
   205  func (r *Root) Remove(v volume.Volume) error {
   206  	r.m.Lock()
   207  	defer r.m.Unlock()
   208  
   209  	lv, ok := v.(*localVolume)
   210  	if !ok {
   211  		return errdefs.System(errors.Errorf("unknown volume type %T", v))
   212  	}
   213  
   214  	if lv.active.count > 0 {
   215  		return errdefs.System(errors.Errorf("volume has active mounts"))
   216  	}
   217  
   218  	if err := lv.unmount(); err != nil {
   219  		return err
   220  	}
   221  
   222  	realPath, err := filepath.EvalSymlinks(lv.path)
   223  	if err != nil {
   224  		if !os.IsNotExist(err) {
   225  			return err
   226  		}
   227  		realPath = filepath.Dir(lv.path)
   228  	}
   229  
   230  	if !r.scopedPath(realPath) {
   231  		return errdefs.System(errors.Errorf("Unable to remove a directory outside of the local volume root %s: %s", r.scope, realPath))
   232  	}
   233  
   234  	if err := removePath(realPath); err != nil {
   235  		return err
   236  	}
   237  
   238  	delete(r.volumes, lv.name)
   239  	return removePath(filepath.Dir(lv.path))
   240  }
   241  
   242  func removePath(path string) error {
   243  	if err := os.RemoveAll(path); err != nil {
   244  		if os.IsNotExist(err) {
   245  			return nil
   246  		}
   247  		return errdefs.System(errors.Wrapf(err, "error removing volume path '%s'", path))
   248  	}
   249  	return nil
   250  }
   251  
   252  // Get looks up the volume for the given name and returns it if found
   253  func (r *Root) Get(name string) (volume.Volume, error) {
   254  	r.m.Lock()
   255  	v, exists := r.volumes[name]
   256  	r.m.Unlock()
   257  	if !exists {
   258  		return nil, ErrNotFound
   259  	}
   260  	return v, nil
   261  }
   262  
   263  // Scope returns the local volume scope
   264  func (r *Root) Scope() string {
   265  	return volume.LocalScope
   266  }
   267  
   268  func (r *Root) validateName(name string) error {
   269  	if len(name) == 1 {
   270  		return errdefs.InvalidParameter(errors.New("volume name is too short, names should be at least two alphanumeric characters"))
   271  	}
   272  	if !volumeNameRegex.MatchString(name) {
   273  		return errdefs.InvalidParameter(errors.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, names.RestrictedNameChars))
   274  	}
   275  	return nil
   276  }
   277  
   278  // localVolume implements the Volume interface from the volume package and
   279  // represents the volumes created by Root.
   280  type localVolume struct {
   281  	m sync.Mutex
   282  	// unique name of the volume
   283  	name string
   284  	// path is the path on the host where the data lives
   285  	path string
   286  	// driverName is the name of the driver that created the volume.
   287  	driverName string
   288  	// opts is the parsed list of options used to create the volume
   289  	opts *optsConfig
   290  	// active refcounts the active mounts
   291  	active activeMount
   292  	// reference to Root instances quotaCtl
   293  	quotaCtl *quota.Control
   294  }
   295  
   296  // Name returns the name of the given Volume.
   297  func (v *localVolume) Name() string {
   298  	return v.name
   299  }
   300  
   301  // DriverName returns the driver that created the given Volume.
   302  func (v *localVolume) DriverName() string {
   303  	return v.driverName
   304  }
   305  
   306  // Path returns the data location.
   307  func (v *localVolume) Path() string {
   308  	return v.path
   309  }
   310  
   311  // CachedPath returns the data location
   312  func (v *localVolume) CachedPath() string {
   313  	return v.path
   314  }
   315  
   316  // Mount implements the localVolume interface, returning the data location.
   317  // If there are any provided mount options, the resources will be mounted at this point
   318  func (v *localVolume) Mount(id string) (string, error) {
   319  	v.m.Lock()
   320  	defer v.m.Unlock()
   321  	logger := logrus.WithField("volume", v.name)
   322  	if v.needsMount() {
   323  		if !v.active.mounted {
   324  			logger.Debug("Mounting volume")
   325  			if err := v.mount(); err != nil {
   326  				return "", errdefs.System(err)
   327  			}
   328  			v.active.mounted = true
   329  		}
   330  		v.active.count++
   331  		logger.WithField("active mounts", v.active).Debug("Decremented active mount count")
   332  	}
   333  	if err := v.postMount(); err != nil {
   334  		return "", err
   335  	}
   336  	return v.path, nil
   337  }
   338  
   339  // Unmount dereferences the id, and if it is the last reference will unmount any resources
   340  // that were previously mounted.
   341  func (v *localVolume) Unmount(id string) error {
   342  	v.m.Lock()
   343  	defer v.m.Unlock()
   344  	logger := logrus.WithField("volume", v.name)
   345  
   346  	// Always decrement the count, even if the unmount fails
   347  	// Essentially docker doesn't care if this fails, it will send an error, but
   348  	// ultimately there's nothing that can be done. If we don't decrement the count
   349  	// this volume can never be removed until a daemon restart occurs.
   350  	if v.needsMount() {
   351  		v.active.count--
   352  		logger.WithField("active mounts", v.active).Debug("Decremented active mount count")
   353  	}
   354  
   355  	if v.active.count > 0 {
   356  		return nil
   357  	}
   358  
   359  	logger.Debug("Unmounting volume")
   360  	return v.unmount()
   361  }
   362  
   363  func (v *localVolume) Status() map[string]interface{} {
   364  	return nil
   365  }
   366  
   367  // LiveRestoreVolume restores reference counts for mounts
   368  // It is assumed that the volume is already mounted since this is only called for active, live-restored containers.
   369  func (v *localVolume) LiveRestoreVolume(ctx context.Context, _ string) error {
   370  	v.m.Lock()
   371  	defer v.m.Unlock()
   372  
   373  	if !v.needsMount() {
   374  		return nil
   375  	}
   376  	v.active.count++
   377  	v.active.mounted = true
   378  	logrus.WithFields(logrus.Fields{
   379  		"volume":        v.name,
   380  		"active mounts": v.active,
   381  	}).Debugf("Live restored volume")
   382  	return nil
   383  }
   384  
   385  // getAddress finds out address/hostname from options
   386  func getAddress(opts string) string {
   387  	optsList := strings.Split(opts, ",")
   388  	for i := 0; i < len(optsList); i++ {
   389  		if strings.HasPrefix(optsList[i], "addr=") {
   390  			addr := strings.SplitN(optsList[i], "=", 2)[1]
   391  			return addr
   392  		}
   393  	}
   394  	return ""
   395  }