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	"crypto/sha256"
24	"fmt"
25	"io/ioutil"
26	"os"
27	"path/filepath"
28
29	"github.com/containerd/containerd/events/exchange"
30	"github.com/containerd/containerd/runtime/linux/runctypes"
31	"github.com/containerd/containerd/runtime/v1/shim"
32	"github.com/containerd/containerd/runtime/v1/shim/client"
33	"github.com/pkg/errors"
34)
35
36// loadBundle loads an existing bundle from disk
37func loadBundle(id, path, workdir string) *bundle {
38	return &bundle{
39		id:      id,
40		path:    path,
41		workDir: workdir,
42	}
43}
44
45// newBundle creates a new bundle on disk at the provided path for the given id
46func newBundle(id, path, workDir string, spec []byte) (b *bundle, err error) {
47	if err := os.MkdirAll(path, 0711); err != nil {
48		return nil, err
49	}
50	path = filepath.Join(path, id)
51	if err := os.Mkdir(path, 0711); err != nil {
52		return nil, err
53	}
54	defer func() {
55		if err != nil {
56			os.RemoveAll(path)
57		}
58	}()
59	workDir = filepath.Join(workDir, id)
60	if err := os.MkdirAll(workDir, 0711); err != nil {
61		return nil, err
62	}
63	defer func() {
64		if err != nil {
65			os.RemoveAll(workDir)
66		}
67	}()
68	err = ioutil.WriteFile(filepath.Join(path, configFilename), spec, 0666)
69	return &bundle{
70		id:      id,
71		path:    path,
72		workDir: workDir,
73	}, err
74}
75
76type bundle struct {
77	id      string
78	path    string
79	workDir string
80}
81
82// ShimOpt specifies shim options for initialization and connection
83type ShimOpt func(*bundle, string, *runctypes.RuncOptions) (shim.Config, client.Opt)
84
85// ShimRemote is a ShimOpt for connecting and starting a remote shim
86func ShimRemote(c *Config, daemonAddress, cgroup string, exitHandler func()) ShimOpt {
87	return func(b *bundle, ns string, ropts *runctypes.RuncOptions) (shim.Config, client.Opt) {
88		config := b.shimConfig(ns, c, ropts)
89		return config,
90			client.WithStart(c.Shim, b.shimAddress(ns), daemonAddress, cgroup, c.ShimDebug, exitHandler)
91	}
92}
93
94// ShimLocal is a ShimOpt for using an in process shim implementation
95func ShimLocal(c *Config, exchange *exchange.Exchange) ShimOpt {
96	return func(b *bundle, ns string, ropts *runctypes.RuncOptions) (shim.Config, client.Opt) {
97		return b.shimConfig(ns, c, ropts), client.WithLocal(exchange)
98	}
99}
100
101// ShimConnect is a ShimOpt for connecting to an existing remote shim
102func ShimConnect(c *Config, onClose func()) ShimOpt {
103	return func(b *bundle, ns string, ropts *runctypes.RuncOptions) (shim.Config, client.Opt) {
104		return b.shimConfig(ns, c, ropts), client.WithConnect(b.decideShimAddress(ns), onClose)
105	}
106}
107
108// NewShimClient connects to the shim managing the bundle and tasks creating it if needed
109func (b *bundle) NewShimClient(ctx context.Context, namespace string, getClientOpts ShimOpt, runcOpts *runctypes.RuncOptions) (*client.Client, error) {
110	cfg, opt := getClientOpts(b, namespace, runcOpts)
111	return client.New(ctx, cfg, opt)
112}
113
114// Delete deletes the bundle from disk
115func (b *bundle) Delete() error {
116	err := atomicDelete(b.path)
117	if err == nil {
118		return atomicDelete(b.workDir)
119	}
120	// error removing the bundle path; still attempt removing work dir
121	err2 := atomicDelete(b.workDir)
122	if err2 == nil {
123		return err
124	}
125	return errors.Wrapf(err, "Failed to remove both bundle and workdir locations: %v", err2)
126}
127
128func (b *bundle) legacyShimAddress(namespace string) string {
129	return filepath.Join(string(filepath.Separator), "containerd-shim", namespace, b.id, "shim.sock")
130}
131
132func (b *bundle) shimAddress(namespace string) string {
133	d := sha256.Sum256([]byte(filepath.Join(namespace, b.id)))
134	return filepath.Join(string(filepath.Separator), "containerd-shim", fmt.Sprintf("%x.sock", d))
135}
136
137func (b *bundle) loadAddress() (string, error) {
138	addressPath := filepath.Join(b.path, "address")
139	data, err := ioutil.ReadFile(addressPath)
140	if err != nil {
141		return "", err
142	}
143	return string(data), nil
144}
145
146func (b *bundle) decideShimAddress(namespace string) string {
147	address, err := b.loadAddress()
148	if err != nil {
149		return b.legacyShimAddress(namespace)
150	}
151	return address
152}
153
154func (b *bundle) shimConfig(namespace string, c *Config, runcOptions *runctypes.RuncOptions) shim.Config {
155	var (
156		criuPath      string
157		runtimeRoot   = c.RuntimeRoot
158		systemdCgroup bool
159	)
160	if runcOptions != nil {
161		criuPath = runcOptions.CriuPath
162		systemdCgroup = runcOptions.SystemdCgroup
163		if runcOptions.RuntimeRoot != "" {
164			runtimeRoot = runcOptions.RuntimeRoot
165		}
166	}
167	return shim.Config{
168		Path:          b.path,
169		WorkDir:       b.workDir,
170		Namespace:     namespace,
171		Criu:          criuPath,
172		RuntimeRoot:   runtimeRoot,
173		SystemdCgroup: systemdCgroup,
174	}
175}
176
177// atomicDelete renames the path to a hidden file before removal
178func atomicDelete(path string) error {
179	// create a hidden dir for an atomic removal
180	atomicPath := filepath.Join(filepath.Dir(path), fmt.Sprintf(".%s", filepath.Base(path)))
181	if err := os.Rename(path, atomicPath); err != nil {
182		return err
183	}
184	return os.RemoveAll(atomicPath)
185}
186