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