github.com/demonoid81/containerd@v1.3.4/runtime/v2/runc/container.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 runc
    20  
    21  import (
    22  	"context"
    23  	"io/ioutil"
    24  	"os"
    25  	"path/filepath"
    26  	"sync"
    27  
    28  	"github.com/containerd/cgroups"
    29  	"github.com/containerd/console"
    30  	"github.com/containerd/containerd/errdefs"
    31  	"github.com/containerd/containerd/mount"
    32  	"github.com/containerd/containerd/namespaces"
    33  	"github.com/containerd/containerd/pkg/process"
    34  	"github.com/containerd/containerd/pkg/stdio"
    35  	"github.com/containerd/containerd/runtime/v2/runc/options"
    36  	"github.com/containerd/containerd/runtime/v2/task"
    37  	"github.com/containerd/typeurl"
    38  	"github.com/pkg/errors"
    39  	"github.com/sirupsen/logrus"
    40  )
    41  
    42  // NewContainer returns a new runc container
    43  func NewContainer(ctx context.Context, platform stdio.Platform, r *task.CreateTaskRequest) (*Container, error) {
    44  	ns, err := namespaces.NamespaceRequired(ctx)
    45  	if err != nil {
    46  		return nil, errors.Wrap(err, "create namespace")
    47  	}
    48  
    49  	var opts options.Options
    50  	if r.Options != nil {
    51  		v, err := typeurl.UnmarshalAny(r.Options)
    52  		if err != nil {
    53  			return nil, err
    54  		}
    55  		opts = *v.(*options.Options)
    56  	}
    57  
    58  	var mounts []process.Mount
    59  	for _, m := range r.Rootfs {
    60  		mounts = append(mounts, process.Mount{
    61  			Type:    m.Type,
    62  			Source:  m.Source,
    63  			Target:  m.Target,
    64  			Options: m.Options,
    65  		})
    66  	}
    67  
    68  	rootfs := ""
    69  	if len(mounts) > 0 {
    70  		rootfs = filepath.Join(r.Bundle, "rootfs")
    71  		if err := os.Mkdir(rootfs, 0711); err != nil && !os.IsExist(err) {
    72  			return nil, err
    73  		}
    74  	}
    75  
    76  	config := &process.CreateConfig{
    77  		ID:               r.ID,
    78  		Bundle:           r.Bundle,
    79  		Runtime:          opts.BinaryName,
    80  		Rootfs:           mounts,
    81  		Terminal:         r.Terminal,
    82  		Stdin:            r.Stdin,
    83  		Stdout:           r.Stdout,
    84  		Stderr:           r.Stderr,
    85  		Checkpoint:       r.Checkpoint,
    86  		ParentCheckpoint: r.ParentCheckpoint,
    87  		Options:          r.Options,
    88  	}
    89  
    90  	if err := WriteRuntime(r.Bundle, opts.BinaryName); err != nil {
    91  		return nil, err
    92  	}
    93  	defer func() {
    94  		if err != nil {
    95  			if err2 := mount.UnmountAll(rootfs, 0); err2 != nil {
    96  				logrus.WithError(err2).Warn("failed to cleanup rootfs mount")
    97  			}
    98  		}
    99  	}()
   100  	for _, rm := range mounts {
   101  		m := &mount.Mount{
   102  			Type:    rm.Type,
   103  			Source:  rm.Source,
   104  			Options: rm.Options,
   105  		}
   106  		if err := m.Mount(rootfs); err != nil {
   107  			return nil, errors.Wrapf(err, "failed to mount rootfs component %v", m)
   108  		}
   109  	}
   110  
   111  	p, err := newInit(
   112  		ctx,
   113  		r.Bundle,
   114  		filepath.Join(r.Bundle, "work"),
   115  		ns,
   116  		platform,
   117  		config,
   118  		&opts,
   119  		rootfs,
   120  	)
   121  	if err != nil {
   122  		return nil, errdefs.ToGRPC(err)
   123  	}
   124  	if err := p.Create(ctx, config); err != nil {
   125  		return nil, errdefs.ToGRPC(err)
   126  	}
   127  	container := &Container{
   128  		ID:              r.ID,
   129  		Bundle:          r.Bundle,
   130  		process:         p,
   131  		processes:       make(map[string]process.Process),
   132  		reservedProcess: make(map[string]struct{}),
   133  	}
   134  	pid := p.Pid()
   135  	if pid > 0 {
   136  		cg, err := cgroups.Load(cgroups.V1, cgroups.PidPath(pid))
   137  		if err != nil {
   138  			logrus.WithError(err).Errorf("loading cgroup for %d", pid)
   139  		}
   140  		container.cgroup = cg
   141  	}
   142  	return container, nil
   143  }
   144  
   145  // ReadRuntime reads the runtime information from the path
   146  func ReadRuntime(path string) (string, error) {
   147  	data, err := ioutil.ReadFile(filepath.Join(path, "runtime"))
   148  	if err != nil {
   149  		return "", err
   150  	}
   151  	return string(data), nil
   152  }
   153  
   154  // WriteRuntime writes the runtime information into the path
   155  func WriteRuntime(path, runtime string) error {
   156  	return ioutil.WriteFile(filepath.Join(path, "runtime"), []byte(runtime), 0600)
   157  }
   158  
   159  func newInit(ctx context.Context, path, workDir, namespace string, platform stdio.Platform,
   160  	r *process.CreateConfig, options *options.Options, rootfs string) (*process.Init, error) {
   161  	runtime := process.NewRunc(options.Root, path, namespace, options.BinaryName, options.CriuPath, options.SystemdCgroup)
   162  	p := process.New(r.ID, runtime, stdio.Stdio{
   163  		Stdin:    r.Stdin,
   164  		Stdout:   r.Stdout,
   165  		Stderr:   r.Stderr,
   166  		Terminal: r.Terminal,
   167  	})
   168  	p.Bundle = r.Bundle
   169  	p.Platform = platform
   170  	p.Rootfs = rootfs
   171  	p.WorkDir = workDir
   172  	p.IoUID = int(options.IoUid)
   173  	p.IoGID = int(options.IoGid)
   174  	p.NoPivotRoot = options.NoPivotRoot
   175  	p.NoNewKeyring = options.NoNewKeyring
   176  	p.CriuWorkPath = options.CriuWorkPath
   177  	if p.CriuWorkPath == "" {
   178  		// if criu work path not set, use container WorkDir
   179  		p.CriuWorkPath = p.WorkDir
   180  	}
   181  	return p, nil
   182  }
   183  
   184  // Container for operating on a runc container and its processes
   185  type Container struct {
   186  	mu sync.Mutex
   187  
   188  	// ID of the container
   189  	ID string
   190  	// Bundle path
   191  	Bundle string
   192  
   193  	cgroup          cgroups.Cgroup
   194  	process         process.Process
   195  	processes       map[string]process.Process
   196  	reservedProcess map[string]struct{}
   197  }
   198  
   199  // All processes in the container
   200  func (c *Container) All() (o []process.Process) {
   201  	c.mu.Lock()
   202  	defer c.mu.Unlock()
   203  
   204  	for _, p := range c.processes {
   205  		o = append(o, p)
   206  	}
   207  	if c.process != nil {
   208  		o = append(o, c.process)
   209  	}
   210  	return o
   211  }
   212  
   213  // ExecdProcesses added to the container
   214  func (c *Container) ExecdProcesses() (o []process.Process) {
   215  	c.mu.Lock()
   216  	defer c.mu.Unlock()
   217  	for _, p := range c.processes {
   218  		o = append(o, p)
   219  	}
   220  	return o
   221  }
   222  
   223  // Pid of the main process of a container
   224  func (c *Container) Pid() int {
   225  	c.mu.Lock()
   226  	defer c.mu.Unlock()
   227  	return c.process.Pid()
   228  }
   229  
   230  // Cgroup of the container
   231  func (c *Container) Cgroup() cgroups.Cgroup {
   232  	c.mu.Lock()
   233  	defer c.mu.Unlock()
   234  	return c.cgroup
   235  }
   236  
   237  // CgroupSet sets the cgroup to the container
   238  func (c *Container) CgroupSet(cg cgroups.Cgroup) {
   239  	c.mu.Lock()
   240  	c.cgroup = cg
   241  	c.mu.Unlock()
   242  }
   243  
   244  // Process returns the process by id
   245  func (c *Container) Process(id string) (process.Process, error) {
   246  	c.mu.Lock()
   247  	defer c.mu.Unlock()
   248  	if id == "" {
   249  		if c.process == nil {
   250  			return nil, errors.Wrapf(errdefs.ErrFailedPrecondition, "container must be created")
   251  		}
   252  		return c.process, nil
   253  	}
   254  	p, ok := c.processes[id]
   255  	if !ok {
   256  		return nil, errors.Wrapf(errdefs.ErrNotFound, "process does not exist %s", id)
   257  	}
   258  	return p, nil
   259  }
   260  
   261  // ReserveProcess checks for the existence of an id and atomically
   262  // reserves the process id if it does not already exist
   263  //
   264  // Returns true if the process id was successfully reserved and a
   265  // cancel func to release the reservation
   266  func (c *Container) ReserveProcess(id string) (bool, func()) {
   267  	c.mu.Lock()
   268  	defer c.mu.Unlock()
   269  
   270  	if _, ok := c.processes[id]; ok {
   271  		return false, nil
   272  	}
   273  	if _, ok := c.reservedProcess[id]; ok {
   274  		return false, nil
   275  	}
   276  	c.reservedProcess[id] = struct{}{}
   277  	return true, func() {
   278  		c.mu.Lock()
   279  		defer c.mu.Unlock()
   280  		delete(c.reservedProcess, id)
   281  	}
   282  }
   283  
   284  // ProcessAdd adds a new process to the container
   285  func (c *Container) ProcessAdd(process process.Process) {
   286  	c.mu.Lock()
   287  	defer c.mu.Unlock()
   288  
   289  	delete(c.reservedProcess, process.ID())
   290  	c.processes[process.ID()] = process
   291  }
   292  
   293  // ProcessRemove removes the process by id from the container
   294  func (c *Container) ProcessRemove(id string) {
   295  	c.mu.Lock()
   296  	defer c.mu.Unlock()
   297  	delete(c.processes, id)
   298  }
   299  
   300  // Start a container process
   301  func (c *Container) Start(ctx context.Context, r *task.StartRequest) (process.Process, error) {
   302  	p, err := c.Process(r.ExecID)
   303  	if err != nil {
   304  		return nil, err
   305  	}
   306  	if err := p.Start(ctx); err != nil {
   307  		return nil, err
   308  	}
   309  	if c.Cgroup() == nil && p.Pid() > 0 {
   310  		cg, err := cgroups.Load(cgroups.V1, cgroups.PidPath(p.Pid()))
   311  		if err != nil {
   312  			logrus.WithError(err).Errorf("loading cgroup for %d", p.Pid())
   313  		}
   314  		c.cgroup = cg
   315  	}
   316  	return p, nil
   317  }
   318  
   319  // Delete the container or a process by id
   320  func (c *Container) Delete(ctx context.Context, r *task.DeleteRequest) (process.Process, error) {
   321  	p, err := c.Process(r.ExecID)
   322  	if err != nil {
   323  		return nil, err
   324  	}
   325  	if err := p.Delete(ctx); err != nil {
   326  		return nil, err
   327  	}
   328  	if r.ExecID != "" {
   329  		c.ProcessRemove(r.ExecID)
   330  	}
   331  	return p, nil
   332  }
   333  
   334  // Exec an additional process
   335  func (c *Container) Exec(ctx context.Context, r *task.ExecProcessRequest) (process.Process, error) {
   336  	process, err := c.process.(*process.Init).Exec(ctx, c.Bundle, &process.ExecConfig{
   337  		ID:       r.ExecID,
   338  		Terminal: r.Terminal,
   339  		Stdin:    r.Stdin,
   340  		Stdout:   r.Stdout,
   341  		Stderr:   r.Stderr,
   342  		Spec:     r.Spec,
   343  	})
   344  	if err != nil {
   345  		return nil, err
   346  	}
   347  	c.ProcessAdd(process)
   348  	return process, nil
   349  }
   350  
   351  // Pause the container
   352  func (c *Container) Pause(ctx context.Context) error {
   353  	return c.process.(*process.Init).Pause(ctx)
   354  }
   355  
   356  // Resume the container
   357  func (c *Container) Resume(ctx context.Context) error {
   358  	return c.process.(*process.Init).Resume(ctx)
   359  }
   360  
   361  // ResizePty of a process
   362  func (c *Container) ResizePty(ctx context.Context, r *task.ResizePtyRequest) error {
   363  	p, err := c.Process(r.ExecID)
   364  	if err != nil {
   365  		return err
   366  	}
   367  	ws := console.WinSize{
   368  		Width:  uint16(r.Width),
   369  		Height: uint16(r.Height),
   370  	}
   371  	return p.Resize(ws)
   372  }
   373  
   374  // Kill a process
   375  func (c *Container) Kill(ctx context.Context, r *task.KillRequest) error {
   376  	p, err := c.Process(r.ExecID)
   377  	if err != nil {
   378  		return err
   379  	}
   380  	return p.Kill(ctx, r.Signal, r.All)
   381  }
   382  
   383  // CloseIO of a process
   384  func (c *Container) CloseIO(ctx context.Context, r *task.CloseIORequest) error {
   385  	p, err := c.Process(r.ExecID)
   386  	if err != nil {
   387  		return err
   388  	}
   389  	if stdin := p.Stdin(); stdin != nil {
   390  		if err := stdin.Close(); err != nil {
   391  			return errors.Wrap(err, "close stdin")
   392  		}
   393  	}
   394  	return nil
   395  }
   396  
   397  // Checkpoint the container
   398  func (c *Container) Checkpoint(ctx context.Context, r *task.CheckpointTaskRequest) error {
   399  	p, err := c.Process("")
   400  	if err != nil {
   401  		return err
   402  	}
   403  	var opts options.CheckpointOptions
   404  	if r.Options != nil {
   405  		v, err := typeurl.UnmarshalAny(r.Options)
   406  		if err != nil {
   407  			return err
   408  		}
   409  		opts = *v.(*options.CheckpointOptions)
   410  	}
   411  	return p.(*process.Init).Checkpoint(ctx, &process.CheckpointConfig{
   412  		Path:                     r.Path,
   413  		Exit:                     opts.Exit,
   414  		AllowOpenTCP:             opts.OpenTcp,
   415  		AllowExternalUnixSockets: opts.ExternalUnixSockets,
   416  		AllowTerminal:            opts.Terminal,
   417  		FileLocks:                opts.FileLocks,
   418  		EmptyNamespaces:          opts.EmptyNamespaces,
   419  		WorkDir:                  opts.WorkPath,
   420  	})
   421  }
   422  
   423  // Update the resource information of a running container
   424  func (c *Container) Update(ctx context.Context, r *task.UpdateTaskRequest) error {
   425  	p, err := c.Process("")
   426  	if err != nil {
   427  		return err
   428  	}
   429  	return p.(*process.Init).Update(ctx, r.Resources)
   430  }
   431  
   432  // HasPid returns true if the container owns a specific pid
   433  func (c *Container) HasPid(pid int) bool {
   434  	if c.Pid() == pid {
   435  		return true
   436  	}
   437  	for _, p := range c.All() {
   438  		if p.Pid() == pid {
   439  			return true
   440  		}
   441  	}
   442  	return false
   443  }