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