github.com/containerd/containerd@v22.0.0-20200918172823-438c87b8e050+incompatible/pkg/process/exec.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  	"fmt"
    24  	"io"
    25  	"os"
    26  	"path/filepath"
    27  	"sync"
    28  	"syscall"
    29  	"time"
    30  
    31  	"golang.org/x/sys/unix"
    32  
    33  	"github.com/containerd/console"
    34  	"github.com/containerd/containerd/errdefs"
    35  	"github.com/containerd/containerd/pkg/stdio"
    36  	"github.com/containerd/fifo"
    37  	runc "github.com/containerd/go-runc"
    38  	specs "github.com/opencontainers/runtime-spec/specs-go"
    39  	"github.com/pkg/errors"
    40  )
    41  
    42  type execProcess struct {
    43  	wg sync.WaitGroup
    44  
    45  	execState execState
    46  
    47  	mu      sync.Mutex
    48  	id      string
    49  	console console.Console
    50  	io      *processIO
    51  	status  int
    52  	exited  time.Time
    53  	pid     safePid
    54  	closers []io.Closer
    55  	stdin   io.Closer
    56  	stdio   stdio.Stdio
    57  	path    string
    58  	spec    specs.Process
    59  
    60  	parent    *Init
    61  	waitBlock chan struct{}
    62  }
    63  
    64  func (e *execProcess) Wait() {
    65  	<-e.waitBlock
    66  }
    67  
    68  func (e *execProcess) ID() string {
    69  	return e.id
    70  }
    71  
    72  func (e *execProcess) Pid() int {
    73  	return e.pid.get()
    74  }
    75  
    76  func (e *execProcess) ExitStatus() int {
    77  	e.mu.Lock()
    78  	defer e.mu.Unlock()
    79  	return e.status
    80  }
    81  
    82  func (e *execProcess) ExitedAt() time.Time {
    83  	e.mu.Lock()
    84  	defer e.mu.Unlock()
    85  	return e.exited
    86  }
    87  
    88  func (e *execProcess) SetExited(status int) {
    89  	e.mu.Lock()
    90  	defer e.mu.Unlock()
    91  
    92  	e.execState.SetExited(status)
    93  }
    94  
    95  func (e *execProcess) setExited(status int) {
    96  	e.status = status
    97  	e.exited = time.Now()
    98  	e.parent.Platform.ShutdownConsole(context.Background(), e.console)
    99  	close(e.waitBlock)
   100  }
   101  
   102  func (e *execProcess) Delete(ctx context.Context) error {
   103  	e.mu.Lock()
   104  	defer e.mu.Unlock()
   105  
   106  	return e.execState.Delete(ctx)
   107  }
   108  
   109  func (e *execProcess) delete(ctx context.Context) error {
   110  	waitTimeout(ctx, &e.wg, 2*time.Second)
   111  	if e.io != nil {
   112  		for _, c := range e.closers {
   113  			c.Close()
   114  		}
   115  		e.io.Close()
   116  	}
   117  	pidfile := filepath.Join(e.path, fmt.Sprintf("%s.pid", e.id))
   118  	// silently ignore error
   119  	os.Remove(pidfile)
   120  	return nil
   121  }
   122  
   123  func (e *execProcess) Resize(ws console.WinSize) error {
   124  	e.mu.Lock()
   125  	defer e.mu.Unlock()
   126  
   127  	return e.execState.Resize(ws)
   128  }
   129  
   130  func (e *execProcess) resize(ws console.WinSize) error {
   131  	if e.console == nil {
   132  		return nil
   133  	}
   134  	return e.console.Resize(ws)
   135  }
   136  
   137  func (e *execProcess) Kill(ctx context.Context, sig uint32, _ bool) error {
   138  	e.mu.Lock()
   139  	defer e.mu.Unlock()
   140  
   141  	return e.execState.Kill(ctx, sig, false)
   142  }
   143  
   144  func (e *execProcess) kill(ctx context.Context, sig uint32, _ bool) error {
   145  	pid := e.pid.get()
   146  	switch {
   147  	case pid == 0:
   148  		return errors.Wrap(errdefs.ErrFailedPrecondition, "process not created")
   149  	case !e.exited.IsZero():
   150  		return errors.Wrapf(errdefs.ErrNotFound, "process already finished")
   151  	default:
   152  		if err := unix.Kill(pid, syscall.Signal(sig)); err != nil {
   153  			return errors.Wrapf(checkKillError(err), "exec kill error")
   154  		}
   155  	}
   156  	return nil
   157  }
   158  
   159  func (e *execProcess) Stdin() io.Closer {
   160  	return e.stdin
   161  }
   162  
   163  func (e *execProcess) Stdio() stdio.Stdio {
   164  	return e.stdio
   165  }
   166  
   167  func (e *execProcess) Start(ctx context.Context) error {
   168  	e.mu.Lock()
   169  	defer e.mu.Unlock()
   170  
   171  	return e.execState.Start(ctx)
   172  }
   173  
   174  func (e *execProcess) start(ctx context.Context) (err error) {
   175  	// The reaper may receive exit signal right after
   176  	// the container is started, before the e.pid is updated.
   177  	// In that case, we want to block the signal handler to
   178  	// access e.pid until it is updated.
   179  	e.pid.Lock()
   180  	defer e.pid.Unlock()
   181  
   182  	var (
   183  		socket  *runc.Socket
   184  		pio     *processIO
   185  		pidFile = newExecPidFile(e.path, e.id)
   186  	)
   187  	if e.stdio.Terminal {
   188  		if socket, err = runc.NewTempConsoleSocket(); err != nil {
   189  			return errors.Wrap(err, "failed to create runc console socket")
   190  		}
   191  		defer socket.Close()
   192  	} else {
   193  		if pio, err = createIO(ctx, e.id, e.parent.IoUID, e.parent.IoGID, e.stdio); err != nil {
   194  			return errors.Wrap(err, "failed to create init process I/O")
   195  		}
   196  		e.io = pio
   197  	}
   198  	opts := &runc.ExecOpts{
   199  		PidFile: pidFile.Path(),
   200  		Detach:  true,
   201  	}
   202  	if pio != nil {
   203  		opts.IO = pio.IO()
   204  	}
   205  	if socket != nil {
   206  		opts.ConsoleSocket = socket
   207  	}
   208  	if err := e.parent.runtime.Exec(ctx, e.parent.id, e.spec, opts); err != nil {
   209  		close(e.waitBlock)
   210  		return e.parent.runtimeError(err, "OCI runtime exec failed")
   211  	}
   212  	if e.stdio.Stdin != "" {
   213  		if err := e.openStdin(e.stdio.Stdin); err != nil {
   214  			return err
   215  		}
   216  	}
   217  	ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
   218  	defer cancel()
   219  	if socket != nil {
   220  		console, err := socket.ReceiveMaster()
   221  		if err != nil {
   222  			return errors.Wrap(err, "failed to retrieve console master")
   223  		}
   224  		if e.console, err = e.parent.Platform.CopyConsole(ctx, console, e.stdio.Stdin, e.stdio.Stdout, e.stdio.Stderr, &e.wg); err != nil {
   225  			return errors.Wrap(err, "failed to start console copy")
   226  		}
   227  	} else {
   228  		if err := pio.Copy(ctx, &e.wg); err != nil {
   229  			return errors.Wrap(err, "failed to start io pipe copy")
   230  		}
   231  	}
   232  	pid, err := pidFile.Read()
   233  	if err != nil {
   234  		return errors.Wrap(err, "failed to retrieve OCI runtime exec pid")
   235  	}
   236  	e.pid.pid = pid
   237  	return nil
   238  }
   239  
   240  func (e *execProcess) openStdin(path string) error {
   241  	sc, err := fifo.OpenFifo(context.Background(), path, syscall.O_WRONLY|syscall.O_NONBLOCK, 0)
   242  	if err != nil {
   243  		return errors.Wrapf(err, "failed to open stdin fifo %s", path)
   244  	}
   245  	e.stdin = sc
   246  	e.closers = append(e.closers, sc)
   247  	return nil
   248  }
   249  
   250  func (e *execProcess) Status(ctx context.Context) (string, error) {
   251  	s, err := e.parent.Status(ctx)
   252  	if err != nil {
   253  		return "", err
   254  	}
   255  	// if the container as a whole is in the pausing/paused state, so are all
   256  	// other processes inside the container, use container state here
   257  	switch s {
   258  	case "paused", "pausing":
   259  		return s, nil
   260  	}
   261  	e.mu.Lock()
   262  	defer e.mu.Unlock()
   263  	return e.execState.Status(ctx)
   264  }