1// +build linux
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
19package linux
20
21import (
22	"context"
23	"sync"
24
25	"github.com/containerd/cgroups"
26	eventstypes "github.com/containerd/containerd/api/events"
27	"github.com/containerd/containerd/api/types/task"
28	"github.com/containerd/containerd/errdefs"
29	"github.com/containerd/containerd/events/exchange"
30	"github.com/containerd/containerd/identifiers"
31	"github.com/containerd/containerd/log"
32	"github.com/containerd/containerd/runtime"
33	"github.com/containerd/containerd/runtime/v1/shim/client"
34	"github.com/containerd/containerd/runtime/v1/shim/v1"
35	"github.com/containerd/ttrpc"
36	"github.com/containerd/typeurl"
37	"github.com/gogo/protobuf/types"
38	"github.com/pkg/errors"
39)
40
41// Task on a linux based system
42type Task struct {
43	mu        sync.Mutex
44	id        string
45	pid       int
46	shim      *client.Client
47	namespace string
48	cg        cgroups.Cgroup
49	events    *exchange.Exchange
50	tasks     *runtime.TaskList
51	bundle    *bundle
52}
53
54func newTask(id, namespace string, pid int, shim *client.Client, events *exchange.Exchange, list *runtime.TaskList, bundle *bundle) (*Task, error) {
55	var (
56		err error
57		cg  cgroups.Cgroup
58	)
59	if pid > 0 {
60		cg, err = cgroups.Load(cgroups.V1, cgroups.PidPath(pid))
61		if err != nil && err != cgroups.ErrCgroupDeleted {
62			return nil, err
63		}
64	}
65	return &Task{
66		id:        id,
67		pid:       pid,
68		shim:      shim,
69		namespace: namespace,
70		cg:        cg,
71		events:    events,
72		tasks:     list,
73		bundle:    bundle,
74	}, nil
75}
76
77// ID of the task
78func (t *Task) ID() string {
79	return t.id
80}
81
82// Namespace of the task
83func (t *Task) Namespace() string {
84	return t.namespace
85}
86
87// PID of the task
88func (t *Task) PID() uint32 {
89	return uint32(t.pid)
90}
91
92// Delete the task and return the exit status
93func (t *Task) Delete(ctx context.Context) (*runtime.Exit, error) {
94	rsp, shimErr := t.shim.Delete(ctx, empty)
95	if shimErr != nil {
96		shimErr = errdefs.FromGRPC(shimErr)
97		if !errdefs.IsNotFound(shimErr) {
98			return nil, shimErr
99		}
100	}
101	t.tasks.Delete(ctx, t.id)
102	if err := t.shim.KillShim(ctx); err != nil {
103		log.G(ctx).WithError(err).Error("failed to kill shim")
104	}
105	if err := t.bundle.Delete(); err != nil {
106		log.G(ctx).WithError(err).Error("failed to delete bundle")
107	}
108	if shimErr != nil {
109		return nil, shimErr
110	}
111	t.events.Publish(ctx, runtime.TaskDeleteEventTopic, &eventstypes.TaskDelete{
112		ContainerID: t.id,
113		ExitStatus:  rsp.ExitStatus,
114		ExitedAt:    rsp.ExitedAt,
115		Pid:         rsp.Pid,
116	})
117	return &runtime.Exit{
118		Status:    rsp.ExitStatus,
119		Timestamp: rsp.ExitedAt,
120		Pid:       rsp.Pid,
121	}, nil
122}
123
124// Start the task
125func (t *Task) Start(ctx context.Context) error {
126	t.mu.Lock()
127	hasCgroup := t.cg != nil
128	t.mu.Unlock()
129	r, err := t.shim.Start(ctx, &shim.StartRequest{
130		ID: t.id,
131	})
132	if err != nil {
133		return errdefs.FromGRPC(err)
134	}
135	t.pid = int(r.Pid)
136	if !hasCgroup {
137		cg, err := cgroups.Load(cgroups.V1, cgroups.PidPath(t.pid))
138		if err != nil && err != cgroups.ErrCgroupDeleted {
139			return err
140		}
141		t.mu.Lock()
142		if err == cgroups.ErrCgroupDeleted {
143			t.cg = nil
144		} else {
145			t.cg = cg
146		}
147		t.mu.Unlock()
148	}
149	t.events.Publish(ctx, runtime.TaskStartEventTopic, &eventstypes.TaskStart{
150		ContainerID: t.id,
151		Pid:         uint32(t.pid),
152	})
153	return nil
154}
155
156// State returns runtime information for the task
157func (t *Task) State(ctx context.Context) (runtime.State, error) {
158	response, err := t.shim.State(ctx, &shim.StateRequest{
159		ID: t.id,
160	})
161	if err != nil {
162		if !errors.Is(err, ttrpc.ErrClosed) {
163			return runtime.State{}, errdefs.FromGRPC(err)
164		}
165		return runtime.State{}, errdefs.ErrNotFound
166	}
167	var status runtime.Status
168	switch response.Status {
169	case task.StatusCreated:
170		status = runtime.CreatedStatus
171	case task.StatusRunning:
172		status = runtime.RunningStatus
173	case task.StatusStopped:
174		status = runtime.StoppedStatus
175	case task.StatusPaused:
176		status = runtime.PausedStatus
177	case task.StatusPausing:
178		status = runtime.PausingStatus
179	}
180	return runtime.State{
181		Pid:        response.Pid,
182		Status:     status,
183		Stdin:      response.Stdin,
184		Stdout:     response.Stdout,
185		Stderr:     response.Stderr,
186		Terminal:   response.Terminal,
187		ExitStatus: response.ExitStatus,
188		ExitedAt:   response.ExitedAt,
189	}, nil
190}
191
192// Pause the task and all processes
193func (t *Task) Pause(ctx context.Context) error {
194	if _, err := t.shim.Pause(ctx, empty); err != nil {
195		return errdefs.FromGRPC(err)
196	}
197	t.events.Publish(ctx, runtime.TaskPausedEventTopic, &eventstypes.TaskPaused{
198		ContainerID: t.id,
199	})
200	return nil
201}
202
203// Resume the task and all processes
204func (t *Task) Resume(ctx context.Context) error {
205	if _, err := t.shim.Resume(ctx, empty); err != nil {
206		return errdefs.FromGRPC(err)
207	}
208	t.events.Publish(ctx, runtime.TaskResumedEventTopic, &eventstypes.TaskResumed{
209		ContainerID: t.id,
210	})
211	return nil
212}
213
214// Kill the task using the provided signal
215//
216// Optionally send the signal to all processes that are a child of the task
217func (t *Task) Kill(ctx context.Context, signal uint32, all bool) error {
218	if _, err := t.shim.Kill(ctx, &shim.KillRequest{
219		ID:     t.id,
220		Signal: signal,
221		All:    all,
222	}); err != nil {
223		return errdefs.FromGRPC(err)
224	}
225	return nil
226}
227
228// Exec creates a new process inside the task
229func (t *Task) Exec(ctx context.Context, id string, opts runtime.ExecOpts) (runtime.Process, error) {
230	if err := identifiers.Validate(id); err != nil {
231		return nil, errors.Wrapf(err, "invalid exec id")
232	}
233	request := &shim.ExecProcessRequest{
234		ID:       id,
235		Stdin:    opts.IO.Stdin,
236		Stdout:   opts.IO.Stdout,
237		Stderr:   opts.IO.Stderr,
238		Terminal: opts.IO.Terminal,
239		Spec:     opts.Spec,
240	}
241	if _, err := t.shim.Exec(ctx, request); err != nil {
242		return nil, errdefs.FromGRPC(err)
243	}
244	return &Process{
245		id: id,
246		t:  t,
247	}, nil
248}
249
250// Pids returns all system level process ids running inside the task
251func (t *Task) Pids(ctx context.Context) ([]runtime.ProcessInfo, error) {
252	resp, err := t.shim.ListPids(ctx, &shim.ListPidsRequest{
253		ID: t.id,
254	})
255	if err != nil {
256		return nil, errdefs.FromGRPC(err)
257	}
258	var processList []runtime.ProcessInfo
259	for _, p := range resp.Processes {
260		processList = append(processList, runtime.ProcessInfo{
261			Pid:  p.Pid,
262			Info: p.Info,
263		})
264	}
265	return processList, nil
266}
267
268// ResizePty changes the side of the task's PTY to the provided width and height
269func (t *Task) ResizePty(ctx context.Context, size runtime.ConsoleSize) error {
270	_, err := t.shim.ResizePty(ctx, &shim.ResizePtyRequest{
271		ID:     t.id,
272		Width:  size.Width,
273		Height: size.Height,
274	})
275	if err != nil {
276		err = errdefs.FromGRPC(err)
277	}
278	return err
279}
280
281// CloseIO closes the provided IO on the task
282func (t *Task) CloseIO(ctx context.Context) error {
283	_, err := t.shim.CloseIO(ctx, &shim.CloseIORequest{
284		ID:    t.id,
285		Stdin: true,
286	})
287	if err != nil {
288		err = errdefs.FromGRPC(err)
289	}
290	return err
291}
292
293// Checkpoint creates a system level dump of the task and process information that can be later restored
294func (t *Task) Checkpoint(ctx context.Context, path string, options *types.Any) error {
295	r := &shim.CheckpointTaskRequest{
296		Path:    path,
297		Options: options,
298	}
299	if _, err := t.shim.Checkpoint(ctx, r); err != nil {
300		return errdefs.FromGRPC(err)
301	}
302	t.events.Publish(ctx, runtime.TaskCheckpointedEventTopic, &eventstypes.TaskCheckpointed{
303		ContainerID: t.id,
304	})
305	return nil
306}
307
308// Update changes runtime information of a running task
309func (t *Task) Update(ctx context.Context, resources *types.Any) error {
310	if _, err := t.shim.Update(ctx, &shim.UpdateTaskRequest{
311		Resources: resources,
312	}); err != nil {
313		return errdefs.FromGRPC(err)
314	}
315	return nil
316}
317
318// Process returns a specific process inside the task by the process id
319func (t *Task) Process(ctx context.Context, id string) (runtime.Process, error) {
320	p := &Process{
321		id: id,
322		t:  t,
323	}
324	if _, err := p.State(ctx); err != nil {
325		return nil, err
326	}
327	return p, nil
328}
329
330// Stats returns runtime specific system level metric information for the task
331func (t *Task) Stats(ctx context.Context) (*types.Any, error) {
332	t.mu.Lock()
333	defer t.mu.Unlock()
334	if t.cg == nil {
335		return nil, errors.Wrap(errdefs.ErrNotFound, "cgroup does not exist")
336	}
337	stats, err := t.cg.Stat(cgroups.IgnoreNotExist)
338	if err != nil {
339		return nil, err
340	}
341	return typeurl.MarshalAny(stats)
342}
343
344// Cgroup returns the underlying cgroup for a linux task
345func (t *Task) Cgroup() (cgroups.Cgroup, error) {
346	t.mu.Lock()
347	defer t.mu.Unlock()
348	if t.cg == nil {
349		return nil, errors.Wrap(errdefs.ErrNotFound, "cgroup does not exist")
350	}
351	return t.cg, nil
352}
353
354// Wait for the task to exit returning the status and timestamp
355func (t *Task) Wait(ctx context.Context) (*runtime.Exit, error) {
356	r, err := t.shim.Wait(ctx, &shim.WaitRequest{
357		ID: t.id,
358	})
359	if err != nil {
360		return nil, err
361	}
362	return &runtime.Exit{
363		Timestamp: r.ExitedAt,
364		Status:    r.ExitStatus,
365	}, nil
366}
367