github.com/demonoid81/containerd@v1.3.4/snapshots/overlay/overlay.go (about) 1 // +build linux 2 3 /* 4 Copyright The containerd Authors. 5 6 Licensed under the Apache License, Version 2.0 (the "License"); 7 you may not use this file except in compliance with the License. 8 You may obtain a copy of the License at 9 10 http://www.apache.org/licenses/LICENSE-2.0 11 12 Unless required by applicable law or agreed to in writing, software 13 distributed under the License is distributed on an "AS IS" BASIS, 14 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 See the License for the specific language governing permissions and 16 limitations under the License. 17 */ 18 19 package overlay 20 21 import ( 22 "context" 23 "fmt" 24 "io/ioutil" 25 "os" 26 "path/filepath" 27 "strings" 28 "syscall" 29 30 "github.com/containerd/containerd/log" 31 "github.com/containerd/containerd/mount" 32 "github.com/containerd/containerd/platforms" 33 "github.com/containerd/containerd/plugin" 34 "github.com/containerd/containerd/snapshots" 35 "github.com/containerd/containerd/snapshots/storage" 36 "github.com/containerd/continuity/fs" 37 "github.com/pkg/errors" 38 ) 39 40 func init() { 41 plugin.Register(&plugin.Registration{ 42 Type: plugin.SnapshotPlugin, 43 ID: "overlayfs", 44 InitFn: func(ic *plugin.InitContext) (interface{}, error) { 45 ic.Meta.Platforms = append(ic.Meta.Platforms, platforms.DefaultSpec()) 46 ic.Meta.Exports["root"] = ic.Root 47 return NewSnapshotter(ic.Root, AsynchronousRemove) 48 }, 49 }) 50 } 51 52 // SnapshotterConfig is used to configure the overlay snapshotter instance 53 type SnapshotterConfig struct { 54 asyncRemove bool 55 } 56 57 // Opt is an option to configure the overlay snapshotter 58 type Opt func(config *SnapshotterConfig) error 59 60 // AsynchronousRemove defers removal of filesystem content until 61 // the Cleanup method is called. Removals will make the snapshot 62 // referred to by the key unavailable and make the key immediately 63 // available for re-use. 64 func AsynchronousRemove(config *SnapshotterConfig) error { 65 config.asyncRemove = true 66 return nil 67 } 68 69 type snapshotter struct { 70 root string 71 ms *storage.MetaStore 72 asyncRemove bool 73 } 74 75 // NewSnapshotter returns a Snapshotter which uses overlayfs. The overlayfs 76 // diffs are stored under the provided root. A metadata file is stored under 77 // the root. 78 func NewSnapshotter(root string, opts ...Opt) (snapshots.Snapshotter, error) { 79 var config SnapshotterConfig 80 for _, opt := range opts { 81 if err := opt(&config); err != nil { 82 return nil, err 83 } 84 } 85 86 if err := os.MkdirAll(root, 0700); err != nil { 87 return nil, err 88 } 89 supportsDType, err := fs.SupportsDType(root) 90 if err != nil { 91 return nil, err 92 } 93 if !supportsDType { 94 return nil, fmt.Errorf("%s does not support d_type. If the backing filesystem is xfs, please reformat with ftype=1 to enable d_type support", root) 95 } 96 ms, err := storage.NewMetaStore(filepath.Join(root, "metadata.db")) 97 if err != nil { 98 return nil, err 99 } 100 101 if err := os.Mkdir(filepath.Join(root, "snapshots"), 0700); err != nil && !os.IsExist(err) { 102 return nil, err 103 } 104 105 return &snapshotter{ 106 root: root, 107 ms: ms, 108 asyncRemove: config.asyncRemove, 109 }, nil 110 } 111 112 // Stat returns the info for an active or committed snapshot by name or 113 // key. 114 // 115 // Should be used for parent resolution, existence checks and to discern 116 // the kind of snapshot. 117 func (o *snapshotter) Stat(ctx context.Context, key string) (snapshots.Info, error) { 118 ctx, t, err := o.ms.TransactionContext(ctx, false) 119 if err != nil { 120 return snapshots.Info{}, err 121 } 122 defer t.Rollback() 123 _, info, _, err := storage.GetInfo(ctx, key) 124 if err != nil { 125 return snapshots.Info{}, err 126 } 127 128 return info, nil 129 } 130 131 func (o *snapshotter) Update(ctx context.Context, info snapshots.Info, fieldpaths ...string) (snapshots.Info, error) { 132 ctx, t, err := o.ms.TransactionContext(ctx, true) 133 if err != nil { 134 return snapshots.Info{}, err 135 } 136 137 info, err = storage.UpdateInfo(ctx, info, fieldpaths...) 138 if err != nil { 139 t.Rollback() 140 return snapshots.Info{}, err 141 } 142 143 if err := t.Commit(); err != nil { 144 return snapshots.Info{}, err 145 } 146 147 return info, nil 148 } 149 150 // Usage returns the resources taken by the snapshot identified by key. 151 // 152 // For active snapshots, this will scan the usage of the overlay "diff" (aka 153 // "upper") directory and may take some time. 154 // 155 // For committed snapshots, the value is returned from the metadata database. 156 func (o *snapshotter) Usage(ctx context.Context, key string) (snapshots.Usage, error) { 157 ctx, t, err := o.ms.TransactionContext(ctx, false) 158 if err != nil { 159 return snapshots.Usage{}, err 160 } 161 id, info, usage, err := storage.GetInfo(ctx, key) 162 t.Rollback() // transaction no longer needed at this point. 163 164 if err != nil { 165 return snapshots.Usage{}, err 166 } 167 168 upperPath := o.upperPath(id) 169 170 if info.Kind == snapshots.KindActive { 171 du, err := fs.DiskUsage(ctx, upperPath) 172 if err != nil { 173 // TODO(stevvooe): Consider not reporting an error in this case. 174 return snapshots.Usage{}, err 175 } 176 177 usage = snapshots.Usage(du) 178 } 179 180 return usage, nil 181 } 182 183 func (o *snapshotter) Prepare(ctx context.Context, key, parent string, opts ...snapshots.Opt) ([]mount.Mount, error) { 184 return o.createSnapshot(ctx, snapshots.KindActive, key, parent, opts) 185 } 186 187 func (o *snapshotter) View(ctx context.Context, key, parent string, opts ...snapshots.Opt) ([]mount.Mount, error) { 188 return o.createSnapshot(ctx, snapshots.KindView, key, parent, opts) 189 } 190 191 // Mounts returns the mounts for the transaction identified by key. Can be 192 // called on an read-write or readonly transaction. 193 // 194 // This can be used to recover mounts after calling View or Prepare. 195 func (o *snapshotter) Mounts(ctx context.Context, key string) ([]mount.Mount, error) { 196 ctx, t, err := o.ms.TransactionContext(ctx, false) 197 if err != nil { 198 return nil, err 199 } 200 s, err := storage.GetSnapshot(ctx, key) 201 t.Rollback() 202 if err != nil { 203 return nil, errors.Wrap(err, "failed to get active mount") 204 } 205 return o.mounts(s), nil 206 } 207 208 func (o *snapshotter) Commit(ctx context.Context, name, key string, opts ...snapshots.Opt) error { 209 ctx, t, err := o.ms.TransactionContext(ctx, true) 210 if err != nil { 211 return err 212 } 213 214 defer func() { 215 if err != nil { 216 if rerr := t.Rollback(); rerr != nil { 217 log.G(ctx).WithError(rerr).Warn("failed to rollback transaction") 218 } 219 } 220 }() 221 222 // grab the existing id 223 id, _, _, err := storage.GetInfo(ctx, key) 224 if err != nil { 225 return err 226 } 227 228 usage, err := fs.DiskUsage(ctx, o.upperPath(id)) 229 if err != nil { 230 return err 231 } 232 233 if _, err = storage.CommitActive(ctx, key, name, snapshots.Usage(usage), opts...); err != nil { 234 return errors.Wrap(err, "failed to commit snapshot") 235 } 236 return t.Commit() 237 } 238 239 // Remove abandons the snapshot identified by key. The snapshot will 240 // immediately become unavailable and unrecoverable. Disk space will 241 // be freed up on the next call to `Cleanup`. 242 func (o *snapshotter) Remove(ctx context.Context, key string) (err error) { 243 ctx, t, err := o.ms.TransactionContext(ctx, true) 244 if err != nil { 245 return err 246 } 247 defer func() { 248 if err != nil { 249 if rerr := t.Rollback(); rerr != nil { 250 log.G(ctx).WithError(rerr).Warn("failed to rollback transaction") 251 } 252 } 253 }() 254 255 _, _, err = storage.Remove(ctx, key) 256 if err != nil { 257 return errors.Wrap(err, "failed to remove") 258 } 259 260 if !o.asyncRemove { 261 var removals []string 262 removals, err = o.getCleanupDirectories(ctx, t) 263 if err != nil { 264 return errors.Wrap(err, "unable to get directories for removal") 265 } 266 267 // Remove directories after the transaction is closed, failures must not 268 // return error since the transaction is committed with the removal 269 // key no longer available. 270 defer func() { 271 if err == nil { 272 for _, dir := range removals { 273 if err := os.RemoveAll(dir); err != nil { 274 log.G(ctx).WithError(err).WithField("path", dir).Warn("failed to remove directory") 275 } 276 } 277 } 278 }() 279 280 } 281 282 return t.Commit() 283 } 284 285 // Walk the committed snapshots. 286 func (o *snapshotter) Walk(ctx context.Context, fn func(context.Context, snapshots.Info) error) error { 287 ctx, t, err := o.ms.TransactionContext(ctx, false) 288 if err != nil { 289 return err 290 } 291 defer t.Rollback() 292 return storage.WalkInfo(ctx, fn) 293 } 294 295 // Cleanup cleans up disk resources from removed or abandoned snapshots 296 func (o *snapshotter) Cleanup(ctx context.Context) error { 297 cleanup, err := o.cleanupDirectories(ctx) 298 if err != nil { 299 return err 300 } 301 302 for _, dir := range cleanup { 303 if err := os.RemoveAll(dir); err != nil { 304 log.G(ctx).WithError(err).WithField("path", dir).Warn("failed to remove directory") 305 } 306 } 307 308 return nil 309 } 310 311 func (o *snapshotter) cleanupDirectories(ctx context.Context) ([]string, error) { 312 // Get a write transaction to ensure no other write transaction can be entered 313 // while the cleanup is scanning. 314 ctx, t, err := o.ms.TransactionContext(ctx, true) 315 if err != nil { 316 return nil, err 317 } 318 319 defer t.Rollback() 320 return o.getCleanupDirectories(ctx, t) 321 } 322 323 func (o *snapshotter) getCleanupDirectories(ctx context.Context, t storage.Transactor) ([]string, error) { 324 ids, err := storage.IDMap(ctx) 325 if err != nil { 326 return nil, err 327 } 328 329 snapshotDir := filepath.Join(o.root, "snapshots") 330 fd, err := os.Open(snapshotDir) 331 if err != nil { 332 return nil, err 333 } 334 defer fd.Close() 335 336 dirs, err := fd.Readdirnames(0) 337 if err != nil { 338 return nil, err 339 } 340 341 cleanup := []string{} 342 for _, d := range dirs { 343 if _, ok := ids[d]; ok { 344 continue 345 } 346 347 cleanup = append(cleanup, filepath.Join(snapshotDir, d)) 348 } 349 350 return cleanup, nil 351 } 352 353 func (o *snapshotter) createSnapshot(ctx context.Context, kind snapshots.Kind, key, parent string, opts []snapshots.Opt) (_ []mount.Mount, err error) { 354 ctx, t, err := o.ms.TransactionContext(ctx, true) 355 if err != nil { 356 return nil, err 357 } 358 359 var td, path string 360 defer func() { 361 if err != nil { 362 if td != "" { 363 if err1 := os.RemoveAll(td); err1 != nil { 364 log.G(ctx).WithError(err1).Warn("failed to cleanup temp snapshot directory") 365 } 366 } 367 if path != "" { 368 if err1 := os.RemoveAll(path); err1 != nil { 369 log.G(ctx).WithError(err1).WithField("path", path).Error("failed to reclaim snapshot directory, directory may need removal") 370 err = errors.Wrapf(err, "failed to remove path: %v", err1) 371 } 372 } 373 } 374 }() 375 376 snapshotDir := filepath.Join(o.root, "snapshots") 377 td, err = o.prepareDirectory(ctx, snapshotDir, kind) 378 if err != nil { 379 if rerr := t.Rollback(); rerr != nil { 380 log.G(ctx).WithError(rerr).Warn("failed to rollback transaction") 381 } 382 return nil, errors.Wrap(err, "failed to create prepare snapshot dir") 383 } 384 rollback := true 385 defer func() { 386 if rollback { 387 if rerr := t.Rollback(); rerr != nil { 388 log.G(ctx).WithError(rerr).Warn("failed to rollback transaction") 389 } 390 } 391 }() 392 393 s, err := storage.CreateSnapshot(ctx, kind, key, parent, opts...) 394 if err != nil { 395 return nil, errors.Wrap(err, "failed to create snapshot") 396 } 397 398 if len(s.ParentIDs) > 0 { 399 st, err := os.Stat(o.upperPath(s.ParentIDs[0])) 400 if err != nil { 401 return nil, errors.Wrap(err, "failed to stat parent") 402 } 403 404 stat := st.Sys().(*syscall.Stat_t) 405 406 if err := os.Lchown(filepath.Join(td, "fs"), int(stat.Uid), int(stat.Gid)); err != nil { 407 if rerr := t.Rollback(); rerr != nil { 408 log.G(ctx).WithError(rerr).Warn("failed to rollback transaction") 409 } 410 return nil, errors.Wrap(err, "failed to chown") 411 } 412 } 413 414 path = filepath.Join(snapshotDir, s.ID) 415 if err = os.Rename(td, path); err != nil { 416 return nil, errors.Wrap(err, "failed to rename") 417 } 418 td = "" 419 420 rollback = false 421 if err = t.Commit(); err != nil { 422 return nil, errors.Wrap(err, "commit failed") 423 } 424 425 return o.mounts(s), nil 426 } 427 428 func (o *snapshotter) prepareDirectory(ctx context.Context, snapshotDir string, kind snapshots.Kind) (string, error) { 429 td, err := ioutil.TempDir(snapshotDir, "new-") 430 if err != nil { 431 return "", errors.Wrap(err, "failed to create temp dir") 432 } 433 434 if err := os.Mkdir(filepath.Join(td, "fs"), 0755); err != nil { 435 return td, err 436 } 437 438 if kind == snapshots.KindActive { 439 if err := os.Mkdir(filepath.Join(td, "work"), 0711); err != nil { 440 return td, err 441 } 442 } 443 444 return td, nil 445 } 446 447 func (o *snapshotter) mounts(s storage.Snapshot) []mount.Mount { 448 if len(s.ParentIDs) == 0 { 449 // if we only have one layer/no parents then just return a bind mount as overlay 450 // will not work 451 roFlag := "rw" 452 if s.Kind == snapshots.KindView { 453 roFlag = "ro" 454 } 455 456 return []mount.Mount{ 457 { 458 Source: o.upperPath(s.ID), 459 Type: "bind", 460 Options: []string{ 461 roFlag, 462 "rbind", 463 }, 464 }, 465 } 466 } 467 var options []string 468 469 if s.Kind == snapshots.KindActive { 470 options = append(options, 471 fmt.Sprintf("workdir=%s", o.workPath(s.ID)), 472 fmt.Sprintf("upperdir=%s", o.upperPath(s.ID)), 473 ) 474 } else if len(s.ParentIDs) == 1 { 475 return []mount.Mount{ 476 { 477 Source: o.upperPath(s.ParentIDs[0]), 478 Type: "bind", 479 Options: []string{ 480 "ro", 481 "rbind", 482 }, 483 }, 484 } 485 } 486 487 parentPaths := make([]string, len(s.ParentIDs)) 488 for i := range s.ParentIDs { 489 parentPaths[i] = o.upperPath(s.ParentIDs[i]) 490 } 491 492 options = append(options, fmt.Sprintf("lowerdir=%s", strings.Join(parentPaths, ":"))) 493 return []mount.Mount{ 494 { 495 Type: "overlay", 496 Source: "overlay", 497 Options: options, 498 }, 499 } 500 501 } 502 503 func (o *snapshotter) upperPath(id string) string { 504 return filepath.Join(o.root, "snapshots", id, "fs") 505 } 506 507 func (o *snapshotter) workPath(id string) string { 508 return filepath.Join(o.root, "snapshots", id, "work") 509 } 510 511 // Close closes the snapshotter 512 func (o *snapshotter) Close() error { 513 return o.ms.Close() 514 }