github.com/containerd/Containerd@v1.4.13/runtime/v1/linux/bundle.go (about) 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 19 package linux 20 21 import ( 22 "context" 23 "crypto/sha256" 24 "encoding/json" 25 "fmt" 26 "io/ioutil" 27 "os" 28 "path/filepath" 29 30 "github.com/containerd/containerd/events/exchange" 31 "github.com/containerd/containerd/runtime/linux/runctypes" 32 "github.com/containerd/containerd/runtime/v1/shim" 33 "github.com/containerd/containerd/runtime/v1/shim/client" 34 "github.com/opencontainers/runtime-spec/specs-go" 35 "github.com/pkg/errors" 36 ) 37 38 // loadBundle loads an existing bundle from disk 39 func loadBundle(id, path, workdir string) *bundle { 40 return &bundle{ 41 id: id, 42 path: path, 43 workDir: workdir, 44 } 45 } 46 47 // newBundle creates a new bundle on disk at the provided path for the given id 48 func newBundle(id, path, workDir string, spec []byte) (b *bundle, err error) { 49 if err := os.MkdirAll(path, 0711); err != nil { 50 return nil, err 51 } 52 path = filepath.Join(path, id) 53 if err := os.Mkdir(path, 0700); err != nil { 54 return nil, err 55 } 56 defer func() { 57 if err != nil { 58 os.RemoveAll(path) 59 } 60 }() 61 if err := prepareBundleDirectoryPermissions(path, spec); err != nil { 62 return nil, err 63 } 64 workDir = filepath.Join(workDir, id) 65 if err := os.MkdirAll(workDir, 0711); err != nil { 66 return nil, err 67 } 68 defer func() { 69 if err != nil { 70 os.RemoveAll(workDir) 71 } 72 }() 73 rootfs := filepath.Join(path, "rootfs") 74 if err := os.MkdirAll(rootfs, 0711); err != nil { 75 return nil, err 76 } 77 err = ioutil.WriteFile(filepath.Join(path, configFilename), spec, 0666) 78 return &bundle{ 79 id: id, 80 path: path, 81 workDir: workDir, 82 }, err 83 } 84 85 // prepareBundleDirectoryPermissions prepares the permissions of the bundle 86 // directory. When user namespaces are enabled, the permissions are modified 87 // to allow the remapped root GID to access the bundle. 88 func prepareBundleDirectoryPermissions(path string, spec []byte) error { 89 gid, err := remappedGID(spec) 90 if err != nil { 91 return err 92 } 93 if gid == 0 { 94 return nil 95 } 96 if err := os.Chown(path, -1, int(gid)); err != nil { 97 return err 98 } 99 return os.Chmod(path, 0710) 100 } 101 102 // ociSpecUserNS is a subset of specs.Spec used to reduce garbage during 103 // unmarshal. 104 type ociSpecUserNS struct { 105 Linux *linuxSpecUserNS 106 } 107 108 // linuxSpecUserNS is a subset of specs.Linux used to reduce garbage during 109 // unmarshal. 110 type linuxSpecUserNS struct { 111 GIDMappings []specs.LinuxIDMapping 112 } 113 114 // remappedGID reads the remapped GID 0 from the OCI spec, if it exists. If 115 // there is no remapping, remappedGID returns 0. If the spec cannot be parsed, 116 // remappedGID returns an error. 117 func remappedGID(spec []byte) (uint32, error) { 118 var ociSpec ociSpecUserNS 119 err := json.Unmarshal(spec, &ociSpec) 120 if err != nil { 121 return 0, err 122 } 123 if ociSpec.Linux == nil || len(ociSpec.Linux.GIDMappings) == 0 { 124 return 0, nil 125 } 126 for _, mapping := range ociSpec.Linux.GIDMappings { 127 if mapping.ContainerID == 0 { 128 return mapping.HostID, nil 129 } 130 } 131 return 0, nil 132 } 133 134 type bundle struct { 135 id string 136 path string 137 workDir string 138 } 139 140 // ShimOpt specifies shim options for initialization and connection 141 type ShimOpt func(*bundle, string, *runctypes.RuncOptions) (shim.Config, client.Opt) 142 143 // ShimRemote is a ShimOpt for connecting and starting a remote shim 144 func ShimRemote(c *Config, daemonAddress, cgroup string, exitHandler func()) ShimOpt { 145 return func(b *bundle, ns string, ropts *runctypes.RuncOptions) (shim.Config, client.Opt) { 146 config := b.shimConfig(ns, c, ropts) 147 return config, 148 client.WithStart(c.Shim, b.shimAddress(ns, daemonAddress), daemonAddress, cgroup, c.ShimDebug, exitHandler) 149 } 150 } 151 152 // ShimLocal is a ShimOpt for using an in process shim implementation 153 func ShimLocal(c *Config, exchange *exchange.Exchange) ShimOpt { 154 return func(b *bundle, ns string, ropts *runctypes.RuncOptions) (shim.Config, client.Opt) { 155 return b.shimConfig(ns, c, ropts), client.WithLocal(exchange) 156 } 157 } 158 159 // ShimConnect is a ShimOpt for connecting to an existing remote shim 160 func ShimConnect(c *Config, onClose func()) ShimOpt { 161 return func(b *bundle, ns string, ropts *runctypes.RuncOptions) (shim.Config, client.Opt) { 162 return b.shimConfig(ns, c, ropts), client.WithConnect(b.decideShimAddress(ns), onClose) 163 } 164 } 165 166 // NewShimClient connects to the shim managing the bundle and tasks creating it if needed 167 func (b *bundle) NewShimClient(ctx context.Context, namespace string, getClientOpts ShimOpt, runcOpts *runctypes.RuncOptions) (*client.Client, error) { 168 cfg, opt := getClientOpts(b, namespace, runcOpts) 169 return client.New(ctx, cfg, opt) 170 } 171 172 // Delete deletes the bundle from disk 173 func (b *bundle) Delete() error { 174 address, _ := b.loadAddress() 175 if address != "" { 176 // we don't care about errors here 177 client.RemoveSocket(address) 178 } 179 err := atomicDelete(b.path) 180 if err == nil { 181 return atomicDelete(b.workDir) 182 } 183 // error removing the bundle path; still attempt removing work dir 184 err2 := atomicDelete(b.workDir) 185 if err2 == nil { 186 return err 187 } 188 return errors.Wrapf(err, "Failed to remove both bundle and workdir locations: %v", err2) 189 } 190 191 func (b *bundle) legacyShimAddress(namespace string) string { 192 return filepath.Join(string(filepath.Separator), "containerd-shim", namespace, b.id, "shim.sock") 193 } 194 195 const socketRoot = "/run/containerd" 196 197 func (b *bundle) shimAddress(namespace, socketPath string) string { 198 d := sha256.Sum256([]byte(filepath.Join(socketPath, namespace, b.id))) 199 return fmt.Sprintf("unix://%s/%x", filepath.Join(socketRoot, "s"), d) 200 } 201 202 func (b *bundle) loadAddress() (string, error) { 203 addressPath := filepath.Join(b.path, "address") 204 data, err := ioutil.ReadFile(addressPath) 205 if err != nil { 206 return "", err 207 } 208 return string(data), nil 209 } 210 211 func (b *bundle) decideShimAddress(namespace string) string { 212 address, err := b.loadAddress() 213 if err != nil { 214 return b.legacyShimAddress(namespace) 215 } 216 return address 217 } 218 219 func (b *bundle) shimConfig(namespace string, c *Config, runcOptions *runctypes.RuncOptions) shim.Config { 220 var ( 221 criuPath string 222 runtimeRoot = c.RuntimeRoot 223 systemdCgroup bool 224 ) 225 if runcOptions != nil { 226 criuPath = runcOptions.CriuPath 227 systemdCgroup = runcOptions.SystemdCgroup 228 if runcOptions.RuntimeRoot != "" { 229 runtimeRoot = runcOptions.RuntimeRoot 230 } 231 } 232 return shim.Config{ 233 Path: b.path, 234 WorkDir: b.workDir, 235 Namespace: namespace, 236 Criu: criuPath, 237 RuntimeRoot: runtimeRoot, 238 SystemdCgroup: systemdCgroup, 239 } 240 } 241 242 // atomicDelete renames the path to a hidden file before removal 243 func atomicDelete(path string) error { 244 // create a hidden dir for an atomic removal 245 atomicPath := filepath.Join(filepath.Dir(path), fmt.Sprintf(".%s", filepath.Base(path))) 246 if err := os.Rename(path, atomicPath); err != nil { 247 if os.IsNotExist(err) { 248 return nil 249 } 250 return err 251 } 252 return os.RemoveAll(atomicPath) 253 }