github.com/containerd/Containerd@v1.4.13/runtime/v1/linux/bundle.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 linux
    20  
    21  import (
    22  	"context"
    23  	"crypto/sha256"
    24  	"encoding/json"
    25  	"fmt"
    26  	"io/ioutil"
    27  	"os"
    28  	"path/filepath"
    29  
    30  	"github.com/containerd/containerd/events/exchange"
    31  	"github.com/containerd/containerd/runtime/linux/runctypes"
    32  	"github.com/containerd/containerd/runtime/v1/shim"
    33  	"github.com/containerd/containerd/runtime/v1/shim/client"
    34  	"github.com/opencontainers/runtime-spec/specs-go"
    35  	"github.com/pkg/errors"
    36  )
    37  
    38  // loadBundle loads an existing bundle from disk
    39  func loadBundle(id, path, workdir string) *bundle {
    40  	return &bundle{
    41  		id:      id,
    42  		path:    path,
    43  		workDir: workdir,
    44  	}
    45  }
    46  
    47  // newBundle creates a new bundle on disk at the provided path for the given id
    48  func newBundle(id, path, workDir string, spec []byte) (b *bundle, err error) {
    49  	if err := os.MkdirAll(path, 0711); err != nil {
    50  		return nil, err
    51  	}
    52  	path = filepath.Join(path, id)
    53  	if err := os.Mkdir(path, 0700); err != nil {
    54  		return nil, err
    55  	}
    56  	defer func() {
    57  		if err != nil {
    58  			os.RemoveAll(path)
    59  		}
    60  	}()
    61  	if err := prepareBundleDirectoryPermissions(path, spec); err != nil {
    62  		return nil, err
    63  	}
    64  	workDir = filepath.Join(workDir, id)
    65  	if err := os.MkdirAll(workDir, 0711); err != nil {
    66  		return nil, err
    67  	}
    68  	defer func() {
    69  		if err != nil {
    70  			os.RemoveAll(workDir)
    71  		}
    72  	}()
    73  	rootfs := filepath.Join(path, "rootfs")
    74  	if err := os.MkdirAll(rootfs, 0711); err != nil {
    75  		return nil, err
    76  	}
    77  	err = ioutil.WriteFile(filepath.Join(path, configFilename), spec, 0666)
    78  	return &bundle{
    79  		id:      id,
    80  		path:    path,
    81  		workDir: workDir,
    82  	}, err
    83  }
    84  
    85  // prepareBundleDirectoryPermissions prepares the permissions of the bundle
    86  // directory. When user namespaces are enabled, the permissions are modified
    87  // to allow the remapped root GID to access the bundle.
    88  func prepareBundleDirectoryPermissions(path string, spec []byte) error {
    89  	gid, err := remappedGID(spec)
    90  	if err != nil {
    91  		return err
    92  	}
    93  	if gid == 0 {
    94  		return nil
    95  	}
    96  	if err := os.Chown(path, -1, int(gid)); err != nil {
    97  		return err
    98  	}
    99  	return os.Chmod(path, 0710)
   100  }
   101  
   102  // ociSpecUserNS is a subset of specs.Spec used to reduce garbage during
   103  // unmarshal.
   104  type ociSpecUserNS struct {
   105  	Linux *linuxSpecUserNS
   106  }
   107  
   108  // linuxSpecUserNS is a subset of specs.Linux used to reduce garbage during
   109  // unmarshal.
   110  type linuxSpecUserNS struct {
   111  	GIDMappings []specs.LinuxIDMapping
   112  }
   113  
   114  // remappedGID reads the remapped GID 0 from the OCI spec, if it exists. If
   115  // there is no remapping, remappedGID returns 0. If the spec cannot be parsed,
   116  // remappedGID returns an error.
   117  func remappedGID(spec []byte) (uint32, error) {
   118  	var ociSpec ociSpecUserNS
   119  	err := json.Unmarshal(spec, &ociSpec)
   120  	if err != nil {
   121  		return 0, err
   122  	}
   123  	if ociSpec.Linux == nil || len(ociSpec.Linux.GIDMappings) == 0 {
   124  		return 0, nil
   125  	}
   126  	for _, mapping := range ociSpec.Linux.GIDMappings {
   127  		if mapping.ContainerID == 0 {
   128  			return mapping.HostID, nil
   129  		}
   130  	}
   131  	return 0, nil
   132  }
   133  
   134  type bundle struct {
   135  	id      string
   136  	path    string
   137  	workDir string
   138  }
   139  
   140  // ShimOpt specifies shim options for initialization and connection
   141  type ShimOpt func(*bundle, string, *runctypes.RuncOptions) (shim.Config, client.Opt)
   142  
   143  // ShimRemote is a ShimOpt for connecting and starting a remote shim
   144  func ShimRemote(c *Config, daemonAddress, cgroup string, exitHandler func()) ShimOpt {
   145  	return func(b *bundle, ns string, ropts *runctypes.RuncOptions) (shim.Config, client.Opt) {
   146  		config := b.shimConfig(ns, c, ropts)
   147  		return config,
   148  			client.WithStart(c.Shim, b.shimAddress(ns, daemonAddress), daemonAddress, cgroup, c.ShimDebug, exitHandler)
   149  	}
   150  }
   151  
   152  // ShimLocal is a ShimOpt for using an in process shim implementation
   153  func ShimLocal(c *Config, exchange *exchange.Exchange) ShimOpt {
   154  	return func(b *bundle, ns string, ropts *runctypes.RuncOptions) (shim.Config, client.Opt) {
   155  		return b.shimConfig(ns, c, ropts), client.WithLocal(exchange)
   156  	}
   157  }
   158  
   159  // ShimConnect is a ShimOpt for connecting to an existing remote shim
   160  func ShimConnect(c *Config, onClose func()) ShimOpt {
   161  	return func(b *bundle, ns string, ropts *runctypes.RuncOptions) (shim.Config, client.Opt) {
   162  		return b.shimConfig(ns, c, ropts), client.WithConnect(b.decideShimAddress(ns), onClose)
   163  	}
   164  }
   165  
   166  // NewShimClient connects to the shim managing the bundle and tasks creating it if needed
   167  func (b *bundle) NewShimClient(ctx context.Context, namespace string, getClientOpts ShimOpt, runcOpts *runctypes.RuncOptions) (*client.Client, error) {
   168  	cfg, opt := getClientOpts(b, namespace, runcOpts)
   169  	return client.New(ctx, cfg, opt)
   170  }
   171  
   172  // Delete deletes the bundle from disk
   173  func (b *bundle) Delete() error {
   174  	address, _ := b.loadAddress()
   175  	if address != "" {
   176  		// we don't care about errors here
   177  		client.RemoveSocket(address)
   178  	}
   179  	err := atomicDelete(b.path)
   180  	if err == nil {
   181  		return atomicDelete(b.workDir)
   182  	}
   183  	// error removing the bundle path; still attempt removing work dir
   184  	err2 := atomicDelete(b.workDir)
   185  	if err2 == nil {
   186  		return err
   187  	}
   188  	return errors.Wrapf(err, "Failed to remove both bundle and workdir locations: %v", err2)
   189  }
   190  
   191  func (b *bundle) legacyShimAddress(namespace string) string {
   192  	return filepath.Join(string(filepath.Separator), "containerd-shim", namespace, b.id, "shim.sock")
   193  }
   194  
   195  const socketRoot = "/run/containerd"
   196  
   197  func (b *bundle) shimAddress(namespace, socketPath string) string {
   198  	d := sha256.Sum256([]byte(filepath.Join(socketPath, namespace, b.id)))
   199  	return fmt.Sprintf("unix://%s/%x", filepath.Join(socketRoot, "s"), d)
   200  }
   201  
   202  func (b *bundle) loadAddress() (string, error) {
   203  	addressPath := filepath.Join(b.path, "address")
   204  	data, err := ioutil.ReadFile(addressPath)
   205  	if err != nil {
   206  		return "", err
   207  	}
   208  	return string(data), nil
   209  }
   210  
   211  func (b *bundle) decideShimAddress(namespace string) string {
   212  	address, err := b.loadAddress()
   213  	if err != nil {
   214  		return b.legacyShimAddress(namespace)
   215  	}
   216  	return address
   217  }
   218  
   219  func (b *bundle) shimConfig(namespace string, c *Config, runcOptions *runctypes.RuncOptions) shim.Config {
   220  	var (
   221  		criuPath      string
   222  		runtimeRoot   = c.RuntimeRoot
   223  		systemdCgroup bool
   224  	)
   225  	if runcOptions != nil {
   226  		criuPath = runcOptions.CriuPath
   227  		systemdCgroup = runcOptions.SystemdCgroup
   228  		if runcOptions.RuntimeRoot != "" {
   229  			runtimeRoot = runcOptions.RuntimeRoot
   230  		}
   231  	}
   232  	return shim.Config{
   233  		Path:          b.path,
   234  		WorkDir:       b.workDir,
   235  		Namespace:     namespace,
   236  		Criu:          criuPath,
   237  		RuntimeRoot:   runtimeRoot,
   238  		SystemdCgroup: systemdCgroup,
   239  	}
   240  }
   241  
   242  // atomicDelete renames the path to a hidden file before removal
   243  func atomicDelete(path string) error {
   244  	// create a hidden dir for an atomic removal
   245  	atomicPath := filepath.Join(filepath.Dir(path), fmt.Sprintf(".%s", filepath.Base(path)))
   246  	if err := os.Rename(path, atomicPath); err != nil {
   247  		if os.IsNotExist(err) {
   248  			return nil
   249  		}
   250  		return err
   251  	}
   252  	return os.RemoveAll(atomicPath)
   253  }