123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264 |
- // +build !windows
- /*
- Copyright The containerd Authors.
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- */
- package process
- import (
- "context"
- "fmt"
- "io"
- "os"
- "path/filepath"
- "sync"
- "syscall"
- "time"
- "golang.org/x/sys/unix"
- "github.com/containerd/console"
- "github.com/containerd/containerd/errdefs"
- "github.com/containerd/containerd/pkg/stdio"
- "github.com/containerd/fifo"
- runc "github.com/containerd/go-runc"
- specs "github.com/opencontainers/runtime-spec/specs-go"
- "github.com/pkg/errors"
- )
- type execProcess struct {
- wg sync.WaitGroup
- execState execState
- mu sync.Mutex
- id string
- console console.Console
- io *processIO
- status int
- exited time.Time
- pid safePid
- closers []io.Closer
- stdin io.Closer
- stdio stdio.Stdio
- path string
- spec specs.Process
- parent *Init
- waitBlock chan struct{}
- }
- func (e *execProcess) Wait() {
- <-e.waitBlock
- }
- func (e *execProcess) ID() string {
- return e.id
- }
- func (e *execProcess) Pid() int {
- return e.pid.get()
- }
- func (e *execProcess) ExitStatus() int {
- e.mu.Lock()
- defer e.mu.Unlock()
- return e.status
- }
- func (e *execProcess) ExitedAt() time.Time {
- e.mu.Lock()
- defer e.mu.Unlock()
- return e.exited
- }
- func (e *execProcess) SetExited(status int) {
- e.mu.Lock()
- defer e.mu.Unlock()
- e.execState.SetExited(status)
- }
- func (e *execProcess) setExited(status int) {
- e.status = status
- e.exited = time.Now()
- e.parent.Platform.ShutdownConsole(context.Background(), e.console)
- close(e.waitBlock)
- }
- func (e *execProcess) Delete(ctx context.Context) error {
- e.mu.Lock()
- defer e.mu.Unlock()
- return e.execState.Delete(ctx)
- }
- func (e *execProcess) delete(ctx context.Context) error {
- waitTimeout(ctx, &e.wg, 2*time.Second)
- if e.io != nil {
- for _, c := range e.closers {
- c.Close()
- }
- e.io.Close()
- }
- pidfile := filepath.Join(e.path, fmt.Sprintf("%s.pid", e.id))
- // silently ignore error
- os.Remove(pidfile)
- return nil
- }
- func (e *execProcess) Resize(ws console.WinSize) error {
- e.mu.Lock()
- defer e.mu.Unlock()
- return e.execState.Resize(ws)
- }
- func (e *execProcess) resize(ws console.WinSize) error {
- if e.console == nil {
- return nil
- }
- return e.console.Resize(ws)
- }
- func (e *execProcess) Kill(ctx context.Context, sig uint32, _ bool) error {
- e.mu.Lock()
- defer e.mu.Unlock()
- return e.execState.Kill(ctx, sig, false)
- }
- func (e *execProcess) kill(ctx context.Context, sig uint32, _ bool) error {
- pid := e.pid.get()
- switch {
- case pid == 0:
- return errors.Wrap(errdefs.ErrFailedPrecondition, "process not created")
- case !e.exited.IsZero():
- return errors.Wrapf(errdefs.ErrNotFound, "process already finished")
- default:
- if err := unix.Kill(pid, syscall.Signal(sig)); err != nil {
- return errors.Wrapf(checkKillError(err), "exec kill error")
- }
- }
- return nil
- }
- func (e *execProcess) Stdin() io.Closer {
- return e.stdin
- }
- func (e *execProcess) Stdio() stdio.Stdio {
- return e.stdio
- }
- func (e *execProcess) Start(ctx context.Context) error {
- e.mu.Lock()
- defer e.mu.Unlock()
- return e.execState.Start(ctx)
- }
- func (e *execProcess) start(ctx context.Context) (err error) {
- // The reaper may receive exit signal right after
- // the container is started, before the e.pid is updated.
- // In that case, we want to block the signal handler to
- // access e.pid until it is updated.
- e.pid.Lock()
- defer e.pid.Unlock()
- var (
- socket *runc.Socket
- pio *processIO
- pidFile = newExecPidFile(e.path, e.id)
- )
- if e.stdio.Terminal {
- if socket, err = runc.NewTempConsoleSocket(); err != nil {
- return errors.Wrap(err, "failed to create runc console socket")
- }
- defer socket.Close()
- } else {
- if pio, err = createIO(ctx, e.id, e.parent.IoUID, e.parent.IoGID, e.stdio); err != nil {
- return errors.Wrap(err, "failed to create init process I/O")
- }
- e.io = pio
- }
- opts := &runc.ExecOpts{
- PidFile: pidFile.Path(),
- Detach: true,
- }
- if pio != nil {
- opts.IO = pio.IO()
- }
- if socket != nil {
- opts.ConsoleSocket = socket
- }
- if err := e.parent.runtime.Exec(ctx, e.parent.id, e.spec, opts); err != nil {
- close(e.waitBlock)
- return e.parent.runtimeError(err, "OCI runtime exec failed")
- }
- if e.stdio.Stdin != "" {
- if err := e.openStdin(e.stdio.Stdin); err != nil {
- return err
- }
- }
- ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
- defer cancel()
- if socket != nil {
- console, err := socket.ReceiveMaster()
- if err != nil {
- return errors.Wrap(err, "failed to retrieve console master")
- }
- if e.console, err = e.parent.Platform.CopyConsole(ctx, console, e.stdio.Stdin, e.stdio.Stdout, e.stdio.Stderr, &e.wg); err != nil {
- return errors.Wrap(err, "failed to start console copy")
- }
- } else {
- if err := pio.Copy(ctx, &e.wg); err != nil {
- return errors.Wrap(err, "failed to start io pipe copy")
- }
- }
- pid, err := pidFile.Read()
- if err != nil {
- return errors.Wrap(err, "failed to retrieve OCI runtime exec pid")
- }
- e.pid.pid = pid
- return nil
- }
- func (e *execProcess) openStdin(path string) error {
- sc, err := fifo.OpenFifo(context.Background(), path, syscall.O_WRONLY|syscall.O_NONBLOCK, 0)
- if err != nil {
- return errors.Wrapf(err, "failed to open stdin fifo %s", path)
- }
- e.stdin = sc
- e.closers = append(e.closers, sc)
- return nil
- }
- func (e *execProcess) Status(ctx context.Context) (string, error) {
- s, err := e.parent.Status(ctx)
- if err != nil {
- return "", err
- }
- // if the container as a whole is in the pausing/paused state, so are all
- // other processes inside the container, use container state here
- switch s {
- case "paused", "pausing":
- return s, nil
- }
- e.mu.Lock()
- defer e.mu.Unlock()
- return e.execState.Status(ctx)
- }
|