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