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