github.com/opencontainers/umoci@v0.4.8-0.20240508124516-656e4836fb0d/oci/layer/generate.go (about)

     1  /*
     2   * umoci: Umoci Modifies Open Containers' Images
     3   * Copyright (C) 2016-2020 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/opencontainers/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 *RepackOptions) (io.ReadCloser, error) {
    47  	var packOptions RepackOptions
    48  	if opt != nil {
    49  		packOptions = *opt
    50  	}
    51  
    52  	reader, writer := io.Pipe()
    53  
    54  	go func() (Err error) {
    55  		// Close with the returned error.
    56  		defer func() {
    57  			if Err != nil {
    58  				log.Warnf("could not generate layer: %v", Err)
    59  			}
    60  			// #nosec G104
    61  			_ = writer.CloseWithError(errors.Wrap(Err, "generate layer"))
    62  		}()
    63  
    64  		// We can't just dump all of the file contents into a tar file. We need
    65  		// to emulate a proper tar generator. Luckily there aren't that many
    66  		// things to emulate (and we can do them all in tar.go).
    67  		tg := newTarGenerator(writer, packOptions.MapOptions)
    68  
    69  		// Sort the delta paths.
    70  		// FIXME: We need to add whiteouts first, otherwise we might end up
    71  		//        doing something silly like deleting a file which we actually
    72  		//        meant to modify.
    73  		sort.Sort(inodeDeltas(deltas))
    74  
    75  		for _, delta := range deltas {
    76  			name := delta.Path()
    77  			fullPath := filepath.Join(path, name)
    78  
    79  			// XXX: It's possible that if we unlink a hardlink, we're going to
    80  			//      AddFile() for no reason. Maybe we should drop nlink= from
    81  			//      the set of keywords we care about?
    82  
    83  			switch delta.Type() {
    84  			case mtree.Modified, mtree.Extra:
    85  				if packOptions.TranslateOverlayWhiteouts {
    86  					fi, err := os.Stat(fullPath)
    87  					if err != nil {
    88  						return errors.Wrapf(err, "couldn't determine overlay whiteout for %s", fullPath)
    89  					}
    90  
    91  					whiteout, err := isOverlayWhiteout(fi)
    92  					if err != nil {
    93  						return err
    94  					}
    95  					if whiteout {
    96  						if err := tg.AddWhiteout(fullPath); err != nil {
    97  							return errors.Wrap(err, "generate whiteout from overlayfs")
    98  						}
    99  					}
   100  					continue
   101  				}
   102  				if err := tg.AddFile(name, fullPath); err != nil {
   103  					log.Warnf("generate layer: could not add file '%s': %s", name, err)
   104  					return errors.Wrap(err, "generate layer file")
   105  				}
   106  			case mtree.Missing:
   107  				if err := tg.AddWhiteout(name); err != nil {
   108  					log.Warnf("generate layer: could not add whiteout '%s': %s", name, err)
   109  					return errors.Wrap(err, "generate whiteout layer file")
   110  				}
   111  			}
   112  		}
   113  
   114  		if err := tg.tw.Close(); err != nil {
   115  			log.Warnf("generate layer: could not close tar.Writer: %s", err)
   116  			return errors.Wrap(err, "close tar writer")
   117  		}
   118  
   119  		return nil
   120  	}()
   121  
   122  	return reader, nil
   123  }
   124  
   125  // GenerateInsertLayer generates a completely new layer from "root"to be
   126  // inserted into the image at "target". If "root" is an empty string then the
   127  // "target" will be removed via a whiteout.
   128  func GenerateInsertLayer(root string, target string, opaque bool, opt *RepackOptions) io.ReadCloser {
   129  	root = CleanPath(root)
   130  
   131  	var packOptions RepackOptions
   132  	if opt != nil {
   133  		packOptions = *opt
   134  	}
   135  
   136  	reader, writer := io.Pipe()
   137  
   138  	go func() (Err error) {
   139  		defer func() {
   140  			if Err != nil {
   141  				log.Warnf("could not generate insert layer: %v", Err)
   142  			}
   143  			// #nosec G104
   144  			_ = writer.CloseWithError(errors.Wrap(Err, "generate insert layer"))
   145  		}()
   146  
   147  		tg := newTarGenerator(writer, packOptions.MapOptions)
   148  
   149  		defer func() {
   150  			if err := tg.tw.Close(); err != nil {
   151  				log.Warnf("generate insert layer: could not close tar.Writer: %s", err)
   152  			}
   153  		}()
   154  
   155  		if opaque {
   156  			if err := tg.AddOpaqueWhiteout(target); err != nil {
   157  				return err
   158  			}
   159  		}
   160  		if root == "" {
   161  			return tg.AddWhiteout(target)
   162  		}
   163  		return unpriv.Walk(root, func(curPath string, info os.FileInfo, err error) error {
   164  			if err != nil {
   165  				return err
   166  			}
   167  
   168  			pathInTar := path.Join(target, curPath[len(root):])
   169  			whiteout, err := isOverlayWhiteout(info)
   170  			if err != nil {
   171  				return err
   172  			}
   173  			if packOptions.TranslateOverlayWhiteouts && whiteout {
   174  				log.Debugf("converting overlayfs whiteout %s to OCI whiteout", pathInTar)
   175  				return tg.AddWhiteout(pathInTar)
   176  			}
   177  
   178  			return tg.AddFile(pathInTar, curPath)
   179  		})
   180  	}()
   181  	return reader
   182  }