github.com/containerd/Containerd@v1.4.13/process.go (about)

     1  /*
     2     Copyright The containerd Authors.
     3  
     4     Licensed under the Apache License, Version 2.0 (the "License");
     5     you may not use this file except in compliance with the License.
     6     You may obtain a copy of the License at
     7  
     8         http://www.apache.org/licenses/LICENSE-2.0
     9  
    10     Unless required by applicable law or agreed to in writing, software
    11     distributed under the License is distributed on an "AS IS" BASIS,
    12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13     See the License for the specific language governing permissions and
    14     limitations under the License.
    15  */
    16  
    17  package containerd
    18  
    19  import (
    20  	"context"
    21  	"strings"
    22  	"syscall"
    23  	"time"
    24  
    25  	"github.com/containerd/containerd/api/services/tasks/v1"
    26  	"github.com/containerd/containerd/cio"
    27  	"github.com/containerd/containerd/errdefs"
    28  	"github.com/pkg/errors"
    29  )
    30  
    31  // Process represents a system process
    32  type Process interface {
    33  	// ID of the process
    34  	ID() string
    35  	// Pid is the system specific process id
    36  	Pid() uint32
    37  	// Start starts the process executing the user's defined binary
    38  	Start(context.Context) error
    39  	// Delete removes the process and any resources allocated returning the exit status
    40  	Delete(context.Context, ...ProcessDeleteOpts) (*ExitStatus, error)
    41  	// Kill sends the provided signal to the process
    42  	Kill(context.Context, syscall.Signal, ...KillOpts) error
    43  	// Wait asynchronously waits for the process to exit, and sends the exit code to the returned channel
    44  	Wait(context.Context) (<-chan ExitStatus, error)
    45  	// CloseIO allows various pipes to be closed on the process
    46  	CloseIO(context.Context, ...IOCloserOpts) error
    47  	// Resize changes the width and height of the process's terminal
    48  	Resize(ctx context.Context, w, h uint32) error
    49  	// IO returns the io set for the process
    50  	IO() cio.IO
    51  	// Status returns the executing status of the process
    52  	Status(context.Context) (Status, error)
    53  }
    54  
    55  // NewExitStatus populates an ExitStatus
    56  func NewExitStatus(code uint32, t time.Time, err error) *ExitStatus {
    57  	return &ExitStatus{
    58  		code:     code,
    59  		exitedAt: t,
    60  		err:      err,
    61  	}
    62  }
    63  
    64  // ExitStatus encapsulates a process's exit status.
    65  // It is used by `Wait()` to return either a process exit code or an error
    66  type ExitStatus struct {
    67  	code     uint32
    68  	exitedAt time.Time
    69  	err      error
    70  }
    71  
    72  // Result returns the exit code and time of the exit status.
    73  // An error may be returned here to which indicates there was an error
    74  //   at some point while waiting for the exit status. It does not signify
    75  //   an error with the process itself.
    76  // If an error is returned, the process may still be running.
    77  func (s ExitStatus) Result() (uint32, time.Time, error) {
    78  	return s.code, s.exitedAt, s.err
    79  }
    80  
    81  // ExitCode returns the exit code of the process.
    82  // This is only valid is Error() returns nil
    83  func (s ExitStatus) ExitCode() uint32 {
    84  	return s.code
    85  }
    86  
    87  // ExitTime returns the exit time of the process
    88  // This is only valid is Error() returns nil
    89  func (s ExitStatus) ExitTime() time.Time {
    90  	return s.exitedAt
    91  }
    92  
    93  // Error returns the error, if any, that occurred while waiting for the
    94  // process.
    95  func (s ExitStatus) Error() error {
    96  	return s.err
    97  }
    98  
    99  type process struct {
   100  	id   string
   101  	task *task
   102  	pid  uint32
   103  	io   cio.IO
   104  }
   105  
   106  func (p *process) ID() string {
   107  	return p.id
   108  }
   109  
   110  // Pid returns the pid of the process
   111  // The pid is not set until start is called and returns
   112  func (p *process) Pid() uint32 {
   113  	return p.pid
   114  }
   115  
   116  // Start starts the exec process
   117  func (p *process) Start(ctx context.Context) error {
   118  	r, err := p.task.client.TaskService().Start(ctx, &tasks.StartRequest{
   119  		ContainerID: p.task.id,
   120  		ExecID:      p.id,
   121  	})
   122  	if err != nil {
   123  		if p.io != nil {
   124  			p.io.Cancel()
   125  			p.io.Wait()
   126  			p.io.Close()
   127  		}
   128  		return errdefs.FromGRPC(err)
   129  	}
   130  	p.pid = r.Pid
   131  	return nil
   132  }
   133  
   134  func (p *process) Kill(ctx context.Context, s syscall.Signal, opts ...KillOpts) error {
   135  	var i KillInfo
   136  	for _, o := range opts {
   137  		if err := o(ctx, &i); err != nil {
   138  			return err
   139  		}
   140  	}
   141  	_, err := p.task.client.TaskService().Kill(ctx, &tasks.KillRequest{
   142  		Signal:      uint32(s),
   143  		ContainerID: p.task.id,
   144  		ExecID:      p.id,
   145  		All:         i.All,
   146  	})
   147  	return errdefs.FromGRPC(err)
   148  }
   149  
   150  func (p *process) Wait(ctx context.Context) (<-chan ExitStatus, error) {
   151  	c := make(chan ExitStatus, 1)
   152  	go func() {
   153  		defer close(c)
   154  		r, err := p.task.client.TaskService().Wait(ctx, &tasks.WaitRequest{
   155  			ContainerID: p.task.id,
   156  			ExecID:      p.id,
   157  		})
   158  		if err != nil {
   159  			c <- ExitStatus{
   160  				code: UnknownExitStatus,
   161  				err:  err,
   162  			}
   163  			return
   164  		}
   165  		c <- ExitStatus{
   166  			code:     r.ExitStatus,
   167  			exitedAt: r.ExitedAt,
   168  		}
   169  	}()
   170  	return c, nil
   171  }
   172  
   173  func (p *process) CloseIO(ctx context.Context, opts ...IOCloserOpts) error {
   174  	r := &tasks.CloseIORequest{
   175  		ContainerID: p.task.id,
   176  		ExecID:      p.id,
   177  	}
   178  	var i IOCloseInfo
   179  	for _, o := range opts {
   180  		o(&i)
   181  	}
   182  	r.Stdin = i.Stdin
   183  	_, err := p.task.client.TaskService().CloseIO(ctx, r)
   184  	return errdefs.FromGRPC(err)
   185  }
   186  
   187  func (p *process) IO() cio.IO {
   188  	return p.io
   189  }
   190  
   191  func (p *process) Resize(ctx context.Context, w, h uint32) error {
   192  	_, err := p.task.client.TaskService().ResizePty(ctx, &tasks.ResizePtyRequest{
   193  		ContainerID: p.task.id,
   194  		Width:       w,
   195  		Height:      h,
   196  		ExecID:      p.id,
   197  	})
   198  	return errdefs.FromGRPC(err)
   199  }
   200  
   201  func (p *process) Delete(ctx context.Context, opts ...ProcessDeleteOpts) (*ExitStatus, error) {
   202  	for _, o := range opts {
   203  		if err := o(ctx, p); err != nil {
   204  			return nil, err
   205  		}
   206  	}
   207  	status, err := p.Status(ctx)
   208  	if err != nil {
   209  		return nil, err
   210  	}
   211  	switch status.Status {
   212  	case Running, Paused, Pausing:
   213  		return nil, errors.Wrapf(errdefs.ErrFailedPrecondition, "process must be stopped before deletion")
   214  	}
   215  	r, err := p.task.client.TaskService().DeleteProcess(ctx, &tasks.DeleteProcessRequest{
   216  		ContainerID: p.task.id,
   217  		ExecID:      p.id,
   218  	})
   219  	if err != nil {
   220  		return nil, errdefs.FromGRPC(err)
   221  	}
   222  	if p.io != nil {
   223  		p.io.Cancel()
   224  		p.io.Wait()
   225  		p.io.Close()
   226  	}
   227  	return &ExitStatus{code: r.ExitStatus, exitedAt: r.ExitedAt}, nil
   228  }
   229  
   230  func (p *process) Status(ctx context.Context) (Status, error) {
   231  	r, err := p.task.client.TaskService().Get(ctx, &tasks.GetRequest{
   232  		ContainerID: p.task.id,
   233  		ExecID:      p.id,
   234  	})
   235  	if err != nil {
   236  		return Status{}, errdefs.FromGRPC(err)
   237  	}
   238  	return Status{
   239  		Status:     ProcessStatus(strings.ToLower(r.Process.Status.String())),
   240  		ExitStatus: r.Process.ExitStatus,
   241  	}, nil
   242  }