|
@@ -5,7 +5,6 @@ package local // import "github.com/docker/docker/volume/local"
|
|
|
|
|
|
import (
|
|
|
"encoding/json"
|
|
|
- "fmt"
|
|
|
"os"
|
|
|
"path/filepath"
|
|
|
"reflect"
|
|
@@ -31,7 +30,7 @@ const (
|
|
|
|
|
|
var (
|
|
|
// ErrNotFound is the typed error returned when the requested volume name can't be found
|
|
|
- ErrNotFound = fmt.Errorf("volume not found")
|
|
|
+ ErrNotFound = errors.New("volume not found")
|
|
|
// volumeNameRegex ensures the name assigned for the volume is valid.
|
|
|
// 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.
|
|
@@ -75,24 +74,19 @@ func New(scope string, rootIdentity idtools.Identity) (*Root, error) {
|
|
|
v := &localVolume{
|
|
|
driverName: r.Name(),
|
|
|
name: name,
|
|
|
- path: r.DataPath(name),
|
|
|
+ rootPath: filepath.Join(r.path, name),
|
|
|
+ path: filepath.Join(r.path, name, volumeDataPathName),
|
|
|
quotaCtl: r.quotaCtl,
|
|
|
}
|
|
|
- r.volumes[name] = v
|
|
|
- if b, err := os.ReadFile(filepath.Join(r.path, name, "opts.json")); err == nil {
|
|
|
- opts := optsConfig{}
|
|
|
- if err := json.Unmarshal(b, &opts); err != nil {
|
|
|
- return nil, errors.Wrapf(err, "error while unmarshaling volume options for volume: %s", name)
|
|
|
- }
|
|
|
- // Make sure this isn't an empty optsConfig.
|
|
|
- // This could be empty due to buggy behavior in older versions of Docker.
|
|
|
- if !reflect.DeepEqual(opts, optsConfig{}) {
|
|
|
- v.opts = &opts
|
|
|
- }
|
|
|
- // unmount anything that may still be mounted (for example, from an
|
|
|
- // unclean shutdown). This is a no-op on windows
|
|
|
- unmount(v.path)
|
|
|
+
|
|
|
+ // unmount anything that may still be mounted (for example, from an
|
|
|
+ // unclean shutdown). This is a no-op on windows
|
|
|
+ unmount(v.path)
|
|
|
+
|
|
|
+ if err := v.loadOpts(); err != nil {
|
|
|
+ return nil, err
|
|
|
}
|
|
|
+ r.volumes[name] = v
|
|
|
}
|
|
|
|
|
|
return r, nil
|
|
@@ -120,11 +114,6 @@ func (r *Root) List() ([]volume.Volume, error) {
|
|
|
return ls, nil
|
|
|
}
|
|
|
|
|
|
-// DataPath returns the constructed path of this volume.
|
|
|
-func (r *Root) DataPath(volumeName string) string {
|
|
|
- return filepath.Join(r.path, volumeName, volumeDataPathName)
|
|
|
-}
|
|
|
-
|
|
|
// Name returns the name of Root, defined in the volume package in the DefaultDriverName constant.
|
|
|
func (r *Root) Name() string {
|
|
|
return volume.DefaultDriverName
|
|
@@ -137,6 +126,9 @@ func (r *Root) Create(name string, opts map[string]string) (volume.Volume, error
|
|
|
if err := r.validateName(name); err != nil {
|
|
|
return nil, err
|
|
|
}
|
|
|
+ if err := r.validateOpts(opts); err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
|
|
|
r.m.Lock()
|
|
|
defer r.m.Unlock()
|
|
@@ -146,44 +138,33 @@ func (r *Root) Create(name string, opts map[string]string) (volume.Volume, error
|
|
|
return v, nil
|
|
|
}
|
|
|
|
|
|
- path := r.DataPath(name)
|
|
|
- volRoot := filepath.Dir(path)
|
|
|
+ v = &localVolume{
|
|
|
+ driverName: r.Name(),
|
|
|
+ name: name,
|
|
|
+ rootPath: filepath.Join(r.path, name),
|
|
|
+ path: filepath.Join(r.path, name, volumeDataPathName),
|
|
|
+ quotaCtl: r.quotaCtl,
|
|
|
+ }
|
|
|
+
|
|
|
// Root dir does not need to be accessed by the remapped root
|
|
|
- if err := idtools.MkdirAllAndChown(volRoot, 0701, idtools.CurrentIdentity()); err != nil {
|
|
|
- return nil, errors.Wrapf(errdefs.System(err), "error while creating volume root path '%s'", volRoot)
|
|
|
+ if err := idtools.MkdirAllAndChown(v.rootPath, 0701, idtools.CurrentIdentity()); err != nil {
|
|
|
+ return nil, errors.Wrapf(errdefs.System(err), "error while creating volume root path '%s'", v.rootPath)
|
|
|
}
|
|
|
|
|
|
// Remapped root does need access to the data path
|
|
|
- if err := idtools.MkdirAllAndChown(path, 0755, r.rootIdentity); err != nil {
|
|
|
- return nil, errors.Wrapf(errdefs.System(err), "error while creating volume data path '%s'", path)
|
|
|
+ if err := idtools.MkdirAllAndChown(v.path, 0755, r.rootIdentity); err != nil {
|
|
|
+ return nil, errors.Wrapf(errdefs.System(err), "error while creating volume data path '%s'", v.path)
|
|
|
}
|
|
|
|
|
|
var err error
|
|
|
defer func() {
|
|
|
if err != nil {
|
|
|
- os.RemoveAll(filepath.Dir(path))
|
|
|
+ os.RemoveAll(v.rootPath)
|
|
|
}
|
|
|
}()
|
|
|
|
|
|
- v = &localVolume{
|
|
|
- driverName: r.Name(),
|
|
|
- name: name,
|
|
|
- path: path,
|
|
|
- quotaCtl: r.quotaCtl,
|
|
|
- }
|
|
|
-
|
|
|
- if len(opts) != 0 {
|
|
|
- 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 = os.WriteFile(filepath.Join(filepath.Dir(path), "opts.json"), b, 0600); err != nil {
|
|
|
- return nil, errdefs.System(errors.Wrap(err, "error while persisting volume options"))
|
|
|
- }
|
|
|
+ if err = v.setOpts(opts); err != nil {
|
|
|
+ return nil, err
|
|
|
}
|
|
|
|
|
|
r.volumes[name] = v
|
|
@@ -204,19 +185,22 @@ func (r *Root) Remove(v volume.Volume) error {
|
|
|
}
|
|
|
|
|
|
if lv.active.count > 0 {
|
|
|
- return errdefs.System(errors.Errorf("volume has active mounts"))
|
|
|
+ return errdefs.System(errors.New("volume has active mounts"))
|
|
|
}
|
|
|
|
|
|
if err := lv.unmount(); err != nil {
|
|
|
return err
|
|
|
}
|
|
|
|
|
|
+ // TODO(thaJeztah) is there a reason we're evaluating the data-path here, and not the volume's rootPath?
|
|
|
realPath, err := filepath.EvalSymlinks(lv.path)
|
|
|
if err != nil {
|
|
|
if !os.IsNotExist(err) {
|
|
|
return err
|
|
|
}
|
|
|
- realPath = filepath.Dir(lv.path)
|
|
|
+ // if the volume's data-directory wasn't found, fall back to using the
|
|
|
+ // volume's root path (see 8d27417bfeff316346d00c07a456b0e1b056e788)
|
|
|
+ realPath = lv.rootPath
|
|
|
}
|
|
|
|
|
|
if realPath == r.path || !strings.HasPrefix(realPath, r.path) {
|
|
@@ -228,7 +212,7 @@ func (r *Root) Remove(v volume.Volume) error {
|
|
|
}
|
|
|
|
|
|
delete(r.volumes, lv.name)
|
|
|
- return removePath(filepath.Dir(lv.path))
|
|
|
+ return removePath(lv.rootPath)
|
|
|
}
|
|
|
|
|
|
func removePath(path string) error {
|
|
@@ -273,6 +257,8 @@ type localVolume struct {
|
|
|
m sync.Mutex
|
|
|
// unique name of the volume
|
|
|
name string
|
|
|
+ // rootPath is the volume's root path, where the volume's metadata is stored.
|
|
|
+ rootPath 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.
|
|
@@ -350,6 +336,39 @@ func (v *localVolume) Status() map[string]interface{} {
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
+func (v *localVolume) loadOpts() error {
|
|
|
+ b, err := os.ReadFile(filepath.Join(v.rootPath, "opts.json"))
|
|
|
+ if err != nil {
|
|
|
+ if !errors.Is(err, os.ErrNotExist) {
|
|
|
+ logrus.WithError(err).Warnf("error while loading volume options for volume: %s", v.name)
|
|
|
+ }
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ opts := optsConfig{}
|
|
|
+ if err := json.Unmarshal(b, &opts); err != nil {
|
|
|
+ return errors.Wrapf(err, "error while unmarshaling volume options for volume: %s", v.name)
|
|
|
+ }
|
|
|
+ // Make sure this isn't an empty optsConfig.
|
|
|
+ // This could be empty due to buggy behavior in older versions of Docker.
|
|
|
+ if !reflect.DeepEqual(opts, optsConfig{}) {
|
|
|
+ v.opts = &opts
|
|
|
+ }
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+func (v *localVolume) saveOpts() error {
|
|
|
+ var b []byte
|
|
|
+ b, err := json.Marshal(v.opts)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ err = os.WriteFile(filepath.Join(v.rootPath, "opts.json"), b, 0600)
|
|
|
+ if err != nil {
|
|
|
+ return errdefs.System(errors.Wrap(err, "error while persisting volume options"))
|
|
|
+ }
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
// getAddress finds out address/hostname from options
|
|
|
func getAddress(opts string) string {
|
|
|
optsList := strings.Split(opts, ",")
|