github.com/dctrud/umoci@v0.4.3-0.20191016193643-05a1d37de015/oci/layer/generate.go (about)

     1  /*
     2   * umoci: Umoci Modifies Open Containers' Images
     3   * Copyright (C) 2016, 2017, 2018 SUSE LLC.
     4   *
     5   * Licensed under the Apache License, Version 2.0 (the "License");
     6   * you may not use this file except in compliance with the License.
     7   * You may obtain a copy of the License at
     8   *
     9   *    http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the License for the specific language governing permissions and
    15   * limitations under the License.
    16   */
    17  
    18  package layer
    19  
    20  import (
    21  	"io"
    22  	"os"
    23  	"path"
    24  	"path/filepath"
    25  	"sort"
    26  
    27  	"github.com/apex/log"
    28  	"github.com/openSUSE/umoci/pkg/unpriv"
    29  	"github.com/pkg/errors"
    30  	"github.com/vbatts/go-mtree"
    31  )
    32  
    33  // inodeDeltas is a wrapper around []mtree.InodeDelta that allows for sorting
    34  // the set of deltas by the pathname.
    35  type inodeDeltas []mtree.InodeDelta
    36  
    37  func (ids inodeDeltas) Len() int           { return len(ids) }
    38  func (ids inodeDeltas) Less(i, j int) bool { return ids[i].Path() < ids[j].Path() }
    39  func (ids inodeDeltas) Swap(i, j int)      { ids[i], ids[j] = ids[j], ids[i] }
    40  
    41  // GenerateLayer creates a new OCI diff layer based on the mtree diff provided.
    42  // All of the mtree.Modified and mtree.Extra blobs are read relative to the
    43  // provided path (which should be the rootfs of the layer that was diffed). The
    44  // returned reader is for the *raw* tar data, it is the caller's responsibility
    45  // to gzip it.
    46  func GenerateLayer(path string, deltas []mtree.InodeDelta, opt *MapOptions) (io.ReadCloser, error) {
    47  	var mapOptions MapOptions
    48  	if opt != nil {
    49  		mapOptions = *opt
    50  	}
    51  
    52  	reader, writer := io.Pipe()
    53  
    54  	go func() (Err error) {
    55  		// Close with the returned error.
    56  		defer func() {
    57  			writer.CloseWithError(errors.Wrap(Err, "generate layer"))
    58  		}()
    59  
    60  		// We can't just dump all of the file contents into a tar file. We need
    61  		// to emulate a proper tar generator. Luckily there aren't that many
    62  		// things to emulate (and we can do them all in tar.go).
    63  		tg := newTarGenerator(writer, mapOptions)
    64  
    65  		// Sort the delta paths.
    66  		// FIXME: We need to add whiteouts first, otherwise we might end up
    67  		//        doing something silly like deleting a file which we actually
    68  		//        meant to modify.
    69  		sort.Sort(inodeDeltas(deltas))
    70  
    71  		for _, delta := range deltas {
    72  			name := delta.Path()
    73  			fullPath := filepath.Join(path, name)
    74  
    75  			// XXX: It's possible that if we unlink a hardlink, we're going to
    76  			//      AddFile() for no reason. Maybe we should drop nlink= from
    77  			//      the set of keywords we care about?
    78  
    79  			switch delta.Type() {
    80  			case mtree.Modified, mtree.Extra:
    81  				if err := tg.AddFile(name, fullPath); err != nil {
    82  					log.Warnf("generate layer: could not add file '%s': %s", name, err)
    83  					return errors.Wrap(err, "generate layer file")
    84  				}
    85  			case mtree.Missing:
    86  				if err := tg.AddWhiteout(name); err != nil {
    87  					log.Warnf("generate layer: could not add whiteout '%s': %s", name, err)
    88  					return errors.Wrap(err, "generate whiteout layer file")
    89  				}
    90  			}
    91  		}
    92  
    93  		if err := tg.tw.Close(); err != nil {
    94  			log.Warnf("generate layer: could not close tar.Writer: %s", err)
    95  			return errors.Wrap(err, "close tar writer")
    96  		}
    97  
    98  		return nil
    99  	}()
   100  
   101  	return reader, nil
   102  }
   103  
   104  // GenerateInsertLayer generates a completely new layer from "root"to be
   105  // inserted into the image at "target". If "root" is an empty string then the
   106  // "target" will be removed via a whiteout.
   107  func GenerateInsertLayer(root string, target string, opaque bool, opt *MapOptions) io.ReadCloser {
   108  	root = CleanPath(root)
   109  
   110  	var mapOptions MapOptions
   111  	if opt != nil {
   112  		mapOptions = *opt
   113  	}
   114  
   115  	reader, writer := io.Pipe()
   116  
   117  	go func() (Err error) {
   118  		defer func() {
   119  			writer.CloseWithError(errors.Wrap(Err, "generate layer"))
   120  		}()
   121  
   122  		tg := newTarGenerator(writer, mapOptions)
   123  
   124  		if opaque {
   125  			if err := tg.AddOpaqueWhiteout(target); err != nil {
   126  				return err
   127  			}
   128  		}
   129  		if root == "" {
   130  			return tg.AddWhiteout(target)
   131  		}
   132  		return unpriv.Walk(root, func(curPath string, info os.FileInfo, err error) error {
   133  			if err != nil {
   134  				return err
   135  			}
   136  
   137  			pathInTar := path.Join(target, curPath[len(root):])
   138  			return tg.AddFile(pathInTar, curPath)
   139  		})
   140  	}()
   141  	return reader
   142  }