github.com/containerd/containerd@v22.0.0-20200918172823-438c87b8e050+incompatible/pkg/process/init.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 process
    20  
    21  import (
    22  	"context"
    23  	"encoding/json"
    24  	"fmt"
    25  	"io"
    26  	"os"
    27  	"path/filepath"
    28  	"strings"
    29  	"sync"
    30  	"time"
    31  
    32  	"github.com/containerd/console"
    33  	"github.com/containerd/containerd/log"
    34  	"github.com/containerd/containerd/mount"
    35  	"github.com/containerd/containerd/pkg/stdio"
    36  	"github.com/containerd/fifo"
    37  	runc "github.com/containerd/go-runc"
    38  	google_protobuf "github.com/gogo/protobuf/types"
    39  	specs "github.com/opencontainers/runtime-spec/specs-go"
    40  	"github.com/pkg/errors"
    41  	"golang.org/x/sys/unix"
    42  )
    43  
    44  // Init represents an initial process for a container
    45  type Init struct {
    46  	wg        sync.WaitGroup
    47  	initState initState
    48  
    49  	// mu is used to ensure that `Start()` and `Exited()` calls return in
    50  	// the right order when invoked in separate go routines.
    51  	// This is the case within the shim implementation as it makes use of
    52  	// the reaper interface.
    53  	mu sync.Mutex
    54  
    55  	waitBlock chan struct{}
    56  
    57  	WorkDir string
    58  
    59  	id       string
    60  	Bundle   string
    61  	console  console.Console
    62  	Platform stdio.Platform
    63  	io       *processIO
    64  	runtime  *runc.Runc
    65  	// pausing preserves the pausing state.
    66  	pausing      *atomicBool
    67  	status       int
    68  	exited       time.Time
    69  	pid          int
    70  	closers      []io.Closer
    71  	stdin        io.Closer
    72  	stdio        stdio.Stdio
    73  	Rootfs       string
    74  	IoUID        int
    75  	IoGID        int
    76  	NoPivotRoot  bool
    77  	NoNewKeyring bool
    78  	CriuWorkPath string
    79  }
    80  
    81  // NewRunc returns a new runc instance for a process
    82  func NewRunc(root, path, namespace, runtime, criu string, systemd bool) *runc.Runc {
    83  	if root == "" {
    84  		root = RuncRoot
    85  	}
    86  	return &runc.Runc{
    87  		Command:       runtime,
    88  		Log:           filepath.Join(path, "log.json"),
    89  		LogFormat:     runc.JSON,
    90  		PdeathSignal:  unix.SIGKILL,
    91  		Root:          filepath.Join(root, namespace),
    92  		Criu:          criu,
    93  		SystemdCgroup: systemd,
    94  	}
    95  }
    96  
    97  // New returns a new process
    98  func New(id string, runtime *runc.Runc, stdio stdio.Stdio) *Init {
    99  	p := &Init{
   100  		id:        id,
   101  		runtime:   runtime,
   102  		pausing:   new(atomicBool),
   103  		stdio:     stdio,
   104  		status:    0,
   105  		waitBlock: make(chan struct{}),
   106  	}
   107  	p.initState = &createdState{p: p}
   108  	return p
   109  }
   110  
   111  // Create the process with the provided config
   112  func (p *Init) Create(ctx context.Context, r *CreateConfig) error {
   113  	var (
   114  		err     error
   115  		socket  *runc.Socket
   116  		pio     *processIO
   117  		pidFile = newPidFile(p.Bundle)
   118  	)
   119  
   120  	if r.Terminal {
   121  		if socket, err = runc.NewTempConsoleSocket(); err != nil {
   122  			return errors.Wrap(err, "failed to create OCI runtime console socket")
   123  		}
   124  		defer socket.Close()
   125  	} else {
   126  		if pio, err = createIO(ctx, p.id, p.IoUID, p.IoGID, p.stdio); err != nil {
   127  			return errors.Wrap(err, "failed to create init process I/O")
   128  		}
   129  		p.io = pio
   130  	}
   131  	if r.Checkpoint != "" {
   132  		return p.createCheckpointedState(r, pidFile)
   133  	}
   134  	opts := &runc.CreateOpts{
   135  		PidFile:      pidFile.Path(),
   136  		NoPivot:      p.NoPivotRoot,
   137  		NoNewKeyring: p.NoNewKeyring,
   138  	}
   139  	if p.io != nil {
   140  		opts.IO = p.io.IO()
   141  	}
   142  	if socket != nil {
   143  		opts.ConsoleSocket = socket
   144  	}
   145  	if err := p.runtime.Create(ctx, r.ID, r.Bundle, opts); err != nil {
   146  		return p.runtimeError(err, "OCI runtime create failed")
   147  	}
   148  	if r.Stdin != "" {
   149  		if err := p.openStdin(r.Stdin); err != nil {
   150  			return err
   151  		}
   152  	}
   153  	ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
   154  	defer cancel()
   155  	if socket != nil {
   156  		console, err := socket.ReceiveMaster()
   157  		if err != nil {
   158  			return errors.Wrap(err, "failed to retrieve console master")
   159  		}
   160  		console, err = p.Platform.CopyConsole(ctx, console, r.Stdin, r.Stdout, r.Stderr, &p.wg)
   161  		if err != nil {
   162  			return errors.Wrap(err, "failed to start console copy")
   163  		}
   164  		p.console = console
   165  	} else {
   166  		if err := pio.Copy(ctx, &p.wg); err != nil {
   167  			return errors.Wrap(err, "failed to start io pipe copy")
   168  		}
   169  	}
   170  	pid, err := pidFile.Read()
   171  	if err != nil {
   172  		return errors.Wrap(err, "failed to retrieve OCI runtime container pid")
   173  	}
   174  	p.pid = pid
   175  	return nil
   176  }
   177  
   178  func (p *Init) openStdin(path string) error {
   179  	sc, err := fifo.OpenFifo(context.Background(), path, unix.O_WRONLY|unix.O_NONBLOCK, 0)
   180  	if err != nil {
   181  		return errors.Wrapf(err, "failed to open stdin fifo %s", path)
   182  	}
   183  	p.stdin = sc
   184  	p.closers = append(p.closers, sc)
   185  	return nil
   186  }
   187  
   188  func (p *Init) createCheckpointedState(r *CreateConfig, pidFile *pidFile) error {
   189  	opts := &runc.RestoreOpts{
   190  		CheckpointOpts: runc.CheckpointOpts{
   191  			ImagePath:  r.Checkpoint,
   192  			WorkDir:    p.CriuWorkPath,
   193  			ParentPath: r.ParentCheckpoint,
   194  		},
   195  		PidFile:     pidFile.Path(),
   196  		IO:          p.io.IO(),
   197  		NoPivot:     p.NoPivotRoot,
   198  		Detach:      true,
   199  		NoSubreaper: true,
   200  	}
   201  	p.initState = &createdCheckpointState{
   202  		p:    p,
   203  		opts: opts,
   204  	}
   205  	return nil
   206  }
   207  
   208  // Wait for the process to exit
   209  func (p *Init) Wait() {
   210  	<-p.waitBlock
   211  }
   212  
   213  // ID of the process
   214  func (p *Init) ID() string {
   215  	return p.id
   216  }
   217  
   218  // Pid of the process
   219  func (p *Init) Pid() int {
   220  	return p.pid
   221  }
   222  
   223  // ExitStatus of the process
   224  func (p *Init) ExitStatus() int {
   225  	p.mu.Lock()
   226  	defer p.mu.Unlock()
   227  
   228  	return p.status
   229  }
   230  
   231  // ExitedAt at time when the process exited
   232  func (p *Init) ExitedAt() time.Time {
   233  	p.mu.Lock()
   234  	defer p.mu.Unlock()
   235  
   236  	return p.exited
   237  }
   238  
   239  // Status of the process
   240  func (p *Init) Status(ctx context.Context) (string, error) {
   241  	if p.pausing.get() {
   242  		return "pausing", nil
   243  	}
   244  
   245  	p.mu.Lock()
   246  	defer p.mu.Unlock()
   247  
   248  	return p.initState.Status(ctx)
   249  }
   250  
   251  // Start the init process
   252  func (p *Init) Start(ctx context.Context) error {
   253  	p.mu.Lock()
   254  	defer p.mu.Unlock()
   255  
   256  	return p.initState.Start(ctx)
   257  }
   258  
   259  func (p *Init) start(ctx context.Context) error {
   260  	err := p.runtime.Start(ctx, p.id)
   261  	return p.runtimeError(err, "OCI runtime start failed")
   262  }
   263  
   264  // SetExited of the init process with the next status
   265  func (p *Init) SetExited(status int) {
   266  	p.mu.Lock()
   267  	defer p.mu.Unlock()
   268  
   269  	p.initState.SetExited(status)
   270  }
   271  
   272  func (p *Init) setExited(status int) {
   273  	p.exited = time.Now()
   274  	p.status = status
   275  	p.Platform.ShutdownConsole(context.Background(), p.console)
   276  	close(p.waitBlock)
   277  }
   278  
   279  // Delete the init process
   280  func (p *Init) Delete(ctx context.Context) error {
   281  	p.mu.Lock()
   282  	defer p.mu.Unlock()
   283  
   284  	return p.initState.Delete(ctx)
   285  }
   286  
   287  func (p *Init) delete(ctx context.Context) error {
   288  	waitTimeout(ctx, &p.wg, 2*time.Second)
   289  	err := p.runtime.Delete(ctx, p.id, nil)
   290  	// ignore errors if a runtime has already deleted the process
   291  	// but we still hold metadata and pipes
   292  	//
   293  	// this is common during a checkpoint, runc will delete the container state
   294  	// after a checkpoint and the container will no longer exist within runc
   295  	if err != nil {
   296  		if strings.Contains(err.Error(), "does not exist") {
   297  			err = nil
   298  		} else {
   299  			err = p.runtimeError(err, "failed to delete task")
   300  		}
   301  	}
   302  	if p.io != nil {
   303  		for _, c := range p.closers {
   304  			c.Close()
   305  		}
   306  		p.io.Close()
   307  	}
   308  	if err2 := mount.UnmountAll(p.Rootfs, 0); err2 != nil {
   309  		log.G(ctx).WithError(err2).Warn("failed to cleanup rootfs mount")
   310  		if err == nil {
   311  			err = errors.Wrap(err2, "failed rootfs umount")
   312  		}
   313  	}
   314  	return err
   315  }
   316  
   317  // Resize the init processes console
   318  func (p *Init) Resize(ws console.WinSize) error {
   319  	p.mu.Lock()
   320  	defer p.mu.Unlock()
   321  
   322  	if p.console == nil {
   323  		return nil
   324  	}
   325  	return p.console.Resize(ws)
   326  }
   327  
   328  // Pause the init process and all its child processes
   329  func (p *Init) Pause(ctx context.Context) error {
   330  	p.mu.Lock()
   331  	defer p.mu.Unlock()
   332  
   333  	return p.initState.Pause(ctx)
   334  }
   335  
   336  // Resume the init process and all its child processes
   337  func (p *Init) Resume(ctx context.Context) error {
   338  	p.mu.Lock()
   339  	defer p.mu.Unlock()
   340  
   341  	return p.initState.Resume(ctx)
   342  }
   343  
   344  // Kill the init process
   345  func (p *Init) Kill(ctx context.Context, signal uint32, all bool) error {
   346  	p.mu.Lock()
   347  	defer p.mu.Unlock()
   348  
   349  	return p.initState.Kill(ctx, signal, all)
   350  }
   351  
   352  func (p *Init) kill(ctx context.Context, signal uint32, all bool) error {
   353  	err := p.runtime.Kill(ctx, p.id, int(signal), &runc.KillOpts{
   354  		All: all,
   355  	})
   356  	return checkKillError(err)
   357  }
   358  
   359  // KillAll processes belonging to the init process
   360  func (p *Init) KillAll(ctx context.Context) error {
   361  	p.mu.Lock()
   362  	defer p.mu.Unlock()
   363  
   364  	err := p.runtime.Kill(ctx, p.id, int(unix.SIGKILL), &runc.KillOpts{
   365  		All: true,
   366  	})
   367  	return p.runtimeError(err, "OCI runtime killall failed")
   368  }
   369  
   370  // Stdin of the process
   371  func (p *Init) Stdin() io.Closer {
   372  	return p.stdin
   373  }
   374  
   375  // Runtime returns the OCI runtime configured for the init process
   376  func (p *Init) Runtime() *runc.Runc {
   377  	return p.runtime
   378  }
   379  
   380  // Exec returns a new child process
   381  func (p *Init) Exec(ctx context.Context, path string, r *ExecConfig) (Process, error) {
   382  	p.mu.Lock()
   383  	defer p.mu.Unlock()
   384  
   385  	return p.initState.Exec(ctx, path, r)
   386  }
   387  
   388  // exec returns a new exec'd process
   389  func (p *Init) exec(ctx context.Context, path string, r *ExecConfig) (Process, error) {
   390  	// process exec request
   391  	var spec specs.Process
   392  	if err := json.Unmarshal(r.Spec.Value, &spec); err != nil {
   393  		return nil, err
   394  	}
   395  	spec.Terminal = r.Terminal
   396  
   397  	e := &execProcess{
   398  		id:     r.ID,
   399  		path:   path,
   400  		parent: p,
   401  		spec:   spec,
   402  		stdio: stdio.Stdio{
   403  			Stdin:    r.Stdin,
   404  			Stdout:   r.Stdout,
   405  			Stderr:   r.Stderr,
   406  			Terminal: r.Terminal,
   407  		},
   408  		waitBlock: make(chan struct{}),
   409  	}
   410  	e.execState = &execCreatedState{p: e}
   411  	return e, nil
   412  }
   413  
   414  // Checkpoint the init process
   415  func (p *Init) Checkpoint(ctx context.Context, r *CheckpointConfig) error {
   416  	p.mu.Lock()
   417  	defer p.mu.Unlock()
   418  
   419  	return p.initState.Checkpoint(ctx, r)
   420  }
   421  
   422  func (p *Init) checkpoint(ctx context.Context, r *CheckpointConfig) error {
   423  	var actions []runc.CheckpointAction
   424  	if !r.Exit {
   425  		actions = append(actions, runc.LeaveRunning)
   426  	}
   427  	// keep criu work directory if criu work dir is set
   428  	work := r.WorkDir
   429  	if work == "" {
   430  		work = filepath.Join(p.WorkDir, "criu-work")
   431  		defer os.RemoveAll(work)
   432  	}
   433  	if err := p.runtime.Checkpoint(ctx, p.id, &runc.CheckpointOpts{
   434  		WorkDir:                  work,
   435  		ImagePath:                r.Path,
   436  		AllowOpenTCP:             r.AllowOpenTCP,
   437  		AllowExternalUnixSockets: r.AllowExternalUnixSockets,
   438  		AllowTerminal:            r.AllowTerminal,
   439  		FileLocks:                r.FileLocks,
   440  		EmptyNamespaces:          r.EmptyNamespaces,
   441  	}, actions...); err != nil {
   442  		dumpLog := filepath.Join(p.Bundle, "criu-dump.log")
   443  		if cerr := copyFile(dumpLog, filepath.Join(work, "dump.log")); cerr != nil {
   444  			log.G(ctx).Error(err)
   445  		}
   446  		return fmt.Errorf("%s path= %s", criuError(err), dumpLog)
   447  	}
   448  	return nil
   449  }
   450  
   451  // Update the processes resource configuration
   452  func (p *Init) Update(ctx context.Context, r *google_protobuf.Any) error {
   453  	p.mu.Lock()
   454  	defer p.mu.Unlock()
   455  
   456  	return p.initState.Update(ctx, r)
   457  }
   458  
   459  func (p *Init) update(ctx context.Context, r *google_protobuf.Any) error {
   460  	var resources specs.LinuxResources
   461  	if err := json.Unmarshal(r.Value, &resources); err != nil {
   462  		return err
   463  	}
   464  	return p.runtime.Update(ctx, p.id, &resources)
   465  }
   466  
   467  // Stdio of the process
   468  func (p *Init) Stdio() stdio.Stdio {
   469  	return p.stdio
   470  }
   471  
   472  func (p *Init) runtimeError(rErr error, msg string) error {
   473  	if rErr == nil {
   474  		return nil
   475  	}
   476  
   477  	rMsg, err := getLastRuntimeError(p.runtime)
   478  	switch {
   479  	case err != nil:
   480  		return errors.Wrapf(rErr, "%s: %s (%s)", msg, "unable to retrieve OCI runtime error", err.Error())
   481  	case rMsg == "":
   482  		return errors.Wrap(rErr, msg)
   483  	default:
   484  		return errors.Errorf("%s: %s", msg, rMsg)
   485  	}
   486  }
   487  
   488  func withConditionalIO(c stdio.Stdio) runc.IOOpt {
   489  	return func(o *runc.IOOption) {
   490  		o.OpenStdin = c.Stdin != ""
   491  		o.OpenStdout = c.Stdout != ""
   492  		o.OpenStderr = c.Stderr != ""
   493  	}
   494  }