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  }