github.com/demonoid81/containerd@v1.3.4/snapshots/btrfs/btrfs.go (about)

     1  // +build linux,!no_btrfs
     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 btrfs
    20  
    21  import (
    22  	"context"
    23  	"fmt"
    24  	"os"
    25  	"path/filepath"
    26  	"strings"
    27  
    28  	"github.com/containerd/btrfs"
    29  	"github.com/containerd/containerd/log"
    30  	"github.com/containerd/containerd/mount"
    31  	"github.com/containerd/containerd/platforms"
    32  	"github.com/containerd/containerd/plugin"
    33  	"github.com/containerd/containerd/snapshots"
    34  	"github.com/containerd/containerd/snapshots/storage"
    35  	"github.com/containerd/continuity/fs"
    36  
    37  	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
    38  	"github.com/pkg/errors"
    39  	"github.com/sirupsen/logrus"
    40  )
    41  
    42  func init() {
    43  	plugin.Register(&plugin.Registration{
    44  		ID:   "btrfs",
    45  		Type: plugin.SnapshotPlugin,
    46  		InitFn: func(ic *plugin.InitContext) (interface{}, error) {
    47  			ic.Meta.Platforms = []ocispec.Platform{platforms.DefaultSpec()}
    48  			ic.Meta.Exports = map[string]string{"root": ic.Root}
    49  			return NewSnapshotter(ic.Root)
    50  		},
    51  	})
    52  }
    53  
    54  type snapshotter struct {
    55  	device string // device of the root
    56  	root   string // root provides paths for internal storage.
    57  	ms     *storage.MetaStore
    58  }
    59  
    60  // NewSnapshotter returns a Snapshotter using btrfs. Uses the provided
    61  // root directory for snapshots and stores the metadata in
    62  // a file in the provided root.
    63  // root needs to be a mount point of btrfs.
    64  func NewSnapshotter(root string) (snapshots.Snapshotter, error) {
    65  	// If directory does not exist, create it
    66  	if _, err := os.Stat(root); err != nil {
    67  		if !os.IsNotExist(err) {
    68  			return nil, err
    69  		}
    70  		if err := os.Mkdir(root, 0755); err != nil {
    71  			return nil, err
    72  		}
    73  	}
    74  
    75  	mnt, err := mount.Lookup(root)
    76  	if err != nil {
    77  		return nil, err
    78  	}
    79  	if mnt.FSType != "btrfs" {
    80  		return nil, errors.Wrapf(plugin.ErrSkipPlugin, "path %s (%s) must be a btrfs filesystem to be used with the btrfs snapshotter", root, mnt.FSType)
    81  	}
    82  	var (
    83  		active    = filepath.Join(root, "active")
    84  		view      = filepath.Join(root, "view")
    85  		snapshots = filepath.Join(root, "snapshots")
    86  	)
    87  
    88  	for _, path := range []string{
    89  		active,
    90  		view,
    91  		snapshots,
    92  	} {
    93  		if err := os.Mkdir(path, 0755); err != nil && !os.IsExist(err) {
    94  			return nil, err
    95  		}
    96  	}
    97  	ms, err := storage.NewMetaStore(filepath.Join(root, "metadata.db"))
    98  	if err != nil {
    99  		return nil, err
   100  	}
   101  
   102  	return &snapshotter{
   103  		device: mnt.Source,
   104  		root:   root,
   105  		ms:     ms,
   106  	}, nil
   107  }
   108  
   109  // Stat returns the info for an active or committed snapshot by name or
   110  // key.
   111  //
   112  // Should be used for parent resolution, existence checks and to discern
   113  // the kind of snapshot.
   114  func (b *snapshotter) Stat(ctx context.Context, key string) (snapshots.Info, error) {
   115  	ctx, t, err := b.ms.TransactionContext(ctx, false)
   116  	if err != nil {
   117  		return snapshots.Info{}, err
   118  	}
   119  	defer t.Rollback()
   120  	_, info, _, err := storage.GetInfo(ctx, key)
   121  	if err != nil {
   122  		return snapshots.Info{}, err
   123  	}
   124  
   125  	return info, nil
   126  }
   127  
   128  func (b *snapshotter) Update(ctx context.Context, info snapshots.Info, fieldpaths ...string) (snapshots.Info, error) {
   129  	ctx, t, err := b.ms.TransactionContext(ctx, true)
   130  	if err != nil {
   131  		return snapshots.Info{}, err
   132  	}
   133  
   134  	info, err = storage.UpdateInfo(ctx, info, fieldpaths...)
   135  	if err != nil {
   136  		t.Rollback()
   137  		return snapshots.Info{}, err
   138  	}
   139  
   140  	if err := t.Commit(); err != nil {
   141  		return snapshots.Info{}, err
   142  	}
   143  
   144  	return info, nil
   145  }
   146  
   147  // Usage retrieves the disk usage of the top-level snapshot.
   148  func (b *snapshotter) Usage(ctx context.Context, key string) (snapshots.Usage, error) {
   149  	return b.usage(ctx, key)
   150  }
   151  
   152  func (b *snapshotter) usage(ctx context.Context, key string) (snapshots.Usage, error) {
   153  	ctx, t, err := b.ms.TransactionContext(ctx, false)
   154  	if err != nil {
   155  		return snapshots.Usage{}, err
   156  	}
   157  	id, info, usage, err := storage.GetInfo(ctx, key)
   158  	var parentID string
   159  	if err == nil && info.Kind == snapshots.KindActive && info.Parent != "" {
   160  		parentID, _, _, err = storage.GetInfo(ctx, info.Parent)
   161  
   162  	}
   163  	t.Rollback() // transaction no longer needed at this point.
   164  
   165  	if err != nil {
   166  		return snapshots.Usage{}, err
   167  	}
   168  
   169  	if info.Kind == snapshots.KindActive {
   170  		var du fs.Usage
   171  		p := filepath.Join(b.root, "active", id)
   172  		if parentID != "" {
   173  			du, err = fs.DiffUsage(ctx, filepath.Join(b.root, "snapshots", parentID), p)
   174  		} else {
   175  			du, err = fs.DiskUsage(ctx, p)
   176  		}
   177  		if err != nil {
   178  			// TODO(stevvooe): Consider not reporting an error in this case.
   179  			return snapshots.Usage{}, err
   180  		}
   181  
   182  		usage = snapshots.Usage(du)
   183  	}
   184  
   185  	return usage, nil
   186  }
   187  
   188  // Walk the committed snapshots.
   189  func (b *snapshotter) Walk(ctx context.Context, fn func(context.Context, snapshots.Info) error) error {
   190  	ctx, t, err := b.ms.TransactionContext(ctx, false)
   191  	if err != nil {
   192  		return err
   193  	}
   194  	defer t.Rollback()
   195  	return storage.WalkInfo(ctx, fn)
   196  }
   197  
   198  func (b *snapshotter) Prepare(ctx context.Context, key, parent string, opts ...snapshots.Opt) ([]mount.Mount, error) {
   199  	return b.makeSnapshot(ctx, snapshots.KindActive, key, parent, opts)
   200  }
   201  
   202  func (b *snapshotter) View(ctx context.Context, key, parent string, opts ...snapshots.Opt) ([]mount.Mount, error) {
   203  	return b.makeSnapshot(ctx, snapshots.KindView, key, parent, opts)
   204  }
   205  
   206  func (b *snapshotter) makeSnapshot(ctx context.Context, kind snapshots.Kind, key, parent string, opts []snapshots.Opt) ([]mount.Mount, error) {
   207  	ctx, t, err := b.ms.TransactionContext(ctx, true)
   208  	if err != nil {
   209  		return nil, err
   210  	}
   211  	defer func() {
   212  		if err != nil && t != nil {
   213  			if rerr := t.Rollback(); rerr != nil {
   214  				log.G(ctx).WithError(rerr).Warn("failed to rollback transaction")
   215  			}
   216  		}
   217  	}()
   218  
   219  	s, err := storage.CreateSnapshot(ctx, kind, key, parent, opts...)
   220  	if err != nil {
   221  		return nil, err
   222  	}
   223  
   224  	target := filepath.Join(b.root, strings.ToLower(s.Kind.String()), s.ID)
   225  
   226  	if len(s.ParentIDs) == 0 {
   227  		// create new subvolume
   228  		// btrfs subvolume create /dir
   229  		if err = btrfs.SubvolCreate(target); err != nil {
   230  			return nil, err
   231  		}
   232  	} else {
   233  		parentp := filepath.Join(b.root, "snapshots", s.ParentIDs[0])
   234  
   235  		var readonly bool
   236  		if kind == snapshots.KindView {
   237  			readonly = true
   238  		}
   239  
   240  		// btrfs subvolume snapshot /parent /subvol
   241  		if err = btrfs.SubvolSnapshot(target, parentp, readonly); err != nil {
   242  			return nil, err
   243  		}
   244  	}
   245  	err = t.Commit()
   246  	t = nil
   247  	if err != nil {
   248  		if derr := btrfs.SubvolDelete(target); derr != nil {
   249  			log.G(ctx).WithError(derr).WithField("subvolume", target).Error("failed to delete subvolume")
   250  		}
   251  		return nil, err
   252  	}
   253  
   254  	return b.mounts(target, s)
   255  }
   256  
   257  func (b *snapshotter) mounts(dir string, s storage.Snapshot) ([]mount.Mount, error) {
   258  	var options []string
   259  
   260  	// get the subvolume id back out for the mount
   261  	sid, err := btrfs.SubvolID(dir)
   262  	if err != nil {
   263  		return nil, err
   264  	}
   265  
   266  	options = append(options, fmt.Sprintf("subvolid=%d", sid))
   267  
   268  	if s.Kind != snapshots.KindActive {
   269  		options = append(options, "ro")
   270  	}
   271  
   272  	return []mount.Mount{
   273  		{
   274  			Type:   "btrfs",
   275  			Source: b.device,
   276  			// NOTE(stevvooe): While it would be nice to use to uuids for
   277  			// mounts, they don't work reliably if the uuids are missing.
   278  			Options: options,
   279  		},
   280  	}, nil
   281  }
   282  
   283  func (b *snapshotter) Commit(ctx context.Context, name, key string, opts ...snapshots.Opt) (err error) {
   284  	usage, err := b.usage(ctx, key)
   285  	if err != nil {
   286  		return errors.Wrap(err, "failed to compute usage")
   287  	}
   288  
   289  	ctx, t, err := b.ms.TransactionContext(ctx, true)
   290  	if err != nil {
   291  		return err
   292  	}
   293  	defer func() {
   294  		if err != nil && t != nil {
   295  			if rerr := t.Rollback(); rerr != nil {
   296  				log.G(ctx).WithError(rerr).Warn("failed to rollback transaction")
   297  			}
   298  		}
   299  	}()
   300  
   301  	id, err := storage.CommitActive(ctx, key, name, usage, opts...) // TODO(stevvooe): Resolve a usage value for btrfs
   302  	if err != nil {
   303  		return errors.Wrap(err, "failed to commit")
   304  	}
   305  
   306  	source := filepath.Join(b.root, "active", id)
   307  	target := filepath.Join(b.root, "snapshots", id)
   308  
   309  	if err := btrfs.SubvolSnapshot(target, source, true); err != nil {
   310  		return err
   311  	}
   312  
   313  	err = t.Commit()
   314  	t = nil
   315  	if err != nil {
   316  		if derr := btrfs.SubvolDelete(target); derr != nil {
   317  			log.G(ctx).WithError(derr).WithField("subvolume", target).Error("failed to delete subvolume")
   318  		}
   319  		return err
   320  	}
   321  
   322  	if derr := btrfs.SubvolDelete(source); derr != nil {
   323  		// Log as warning, only needed for cleanup, will not cause name collision
   324  		log.G(ctx).WithError(derr).WithField("subvolume", source).Warn("failed to delete subvolume")
   325  	}
   326  
   327  	return nil
   328  }
   329  
   330  // Mounts returns the mounts for the transaction identified by key. Can be
   331  // called on an read-write or readonly transaction.
   332  //
   333  // This can be used to recover mounts after calling View or Prepare.
   334  func (b *snapshotter) Mounts(ctx context.Context, key string) ([]mount.Mount, error) {
   335  	ctx, t, err := b.ms.TransactionContext(ctx, false)
   336  	if err != nil {
   337  		return nil, err
   338  	}
   339  	s, err := storage.GetSnapshot(ctx, key)
   340  	t.Rollback()
   341  	if err != nil {
   342  		return nil, errors.Wrap(err, "failed to get active snapshot")
   343  	}
   344  
   345  	dir := filepath.Join(b.root, strings.ToLower(s.Kind.String()), s.ID)
   346  	return b.mounts(dir, s)
   347  }
   348  
   349  // Remove abandons the transaction identified by key. All resources
   350  // associated with the key will be removed.
   351  func (b *snapshotter) Remove(ctx context.Context, key string) (err error) {
   352  	var (
   353  		source, removed string
   354  		readonly        bool
   355  	)
   356  
   357  	ctx, t, err := b.ms.TransactionContext(ctx, true)
   358  	if err != nil {
   359  		return err
   360  	}
   361  	defer func() {
   362  		if err != nil && t != nil {
   363  			if rerr := t.Rollback(); rerr != nil {
   364  				log.G(ctx).WithError(rerr).Warn("failed to rollback transaction")
   365  			}
   366  		}
   367  
   368  		if removed != "" {
   369  			if derr := btrfs.SubvolDelete(removed); derr != nil {
   370  				log.G(ctx).WithError(derr).WithField("subvolume", removed).Warn("failed to delete subvolume")
   371  			}
   372  		}
   373  	}()
   374  
   375  	id, k, err := storage.Remove(ctx, key)
   376  	if err != nil {
   377  		return errors.Wrap(err, "failed to remove snapshot")
   378  	}
   379  
   380  	switch k {
   381  	case snapshots.KindView:
   382  		source = filepath.Join(b.root, "view", id)
   383  		removed = filepath.Join(b.root, "view", "rm-"+id)
   384  		readonly = true
   385  	case snapshots.KindActive:
   386  		source = filepath.Join(b.root, "active", id)
   387  		removed = filepath.Join(b.root, "active", "rm-"+id)
   388  	case snapshots.KindCommitted:
   389  		source = filepath.Join(b.root, "snapshots", id)
   390  		removed = filepath.Join(b.root, "snapshots", "rm-"+id)
   391  		readonly = true
   392  	}
   393  
   394  	if err := btrfs.SubvolSnapshot(removed, source, readonly); err != nil {
   395  		removed = ""
   396  		return err
   397  	}
   398  
   399  	if err := btrfs.SubvolDelete(source); err != nil {
   400  		return errors.Wrapf(err, "failed to remove snapshot %v", source)
   401  	}
   402  
   403  	err = t.Commit()
   404  	t = nil
   405  	if err != nil {
   406  		// Attempt to restore source
   407  		if err1 := btrfs.SubvolSnapshot(source, removed, readonly); err1 != nil {
   408  			log.G(ctx).WithFields(logrus.Fields{
   409  				logrus.ErrorKey: err1,
   410  				"subvolume":     source,
   411  				"renamed":       removed,
   412  			}).Error("failed to restore subvolume from renamed")
   413  			// Keep removed to allow for manual restore
   414  			removed = ""
   415  		}
   416  		return err
   417  	}
   418  
   419  	return nil
   420  }
   421  
   422  // Close closes the snapshotter
   423  func (b *snapshotter) Close() error {
   424  	return b.ms.Close()
   425  }