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 }