github.com/containerd/containerd@v22.0.0-20200918172823-438c87b8e050+incompatible/runtime/v2/bundle.go (about) 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 17 package v2 18 19 import ( 20 "context" 21 "fmt" 22 "io/ioutil" 23 "os" 24 "path/filepath" 25 26 "github.com/containerd/containerd/identifiers" 27 "github.com/containerd/containerd/mount" 28 "github.com/containerd/containerd/namespaces" 29 "github.com/pkg/errors" 30 ) 31 32 const configFilename = "config.json" 33 34 // LoadBundle loads an existing bundle from disk 35 func LoadBundle(ctx context.Context, root, id string) (*Bundle, error) { 36 ns, err := namespaces.NamespaceRequired(ctx) 37 if err != nil { 38 return nil, err 39 } 40 return &Bundle{ 41 ID: id, 42 Path: filepath.Join(root, ns, id), 43 Namespace: ns, 44 }, nil 45 } 46 47 // NewBundle returns a new bundle on disk 48 func NewBundle(ctx context.Context, root, state, id string, spec []byte) (b *Bundle, err error) { 49 if err := identifiers.Validate(id); err != nil { 50 return nil, errors.Wrapf(err, "invalid task id %s", id) 51 } 52 53 ns, err := namespaces.NamespaceRequired(ctx) 54 if err != nil { 55 return nil, err 56 } 57 work := filepath.Join(root, ns, id) 58 b = &Bundle{ 59 ID: id, 60 Path: filepath.Join(state, ns, id), 61 Namespace: ns, 62 } 63 var paths []string 64 defer func() { 65 if err != nil { 66 for _, d := range paths { 67 os.RemoveAll(d) 68 } 69 } 70 }() 71 // create state directory for the bundle 72 if err := os.MkdirAll(filepath.Dir(b.Path), 0711); err != nil { 73 return nil, err 74 } 75 if err := os.Mkdir(b.Path, 0711); err != nil { 76 return nil, err 77 } 78 paths = append(paths, b.Path) 79 // create working directory for the bundle 80 if err := os.MkdirAll(filepath.Dir(work), 0711); err != nil { 81 return nil, err 82 } 83 rootfs := filepath.Join(b.Path, "rootfs") 84 if err := os.MkdirAll(rootfs, 0711); err != nil { 85 return nil, err 86 } 87 paths = append(paths, rootfs) 88 if err := os.Mkdir(work, 0711); err != nil { 89 if !os.IsExist(err) { 90 return nil, err 91 } 92 os.RemoveAll(work) 93 if err := os.Mkdir(work, 0711); err != nil { 94 return nil, err 95 } 96 } 97 paths = append(paths, work) 98 // symlink workdir 99 if err := os.Symlink(work, filepath.Join(b.Path, "work")); err != nil { 100 return nil, err 101 } 102 // write the spec to the bundle 103 err = ioutil.WriteFile(filepath.Join(b.Path, configFilename), spec, 0666) 104 return b, err 105 } 106 107 // Bundle represents an OCI bundle 108 type Bundle struct { 109 // ID of the bundle 110 ID string 111 // Path to the bundle 112 Path string 113 // Namespace of the bundle 114 Namespace string 115 } 116 117 // Delete a bundle atomically 118 func (b *Bundle) Delete() error { 119 work, werr := os.Readlink(filepath.Join(b.Path, "work")) 120 rootfs := filepath.Join(b.Path, "rootfs") 121 if err := mount.UnmountAll(rootfs, 0); err != nil { 122 return errors.Wrapf(err, "unmount rootfs %s", rootfs) 123 } 124 if err := os.Remove(rootfs); err != nil && !os.IsNotExist(err) { 125 return errors.Wrap(err, "failed to remove bundle rootfs") 126 } 127 err := atomicDelete(b.Path) 128 if err == nil { 129 if werr == nil { 130 return atomicDelete(work) 131 } 132 return nil 133 } 134 // error removing the bundle path; still attempt removing work dir 135 var err2 error 136 if werr == nil { 137 err2 = atomicDelete(work) 138 if err2 == nil { 139 return err 140 } 141 } 142 return errors.Wrapf(err, "failed to remove both bundle and workdir locations: %v", err2) 143 } 144 145 // atomicDelete renames the path to a hidden file before removal 146 func atomicDelete(path string) error { 147 // create a hidden dir for an atomic removal 148 atomicPath := filepath.Join(filepath.Dir(path), fmt.Sprintf(".%s", filepath.Base(path))) 149 if err := os.Rename(path, atomicPath); err != nil { 150 if os.IsNotExist(err) { 151 return nil 152 } 153 return err 154 } 155 return os.RemoveAll(atomicPath) 156 }