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