github.com/containerd/Containerd@v1.4.13/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, 0700); err != nil { 76 return nil, err 77 } 78 if err := prepareBundleDirectoryPermissions(b.Path, spec); err != nil { 79 return nil, err 80 } 81 paths = append(paths, b.Path) 82 // create working directory for the bundle 83 if err := os.MkdirAll(filepath.Dir(work), 0711); err != nil { 84 return nil, err 85 } 86 rootfs := filepath.Join(b.Path, "rootfs") 87 if err := os.MkdirAll(rootfs, 0711); err != nil { 88 return nil, err 89 } 90 paths = append(paths, rootfs) 91 if err := os.Mkdir(work, 0711); err != nil { 92 if !os.IsExist(err) { 93 return nil, err 94 } 95 os.RemoveAll(work) 96 if err := os.Mkdir(work, 0711); err != nil { 97 return nil, err 98 } 99 } 100 paths = append(paths, work) 101 // symlink workdir 102 if err := os.Symlink(work, filepath.Join(b.Path, "work")); err != nil { 103 return nil, err 104 } 105 // write the spec to the bundle 106 err = ioutil.WriteFile(filepath.Join(b.Path, configFilename), spec, 0666) 107 return b, err 108 } 109 110 // Bundle represents an OCI bundle 111 type Bundle struct { 112 // ID of the bundle 113 ID string 114 // Path to the bundle 115 Path string 116 // Namespace of the bundle 117 Namespace string 118 } 119 120 // Delete a bundle atomically 121 func (b *Bundle) Delete() error { 122 work, werr := os.Readlink(filepath.Join(b.Path, "work")) 123 rootfs := filepath.Join(b.Path, "rootfs") 124 if err := mount.UnmountAll(rootfs, 0); err != nil { 125 return errors.Wrapf(err, "unmount rootfs %s", rootfs) 126 } 127 if err := os.Remove(rootfs); err != nil && !os.IsNotExist(err) { 128 return errors.Wrap(err, "failed to remove bundle rootfs") 129 } 130 err := atomicDelete(b.Path) 131 if err == nil { 132 if werr == nil { 133 return atomicDelete(work) 134 } 135 return nil 136 } 137 // error removing the bundle path; still attempt removing work dir 138 var err2 error 139 if werr == nil { 140 err2 = atomicDelete(work) 141 if err2 == nil { 142 return err 143 } 144 } 145 return errors.Wrapf(err, "failed to remove both bundle and workdir locations: %v", err2) 146 } 147 148 // atomicDelete renames the path to a hidden file before removal 149 func atomicDelete(path string) error { 150 // create a hidden dir for an atomic removal 151 atomicPath := filepath.Join(filepath.Dir(path), fmt.Sprintf(".%s", filepath.Base(path))) 152 if err := os.Rename(path, atomicPath); err != nil { 153 if os.IsNotExist(err) { 154 return nil 155 } 156 return err 157 } 158 return os.RemoveAll(atomicPath) 159 }