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  }