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 v2
18
19import (
20	"bytes"
21	"context"
22	"io"
23	"os"
24	gruntime "runtime"
25	"strings"
26
27	"github.com/containerd/containerd/events/exchange"
28	"github.com/containerd/containerd/log"
29	"github.com/containerd/containerd/namespaces"
30	"github.com/containerd/containerd/runtime"
31	client "github.com/containerd/containerd/runtime/v2/shim"
32	"github.com/containerd/containerd/runtime/v2/task"
33	"github.com/containerd/ttrpc"
34	"github.com/gogo/protobuf/types"
35	"github.com/pkg/errors"
36	"github.com/sirupsen/logrus"
37)
38
39func shimBinary(ctx context.Context, bundle *Bundle, runtime, containerdAddress string, containerdTTRPCAddress string, events *exchange.Exchange, rt *runtime.TaskList) *binary {
40	return &binary{
41		bundle:                 bundle,
42		runtime:                runtime,
43		containerdAddress:      containerdAddress,
44		containerdTTRPCAddress: containerdTTRPCAddress,
45		events:                 events,
46		rtTasks:                rt,
47	}
48}
49
50type binary struct {
51	runtime                string
52	containerdAddress      string
53	containerdTTRPCAddress string
54	bundle                 *Bundle
55	events                 *exchange.Exchange
56	rtTasks                *runtime.TaskList
57}
58
59func (b *binary) Start(ctx context.Context, opts *types.Any, onClose func()) (_ *shim, err error) {
60	args := []string{"-id", b.bundle.ID}
61	if logrus.GetLevel() == logrus.DebugLevel {
62		args = append(args, "-debug")
63	}
64	args = append(args, "start")
65
66	cmd, err := client.Command(
67		ctx,
68		b.runtime,
69		b.containerdAddress,
70		b.containerdTTRPCAddress,
71		b.bundle.Path,
72		opts,
73		args...,
74	)
75	if err != nil {
76		return nil, err
77	}
78	// Windows needs a namespace when openShimLog
79	ns, _ := namespaces.Namespace(ctx)
80	shimCtx, cancelShimLog := context.WithCancel(namespaces.WithNamespace(context.Background(), ns))
81	defer func() {
82		if err != nil {
83			cancelShimLog()
84		}
85	}()
86	f, err := openShimLog(shimCtx, b.bundle, client.AnonDialer)
87	if err != nil {
88		return nil, errors.Wrap(err, "open shim log pipe")
89	}
90	defer func() {
91		if err != nil {
92			f.Close()
93		}
94	}()
95	// open the log pipe and block until the writer is ready
96	// this helps with synchronization of the shim
97	// copy the shim's logs to containerd's output
98	go func() {
99		defer f.Close()
100		_, err := io.Copy(os.Stderr, f)
101		err = checkCopyShimLogError(ctx, err)
102		if err != nil {
103			log.G(ctx).WithError(err).Error("copy shim log")
104		}
105	}()
106	out, err := cmd.CombinedOutput()
107	if err != nil {
108		return nil, errors.Wrapf(err, "%s", out)
109	}
110	address := strings.TrimSpace(string(out))
111	conn, err := client.Connect(address, client.AnonDialer)
112	if err != nil {
113		return nil, err
114	}
115	onCloseWithShimLog := func() {
116		onClose()
117		cancelShimLog()
118	}
119	client := ttrpc.NewClient(conn, ttrpc.WithOnClose(onCloseWithShimLog))
120	return &shim{
121		bundle:  b.bundle,
122		client:  client,
123		task:    task.NewTaskClient(client),
124		events:  b.events,
125		rtTasks: b.rtTasks,
126	}, nil
127}
128
129func (b *binary) Delete(ctx context.Context) (*runtime.Exit, error) {
130	log.G(ctx).Info("cleaning up dead shim")
131
132	// Windows cannot delete the current working directory while an
133	// executable is in use with it. For the cleanup case we invoke with the
134	// default work dir and forward the bundle path on the cmdline.
135	var bundlePath string
136	if gruntime.GOOS != "windows" {
137		bundlePath = b.bundle.Path
138	}
139
140	cmd, err := client.Command(ctx,
141		b.runtime,
142		b.containerdAddress,
143		b.containerdTTRPCAddress,
144		bundlePath,
145		nil,
146		"-id", b.bundle.ID,
147		"-bundle", b.bundle.Path,
148		"delete")
149	if err != nil {
150		return nil, err
151	}
152	var (
153		out  = bytes.NewBuffer(nil)
154		errb = bytes.NewBuffer(nil)
155	)
156	cmd.Stdout = out
157	cmd.Stderr = errb
158	if err := cmd.Run(); err != nil {
159		return nil, errors.Wrapf(err, "%s", errb.String())
160	}
161	s := errb.String()
162	if s != "" {
163		log.G(ctx).Warnf("cleanup warnings %s", s)
164	}
165	var response task.DeleteResponse
166	if err := response.Unmarshal(out.Bytes()); err != nil {
167		return nil, err
168	}
169	if err := b.bundle.Delete(); err != nil {
170		return nil, err
171	}
172	return &runtime.Exit{
173		Status:    response.ExitStatus,
174		Timestamp: response.ExitedAt,
175		Pid:       response.Pid,
176	}, nil
177}
178