github.com/jiasir/docker@v1.3.3-0.20170609024000-252e610103e7/plugin/backend_linux.go (about)

     1  // +build linux
     2  
     3  package plugin
     4  
     5  import (
     6  	"archive/tar"
     7  	"compress/gzip"
     8  	"encoding/json"
     9  	"fmt"
    10  	"io"
    11  	"io/ioutil"
    12  	"net/http"
    13  	"os"
    14  	"path"
    15  	"path/filepath"
    16  	"sort"
    17  	"strings"
    18  
    19  	"github.com/Sirupsen/logrus"
    20  	"github.com/docker/distribution/manifest/schema2"
    21  	"github.com/docker/distribution/reference"
    22  	"github.com/docker/docker/api/types"
    23  	"github.com/docker/docker/api/types/filters"
    24  	"github.com/docker/docker/distribution"
    25  	progressutils "github.com/docker/docker/distribution/utils"
    26  	"github.com/docker/docker/distribution/xfer"
    27  	"github.com/docker/docker/dockerversion"
    28  	"github.com/docker/docker/image"
    29  	"github.com/docker/docker/layer"
    30  	"github.com/docker/docker/pkg/authorization"
    31  	"github.com/docker/docker/pkg/chrootarchive"
    32  	"github.com/docker/docker/pkg/mount"
    33  	"github.com/docker/docker/pkg/pools"
    34  	"github.com/docker/docker/pkg/progress"
    35  	"github.com/docker/docker/plugin/v2"
    36  	refstore "github.com/docker/docker/reference"
    37  	"github.com/opencontainers/go-digest"
    38  	"github.com/pkg/errors"
    39  	"golang.org/x/net/context"
    40  )
    41  
    42  var acceptedPluginFilterTags = map[string]bool{
    43  	"enabled":    true,
    44  	"capability": true,
    45  }
    46  
    47  // Disable deactivates a plugin. This means resources (volumes, networks) cant use them.
    48  func (pm *Manager) Disable(refOrID string, config *types.PluginDisableConfig) error {
    49  	p, err := pm.config.Store.GetV2Plugin(refOrID)
    50  	if err != nil {
    51  		return err
    52  	}
    53  	pm.mu.RLock()
    54  	c := pm.cMap[p]
    55  	pm.mu.RUnlock()
    56  
    57  	if !config.ForceDisable && p.GetRefCount() > 0 {
    58  		return fmt.Errorf("plugin %s is in use", p.Name())
    59  	}
    60  
    61  	for _, typ := range p.GetTypes() {
    62  		if typ.Capability == authorization.AuthZApiImplements {
    63  			authzList := pm.config.AuthzMiddleware.GetAuthzPlugins()
    64  			for i, authPlugin := range authzList {
    65  				if authPlugin.Name() == p.Name() {
    66  					// Remove plugin from authzmiddleware chain
    67  					authzList = append(authzList[:i], authzList[i+1:]...)
    68  					pm.config.AuthzMiddleware.SetAuthzPlugins(authzList)
    69  				}
    70  			}
    71  		}
    72  	}
    73  
    74  	if err := pm.disable(p, c); err != nil {
    75  		return err
    76  	}
    77  	pm.config.LogPluginEvent(p.GetID(), refOrID, "disable")
    78  	return nil
    79  }
    80  
    81  // Enable activates a plugin, which implies that they are ready to be used by containers.
    82  func (pm *Manager) Enable(refOrID string, config *types.PluginEnableConfig) error {
    83  	p, err := pm.config.Store.GetV2Plugin(refOrID)
    84  	if err != nil {
    85  		return err
    86  	}
    87  
    88  	c := &controller{timeoutInSecs: config.Timeout}
    89  	if err := pm.enable(p, c, false); err != nil {
    90  		return err
    91  	}
    92  	pm.config.LogPluginEvent(p.GetID(), refOrID, "enable")
    93  	return nil
    94  }
    95  
    96  // Inspect examines a plugin config
    97  func (pm *Manager) Inspect(refOrID string) (tp *types.Plugin, err error) {
    98  	p, err := pm.config.Store.GetV2Plugin(refOrID)
    99  	if err != nil {
   100  		return nil, err
   101  	}
   102  
   103  	return &p.PluginObj, nil
   104  }
   105  
   106  func (pm *Manager) pull(ctx context.Context, ref reference.Named, config *distribution.ImagePullConfig, outStream io.Writer) error {
   107  	if outStream != nil {
   108  		// Include a buffer so that slow client connections don't affect
   109  		// transfer performance.
   110  		progressChan := make(chan progress.Progress, 100)
   111  
   112  		writesDone := make(chan struct{})
   113  
   114  		defer func() {
   115  			close(progressChan)
   116  			<-writesDone
   117  		}()
   118  
   119  		var cancelFunc context.CancelFunc
   120  		ctx, cancelFunc = context.WithCancel(ctx)
   121  
   122  		go func() {
   123  			progressutils.WriteDistributionProgress(cancelFunc, outStream, progressChan)
   124  			close(writesDone)
   125  		}()
   126  
   127  		config.ProgressOutput = progress.ChanOutput(progressChan)
   128  	} else {
   129  		config.ProgressOutput = progress.DiscardOutput()
   130  	}
   131  	return distribution.Pull(ctx, ref, config)
   132  }
   133  
   134  type tempConfigStore struct {
   135  	config       []byte
   136  	configDigest digest.Digest
   137  }
   138  
   139  func (s *tempConfigStore) Put(c []byte) (digest.Digest, error) {
   140  	dgst := digest.FromBytes(c)
   141  
   142  	s.config = c
   143  	s.configDigest = dgst
   144  
   145  	return dgst, nil
   146  }
   147  
   148  func (s *tempConfigStore) Get(d digest.Digest) ([]byte, error) {
   149  	if d != s.configDigest {
   150  		return nil, fmt.Errorf("digest not found")
   151  	}
   152  	return s.config, nil
   153  }
   154  
   155  func (s *tempConfigStore) RootFSFromConfig(c []byte) (*image.RootFS, error) {
   156  	return configToRootFS(c)
   157  }
   158  
   159  func computePrivileges(c types.PluginConfig) (types.PluginPrivileges, error) {
   160  	var privileges types.PluginPrivileges
   161  	if c.Network.Type != "null" && c.Network.Type != "bridge" && c.Network.Type != "" {
   162  		privileges = append(privileges, types.PluginPrivilege{
   163  			Name:        "network",
   164  			Description: "permissions to access a network",
   165  			Value:       []string{c.Network.Type},
   166  		})
   167  	}
   168  	if c.IpcHost {
   169  		privileges = append(privileges, types.PluginPrivilege{
   170  			Name:        "host ipc namespace",
   171  			Description: "allow access to host ipc namespace",
   172  			Value:       []string{"true"},
   173  		})
   174  	}
   175  	if c.PidHost {
   176  		privileges = append(privileges, types.PluginPrivilege{
   177  			Name:        "host pid namespace",
   178  			Description: "allow access to host pid namespace",
   179  			Value:       []string{"true"},
   180  		})
   181  	}
   182  	for _, mount := range c.Mounts {
   183  		if mount.Source != nil {
   184  			privileges = append(privileges, types.PluginPrivilege{
   185  				Name:        "mount",
   186  				Description: "host path to mount",
   187  				Value:       []string{*mount.Source},
   188  			})
   189  		}
   190  	}
   191  	for _, device := range c.Linux.Devices {
   192  		if device.Path != nil {
   193  			privileges = append(privileges, types.PluginPrivilege{
   194  				Name:        "device",
   195  				Description: "host device to access",
   196  				Value:       []string{*device.Path},
   197  			})
   198  		}
   199  	}
   200  	if c.Linux.AllowAllDevices {
   201  		privileges = append(privileges, types.PluginPrivilege{
   202  			Name:        "allow-all-devices",
   203  			Description: "allow 'rwm' access to all devices",
   204  			Value:       []string{"true"},
   205  		})
   206  	}
   207  	if len(c.Linux.Capabilities) > 0 {
   208  		privileges = append(privileges, types.PluginPrivilege{
   209  			Name:        "capabilities",
   210  			Description: "list of additional capabilities required",
   211  			Value:       c.Linux.Capabilities,
   212  		})
   213  	}
   214  
   215  	return privileges, nil
   216  }
   217  
   218  // Privileges pulls a plugin config and computes the privileges required to install it.
   219  func (pm *Manager) Privileges(ctx context.Context, ref reference.Named, metaHeader http.Header, authConfig *types.AuthConfig) (types.PluginPrivileges, error) {
   220  	// create image store instance
   221  	cs := &tempConfigStore{}
   222  
   223  	// DownloadManager not defined because only pulling configuration.
   224  	pluginPullConfig := &distribution.ImagePullConfig{
   225  		Config: distribution.Config{
   226  			MetaHeaders:      metaHeader,
   227  			AuthConfig:       authConfig,
   228  			RegistryService:  pm.config.RegistryService,
   229  			ImageEventLogger: func(string, string, string) {},
   230  			ImageStore:       cs,
   231  		},
   232  		Schema2Types: distribution.PluginTypes,
   233  	}
   234  
   235  	if err := pm.pull(ctx, ref, pluginPullConfig, nil); err != nil {
   236  		return nil, err
   237  	}
   238  
   239  	if cs.config == nil {
   240  		return nil, errors.New("no configuration pulled")
   241  	}
   242  	var config types.PluginConfig
   243  	if err := json.Unmarshal(cs.config, &config); err != nil {
   244  		return nil, err
   245  	}
   246  
   247  	return computePrivileges(config)
   248  }
   249  
   250  // Upgrade upgrades a plugin
   251  func (pm *Manager) Upgrade(ctx context.Context, ref reference.Named, name string, metaHeader http.Header, authConfig *types.AuthConfig, privileges types.PluginPrivileges, outStream io.Writer) (err error) {
   252  	p, err := pm.config.Store.GetV2Plugin(name)
   253  	if err != nil {
   254  		return errors.Wrap(err, "plugin must be installed before upgrading")
   255  	}
   256  
   257  	if p.IsEnabled() {
   258  		return fmt.Errorf("plugin must be disabled before upgrading")
   259  	}
   260  
   261  	pm.muGC.RLock()
   262  	defer pm.muGC.RUnlock()
   263  
   264  	// revalidate because Pull is public
   265  	if _, err := reference.ParseNormalizedNamed(name); err != nil {
   266  		return errors.Wrapf(err, "failed to parse %q", name)
   267  	}
   268  
   269  	tmpRootFSDir, err := ioutil.TempDir(pm.tmpDir(), ".rootfs")
   270  	if err != nil {
   271  		return err
   272  	}
   273  	defer os.RemoveAll(tmpRootFSDir)
   274  
   275  	dm := &downloadManager{
   276  		tmpDir:    tmpRootFSDir,
   277  		blobStore: pm.blobStore,
   278  	}
   279  
   280  	pluginPullConfig := &distribution.ImagePullConfig{
   281  		Config: distribution.Config{
   282  			MetaHeaders:      metaHeader,
   283  			AuthConfig:       authConfig,
   284  			RegistryService:  pm.config.RegistryService,
   285  			ImageEventLogger: pm.config.LogPluginEvent,
   286  			ImageStore:       dm,
   287  		},
   288  		DownloadManager: dm, // todo: reevaluate if possible to substitute distribution/xfer dependencies instead
   289  		Schema2Types:    distribution.PluginTypes,
   290  	}
   291  
   292  	err = pm.pull(ctx, ref, pluginPullConfig, outStream)
   293  	if err != nil {
   294  		go pm.GC()
   295  		return err
   296  	}
   297  
   298  	if err := pm.upgradePlugin(p, dm.configDigest, dm.blobs, tmpRootFSDir, &privileges); err != nil {
   299  		return err
   300  	}
   301  	p.PluginObj.PluginReference = ref.String()
   302  	return nil
   303  }
   304  
   305  // Pull pulls a plugin, check if the correct privileges are provided and install the plugin.
   306  func (pm *Manager) Pull(ctx context.Context, ref reference.Named, name string, metaHeader http.Header, authConfig *types.AuthConfig, privileges types.PluginPrivileges, outStream io.Writer) (err error) {
   307  	pm.muGC.RLock()
   308  	defer pm.muGC.RUnlock()
   309  
   310  	// revalidate because Pull is public
   311  	nameref, err := reference.ParseNormalizedNamed(name)
   312  	if err != nil {
   313  		return errors.Wrapf(err, "failed to parse %q", name)
   314  	}
   315  	name = reference.FamiliarString(reference.TagNameOnly(nameref))
   316  
   317  	if err := pm.config.Store.validateName(name); err != nil {
   318  		return err
   319  	}
   320  
   321  	tmpRootFSDir, err := ioutil.TempDir(pm.tmpDir(), ".rootfs")
   322  	if err != nil {
   323  		return err
   324  	}
   325  	defer os.RemoveAll(tmpRootFSDir)
   326  
   327  	dm := &downloadManager{
   328  		tmpDir:    tmpRootFSDir,
   329  		blobStore: pm.blobStore,
   330  	}
   331  
   332  	pluginPullConfig := &distribution.ImagePullConfig{
   333  		Config: distribution.Config{
   334  			MetaHeaders:      metaHeader,
   335  			AuthConfig:       authConfig,
   336  			RegistryService:  pm.config.RegistryService,
   337  			ImageEventLogger: pm.config.LogPluginEvent,
   338  			ImageStore:       dm,
   339  		},
   340  		DownloadManager: dm, // todo: reevaluate if possible to substitute distribution/xfer dependencies instead
   341  		Schema2Types:    distribution.PluginTypes,
   342  	}
   343  
   344  	err = pm.pull(ctx, ref, pluginPullConfig, outStream)
   345  	if err != nil {
   346  		go pm.GC()
   347  		return err
   348  	}
   349  
   350  	p, err := pm.createPlugin(name, dm.configDigest, dm.blobs, tmpRootFSDir, &privileges)
   351  	if err != nil {
   352  		return err
   353  	}
   354  	p.PluginObj.PluginReference = ref.String()
   355  
   356  	return nil
   357  }
   358  
   359  // List displays the list of plugins and associated metadata.
   360  func (pm *Manager) List(pluginFilters filters.Args) ([]types.Plugin, error) {
   361  	if err := pluginFilters.Validate(acceptedPluginFilterTags); err != nil {
   362  		return nil, err
   363  	}
   364  
   365  	enabledOnly := false
   366  	disabledOnly := false
   367  	if pluginFilters.Include("enabled") {
   368  		if pluginFilters.ExactMatch("enabled", "true") {
   369  			enabledOnly = true
   370  		} else if pluginFilters.ExactMatch("enabled", "false") {
   371  			disabledOnly = true
   372  		} else {
   373  			return nil, fmt.Errorf("Invalid filter 'enabled=%s'", pluginFilters.Get("enabled"))
   374  		}
   375  	}
   376  
   377  	plugins := pm.config.Store.GetAll()
   378  	out := make([]types.Plugin, 0, len(plugins))
   379  
   380  next:
   381  	for _, p := range plugins {
   382  		if enabledOnly && !p.PluginObj.Enabled {
   383  			continue
   384  		}
   385  		if disabledOnly && p.PluginObj.Enabled {
   386  			continue
   387  		}
   388  		if pluginFilters.Include("capability") {
   389  			for _, f := range p.GetTypes() {
   390  				if !pluginFilters.Match("capability", f.Capability) {
   391  					continue next
   392  				}
   393  			}
   394  		}
   395  		out = append(out, p.PluginObj)
   396  	}
   397  	return out, nil
   398  }
   399  
   400  // Push pushes a plugin to the store.
   401  func (pm *Manager) Push(ctx context.Context, name string, metaHeader http.Header, authConfig *types.AuthConfig, outStream io.Writer) error {
   402  	p, err := pm.config.Store.GetV2Plugin(name)
   403  	if err != nil {
   404  		return err
   405  	}
   406  
   407  	ref, err := reference.ParseNormalizedNamed(p.Name())
   408  	if err != nil {
   409  		return errors.Wrapf(err, "plugin has invalid name %v for push", p.Name())
   410  	}
   411  
   412  	var po progress.Output
   413  	if outStream != nil {
   414  		// Include a buffer so that slow client connections don't affect
   415  		// transfer performance.
   416  		progressChan := make(chan progress.Progress, 100)
   417  
   418  		writesDone := make(chan struct{})
   419  
   420  		defer func() {
   421  			close(progressChan)
   422  			<-writesDone
   423  		}()
   424  
   425  		var cancelFunc context.CancelFunc
   426  		ctx, cancelFunc = context.WithCancel(ctx)
   427  
   428  		go func() {
   429  			progressutils.WriteDistributionProgress(cancelFunc, outStream, progressChan)
   430  			close(writesDone)
   431  		}()
   432  
   433  		po = progress.ChanOutput(progressChan)
   434  	} else {
   435  		po = progress.DiscardOutput()
   436  	}
   437  
   438  	// TODO: replace these with manager
   439  	is := &pluginConfigStore{
   440  		pm:     pm,
   441  		plugin: p,
   442  	}
   443  	ls := &pluginLayerProvider{
   444  		pm:     pm,
   445  		plugin: p,
   446  	}
   447  	rs := &pluginReference{
   448  		name:     ref,
   449  		pluginID: p.Config,
   450  	}
   451  
   452  	uploadManager := xfer.NewLayerUploadManager(3)
   453  
   454  	imagePushConfig := &distribution.ImagePushConfig{
   455  		Config: distribution.Config{
   456  			MetaHeaders:      metaHeader,
   457  			AuthConfig:       authConfig,
   458  			ProgressOutput:   po,
   459  			RegistryService:  pm.config.RegistryService,
   460  			ReferenceStore:   rs,
   461  			ImageEventLogger: pm.config.LogPluginEvent,
   462  			ImageStore:       is,
   463  			RequireSchema2:   true,
   464  		},
   465  		ConfigMediaType: schema2.MediaTypePluginConfig,
   466  		LayerStore:      ls,
   467  		UploadManager:   uploadManager,
   468  	}
   469  
   470  	return distribution.Push(ctx, ref, imagePushConfig)
   471  }
   472  
   473  type pluginReference struct {
   474  	name     reference.Named
   475  	pluginID digest.Digest
   476  }
   477  
   478  func (r *pluginReference) References(id digest.Digest) []reference.Named {
   479  	if r.pluginID != id {
   480  		return nil
   481  	}
   482  	return []reference.Named{r.name}
   483  }
   484  
   485  func (r *pluginReference) ReferencesByName(ref reference.Named) []refstore.Association {
   486  	return []refstore.Association{
   487  		{
   488  			Ref: r.name,
   489  			ID:  r.pluginID,
   490  		},
   491  	}
   492  }
   493  
   494  func (r *pluginReference) Get(ref reference.Named) (digest.Digest, error) {
   495  	if r.name.String() != ref.String() {
   496  		return digest.Digest(""), refstore.ErrDoesNotExist
   497  	}
   498  	return r.pluginID, nil
   499  }
   500  
   501  func (r *pluginReference) AddTag(ref reference.Named, id digest.Digest, force bool) error {
   502  	// Read only, ignore
   503  	return nil
   504  }
   505  func (r *pluginReference) AddDigest(ref reference.Canonical, id digest.Digest, force bool) error {
   506  	// Read only, ignore
   507  	return nil
   508  }
   509  func (r *pluginReference) Delete(ref reference.Named) (bool, error) {
   510  	// Read only, ignore
   511  	return false, nil
   512  }
   513  
   514  type pluginConfigStore struct {
   515  	pm     *Manager
   516  	plugin *v2.Plugin
   517  }
   518  
   519  func (s *pluginConfigStore) Put([]byte) (digest.Digest, error) {
   520  	return digest.Digest(""), errors.New("cannot store config on push")
   521  }
   522  
   523  func (s *pluginConfigStore) Get(d digest.Digest) ([]byte, error) {
   524  	if s.plugin.Config != d {
   525  		return nil, errors.New("plugin not found")
   526  	}
   527  	rwc, err := s.pm.blobStore.Get(d)
   528  	if err != nil {
   529  		return nil, err
   530  	}
   531  	defer rwc.Close()
   532  	return ioutil.ReadAll(rwc)
   533  }
   534  
   535  func (s *pluginConfigStore) RootFSFromConfig(c []byte) (*image.RootFS, error) {
   536  	return configToRootFS(c)
   537  }
   538  
   539  type pluginLayerProvider struct {
   540  	pm     *Manager
   541  	plugin *v2.Plugin
   542  }
   543  
   544  func (p *pluginLayerProvider) Get(id layer.ChainID) (distribution.PushLayer, error) {
   545  	rootFS := rootFSFromPlugin(p.plugin.PluginObj.Config.Rootfs)
   546  	var i int
   547  	for i = 1; i <= len(rootFS.DiffIDs); i++ {
   548  		if layer.CreateChainID(rootFS.DiffIDs[:i]) == id {
   549  			break
   550  		}
   551  	}
   552  	if i > len(rootFS.DiffIDs) {
   553  		return nil, errors.New("layer not found")
   554  	}
   555  	return &pluginLayer{
   556  		pm:      p.pm,
   557  		diffIDs: rootFS.DiffIDs[:i],
   558  		blobs:   p.plugin.Blobsums[:i],
   559  	}, nil
   560  }
   561  
   562  type pluginLayer struct {
   563  	pm      *Manager
   564  	diffIDs []layer.DiffID
   565  	blobs   []digest.Digest
   566  }
   567  
   568  func (l *pluginLayer) ChainID() layer.ChainID {
   569  	return layer.CreateChainID(l.diffIDs)
   570  }
   571  
   572  func (l *pluginLayer) DiffID() layer.DiffID {
   573  	return l.diffIDs[len(l.diffIDs)-1]
   574  }
   575  
   576  func (l *pluginLayer) Parent() distribution.PushLayer {
   577  	if len(l.diffIDs) == 1 {
   578  		return nil
   579  	}
   580  	return &pluginLayer{
   581  		pm:      l.pm,
   582  		diffIDs: l.diffIDs[:len(l.diffIDs)-1],
   583  		blobs:   l.blobs[:len(l.diffIDs)-1],
   584  	}
   585  }
   586  
   587  func (l *pluginLayer) Open() (io.ReadCloser, error) {
   588  	return l.pm.blobStore.Get(l.blobs[len(l.diffIDs)-1])
   589  }
   590  
   591  func (l *pluginLayer) Size() (int64, error) {
   592  	return l.pm.blobStore.Size(l.blobs[len(l.diffIDs)-1])
   593  }
   594  
   595  func (l *pluginLayer) MediaType() string {
   596  	return schema2.MediaTypeLayer
   597  }
   598  
   599  func (l *pluginLayer) Release() {
   600  	// Nothing needs to be release, no references held
   601  }
   602  
   603  // Remove deletes plugin's root directory.
   604  func (pm *Manager) Remove(name string, config *types.PluginRmConfig) error {
   605  	p, err := pm.config.Store.GetV2Plugin(name)
   606  	pm.mu.RLock()
   607  	c := pm.cMap[p]
   608  	pm.mu.RUnlock()
   609  
   610  	if err != nil {
   611  		return err
   612  	}
   613  
   614  	if !config.ForceRemove {
   615  		if p.GetRefCount() > 0 {
   616  			return fmt.Errorf("plugin %s is in use", p.Name())
   617  		}
   618  		if p.IsEnabled() {
   619  			return fmt.Errorf("plugin %s is enabled", p.Name())
   620  		}
   621  	}
   622  
   623  	if p.IsEnabled() {
   624  		if err := pm.disable(p, c); err != nil {
   625  			logrus.Errorf("failed to disable plugin '%s': %s", p.Name(), err)
   626  		}
   627  	}
   628  
   629  	defer func() {
   630  		go pm.GC()
   631  	}()
   632  
   633  	id := p.GetID()
   634  	pm.config.Store.Remove(p)
   635  	pluginDir := filepath.Join(pm.config.Root, id)
   636  	if err := recursiveUnmount(pluginDir); err != nil {
   637  		logrus.WithField("dir", pluginDir).WithField("id", id).Warn(err)
   638  	}
   639  	if err := os.RemoveAll(pluginDir); err != nil {
   640  		logrus.Warnf("unable to remove %q from plugin remove: %v", pluginDir, err)
   641  	}
   642  	pm.config.LogPluginEvent(id, name, "remove")
   643  	return nil
   644  }
   645  
   646  func getMounts(root string) ([]string, error) {
   647  	infos, err := mount.GetMounts()
   648  	if err != nil {
   649  		return nil, errors.Wrap(err, "failed to read mount table")
   650  	}
   651  
   652  	var mounts []string
   653  	for _, m := range infos {
   654  		if strings.HasPrefix(m.Mountpoint, root) {
   655  			mounts = append(mounts, m.Mountpoint)
   656  		}
   657  	}
   658  
   659  	return mounts, nil
   660  }
   661  
   662  func recursiveUnmount(root string) error {
   663  	mounts, err := getMounts(root)
   664  	if err != nil {
   665  		return err
   666  	}
   667  
   668  	// sort in reverse-lexicographic order so the root mount will always be last
   669  	sort.Sort(sort.Reverse(sort.StringSlice(mounts)))
   670  
   671  	for i, m := range mounts {
   672  		if err := mount.Unmount(m); err != nil {
   673  			if i == len(mounts)-1 {
   674  				return errors.Wrapf(err, "error performing recursive unmount on %s", root)
   675  			}
   676  			logrus.WithError(err).WithField("mountpoint", m).Warn("could not unmount")
   677  		}
   678  	}
   679  
   680  	return nil
   681  }
   682  
   683  // Set sets plugin args
   684  func (pm *Manager) Set(name string, args []string) error {
   685  	p, err := pm.config.Store.GetV2Plugin(name)
   686  	if err != nil {
   687  		return err
   688  	}
   689  	if err := p.Set(args); err != nil {
   690  		return err
   691  	}
   692  	return pm.save(p)
   693  }
   694  
   695  // CreateFromContext creates a plugin from the given pluginDir which contains
   696  // both the rootfs and the config.json and a repoName with optional tag.
   697  func (pm *Manager) CreateFromContext(ctx context.Context, tarCtx io.ReadCloser, options *types.PluginCreateOptions) (err error) {
   698  	pm.muGC.RLock()
   699  	defer pm.muGC.RUnlock()
   700  
   701  	ref, err := reference.ParseNormalizedNamed(options.RepoName)
   702  	if err != nil {
   703  		return errors.Wrapf(err, "failed to parse reference %v", options.RepoName)
   704  	}
   705  	if _, ok := ref.(reference.Canonical); ok {
   706  		return errors.Errorf("canonical references are not permitted")
   707  	}
   708  	name := reference.FamiliarString(reference.TagNameOnly(ref))
   709  
   710  	if err := pm.config.Store.validateName(name); err != nil { // fast check, real check is in createPlugin()
   711  		return err
   712  	}
   713  
   714  	tmpRootFSDir, err := ioutil.TempDir(pm.tmpDir(), ".rootfs")
   715  	if err != nil {
   716  		return errors.Wrap(err, "failed to create temp directory")
   717  	}
   718  	defer os.RemoveAll(tmpRootFSDir)
   719  
   720  	var configJSON []byte
   721  	rootFS := splitConfigRootFSFromTar(tarCtx, &configJSON)
   722  
   723  	rootFSBlob, err := pm.blobStore.New()
   724  	if err != nil {
   725  		return err
   726  	}
   727  	defer rootFSBlob.Close()
   728  	gzw := gzip.NewWriter(rootFSBlob)
   729  	layerDigester := digest.Canonical.Digester()
   730  	rootFSReader := io.TeeReader(rootFS, io.MultiWriter(gzw, layerDigester.Hash()))
   731  
   732  	if err := chrootarchive.Untar(rootFSReader, tmpRootFSDir, nil); err != nil {
   733  		return err
   734  	}
   735  	if err := rootFS.Close(); err != nil {
   736  		return err
   737  	}
   738  
   739  	if configJSON == nil {
   740  		return errors.New("config not found")
   741  	}
   742  
   743  	if err := gzw.Close(); err != nil {
   744  		return errors.Wrap(err, "error closing gzip writer")
   745  	}
   746  
   747  	var config types.PluginConfig
   748  	if err := json.Unmarshal(configJSON, &config); err != nil {
   749  		return errors.Wrap(err, "failed to parse config")
   750  	}
   751  
   752  	if err := pm.validateConfig(config); err != nil {
   753  		return err
   754  	}
   755  
   756  	pm.mu.Lock()
   757  	defer pm.mu.Unlock()
   758  
   759  	rootFSBlobsum, err := rootFSBlob.Commit()
   760  	if err != nil {
   761  		return err
   762  	}
   763  	defer func() {
   764  		if err != nil {
   765  			go pm.GC()
   766  		}
   767  	}()
   768  
   769  	config.Rootfs = &types.PluginConfigRootfs{
   770  		Type:    "layers",
   771  		DiffIds: []string{layerDigester.Digest().String()},
   772  	}
   773  
   774  	config.DockerVersion = dockerversion.Version
   775  
   776  	configBlob, err := pm.blobStore.New()
   777  	if err != nil {
   778  		return err
   779  	}
   780  	defer configBlob.Close()
   781  	if err := json.NewEncoder(configBlob).Encode(config); err != nil {
   782  		return errors.Wrap(err, "error encoding json config")
   783  	}
   784  	configBlobsum, err := configBlob.Commit()
   785  	if err != nil {
   786  		return err
   787  	}
   788  
   789  	p, err := pm.createPlugin(name, configBlobsum, []digest.Digest{rootFSBlobsum}, tmpRootFSDir, nil)
   790  	if err != nil {
   791  		return err
   792  	}
   793  	p.PluginObj.PluginReference = name
   794  
   795  	pm.config.LogPluginEvent(p.PluginObj.ID, name, "create")
   796  
   797  	return nil
   798  }
   799  
   800  func (pm *Manager) validateConfig(config types.PluginConfig) error {
   801  	return nil // TODO:
   802  }
   803  
   804  func splitConfigRootFSFromTar(in io.ReadCloser, config *[]byte) io.ReadCloser {
   805  	pr, pw := io.Pipe()
   806  	go func() {
   807  		tarReader := tar.NewReader(in)
   808  		tarWriter := tar.NewWriter(pw)
   809  		defer in.Close()
   810  
   811  		hasRootFS := false
   812  
   813  		for {
   814  			hdr, err := tarReader.Next()
   815  			if err == io.EOF {
   816  				if !hasRootFS {
   817  					pw.CloseWithError(errors.Wrap(err, "no rootfs found"))
   818  					return
   819  				}
   820  				// Signals end of archive.
   821  				tarWriter.Close()
   822  				pw.Close()
   823  				return
   824  			}
   825  			if err != nil {
   826  				pw.CloseWithError(errors.Wrap(err, "failed to read from tar"))
   827  				return
   828  			}
   829  
   830  			content := io.Reader(tarReader)
   831  			name := path.Clean(hdr.Name)
   832  			if path.IsAbs(name) {
   833  				name = name[1:]
   834  			}
   835  			if name == configFileName {
   836  				dt, err := ioutil.ReadAll(content)
   837  				if err != nil {
   838  					pw.CloseWithError(errors.Wrapf(err, "failed to read %s", configFileName))
   839  					return
   840  				}
   841  				*config = dt
   842  			}
   843  			if parts := strings.Split(name, "/"); len(parts) != 0 && parts[0] == rootFSFileName {
   844  				hdr.Name = path.Clean(path.Join(parts[1:]...))
   845  				if hdr.Typeflag == tar.TypeLink && strings.HasPrefix(strings.ToLower(hdr.Linkname), rootFSFileName+"/") {
   846  					hdr.Linkname = hdr.Linkname[len(rootFSFileName)+1:]
   847  				}
   848  				if err := tarWriter.WriteHeader(hdr); err != nil {
   849  					pw.CloseWithError(errors.Wrap(err, "error writing tar header"))
   850  					return
   851  				}
   852  				if _, err := pools.Copy(tarWriter, content); err != nil {
   853  					pw.CloseWithError(errors.Wrap(err, "error copying tar data"))
   854  					return
   855  				}
   856  				hasRootFS = true
   857  			} else {
   858  				io.Copy(ioutil.Discard, content)
   859  			}
   860  		}
   861  	}()
   862  	return pr
   863  }