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  }