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 }