github.com/containerd/Containerd@v1.4.13/snapshots/native/native.go (about)

     1  /*
     2     Copyright The containerd Authors.
     3  
     4     Licensed under the Apache License, Version 2.0 (the "License");
     5     you may not use this file except in compliance with the License.
     6     You may obtain a copy of the License at
     7  
     8         http://www.apache.org/licenses/LICENSE-2.0
     9  
    10     Unless required by applicable law or agreed to in writing, software
    11     distributed under the License is distributed on an "AS IS" BASIS,
    12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13     See the License for the specific language governing permissions and
    14     limitations under the License.
    15  */
    16  
    17  package native
    18  
    19  import (
    20  	"context"
    21  	"io/ioutil"
    22  	"os"
    23  	"path/filepath"
    24  
    25  	"github.com/containerd/containerd/log"
    26  	"github.com/containerd/containerd/mount"
    27  	"github.com/containerd/containerd/platforms"
    28  	"github.com/containerd/containerd/plugin"
    29  	"github.com/containerd/containerd/snapshots"
    30  	"github.com/containerd/containerd/snapshots/storage"
    31  
    32  	"github.com/containerd/continuity/fs"
    33  	"github.com/pkg/errors"
    34  )
    35  
    36  func init() {
    37  	plugin.Register(&plugin.Registration{
    38  		Type: plugin.SnapshotPlugin,
    39  		ID:   "native",
    40  		InitFn: func(ic *plugin.InitContext) (interface{}, error) {
    41  			ic.Meta.Platforms = append(ic.Meta.Platforms, platforms.DefaultSpec())
    42  			return NewSnapshotter(ic.Root)
    43  		},
    44  	})
    45  }
    46  
    47  type snapshotter struct {
    48  	root string
    49  	ms   *storage.MetaStore
    50  }
    51  
    52  // NewSnapshotter returns a Snapshotter which copies layers on the underlying
    53  // file system. A metadata file is stored under the root.
    54  func NewSnapshotter(root string) (snapshots.Snapshotter, error) {
    55  	if err := os.MkdirAll(root, 0700); err != nil {
    56  		return nil, err
    57  	}
    58  	ms, err := storage.NewMetaStore(filepath.Join(root, "metadata.db"))
    59  	if err != nil {
    60  		return nil, err
    61  	}
    62  
    63  	if err := os.Mkdir(filepath.Join(root, "snapshots"), 0700); err != nil && !os.IsExist(err) {
    64  		return nil, err
    65  	}
    66  
    67  	return &snapshotter{
    68  		root: root,
    69  		ms:   ms,
    70  	}, nil
    71  }
    72  
    73  // Stat returns the info for an active or committed snapshot by name or
    74  // key.
    75  //
    76  // Should be used for parent resolution, existence checks and to discern
    77  // the kind of snapshot.
    78  func (o *snapshotter) Stat(ctx context.Context, key string) (snapshots.Info, error) {
    79  	ctx, t, err := o.ms.TransactionContext(ctx, false)
    80  	if err != nil {
    81  		return snapshots.Info{}, err
    82  	}
    83  	defer t.Rollback()
    84  	_, info, _, err := storage.GetInfo(ctx, key)
    85  	if err != nil {
    86  		return snapshots.Info{}, err
    87  	}
    88  
    89  	return info, nil
    90  }
    91  
    92  func (o *snapshotter) Update(ctx context.Context, info snapshots.Info, fieldpaths ...string) (snapshots.Info, error) {
    93  	ctx, t, err := o.ms.TransactionContext(ctx, true)
    94  	if err != nil {
    95  		return snapshots.Info{}, err
    96  	}
    97  
    98  	info, err = storage.UpdateInfo(ctx, info, fieldpaths...)
    99  	if err != nil {
   100  		t.Rollback()
   101  		return snapshots.Info{}, err
   102  	}
   103  
   104  	if err := t.Commit(); err != nil {
   105  		return snapshots.Info{}, err
   106  	}
   107  
   108  	return info, nil
   109  }
   110  
   111  func (o *snapshotter) Usage(ctx context.Context, key string) (snapshots.Usage, error) {
   112  	ctx, t, err := o.ms.TransactionContext(ctx, false)
   113  	if err != nil {
   114  		return snapshots.Usage{}, err
   115  	}
   116  	defer t.Rollback()
   117  
   118  	id, info, usage, err := storage.GetInfo(ctx, key)
   119  	if err != nil {
   120  		return snapshots.Usage{}, err
   121  	}
   122  
   123  	if info.Kind == snapshots.KindActive {
   124  		du, err := fs.DiskUsage(ctx, o.getSnapshotDir(id))
   125  		if err != nil {
   126  			return snapshots.Usage{}, err
   127  		}
   128  		usage = snapshots.Usage(du)
   129  	}
   130  
   131  	return usage, nil
   132  }
   133  
   134  func (o *snapshotter) Prepare(ctx context.Context, key, parent string, opts ...snapshots.Opt) ([]mount.Mount, error) {
   135  	return o.createSnapshot(ctx, snapshots.KindActive, key, parent, opts)
   136  }
   137  
   138  func (o *snapshotter) View(ctx context.Context, key, parent string, opts ...snapshots.Opt) ([]mount.Mount, error) {
   139  	return o.createSnapshot(ctx, snapshots.KindView, key, parent, opts)
   140  }
   141  
   142  // Mounts returns the mounts for the transaction identified by key. Can be
   143  // called on an read-write or readonly transaction.
   144  //
   145  // This can be used to recover mounts after calling View or Prepare.
   146  func (o *snapshotter) Mounts(ctx context.Context, key string) ([]mount.Mount, error) {
   147  	ctx, t, err := o.ms.TransactionContext(ctx, false)
   148  	if err != nil {
   149  		return nil, err
   150  	}
   151  	s, err := storage.GetSnapshot(ctx, key)
   152  	t.Rollback()
   153  	if err != nil {
   154  		return nil, errors.Wrap(err, "failed to get snapshot mount")
   155  	}
   156  	return o.mounts(s), nil
   157  }
   158  
   159  func (o *snapshotter) Commit(ctx context.Context, name, key string, opts ...snapshots.Opt) error {
   160  	ctx, t, err := o.ms.TransactionContext(ctx, true)
   161  	if err != nil {
   162  		return err
   163  	}
   164  
   165  	id, _, _, err := storage.GetInfo(ctx, key)
   166  	if err != nil {
   167  		return err
   168  	}
   169  
   170  	usage, err := fs.DiskUsage(ctx, o.getSnapshotDir(id))
   171  	if err != nil {
   172  		return err
   173  	}
   174  
   175  	if _, err := storage.CommitActive(ctx, key, name, snapshots.Usage(usage), opts...); err != nil {
   176  		if rerr := t.Rollback(); rerr != nil {
   177  			log.G(ctx).WithError(rerr).Warn("failed to rollback transaction")
   178  		}
   179  		return errors.Wrap(err, "failed to commit snapshot")
   180  	}
   181  	return t.Commit()
   182  }
   183  
   184  // Remove abandons the transaction identified by key. All resources
   185  // associated with the key will be removed.
   186  func (o *snapshotter) Remove(ctx context.Context, key string) (err error) {
   187  	ctx, t, err := o.ms.TransactionContext(ctx, true)
   188  	if err != nil {
   189  		return err
   190  	}
   191  	defer func() {
   192  		if err != nil && t != nil {
   193  			if rerr := t.Rollback(); rerr != nil {
   194  				log.G(ctx).WithError(rerr).Warn("failed to rollback transaction")
   195  			}
   196  		}
   197  	}()
   198  
   199  	id, _, err := storage.Remove(ctx, key)
   200  	if err != nil {
   201  		return errors.Wrap(err, "failed to remove")
   202  	}
   203  
   204  	path := o.getSnapshotDir(id)
   205  	renamed := filepath.Join(o.root, "snapshots", "rm-"+id)
   206  	if err := os.Rename(path, renamed); err != nil {
   207  		if !os.IsNotExist(err) {
   208  			return errors.Wrap(err, "failed to rename")
   209  		}
   210  		renamed = ""
   211  	}
   212  
   213  	err = t.Commit()
   214  	t = nil
   215  	if err != nil {
   216  		if renamed != "" {
   217  			if err1 := os.Rename(renamed, path); err1 != nil {
   218  				// May cause inconsistent data on disk
   219  				log.G(ctx).WithError(err1).WithField("path", renamed).Errorf("failed to rename after failed commit")
   220  			}
   221  		}
   222  		return errors.Wrap(err, "failed to commit")
   223  	}
   224  	if renamed != "" {
   225  		if err := os.RemoveAll(renamed); err != nil {
   226  			// Must be cleaned up, any "rm-*" could be removed if no active transactions
   227  			log.G(ctx).WithError(err).WithField("path", renamed).Warnf("failed to remove root filesystem")
   228  		}
   229  	}
   230  
   231  	return nil
   232  }
   233  
   234  // Walk the committed snapshots.
   235  func (o *snapshotter) Walk(ctx context.Context, fn snapshots.WalkFunc, fs ...string) error {
   236  	ctx, t, err := o.ms.TransactionContext(ctx, false)
   237  	if err != nil {
   238  		return err
   239  	}
   240  	defer t.Rollback()
   241  	return storage.WalkInfo(ctx, fn, fs...)
   242  }
   243  
   244  func (o *snapshotter) createSnapshot(ctx context.Context, kind snapshots.Kind, key, parent string, opts []snapshots.Opt) (_ []mount.Mount, err error) {
   245  	var (
   246  		path, td string
   247  	)
   248  
   249  	if kind == snapshots.KindActive || parent == "" {
   250  		td, err = ioutil.TempDir(filepath.Join(o.root, "snapshots"), "new-")
   251  		if err != nil {
   252  			return nil, errors.Wrap(err, "failed to create temp dir")
   253  		}
   254  		if err := os.Chmod(td, 0755); err != nil {
   255  			return nil, errors.Wrapf(err, "failed to chmod %s to 0755", td)
   256  		}
   257  		defer func() {
   258  			if err != nil {
   259  				if td != "" {
   260  					if err1 := os.RemoveAll(td); err1 != nil {
   261  						err = errors.Wrapf(err, "remove failed: %v", err1)
   262  					}
   263  				}
   264  				if path != "" {
   265  					if err1 := os.RemoveAll(path); err1 != nil {
   266  						err = errors.Wrapf(err, "failed to remove path: %v", err1)
   267  					}
   268  				}
   269  			}
   270  		}()
   271  	}
   272  
   273  	ctx, t, err := o.ms.TransactionContext(ctx, true)
   274  	if err != nil {
   275  		return nil, err
   276  	}
   277  
   278  	s, err := storage.CreateSnapshot(ctx, kind, key, parent, opts...)
   279  	if err != nil {
   280  		if rerr := t.Rollback(); rerr != nil {
   281  			log.G(ctx).WithError(rerr).Warn("failed to rollback transaction")
   282  		}
   283  		return nil, errors.Wrap(err, "failed to create snapshot")
   284  	}
   285  
   286  	if td != "" {
   287  		if len(s.ParentIDs) > 0 {
   288  			parent := o.getSnapshotDir(s.ParentIDs[0])
   289  			xattrErrorHandler := func(dst, src, xattrKey string, copyErr error) error {
   290  				// security.* xattr cannot be copied in most cases (moby/buildkit#1189)
   291  				log.G(ctx).WithError(copyErr).Debugf("failed to copy xattr %q", xattrKey)
   292  				return nil
   293  			}
   294  			copyDirOpts := []fs.CopyDirOpt{
   295  				fs.WithXAttrErrorHandler(xattrErrorHandler),
   296  			}
   297  			if err := fs.CopyDir(td, parent, copyDirOpts...); err != nil {
   298  				return nil, errors.Wrap(err, "copying of parent failed")
   299  			}
   300  		}
   301  
   302  		path = o.getSnapshotDir(s.ID)
   303  		if err := os.Rename(td, path); err != nil {
   304  			if rerr := t.Rollback(); rerr != nil {
   305  				log.G(ctx).WithError(rerr).Warn("failed to rollback transaction")
   306  			}
   307  			return nil, errors.Wrap(err, "failed to rename")
   308  		}
   309  		td = ""
   310  	}
   311  
   312  	if err := t.Commit(); err != nil {
   313  		return nil, errors.Wrap(err, "commit failed")
   314  	}
   315  
   316  	return o.mounts(s), nil
   317  }
   318  
   319  func (o *snapshotter) getSnapshotDir(id string) string {
   320  	return filepath.Join(o.root, "snapshots", id)
   321  }
   322  
   323  func (o *snapshotter) mounts(s storage.Snapshot) []mount.Mount {
   324  	var (
   325  		roFlag string
   326  		source string
   327  	)
   328  
   329  	if s.Kind == snapshots.KindView {
   330  		roFlag = "ro"
   331  	} else {
   332  		roFlag = "rw"
   333  	}
   334  
   335  	if len(s.ParentIDs) == 0 || s.Kind == snapshots.KindActive {
   336  		source = o.getSnapshotDir(s.ID)
   337  	} else {
   338  		source = o.getSnapshotDir(s.ParentIDs[0])
   339  	}
   340  
   341  	return []mount.Mount{
   342  		{
   343  			Source: source,
   344  			Type:   "bind",
   345  			Options: []string{
   346  				roFlag,
   347  				"rbind",
   348  			},
   349  		},
   350  	}
   351  }
   352  
   353  // Close closes the snapshotter
   354  func (o *snapshotter) Close() error {
   355  	return o.ms.Close()
   356  }