|
@@ -9,6 +9,7 @@ import (
|
|
|
|
|
|
"github.com/containerd/log"
|
|
"github.com/containerd/log"
|
|
mounttypes "github.com/docker/docker/api/types/mount"
|
|
mounttypes "github.com/docker/docker/api/types/mount"
|
|
|
|
+ "github.com/docker/docker/internal/safepath"
|
|
"github.com/docker/docker/pkg/idtools"
|
|
"github.com/docker/docker/pkg/idtools"
|
|
"github.com/docker/docker/pkg/stringid"
|
|
"github.com/docker/docker/pkg/stringid"
|
|
"github.com/docker/docker/volume"
|
|
"github.com/docker/docker/volume"
|
|
@@ -74,14 +75,34 @@ type MountPoint struct {
|
|
// Specifically needed for containers which are running and calls to `docker cp`
|
|
// Specifically needed for containers which are running and calls to `docker cp`
|
|
// because both these actions require mounting the volumes.
|
|
// because both these actions require mounting the volumes.
|
|
active int
|
|
active int
|
|
|
|
+
|
|
|
|
+ // SafePaths created by Setup that should be cleaned up before unmounting
|
|
|
|
+ // the volume.
|
|
|
|
+ safePaths []*safepath.SafePath
|
|
}
|
|
}
|
|
|
|
|
|
-// Cleanup frees resources used by the mountpoint
|
|
|
|
-func (m *MountPoint) Cleanup() error {
|
|
|
|
|
|
+// Cleanup frees resources used by the mountpoint and cleans up all the paths
|
|
|
|
+// returned by Setup that hasn't been cleaned up by the caller.
|
|
|
|
+func (m *MountPoint) Cleanup(ctx context.Context) error {
|
|
if m.Volume == nil || m.ID == "" {
|
|
if m.Volume == nil || m.ID == "" {
|
|
return nil
|
|
return nil
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ for _, p := range m.safePaths {
|
|
|
|
+ if !p.IsValid() {
|
|
|
|
+ continue
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ err := p.Close(ctx)
|
|
|
|
+ base, sub := p.SourcePath()
|
|
|
|
+ log.G(ctx).WithFields(log.Fields{
|
|
|
|
+ "error": err,
|
|
|
|
+ "path": p.Path(),
|
|
|
|
+ "sourceBase": base,
|
|
|
|
+ "sourceSubpath": sub,
|
|
|
|
+ }).Warn("cleaning up SafePath that hasn't been cleaned up by the caller")
|
|
|
|
+ }
|
|
|
|
+
|
|
if err := m.Volume.Unmount(m.ID); err != nil {
|
|
if err := m.Volume.Unmount(m.ID); err != nil {
|
|
return errors.Wrapf(err, "error unmounting volume %s", m.Volume.Name())
|
|
return errors.Wrapf(err, "error unmounting volume %s", m.Volume.Name())
|
|
}
|
|
}
|
|
@@ -97,30 +118,42 @@ func (m *MountPoint) Cleanup() error {
|
|
// configured, or creating the source directory if supplied.
|
|
// configured, or creating the source directory if supplied.
|
|
// The, optional, checkFun parameter allows doing additional checking
|
|
// The, optional, checkFun parameter allows doing additional checking
|
|
// before creating the source directory on the host.
|
|
// before creating the source directory on the host.
|
|
-func (m *MountPoint) Setup(mountLabel string, rootIDs idtools.Identity, checkFun func(m *MountPoint) error) (path string, err error) {
|
|
|
|
|
|
+//
|
|
|
|
+// The returned path can be a temporary path, caller is responsible to
|
|
|
|
+// call the returned cleanup function as soon as the path is not needed.
|
|
|
|
+// Cleanup doesn't unmount the underlying volumes (if any), it only
|
|
|
|
+// frees up the resources that were needed to guarantee that the path
|
|
|
|
+// still points to the same target (to avoid TOCTOU attack).
|
|
|
|
+//
|
|
|
|
+// Cleanup function doesn't need to be called when error is returned.
|
|
|
|
+func (m *MountPoint) Setup(ctx context.Context, mountLabel string, rootIDs idtools.Identity, checkFun func(m *MountPoint) error) (path string, cleanup func(context.Context) error, retErr error) {
|
|
if m.SkipMountpointCreation {
|
|
if m.SkipMountpointCreation {
|
|
- return m.Source, nil
|
|
|
|
|
|
+ return m.Source, noCleanup, nil
|
|
}
|
|
}
|
|
|
|
|
|
defer func() {
|
|
defer func() {
|
|
- if err != nil || !label.RelabelNeeded(m.Mode) {
|
|
|
|
|
|
+ if retErr != nil || !label.RelabelNeeded(m.Mode) {
|
|
return
|
|
return
|
|
}
|
|
}
|
|
|
|
|
|
- var sourcePath string
|
|
|
|
- sourcePath, err = filepath.EvalSymlinks(m.Source)
|
|
|
|
|
|
+ sourcePath, err := filepath.EvalSymlinks(path)
|
|
if err != nil {
|
|
if err != nil {
|
|
path = ""
|
|
path = ""
|
|
- err = errors.Wrapf(err, "error evaluating symlinks from mount source %q", m.Source)
|
|
|
|
|
|
+ retErr = errors.Wrapf(err, "error evaluating symlinks from mount source %q", m.Source)
|
|
|
|
+ if cleanupErr := cleanup(ctx); cleanupErr != nil {
|
|
|
|
+ log.G(ctx).WithError(cleanupErr).Warn("failed to cleanup after error")
|
|
|
|
+ }
|
|
|
|
+ cleanup = noCleanup
|
|
return
|
|
return
|
|
}
|
|
}
|
|
err = label.Relabel(sourcePath, mountLabel, label.IsShared(m.Mode))
|
|
err = label.Relabel(sourcePath, mountLabel, label.IsShared(m.Mode))
|
|
- if errors.Is(err, syscall.ENOTSUP) {
|
|
|
|
- err = nil
|
|
|
|
- }
|
|
|
|
- if err != nil {
|
|
|
|
|
|
+ if err != nil && !errors.Is(err, syscall.ENOTSUP) {
|
|
path = ""
|
|
path = ""
|
|
- err = errors.Wrapf(err, "error setting label on mount source '%s'", sourcePath)
|
|
|
|
|
|
+ retErr = errors.Wrapf(err, "error setting label on mount source '%s'", sourcePath)
|
|
|
|
+ if cleanupErr := cleanup(ctx); cleanupErr != nil {
|
|
|
|
+ log.G(ctx).WithError(cleanupErr).Warn("failed to cleanup after error")
|
|
|
|
+ }
|
|
|
|
+ cleanup = noCleanup
|
|
}
|
|
}
|
|
}()
|
|
}()
|
|
|
|
|
|
@@ -129,18 +162,36 @@ func (m *MountPoint) Setup(mountLabel string, rootIDs idtools.Identity, checkFun
|
|
if id == "" {
|
|
if id == "" {
|
|
id = stringid.GenerateRandomID()
|
|
id = stringid.GenerateRandomID()
|
|
}
|
|
}
|
|
- path, err := m.Volume.Mount(id)
|
|
|
|
|
|
+ volumePath, err := m.Volume.Mount(id)
|
|
if err != nil {
|
|
if err != nil {
|
|
- return "", errors.Wrapf(err, "error while mounting volume '%s'", m.Source)
|
|
|
|
|
|
+ return "", noCleanup, errors.Wrapf(err, "error while mounting volume '%s'", m.Source)
|
|
}
|
|
}
|
|
|
|
|
|
m.ID = id
|
|
m.ID = id
|
|
|
|
+ clean := noCleanup
|
|
|
|
+ if m.Spec.VolumeOptions != nil && m.Spec.VolumeOptions.Subpath != "" {
|
|
|
|
+ subpath := m.Spec.VolumeOptions.Subpath
|
|
|
|
+
|
|
|
|
+ safePath, err := safepath.Join(ctx, volumePath, subpath)
|
|
|
|
+ if err != nil {
|
|
|
|
+ if err := m.Volume.Unmount(id); err != nil {
|
|
|
|
+ log.G(ctx).WithError(err).Error("failed to unmount after safepath.Join failed")
|
|
|
|
+ }
|
|
|
|
+ return "", noCleanup, err
|
|
|
|
+ }
|
|
|
|
+ m.safePaths = append(m.safePaths, safePath)
|
|
|
|
+ log.G(ctx).Debugf("mounting (%s|%s) via %s", volumePath, subpath, safePath.Path())
|
|
|
|
+
|
|
|
|
+ clean = safePath.Close
|
|
|
|
+ volumePath = safePath.Path()
|
|
|
|
+ }
|
|
|
|
+
|
|
m.active++
|
|
m.active++
|
|
- return path, nil
|
|
|
|
|
|
+ return volumePath, clean, nil
|
|
}
|
|
}
|
|
|
|
|
|
if len(m.Source) == 0 {
|
|
if len(m.Source) == 0 {
|
|
- return "", fmt.Errorf("Unable to setup mount point, neither source nor volume defined")
|
|
|
|
|
|
+ return "", noCleanup, fmt.Errorf("Unable to setup mount point, neither source nor volume defined")
|
|
}
|
|
}
|
|
|
|
|
|
if m.Type == mounttypes.TypeBind {
|
|
if m.Type == mounttypes.TypeBind {
|
|
@@ -149,7 +200,7 @@ func (m *MountPoint) Setup(mountLabel string, rootIDs idtools.Identity, checkFun
|
|
// the process of shutting down.
|
|
// the process of shutting down.
|
|
if checkFun != nil {
|
|
if checkFun != nil {
|
|
if err := checkFun(m); err != nil {
|
|
if err := checkFun(m); err != nil {
|
|
- return "", err
|
|
|
|
|
|
+ return "", noCleanup, err
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
@@ -158,12 +209,12 @@ func (m *MountPoint) Setup(mountLabel string, rootIDs idtools.Identity, checkFun
|
|
if err := idtools.MkdirAllAndChownNew(m.Source, 0o755, rootIDs); err != nil {
|
|
if err := idtools.MkdirAllAndChownNew(m.Source, 0o755, rootIDs); err != nil {
|
|
if perr, ok := err.(*os.PathError); ok {
|
|
if perr, ok := err.(*os.PathError); ok {
|
|
if perr.Err != syscall.ENOTDIR {
|
|
if perr.Err != syscall.ENOTDIR {
|
|
- return "", errors.Wrapf(err, "error while creating mount source path '%s'", m.Source)
|
|
|
|
|
|
+ return "", noCleanup, errors.Wrapf(err, "error while creating mount source path '%s'", m.Source)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- return m.Source, nil
|
|
|
|
|
|
+ return m.Source, noCleanup, nil
|
|
}
|
|
}
|
|
|
|
|
|
func (m *MountPoint) LiveRestore(ctx context.Context) error {
|
|
func (m *MountPoint) LiveRestore(ctx context.Context) error {
|
|
@@ -207,3 +258,8 @@ func errInvalidMode(mode string) error {
|
|
func errInvalidSpec(spec string) error {
|
|
func errInvalidSpec(spec string) error {
|
|
return errors.Errorf("invalid volume specification: '%s'", spec)
|
|
return errors.Errorf("invalid volume specification: '%s'", spec)
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+// noCleanup is a no-op cleanup function.
|
|
|
|
+func noCleanup(_ context.Context) error {
|
|
|
|
+ return nil
|
|
|
|
+}
|