github.com/containerd/Containerd@v1.4.13/diff/lcow/lcow.go (about) 1 // +build windows 2 3 /* 4 Copyright The containerd Authors. 5 6 Licensed under the Apache License, Version 2.0 (the "License"); 7 you may not use this file except in compliance with the License. 8 You may obtain a copy of the License at 9 10 http://www.apache.org/licenses/LICENSE-2.0 11 12 Unless required by applicable law or agreed to in writing, software 13 distributed under the License is distributed on an "AS IS" BASIS, 14 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 See the License for the specific language governing permissions and 16 limitations under the License. 17 */ 18 19 package lcow 20 21 import ( 22 "context" 23 "io" 24 "io/ioutil" 25 "os" 26 "path" 27 "time" 28 29 "github.com/Microsoft/go-winio/pkg/security" 30 "github.com/Microsoft/hcsshim/ext4/tar2ext4" 31 "github.com/containerd/containerd/content" 32 "github.com/containerd/containerd/diff" 33 "github.com/containerd/containerd/errdefs" 34 "github.com/containerd/containerd/log" 35 "github.com/containerd/containerd/metadata" 36 "github.com/containerd/containerd/mount" 37 "github.com/containerd/containerd/plugin" 38 digest "github.com/opencontainers/go-digest" 39 ocispec "github.com/opencontainers/image-spec/specs-go/v1" 40 "github.com/pkg/errors" 41 "github.com/sirupsen/logrus" 42 ) 43 44 const ( 45 // maxLcowVhdSizeGB is the max size in GB of any layer 46 maxLcowVhdSizeGB = 128 * 1024 * 1024 * 1024 47 ) 48 49 func init() { 50 plugin.Register(&plugin.Registration{ 51 Type: plugin.DiffPlugin, 52 ID: "windows-lcow", 53 Requires: []plugin.Type{ 54 plugin.MetadataPlugin, 55 }, 56 InitFn: func(ic *plugin.InitContext) (interface{}, error) { 57 md, err := ic.Get(plugin.MetadataPlugin) 58 if err != nil { 59 return nil, err 60 } 61 62 ic.Meta.Platforms = append(ic.Meta.Platforms, ocispec.Platform{ 63 OS: "linux", 64 Architecture: "amd64", 65 }) 66 return NewWindowsLcowDiff(md.(*metadata.DB).ContentStore()) 67 }, 68 }) 69 } 70 71 // CompareApplier handles both comparison and 72 // application of layer diffs. 73 type CompareApplier interface { 74 diff.Applier 75 diff.Comparer 76 } 77 78 // windowsLcowDiff does filesystem comparison and application 79 // for Windows specific Linux layer diffs. 80 type windowsLcowDiff struct { 81 store content.Store 82 } 83 84 var emptyDesc = ocispec.Descriptor{} 85 86 // NewWindowsLcowDiff is the Windows LCOW container layer implementation 87 // for comparing and applying Linux filesystem layers on Windows 88 func NewWindowsLcowDiff(store content.Store) (CompareApplier, error) { 89 return windowsLcowDiff{ 90 store: store, 91 }, nil 92 } 93 94 // Apply applies the content associated with the provided digests onto the 95 // provided mounts. Archive content will be extracted and decompressed if 96 // necessary. 97 func (s windowsLcowDiff) Apply(ctx context.Context, desc ocispec.Descriptor, mounts []mount.Mount, opts ...diff.ApplyOpt) (d ocispec.Descriptor, err error) { 98 t1 := time.Now() 99 defer func() { 100 if err == nil { 101 log.G(ctx).WithFields(logrus.Fields{ 102 "d": time.Since(t1), 103 "dgst": desc.Digest, 104 "size": desc.Size, 105 "media": desc.MediaType, 106 }).Debugf("diff applied") 107 } 108 }() 109 110 var config diff.ApplyConfig 111 for _, o := range opts { 112 if err := o(ctx, desc, &config); err != nil { 113 return emptyDesc, errors.Wrap(err, "failed to apply config opt") 114 } 115 } 116 117 layer, _, err := mountsToLayerAndParents(mounts) 118 if err != nil { 119 return emptyDesc, err 120 } 121 122 ra, err := s.store.ReaderAt(ctx, desc) 123 if err != nil { 124 return emptyDesc, errors.Wrap(err, "failed to get reader from content store") 125 } 126 defer ra.Close() 127 128 processor := diff.NewProcessorChain(desc.MediaType, content.NewReader(ra)) 129 for { 130 if processor, err = diff.GetProcessor(ctx, processor, config.ProcessorPayloads); err != nil { 131 return emptyDesc, errors.Wrapf(err, "failed to get stream processor for %s", desc.MediaType) 132 } 133 if processor.MediaType() == ocispec.MediaTypeImageLayer { 134 break 135 } 136 } 137 defer processor.Close() 138 139 // Calculate the Digest as we go 140 digester := digest.Canonical.Digester() 141 rc := &readCounter{ 142 r: io.TeeReader(processor, digester.Hash()), 143 } 144 145 layerPath := path.Join(layer, "layer.vhd") 146 outFile, err := os.Create(layerPath) 147 if err != nil { 148 return emptyDesc, err 149 } 150 defer func() { 151 if err != nil { 152 outFile.Close() 153 os.Remove(layerPath) 154 } 155 }() 156 157 err = tar2ext4.Convert(rc, outFile, tar2ext4.ConvertWhiteout, tar2ext4.AppendVhdFooter, tar2ext4.MaximumDiskSize(maxLcowVhdSizeGB)) 158 if err != nil { 159 return emptyDesc, errors.Wrapf(err, "failed to convert tar2ext4 vhd") 160 } 161 err = outFile.Sync() 162 if err != nil { 163 return emptyDesc, errors.Wrapf(err, "failed to sync tar2ext4 vhd to disk") 164 } 165 outFile.Close() 166 167 // Read any trailing data 168 if _, err := io.Copy(ioutil.Discard, rc); err != nil { 169 return emptyDesc, err 170 } 171 172 err = security.GrantVmGroupAccess(layerPath) 173 if err != nil { 174 return emptyDesc, errors.Wrapf(err, "failed GrantVmGroupAccess on layer vhd: %v", layerPath) 175 } 176 177 return ocispec.Descriptor{ 178 MediaType: ocispec.MediaTypeImageLayer, 179 Size: rc.c, 180 Digest: digester.Digest(), 181 }, nil 182 } 183 184 // Compare creates a diff between the given mounts and uploads the result 185 // to the content store. 186 func (s windowsLcowDiff) Compare(ctx context.Context, lower, upper []mount.Mount, opts ...diff.Opt) (d ocispec.Descriptor, err error) { 187 return emptyDesc, errors.Wrap(errdefs.ErrNotImplemented, "windowsLcowDiff does not implement Compare method") 188 } 189 190 type readCounter struct { 191 r io.Reader 192 c int64 193 } 194 195 func (rc *readCounter) Read(p []byte) (n int, err error) { 196 n, err = rc.r.Read(p) 197 rc.c += int64(n) 198 return 199 } 200 201 func mountsToLayerAndParents(mounts []mount.Mount) (string, []string, error) { 202 if len(mounts) != 1 { 203 return "", nil, errors.Wrap(errdefs.ErrInvalidArgument, "number of mounts should always be 1 for Windows lcow-layers") 204 } 205 mnt := mounts[0] 206 if mnt.Type != "lcow-layer" { 207 return "", nil, errors.Wrap(errdefs.ErrInvalidArgument, "mount layer type must be lcow-layer") 208 } 209 210 parentLayerPaths, err := mnt.GetParentPaths() 211 if err != nil { 212 return "", nil, err 213 } 214 215 return mnt.Source, parentLayerPaths, nil 216 }