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