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