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