gopkg.in/docker/docker.v20@v20.10.27/volume/local/local.go (about) 1 // Package local provides the default implementation for volumes. It 2 // is used to mount data volume containers and directories local to 3 // the host server. 4 package local // import "github.com/docker/docker/volume/local" 5 6 import ( 7 "context" 8 "encoding/json" 9 "fmt" 10 "os" 11 "path/filepath" 12 "reflect" 13 "strings" 14 "sync" 15 16 "github.com/docker/docker/daemon/names" 17 "github.com/docker/docker/errdefs" 18 "github.com/docker/docker/pkg/idtools" 19 "github.com/docker/docker/quota" 20 "github.com/docker/docker/volume" 21 "github.com/pkg/errors" 22 "github.com/sirupsen/logrus" 23 ) 24 25 // VolumeDataPathName is the name of the directory where the volume data is stored. 26 // It uses a very distinctive name to avoid collisions migrating data between 27 // Docker versions. 28 const ( 29 VolumeDataPathName = "_data" 30 volumesPathName = "volumes" 31 ) 32 33 var ( 34 // ErrNotFound is the typed error returned when the requested volume name can't be found 35 ErrNotFound = fmt.Errorf("volume not found") 36 // volumeNameRegex ensures the name assigned for the volume is valid. 37 // This name is used to create the bind directory, so we need to avoid characters that 38 // would make the path to escape the root directory. 39 volumeNameRegex = names.RestrictedNamePattern 40 41 _ volume.LiveRestorer = (*localVolume)(nil) 42 ) 43 44 type activeMount struct { 45 count uint64 46 mounted bool 47 } 48 49 // New instantiates a new Root instance with the provided scope. Scope 50 // is the base path that the Root instance uses to store its 51 // volumes. The base path is created here if it does not exist. 52 func New(scope string, rootIdentity idtools.Identity) (*Root, error) { 53 rootDirectory := filepath.Join(scope, volumesPathName) 54 55 if err := idtools.MkdirAllAndChown(rootDirectory, 0701, idtools.CurrentIdentity()); err != nil { 56 return nil, err 57 } 58 59 r := &Root{ 60 scope: scope, 61 path: rootDirectory, 62 volumes: make(map[string]*localVolume), 63 rootIdentity: rootIdentity, 64 } 65 66 dirs, err := os.ReadDir(rootDirectory) 67 if err != nil { 68 return nil, err 69 } 70 71 if r.quotaCtl, err = quota.NewControl(rootDirectory); err != nil { 72 logrus.Debugf("No quota support for local volumes in %s: %v", rootDirectory, err) 73 } 74 75 for _, d := range dirs { 76 if !d.IsDir() { 77 continue 78 } 79 80 name := filepath.Base(d.Name()) 81 v := &localVolume{ 82 driverName: r.Name(), 83 name: name, 84 path: r.DataPath(name), 85 quotaCtl: r.quotaCtl, 86 } 87 r.volumes[name] = v 88 optsFilePath := filepath.Join(rootDirectory, name, "opts.json") 89 if b, err := os.ReadFile(optsFilePath); err == nil { 90 opts := optsConfig{} 91 if err := json.Unmarshal(b, &opts); err != nil { 92 return nil, errors.Wrapf(err, "error while unmarshaling volume options for volume: %s", name) 93 } 94 // Make sure this isn't an empty optsConfig. 95 // This could be empty due to buggy behavior in older versions of Docker. 96 if !reflect.DeepEqual(opts, optsConfig{}) { 97 v.opts = &opts 98 } 99 // unmount anything that may still be mounted (for example, from an 100 // unclean shutdown). This is a no-op on windows 101 unmount(v.path) 102 } 103 } 104 105 return r, nil 106 } 107 108 // Root implements the Driver interface for the volume package and 109 // manages the creation/removal of volumes. It uses only standard vfs 110 // commands to create/remove dirs within its provided scope. 111 type Root struct { 112 m sync.Mutex 113 scope string 114 path string 115 quotaCtl *quota.Control 116 volumes map[string]*localVolume 117 rootIdentity idtools.Identity 118 } 119 120 // List lists all the volumes 121 func (r *Root) List() ([]volume.Volume, error) { 122 var ls []volume.Volume 123 r.m.Lock() 124 for _, v := range r.volumes { 125 ls = append(ls, v) 126 } 127 r.m.Unlock() 128 return ls, nil 129 } 130 131 // DataPath returns the constructed path of this volume. 132 func (r *Root) DataPath(volumeName string) string { 133 return filepath.Join(r.path, volumeName, VolumeDataPathName) 134 } 135 136 // Name returns the name of Root, defined in the volume package in the DefaultDriverName constant. 137 func (r *Root) Name() string { 138 return volume.DefaultDriverName 139 } 140 141 // Create creates a new volume.Volume with the provided name, creating 142 // the underlying directory tree required for this volume in the 143 // process. 144 func (r *Root) Create(name string, opts map[string]string) (volume.Volume, error) { 145 if err := r.validateName(name); err != nil { 146 return nil, err 147 } 148 149 r.m.Lock() 150 defer r.m.Unlock() 151 152 v, exists := r.volumes[name] 153 if exists { 154 return v, nil 155 } 156 157 path := r.DataPath(name) 158 volRoot := filepath.Dir(path) 159 // Root dir does not need to be accessed by the remapped root 160 if err := idtools.MkdirAllAndChown(volRoot, 0701, idtools.CurrentIdentity()); err != nil { 161 return nil, errors.Wrapf(errdefs.System(err), "error while creating volume root path '%s'", volRoot) 162 } 163 164 // Remapped root does need access to the data path 165 if err := idtools.MkdirAllAndChown(path, 0755, r.rootIdentity); err != nil { 166 return nil, errors.Wrapf(errdefs.System(err), "error while creating volume data path '%s'", path) 167 } 168 169 var err error 170 defer func() { 171 if err != nil { 172 os.RemoveAll(filepath.Dir(path)) 173 } 174 }() 175 176 v = &localVolume{ 177 driverName: r.Name(), 178 name: name, 179 path: path, 180 quotaCtl: r.quotaCtl, 181 } 182 183 if len(opts) != 0 { 184 if err = setOpts(v, opts); err != nil { 185 return nil, err 186 } 187 var b []byte 188 b, err = json.Marshal(v.opts) 189 if err != nil { 190 return nil, err 191 } 192 if err = os.WriteFile(filepath.Join(filepath.Dir(path), "opts.json"), b, 0600); err != nil { 193 return nil, errdefs.System(errors.Wrap(err, "error while persisting volume options")) 194 } 195 } 196 197 r.volumes[name] = v 198 return v, nil 199 } 200 201 // Remove removes the specified volume and all underlying data. If the 202 // given volume does not belong to this driver and an error is 203 // returned. The volume is reference counted, if all references are 204 // not released then the volume is not removed. 205 func (r *Root) Remove(v volume.Volume) error { 206 r.m.Lock() 207 defer r.m.Unlock() 208 209 lv, ok := v.(*localVolume) 210 if !ok { 211 return errdefs.System(errors.Errorf("unknown volume type %T", v)) 212 } 213 214 if lv.active.count > 0 { 215 return errdefs.System(errors.Errorf("volume has active mounts")) 216 } 217 218 if err := lv.unmount(); err != nil { 219 return err 220 } 221 222 realPath, err := filepath.EvalSymlinks(lv.path) 223 if err != nil { 224 if !os.IsNotExist(err) { 225 return err 226 } 227 realPath = filepath.Dir(lv.path) 228 } 229 230 if !r.scopedPath(realPath) { 231 return errdefs.System(errors.Errorf("Unable to remove a directory outside of the local volume root %s: %s", r.scope, realPath)) 232 } 233 234 if err := removePath(realPath); err != nil { 235 return err 236 } 237 238 delete(r.volumes, lv.name) 239 return removePath(filepath.Dir(lv.path)) 240 } 241 242 func removePath(path string) error { 243 if err := os.RemoveAll(path); err != nil { 244 if os.IsNotExist(err) { 245 return nil 246 } 247 return errdefs.System(errors.Wrapf(err, "error removing volume path '%s'", path)) 248 } 249 return nil 250 } 251 252 // Get looks up the volume for the given name and returns it if found 253 func (r *Root) Get(name string) (volume.Volume, error) { 254 r.m.Lock() 255 v, exists := r.volumes[name] 256 r.m.Unlock() 257 if !exists { 258 return nil, ErrNotFound 259 } 260 return v, nil 261 } 262 263 // Scope returns the local volume scope 264 func (r *Root) Scope() string { 265 return volume.LocalScope 266 } 267 268 func (r *Root) validateName(name string) error { 269 if len(name) == 1 { 270 return errdefs.InvalidParameter(errors.New("volume name is too short, names should be at least two alphanumeric characters")) 271 } 272 if !volumeNameRegex.MatchString(name) { 273 return errdefs.InvalidParameter(errors.Errorf("%q includes invalid characters for a local volume name, only %q are allowed. If you intended to pass a host directory, use absolute path", name, names.RestrictedNameChars)) 274 } 275 return nil 276 } 277 278 // localVolume implements the Volume interface from the volume package and 279 // represents the volumes created by Root. 280 type localVolume struct { 281 m sync.Mutex 282 // unique name of the volume 283 name string 284 // path is the path on the host where the data lives 285 path string 286 // driverName is the name of the driver that created the volume. 287 driverName string 288 // opts is the parsed list of options used to create the volume 289 opts *optsConfig 290 // active refcounts the active mounts 291 active activeMount 292 // reference to Root instances quotaCtl 293 quotaCtl *quota.Control 294 } 295 296 // Name returns the name of the given Volume. 297 func (v *localVolume) Name() string { 298 return v.name 299 } 300 301 // DriverName returns the driver that created the given Volume. 302 func (v *localVolume) DriverName() string { 303 return v.driverName 304 } 305 306 // Path returns the data location. 307 func (v *localVolume) Path() string { 308 return v.path 309 } 310 311 // CachedPath returns the data location 312 func (v *localVolume) CachedPath() string { 313 return v.path 314 } 315 316 // Mount implements the localVolume interface, returning the data location. 317 // If there are any provided mount options, the resources will be mounted at this point 318 func (v *localVolume) Mount(id string) (string, error) { 319 v.m.Lock() 320 defer v.m.Unlock() 321 logger := logrus.WithField("volume", v.name) 322 if v.needsMount() { 323 if !v.active.mounted { 324 logger.Debug("Mounting volume") 325 if err := v.mount(); err != nil { 326 return "", errdefs.System(err) 327 } 328 v.active.mounted = true 329 } 330 v.active.count++ 331 logger.WithField("active mounts", v.active).Debug("Decremented active mount count") 332 } 333 if err := v.postMount(); err != nil { 334 return "", err 335 } 336 return v.path, nil 337 } 338 339 // Unmount dereferences the id, and if it is the last reference will unmount any resources 340 // that were previously mounted. 341 func (v *localVolume) Unmount(id string) error { 342 v.m.Lock() 343 defer v.m.Unlock() 344 logger := logrus.WithField("volume", v.name) 345 346 // Always decrement the count, even if the unmount fails 347 // Essentially docker doesn't care if this fails, it will send an error, but 348 // ultimately there's nothing that can be done. If we don't decrement the count 349 // this volume can never be removed until a daemon restart occurs. 350 if v.needsMount() { 351 v.active.count-- 352 logger.WithField("active mounts", v.active).Debug("Decremented active mount count") 353 } 354 355 if v.active.count > 0 { 356 return nil 357 } 358 359 logger.Debug("Unmounting volume") 360 return v.unmount() 361 } 362 363 func (v *localVolume) Status() map[string]interface{} { 364 return nil 365 } 366 367 // LiveRestoreVolume restores reference counts for mounts 368 // It is assumed that the volume is already mounted since this is only called for active, live-restored containers. 369 func (v *localVolume) LiveRestoreVolume(ctx context.Context, _ string) error { 370 v.m.Lock() 371 defer v.m.Unlock() 372 373 if !v.needsMount() { 374 return nil 375 } 376 v.active.count++ 377 v.active.mounted = true 378 logrus.WithFields(logrus.Fields{ 379 "volume": v.name, 380 "active mounts": v.active, 381 }).Debugf("Live restored volume") 382 return nil 383 } 384 385 // getAddress finds out address/hostname from options 386 func getAddress(opts string) string { 387 optsList := strings.Split(opts, ",") 388 for i := 0; i < len(optsList); i++ { 389 if strings.HasPrefix(optsList[i], "addr=") { 390 addr := strings.SplitN(optsList[i], "=", 2)[1] 391 return addr 392 } 393 } 394 return "" 395 }