github.com/moby/docker@v26.1.3+incompatible/volume/mounts/mounts.go (about)

     1  package mounts // import "github.com/docker/docker/volume/mounts"
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"os"
     7  	"path/filepath"
     8  	"runtime/debug"
     9  	"syscall"
    10  
    11  	"github.com/containerd/log"
    12  	mounttypes "github.com/docker/docker/api/types/mount"
    13  	"github.com/docker/docker/internal/safepath"
    14  	"github.com/docker/docker/pkg/idtools"
    15  	"github.com/docker/docker/pkg/stringid"
    16  	"github.com/docker/docker/volume"
    17  	"github.com/opencontainers/selinux/go-selinux/label"
    18  	"github.com/pkg/errors"
    19  )
    20  
    21  // MountPoint is the intersection point between a volume and a container. It
    22  // specifies which volume is to be used and where inside a container it should
    23  // be mounted.
    24  //
    25  // Note that this type is embedded in `container.Container` object and persisted to disk.
    26  // Changes to this struct need to by synced with on disk state.
    27  type MountPoint struct {
    28  	// Source is the source path of the mount.
    29  	// E.g. `mount --bind /foo /bar`, `/foo` is the `Source`.
    30  	Source string
    31  	// Destination is the path relative to the container root (`/`) to the mount point
    32  	// It is where the `Source` is mounted to
    33  	Destination string
    34  	// RW is set to true when the mountpoint should be mounted as read-write
    35  	RW bool
    36  	// Name is the name reference to the underlying data defined by `Source`
    37  	// e.g., the volume name
    38  	Name string
    39  	// Driver is the volume driver used to create the volume (if it is a volume)
    40  	Driver string
    41  	// Type of mount to use, see `Type<foo>` definitions in github.com/docker/docker/api/types/mount
    42  	Type mounttypes.Type `json:",omitempty"`
    43  	// Volume is the volume providing data to this mountpoint.
    44  	// This is nil unless `Type` is set to `TypeVolume`
    45  	Volume volume.Volume `json:"-"`
    46  
    47  	// Mode is the comma separated list of options supplied by the user when creating
    48  	// the bind/volume mount.
    49  	// Note Mode is not used on Windows
    50  	Mode string `json:"Relabel,omitempty"` // Originally field was `Relabel`"
    51  
    52  	// Propagation describes how the mounts are propagated from the host into the
    53  	// mount point, and vice-versa.
    54  	// See https://www.kernel.org/doc/Documentation/filesystems/sharedsubtree.txt
    55  	// Note Propagation is not used on Windows
    56  	Propagation mounttypes.Propagation `json:",omitempty"` // Mount propagation string
    57  
    58  	// Specifies if data should be copied from the container before the first mount
    59  	// Use a pointer here so we can tell if the user set this value explicitly
    60  	// This allows us to error out when the user explicitly enabled copy but we can't copy due to the volume being populated
    61  	CopyData bool `json:"-"`
    62  	// ID is the opaque ID used to pass to the volume driver.
    63  	// This should be set by calls to `Mount` and unset by calls to `Unmount`
    64  	ID string `json:",omitempty"`
    65  
    66  	// Spec is a copy of the API request that created this mount.
    67  	Spec mounttypes.Mount
    68  
    69  	// Some bind mounts should not be automatically created.
    70  	// (Some are auto-created for backwards-compatibility)
    71  	// This is checked on the API but setting this here prevents race conditions.
    72  	// where a bind dir existed during validation was removed before reaching the setup code.
    73  	SkipMountpointCreation bool
    74  
    75  	// Track usage of this mountpoint
    76  	// Specifically needed for containers which are running and calls to `docker cp`
    77  	// because both these actions require mounting the volumes.
    78  	active int
    79  
    80  	// SafePaths created by Setup that should be cleaned up before unmounting
    81  	// the volume.
    82  	safePaths []*safepath.SafePath
    83  }
    84  
    85  // Cleanup frees resources used by the mountpoint and cleans up all the paths
    86  // returned by Setup that hasn't been cleaned up by the caller.
    87  func (m *MountPoint) Cleanup(ctx context.Context) error {
    88  	if m.Volume == nil || m.ID == "" {
    89  		return nil
    90  	}
    91  
    92  	logger := log.G(ctx).WithFields(log.Fields{"active": m.active, "id": m.ID})
    93  
    94  	// TODO: Remove once the real bug is fixed: https://github.com/moby/moby/issues/46508
    95  	if m.active == 0 {
    96  		logger.Error("An attempt to decrement a zero mount count")
    97  		logger.Error(string(debug.Stack()))
    98  		return nil
    99  	}
   100  
   101  	for _, p := range m.safePaths {
   102  		if !p.IsValid() {
   103  			continue
   104  		}
   105  
   106  		err := p.Close(ctx)
   107  		base, sub := p.SourcePath()
   108  		log.G(ctx).WithFields(log.Fields{
   109  			"error":         err,
   110  			"path":          p.Path(),
   111  			"sourceBase":    base,
   112  			"sourceSubpath": sub,
   113  		}).Warn("cleaning up SafePath that hasn't been cleaned up by the caller")
   114  	}
   115  
   116  	if err := m.Volume.Unmount(m.ID); err != nil {
   117  		return errors.Wrapf(err, "error unmounting volume %s", m.Volume.Name())
   118  	}
   119  
   120  	m.active--
   121  	logger.Debug("MountPoint.Cleanup Decrement active count")
   122  
   123  	if m.active == 0 {
   124  		m.ID = ""
   125  	}
   126  	return nil
   127  }
   128  
   129  // Setup sets up a mount point by either mounting the volume if it is
   130  // configured, or creating the source directory if supplied.
   131  // The, optional, checkFun parameter allows doing additional checking
   132  // before creating the source directory on the host.
   133  //
   134  // The returned path can be a temporary path, caller is responsible to
   135  // call the returned cleanup function as soon as the path is not needed.
   136  // Cleanup doesn't unmount the underlying volumes (if any), it only
   137  // frees up the resources that were needed to guarantee that the path
   138  // still points to the same target (to avoid TOCTOU attack).
   139  //
   140  // Cleanup function doesn't need to be called when error is returned.
   141  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) {
   142  	if m.SkipMountpointCreation {
   143  		return m.Source, noCleanup, nil
   144  	}
   145  
   146  	defer func() {
   147  		if retErr != nil || !label.RelabelNeeded(m.Mode) {
   148  			return
   149  		}
   150  
   151  		sourcePath, err := filepath.EvalSymlinks(path)
   152  		if err != nil {
   153  			path = ""
   154  			retErr = errors.Wrapf(err, "error evaluating symlinks from mount source %q", m.Source)
   155  			if cleanupErr := cleanup(ctx); cleanupErr != nil {
   156  				log.G(ctx).WithError(cleanupErr).Warn("failed to cleanup after error")
   157  			}
   158  			cleanup = noCleanup
   159  			return
   160  		}
   161  		err = label.Relabel(sourcePath, mountLabel, label.IsShared(m.Mode))
   162  		if err != nil && !errors.Is(err, syscall.ENOTSUP) {
   163  			path = ""
   164  			retErr = errors.Wrapf(err, "error setting label on mount source '%s'", sourcePath)
   165  			if cleanupErr := cleanup(ctx); cleanupErr != nil {
   166  				log.G(ctx).WithError(cleanupErr).Warn("failed to cleanup after error")
   167  			}
   168  			cleanup = noCleanup
   169  		}
   170  	}()
   171  
   172  	if m.Volume != nil {
   173  		id := m.ID
   174  		if id == "" {
   175  			id = stringid.GenerateRandomID()
   176  		}
   177  		volumePath, err := m.Volume.Mount(id)
   178  		if err != nil {
   179  			return "", noCleanup, errors.Wrapf(err, "error while mounting volume '%s'", m.Source)
   180  		}
   181  
   182  		m.ID = id
   183  		clean := noCleanup
   184  		if m.Spec.VolumeOptions != nil && m.Spec.VolumeOptions.Subpath != "" {
   185  			subpath := m.Spec.VolumeOptions.Subpath
   186  
   187  			safePath, err := safepath.Join(ctx, volumePath, subpath)
   188  			if err != nil {
   189  				if err := m.Volume.Unmount(id); err != nil {
   190  					log.G(ctx).WithError(err).Error("failed to unmount after safepath.Join failed")
   191  				}
   192  				return "", noCleanup, err
   193  			}
   194  			m.safePaths = append(m.safePaths, safePath)
   195  			log.G(ctx).Debugf("mounting (%s|%s) via %s", volumePath, subpath, safePath.Path())
   196  
   197  			clean = safePath.Close
   198  			volumePath = safePath.Path()
   199  		}
   200  
   201  		m.active++
   202  		return volumePath, clean, nil
   203  	}
   204  
   205  	if len(m.Source) == 0 {
   206  		return "", noCleanup, fmt.Errorf("Unable to setup mount point, neither source nor volume defined")
   207  	}
   208  
   209  	if m.Type == mounttypes.TypeBind {
   210  		// Before creating the source directory on the host, invoke checkFun if it's not nil. One of
   211  		// the use case is to forbid creating the daemon socket as a directory if the daemon is in
   212  		// the process of shutting down.
   213  		if checkFun != nil {
   214  			if err := checkFun(m); err != nil {
   215  				return "", noCleanup, err
   216  			}
   217  		}
   218  
   219  		// idtools.MkdirAllNewAs() produces an error if m.Source exists and is a file (not a directory)
   220  		// also, makes sure that if the directory is created, the correct remapped rootUID/rootGID will own it
   221  		if err := idtools.MkdirAllAndChownNew(m.Source, 0o755, rootIDs); err != nil {
   222  			if perr, ok := err.(*os.PathError); ok {
   223  				if perr.Err != syscall.ENOTDIR {
   224  					return "", noCleanup, errors.Wrapf(err, "error while creating mount source path '%s'", m.Source)
   225  				}
   226  			}
   227  		}
   228  	}
   229  	return m.Source, noCleanup, nil
   230  }
   231  
   232  func (m *MountPoint) LiveRestore(ctx context.Context) error {
   233  	if m.Volume == nil {
   234  		log.G(ctx).Debug("No volume to restore")
   235  		return nil
   236  	}
   237  
   238  	lrv, ok := m.Volume.(volume.LiveRestorer)
   239  	if !ok {
   240  		log.G(ctx).WithField("volume", m.Volume.Name()).Debugf("Volume does not support live restore: %T", m.Volume)
   241  		return nil
   242  	}
   243  
   244  	id := m.ID
   245  	if id == "" {
   246  		id = stringid.GenerateRandomID()
   247  	}
   248  
   249  	if err := lrv.LiveRestoreVolume(ctx, id); err != nil {
   250  		return errors.Wrapf(err, "error while restoring volume '%s'", m.Source)
   251  	}
   252  
   253  	m.ID = id
   254  	m.active++
   255  	return nil
   256  }
   257  
   258  // Path returns the path of a volume in a mount point.
   259  func (m *MountPoint) Path() string {
   260  	if m.Volume != nil {
   261  		return m.Volume.Path()
   262  	}
   263  	return m.Source
   264  }
   265  
   266  func errInvalidMode(mode string) error {
   267  	return errors.Errorf("invalid mode: %v", mode)
   268  }
   269  
   270  func errInvalidSpec(spec string) error {
   271  	return errors.Errorf("invalid volume specification: '%s'", spec)
   272  }
   273  
   274  // noCleanup is a no-op cleanup function.
   275  func noCleanup(_ context.Context) error {
   276  	return nil
   277  }