github.com/lalkh/containerd@v1.4.3/snapshots/windows/windows.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 windows 20 21 import ( 22 "context" 23 "encoding/json" 24 "os" 25 "path/filepath" 26 "strconv" 27 "strings" 28 29 winfs "github.com/Microsoft/go-winio/pkg/fs" 30 "github.com/Microsoft/go-winio/vhd" 31 "github.com/Microsoft/hcsshim" 32 "github.com/containerd/containerd/errdefs" 33 "github.com/containerd/containerd/log" 34 "github.com/containerd/containerd/mount" 35 "github.com/containerd/containerd/plugin" 36 "github.com/containerd/containerd/snapshots" 37 "github.com/containerd/containerd/snapshots/storage" 38 "github.com/containerd/continuity/fs" 39 "github.com/pkg/errors" 40 ) 41 42 func init() { 43 plugin.Register(&plugin.Registration{ 44 Type: plugin.SnapshotPlugin, 45 ID: "windows", 46 InitFn: func(ic *plugin.InitContext) (interface{}, error) { 47 return NewSnapshotter(ic.Root) 48 }, 49 }) 50 } 51 52 const ( 53 rootfsSizeLabel = "containerd.io/snapshot/io.microsoft.container.storage.rootfs.size-gb" 54 ) 55 56 type snapshotter struct { 57 root string 58 info hcsshim.DriverInfo 59 ms *storage.MetaStore 60 } 61 62 // NewSnapshotter returns a new windows snapshotter 63 func NewSnapshotter(root string) (snapshots.Snapshotter, error) { 64 fsType, err := winfs.GetFileSystemType(root) 65 if err != nil { 66 return nil, err 67 } 68 if strings.ToLower(fsType) != "ntfs" { 69 return nil, errors.Wrapf(errdefs.ErrInvalidArgument, "%s is not on an NTFS volume - only NTFS volumes are supported", root) 70 } 71 72 if err := os.MkdirAll(root, 0700); err != nil { 73 return nil, err 74 } 75 ms, err := storage.NewMetaStore(filepath.Join(root, "metadata.db")) 76 if err != nil { 77 return nil, err 78 } 79 80 if err := os.Mkdir(filepath.Join(root, "snapshots"), 0700); err != nil && !os.IsExist(err) { 81 return nil, err 82 } 83 84 return &snapshotter{ 85 info: hcsshim.DriverInfo{ 86 HomeDir: filepath.Join(root, "snapshots"), 87 }, 88 root: root, 89 ms: ms, 90 }, nil 91 } 92 93 // Stat returns the info for an active or committed snapshot by name or 94 // key. 95 // 96 // Should be used for parent resolution, existence checks and to discern 97 // the kind of snapshot. 98 func (s *snapshotter) Stat(ctx context.Context, key string) (snapshots.Info, error) { 99 ctx, t, err := s.ms.TransactionContext(ctx, false) 100 if err != nil { 101 return snapshots.Info{}, err 102 } 103 defer t.Rollback() 104 105 _, info, _, err := storage.GetInfo(ctx, key) 106 return info, err 107 } 108 109 func (s *snapshotter) Update(ctx context.Context, info snapshots.Info, fieldpaths ...string) (snapshots.Info, error) { 110 ctx, t, err := s.ms.TransactionContext(ctx, true) 111 if err != nil { 112 return snapshots.Info{}, err 113 } 114 defer t.Rollback() 115 116 info, err = storage.UpdateInfo(ctx, info, fieldpaths...) 117 if err != nil { 118 return snapshots.Info{}, err 119 } 120 121 if err := t.Commit(); err != nil { 122 return snapshots.Info{}, err 123 } 124 125 return info, nil 126 } 127 128 func (s *snapshotter) Usage(ctx context.Context, key string) (snapshots.Usage, error) { 129 ctx, t, err := s.ms.TransactionContext(ctx, false) 130 if err != nil { 131 return snapshots.Usage{}, err 132 } 133 id, info, usage, err := storage.GetInfo(ctx, key) 134 t.Rollback() // transaction no longer needed at this point. 135 136 if err != nil { 137 return snapshots.Usage{}, err 138 } 139 140 if info.Kind == snapshots.KindActive { 141 path := s.getSnapshotDir(id) 142 du, err := fs.DiskUsage(ctx, path) 143 if err != nil { 144 return snapshots.Usage{}, err 145 } 146 147 usage = snapshots.Usage(du) 148 } 149 150 return usage, nil 151 } 152 153 func (s *snapshotter) Prepare(ctx context.Context, key, parent string, opts ...snapshots.Opt) ([]mount.Mount, error) { 154 return s.createSnapshot(ctx, snapshots.KindActive, key, parent, opts) 155 } 156 157 func (s *snapshotter) View(ctx context.Context, key, parent string, opts ...snapshots.Opt) ([]mount.Mount, error) { 158 return s.createSnapshot(ctx, snapshots.KindView, key, parent, opts) 159 } 160 161 // Mounts returns the mounts for the transaction identified by key. Can be 162 // called on an read-write or readonly transaction. 163 // 164 // This can be used to recover mounts after calling View or Prepare. 165 func (s *snapshotter) Mounts(ctx context.Context, key string) ([]mount.Mount, error) { 166 ctx, t, err := s.ms.TransactionContext(ctx, false) 167 if err != nil { 168 return nil, err 169 } 170 defer t.Rollback() 171 172 snapshot, err := storage.GetSnapshot(ctx, key) 173 if err != nil { 174 return nil, errors.Wrap(err, "failed to get snapshot mount") 175 } 176 return s.mounts(snapshot), nil 177 } 178 179 func (s *snapshotter) Commit(ctx context.Context, name, key string, opts ...snapshots.Opt) error { 180 ctx, t, err := s.ms.TransactionContext(ctx, true) 181 if err != nil { 182 return err 183 } 184 185 defer func() { 186 if err != nil { 187 if rerr := t.Rollback(); rerr != nil { 188 log.G(ctx).WithError(rerr).Warn("failed to rollback transaction") 189 } 190 } 191 }() 192 193 // grab the existing id 194 id, _, _, err := storage.GetInfo(ctx, key) 195 if err != nil { 196 return err 197 } 198 199 usage, err := fs.DiskUsage(ctx, s.getSnapshotDir(id)) 200 if err != nil { 201 return err 202 } 203 204 if _, err = storage.CommitActive(ctx, key, name, snapshots.Usage(usage), opts...); err != nil { 205 return errors.Wrap(err, "failed to commit snapshot") 206 } 207 return t.Commit() 208 } 209 210 // Remove abandons the transaction identified by key. All resources 211 // associated with the key will be removed. 212 func (s *snapshotter) Remove(ctx context.Context, key string) error { 213 ctx, t, err := s.ms.TransactionContext(ctx, true) 214 if err != nil { 215 return err 216 } 217 defer t.Rollback() 218 219 id, _, err := storage.Remove(ctx, key) 220 if err != nil { 221 return errors.Wrap(err, "failed to remove") 222 } 223 224 path := s.getSnapshotDir(id) 225 renamedID := "rm-" + id 226 renamed := s.getSnapshotDir(renamedID) 227 if err := os.Rename(path, renamed); err != nil && !os.IsNotExist(err) { 228 if !os.IsPermission(err) { 229 return err 230 } 231 // If permission denied, it's possible that the scratch is still mounted, an 232 // artifact after a hard daemon crash for example. Worth a shot to try detaching it 233 // before retrying the rename. 234 if detachErr := vhd.DetachVhd(filepath.Join(path, "sandbox.vhdx")); detachErr != nil { 235 return errors.Wrapf(err, "failed to detach VHD: %s", detachErr) 236 } 237 if renameErr := os.Rename(path, renamed); renameErr != nil && !os.IsNotExist(renameErr) { 238 return errors.Wrapf(err, "second rename attempt following detach failed: %s", renameErr) 239 } 240 } 241 242 if err := t.Commit(); err != nil { 243 if err1 := os.Rename(renamed, path); err1 != nil { 244 // May cause inconsistent data on disk 245 log.G(ctx).WithError(err1).WithField("path", renamed).Errorf("Failed to rename after failed commit") 246 } 247 return errors.Wrap(err, "failed to commit") 248 } 249 250 if err := hcsshim.DestroyLayer(s.info, renamedID); err != nil { 251 // Must be cleaned up, any "rm-*" could be removed if no active transactions 252 log.G(ctx).WithError(err).WithField("path", renamed).Warnf("Failed to remove root filesystem") 253 } 254 255 return nil 256 } 257 258 // Walk the committed snapshots. 259 func (s *snapshotter) Walk(ctx context.Context, fn snapshots.WalkFunc, fs ...string) error { 260 ctx, t, err := s.ms.TransactionContext(ctx, false) 261 if err != nil { 262 return err 263 } 264 defer t.Rollback() 265 266 return storage.WalkInfo(ctx, fn, fs...) 267 } 268 269 // Close closes the snapshotter 270 func (s *snapshotter) Close() error { 271 return s.ms.Close() 272 } 273 274 func (s *snapshotter) mounts(sn storage.Snapshot) []mount.Mount { 275 var ( 276 roFlag string 277 source string 278 parentLayerPaths []string 279 ) 280 281 if sn.Kind == snapshots.KindView { 282 roFlag = "ro" 283 } else { 284 roFlag = "rw" 285 } 286 287 if len(sn.ParentIDs) == 0 || sn.Kind == snapshots.KindActive { 288 source = s.getSnapshotDir(sn.ID) 289 parentLayerPaths = s.parentIDsToParentPaths(sn.ParentIDs) 290 } else { 291 source = s.getSnapshotDir(sn.ParentIDs[0]) 292 parentLayerPaths = s.parentIDsToParentPaths(sn.ParentIDs[1:]) 293 } 294 295 // error is not checked here, as a string array will never fail to Marshal 296 parentLayersJSON, _ := json.Marshal(parentLayerPaths) 297 parentLayersOption := mount.ParentLayerPathsFlag + string(parentLayersJSON) 298 299 var mounts []mount.Mount 300 mounts = append(mounts, mount.Mount{ 301 Source: source, 302 Type: "windows-layer", 303 Options: []string{ 304 roFlag, 305 parentLayersOption, 306 }, 307 }) 308 309 return mounts 310 } 311 312 func (s *snapshotter) getSnapshotDir(id string) string { 313 return filepath.Join(s.root, "snapshots", id) 314 } 315 316 func (s *snapshotter) createSnapshot(ctx context.Context, kind snapshots.Kind, key, parent string, opts []snapshots.Opt) ([]mount.Mount, error) { 317 ctx, t, err := s.ms.TransactionContext(ctx, true) 318 if err != nil { 319 return nil, err 320 } 321 defer t.Rollback() 322 323 newSnapshot, err := storage.CreateSnapshot(ctx, kind, key, parent, opts...) 324 if err != nil { 325 return nil, errors.Wrap(err, "failed to create snapshot") 326 } 327 328 if kind == snapshots.KindActive { 329 parentLayerPaths := s.parentIDsToParentPaths(newSnapshot.ParentIDs) 330 331 var parentPath string 332 if len(parentLayerPaths) != 0 { 333 parentPath = parentLayerPaths[0] 334 } 335 336 if err := hcsshim.CreateSandboxLayer(s.info, newSnapshot.ID, parentPath, parentLayerPaths); err != nil { 337 return nil, errors.Wrap(err, "failed to create sandbox layer") 338 } 339 340 var snapshotInfo snapshots.Info 341 for _, o := range opts { 342 o(&snapshotInfo) 343 } 344 345 var sizeGB int 346 if sizeGBstr, ok := snapshotInfo.Labels[rootfsSizeLabel]; ok { 347 i32, err := strconv.ParseInt(sizeGBstr, 10, 32) 348 if err != nil { 349 return nil, errors.Wrapf(err, "failed to parse annotation %q=%q", rootfsSizeLabel, sizeGBstr) 350 } 351 sizeGB = int(i32) 352 } 353 354 if sizeGB > 0 { 355 const gbToByte = 1024 * 1024 * 1024 356 if err := hcsshim.ExpandSandboxSize(s.info, newSnapshot.ID, uint64(gbToByte*sizeGB)); err != nil { 357 return nil, errors.Wrapf(err, "failed to expand scratch size to %d GB", sizeGB) 358 } 359 } 360 } 361 362 if err := t.Commit(); err != nil { 363 return nil, errors.Wrap(err, "commit failed") 364 } 365 366 return s.mounts(newSnapshot), nil 367 } 368 369 func (s *snapshotter) parentIDsToParentPaths(parentIDs []string) []string { 370 var parentLayerPaths []string 371 for _, ID := range parentIDs { 372 parentLayerPaths = append(parentLayerPaths, s.getSnapshotDir(ID)) 373 } 374 return parentLayerPaths 375 }