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