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