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
17package containerd
18
19import (
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
32type 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
56func 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
66type 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.
77func (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
83func (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
89func (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.
95func (s ExitStatus) Error() error {
96	return s.err
97}
98
99type process struct {
100	id   string
101	task *task
102	pid  uint32
103	io   cio.IO
104}
105
106func (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
112func (p *process) Pid() uint32 {
113	return p.pid
114}
115
116// Start starts the exec process
117func (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
134func (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
150func (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
173func (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
187func (p *process) IO() cio.IO {
188	return p.io
189}
190
191func (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
201func (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
230func (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}
243