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