github.com/demonoid81/containerd@v1.3.4/snapshots/windows/windows.go (about)

     1  // +build windows
     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 windows
    20  
    21  import (
    22  	"context"
    23  	"encoding/json"
    24  	"os"
    25  	"path/filepath"
    26  	"strings"
    27  
    28  	winfs "github.com/Microsoft/go-winio/pkg/fs"
    29  	"github.com/Microsoft/go-winio/vhd"
    30  	"github.com/Microsoft/hcsshim"
    31  	"github.com/containerd/containerd/errdefs"
    32  	"github.com/containerd/containerd/log"
    33  	"github.com/containerd/containerd/mount"
    34  	"github.com/containerd/containerd/plugin"
    35  	"github.com/containerd/containerd/snapshots"
    36  	"github.com/containerd/containerd/snapshots/storage"
    37  	"github.com/containerd/continuity/fs"
    38  	"github.com/pkg/errors"
    39  )
    40  
    41  func init() {
    42  	plugin.Register(&plugin.Registration{
    43  		Type: plugin.SnapshotPlugin,
    44  		ID:   "windows",
    45  		InitFn: func(ic *plugin.InitContext) (interface{}, error) {
    46  			return NewSnapshotter(ic.Root)
    47  		},
    48  	})
    49  }
    50  
    51  type snapshotter struct {
    52  	root string
    53  	info hcsshim.DriverInfo
    54  	ms   *storage.MetaStore
    55  }
    56  
    57  // NewSnapshotter returns a new windows snapshotter
    58  func NewSnapshotter(root string) (snapshots.Snapshotter, error) {
    59  	fsType, err := winfs.GetFileSystemType(root)
    60  	if err != nil {
    61  		return nil, err
    62  	}
    63  	if strings.ToLower(fsType) != "ntfs" {
    64  		return nil, errors.Wrapf(errdefs.ErrInvalidArgument, "%s is not on an NTFS volume - only NTFS volumes are supported", root)
    65  	}
    66  
    67  	if err := os.MkdirAll(root, 0700); err != nil {
    68  		return nil, err
    69  	}
    70  	ms, err := storage.NewMetaStore(filepath.Join(root, "metadata.db"))
    71  	if err != nil {
    72  		return nil, err
    73  	}
    74  
    75  	if err := os.Mkdir(filepath.Join(root, "snapshots"), 0700); err != nil && !os.IsExist(err) {
    76  		return nil, err
    77  	}
    78  
    79  	return &snapshotter{
    80  		info: hcsshim.DriverInfo{
    81  			HomeDir: filepath.Join(root, "snapshots"),
    82  		},
    83  		root: root,
    84  		ms:   ms,
    85  	}, nil
    86  }
    87  
    88  // Stat returns the info for an active or committed snapshot by name or
    89  // key.
    90  //
    91  // Should be used for parent resolution, existence checks and to discern
    92  // the kind of snapshot.
    93  func (s *snapshotter) Stat(ctx context.Context, key string) (snapshots.Info, error) {
    94  	ctx, t, err := s.ms.TransactionContext(ctx, false)
    95  	if err != nil {
    96  		return snapshots.Info{}, err
    97  	}
    98  	defer t.Rollback()
    99  
   100  	_, info, _, err := storage.GetInfo(ctx, key)
   101  	return info, err
   102  }
   103  
   104  func (s *snapshotter) Update(ctx context.Context, info snapshots.Info, fieldpaths ...string) (snapshots.Info, error) {
   105  	ctx, t, err := s.ms.TransactionContext(ctx, true)
   106  	if err != nil {
   107  		return snapshots.Info{}, err
   108  	}
   109  	defer t.Rollback()
   110  
   111  	info, err = storage.UpdateInfo(ctx, info, fieldpaths...)
   112  	if err != nil {
   113  		return snapshots.Info{}, err
   114  	}
   115  
   116  	if err := t.Commit(); err != nil {
   117  		return snapshots.Info{}, err
   118  	}
   119  
   120  	return info, nil
   121  }
   122  
   123  func (s *snapshotter) Usage(ctx context.Context, key string) (snapshots.Usage, error) {
   124  	ctx, t, err := s.ms.TransactionContext(ctx, false)
   125  	if err != nil {
   126  		return snapshots.Usage{}, err
   127  	}
   128  	defer t.Rollback()
   129  
   130  	_, info, usage, err := storage.GetInfo(ctx, key)
   131  	if err != nil {
   132  		return snapshots.Usage{}, err
   133  	}
   134  
   135  	if info.Kind == snapshots.KindActive {
   136  		du := fs.Usage{
   137  			Size: 0,
   138  		}
   139  		usage = snapshots.Usage(du)
   140  	}
   141  
   142  	return usage, nil
   143  }
   144  
   145  func (s *snapshotter) Prepare(ctx context.Context, key, parent string, opts ...snapshots.Opt) ([]mount.Mount, error) {
   146  	return s.createSnapshot(ctx, snapshots.KindActive, key, parent, opts)
   147  }
   148  
   149  func (s *snapshotter) View(ctx context.Context, key, parent string, opts ...snapshots.Opt) ([]mount.Mount, error) {
   150  	return s.createSnapshot(ctx, snapshots.KindView, key, parent, opts)
   151  }
   152  
   153  // Mounts returns the mounts for the transaction identified by key. Can be
   154  // called on an read-write or readonly transaction.
   155  //
   156  // This can be used to recover mounts after calling View or Prepare.
   157  func (s *snapshotter) Mounts(ctx context.Context, key string) ([]mount.Mount, error) {
   158  	ctx, t, err := s.ms.TransactionContext(ctx, false)
   159  	if err != nil {
   160  		return nil, err
   161  	}
   162  	defer t.Rollback()
   163  
   164  	snapshot, err := storage.GetSnapshot(ctx, key)
   165  	if err != nil {
   166  		return nil, errors.Wrap(err, "failed to get snapshot mount")
   167  	}
   168  	return s.mounts(snapshot), nil
   169  }
   170  
   171  func (s *snapshotter) Commit(ctx context.Context, name, key string, opts ...snapshots.Opt) error {
   172  	ctx, t, err := s.ms.TransactionContext(ctx, true)
   173  	if err != nil {
   174  		return err
   175  	}
   176  	defer t.Rollback()
   177  
   178  	usage := fs.Usage{
   179  		Size: 0,
   180  	}
   181  
   182  	if _, err = storage.CommitActive(ctx, key, name, snapshots.Usage(usage), opts...); err != nil {
   183  		return errors.Wrap(err, "failed to commit snapshot")
   184  	}
   185  
   186  	if err := t.Commit(); err != nil {
   187  		return err
   188  	}
   189  	return nil
   190  }
   191  
   192  // Remove abandons the transaction identified by key. All resources
   193  // associated with the key will be removed.
   194  func (s *snapshotter) Remove(ctx context.Context, key string) error {
   195  	ctx, t, err := s.ms.TransactionContext(ctx, true)
   196  	if err != nil {
   197  		return err
   198  	}
   199  	defer t.Rollback()
   200  
   201  	id, _, err := storage.Remove(ctx, key)
   202  	if err != nil {
   203  		return errors.Wrap(err, "failed to remove")
   204  	}
   205  
   206  	path := s.getSnapshotDir(id)
   207  	renamedID := "rm-" + id
   208  	renamed := s.getSnapshotDir(renamedID)
   209  	if err := os.Rename(path, renamed); err != nil && !os.IsNotExist(err) {
   210  		if !os.IsPermission(err) {
   211  			return err
   212  		}
   213  		// If permission denied, it's possible that the scratch is still mounted, an
   214  		// artifact after a hard daemon crash for example. Worth a shot to try detaching it
   215  		// before retrying the rename.
   216  		if detachErr := vhd.DetachVhd(filepath.Join(path, "sandbox.vhdx")); detachErr != nil {
   217  			return errors.Wrapf(err, "failed to detach VHD: %s", detachErr)
   218  		}
   219  		if renameErr := os.Rename(path, renamed); renameErr != nil && !os.IsNotExist(renameErr) {
   220  			return errors.Wrapf(err, "second rename attempt following detach failed: %s", renameErr)
   221  		}
   222  	}
   223  
   224  	if err := t.Commit(); err != nil {
   225  		if err1 := os.Rename(renamed, path); err1 != nil {
   226  			// May cause inconsistent data on disk
   227  			log.G(ctx).WithError(err1).WithField("path", renamed).Errorf("Failed to rename after failed commit")
   228  		}
   229  		return errors.Wrap(err, "failed to commit")
   230  	}
   231  
   232  	if err := hcsshim.DestroyLayer(s.info, renamedID); err != nil {
   233  		// Must be cleaned up, any "rm-*" could be removed if no active transactions
   234  		log.G(ctx).WithError(err).WithField("path", renamed).Warnf("Failed to remove root filesystem")
   235  	}
   236  
   237  	return nil
   238  }
   239  
   240  // Walk the committed snapshots.
   241  func (s *snapshotter) Walk(ctx context.Context, fn func(context.Context, snapshots.Info) error) error {
   242  	ctx, t, err := s.ms.TransactionContext(ctx, false)
   243  	if err != nil {
   244  		return err
   245  	}
   246  	defer t.Rollback()
   247  
   248  	return storage.WalkInfo(ctx, fn)
   249  }
   250  
   251  // Close closes the snapshotter
   252  func (s *snapshotter) Close() error {
   253  	return s.ms.Close()
   254  }
   255  
   256  func (s *snapshotter) mounts(sn storage.Snapshot) []mount.Mount {
   257  	var (
   258  		roFlag           string
   259  		source           string
   260  		parentLayerPaths []string
   261  	)
   262  
   263  	if sn.Kind == snapshots.KindView {
   264  		roFlag = "ro"
   265  	} else {
   266  		roFlag = "rw"
   267  	}
   268  
   269  	if len(sn.ParentIDs) == 0 || sn.Kind == snapshots.KindActive {
   270  		source = s.getSnapshotDir(sn.ID)
   271  		parentLayerPaths = s.parentIDsToParentPaths(sn.ParentIDs)
   272  	} else {
   273  		source = s.getSnapshotDir(sn.ParentIDs[0])
   274  		parentLayerPaths = s.parentIDsToParentPaths(sn.ParentIDs[1:])
   275  	}
   276  
   277  	// error is not checked here, as a string array will never fail to Marshal
   278  	parentLayersJSON, _ := json.Marshal(parentLayerPaths)
   279  	parentLayersOption := mount.ParentLayerPathsFlag + string(parentLayersJSON)
   280  
   281  	var mounts []mount.Mount
   282  	mounts = append(mounts, mount.Mount{
   283  		Source: source,
   284  		Type:   "windows-layer",
   285  		Options: []string{
   286  			roFlag,
   287  			parentLayersOption,
   288  		},
   289  	})
   290  
   291  	return mounts
   292  }
   293  
   294  func (s *snapshotter) getSnapshotDir(id string) string {
   295  	return filepath.Join(s.root, "snapshots", id)
   296  }
   297  
   298  func (s *snapshotter) createSnapshot(ctx context.Context, kind snapshots.Kind, key, parent string, opts []snapshots.Opt) ([]mount.Mount, error) {
   299  	ctx, t, err := s.ms.TransactionContext(ctx, true)
   300  	if err != nil {
   301  		return nil, err
   302  	}
   303  	defer t.Rollback()
   304  
   305  	newSnapshot, err := storage.CreateSnapshot(ctx, kind, key, parent, opts...)
   306  	if err != nil {
   307  		return nil, errors.Wrap(err, "failed to create snapshot")
   308  	}
   309  
   310  	if kind == snapshots.KindActive {
   311  		parentLayerPaths := s.parentIDsToParentPaths(newSnapshot.ParentIDs)
   312  
   313  		var parentPath string
   314  		if len(parentLayerPaths) != 0 {
   315  			parentPath = parentLayerPaths[0]
   316  		}
   317  
   318  		if err := hcsshim.CreateSandboxLayer(s.info, newSnapshot.ID, parentPath, parentLayerPaths); err != nil {
   319  			return nil, errors.Wrap(err, "failed to create sandbox layer")
   320  		}
   321  
   322  		// TODO(darrenstahlmsft): Allow changing sandbox size
   323  	}
   324  
   325  	if err := t.Commit(); err != nil {
   326  		return nil, errors.Wrap(err, "commit failed")
   327  	}
   328  
   329  	return s.mounts(newSnapshot), nil
   330  }
   331  
   332  func (s *snapshotter) parentIDsToParentPaths(parentIDs []string) []string {
   333  	var parentLayerPaths []string
   334  	for _, ID := range parentIDs {
   335  		parentLayerPaths = append(parentLayerPaths, s.getSnapshotDir(ID))
   336  	}
   337  	return parentLayerPaths
   338  }