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 }